Merge branch 'next' into fix/rollback-uses-correct-commit
This commit is contained in:
commit
175e5b3c6d
10 changed files with 119 additions and 20 deletions
|
|
@ -25,7 +25,7 @@ public function handle(Request $request, Closure $next): Response
|
||||||
}
|
}
|
||||||
$force_password_reset = auth()->user()->force_password_reset;
|
$force_password_reset = auth()->user()->force_password_reset;
|
||||||
if ($force_password_reset) {
|
if ($force_password_reset) {
|
||||||
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
|
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'two-factor-challenge' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ function allowedPathsForUnsubscribedAccounts()
|
||||||
'login',
|
'login',
|
||||||
'logout',
|
'logout',
|
||||||
'force-password-reset',
|
'force-password-reset',
|
||||||
|
'two-factor-challenge',
|
||||||
'livewire/update',
|
'livewire/update',
|
||||||
'admin',
|
'admin',
|
||||||
];
|
];
|
||||||
|
|
@ -95,6 +96,7 @@ function allowedPathsForInvalidAccounts()
|
||||||
'logout',
|
'logout',
|
||||||
'verify',
|
'verify',
|
||||||
'force-password-reset',
|
'force-password-reset',
|
||||||
|
'two-factor-challenge',
|
||||||
'livewire/update',
|
'livewire/update',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ class="flex absolute inset-y-0 right-0 items-center pr-2 cursor-pointer dark:hov
|
||||||
<path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />
|
<path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />
|
||||||
</svg>
|
</svg>
|
||||||
{{-- Eye-off icon (shown when password is visible) --}}
|
{{-- Eye-off icon (shown when password is visible) --}}
|
||||||
<svg x-show="type === 'text'" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg x-cloak x-show="type === 'text'" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />
|
<path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,11 @@
|
||||||
<div>
|
<div>
|
||||||
<p class="font-mono font-semibold text-7xl dark:text-warning">419</p>
|
<p class="font-mono font-semibold text-7xl dark:text-warning">419</p>
|
||||||
<h1 class="mt-4 font-bold tracking-tight dark:text-white">This page is definitely old, not like you!</h1>
|
<h1 class="mt-4 font-bold tracking-tight dark:text-white">This page is definitely old, not like you!</h1>
|
||||||
<p class="text-base leading-7 dark:text-neutral-300 text-black">Sorry, we couldn't find the page you're looking
|
<p class="text-base leading-7 dark:text-neutral-300 text-black">Your session has expired. Please log in again to continue.
|
||||||
for.
|
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center mt-10 gap-x-2">
|
<div class="flex items-center mt-10 gap-x-2">
|
||||||
<a href="{{ url()->previous() }}">
|
<a href="/login">
|
||||||
<x-forms.button>Go back</x-forms.button>
|
<x-forms.button>Back to Login</x-forms.button>
|
||||||
</a>
|
|
||||||
<a href="{{ route('dashboard') }}" {{ wireNavigate() }}>
|
|
||||||
<x-forms.button>Dashboard</x-forms.button>
|
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
||||||
support
|
support
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,26 @@
|
||||||
|
|
||||||
services:
|
services:
|
||||||
beszel-agent:
|
beszel-agent:
|
||||||
image: 'henrygd/beszel-agent:0.16.1' # Released on 14 Nov 2025
|
image: 'henrygd/beszel-agent:0.18.4' # Released on 21 Feb 2026
|
||||||
|
network_mode: host # Network stats graphs won't work if agent cannot access host system network stack
|
||||||
environment:
|
environment:
|
||||||
|
# Required
|
||||||
- LISTEN=/beszel_socket/beszel.sock
|
- LISTEN=/beszel_socket/beszel.sock
|
||||||
- HUB_URL=${HUB_URL?}
|
- HUB_URL=$SERVICE_URL_BESZEL
|
||||||
- 'TOKEN=${TOKEN?}'
|
- TOKEN=${TOKEN} # From hub token settings
|
||||||
- 'KEY=${KEY?}'
|
- KEY=${KEY} # SSH public key(s) from hub
|
||||||
|
# Optional
|
||||||
|
- DISABLE_SSH=${DISABLE_SSH:-false} # Disable SSH
|
||||||
|
- LOG_LEVEL=${LOG_LEVEL:-warn} # Logging level
|
||||||
|
- SKIP_GPU=${SKIP_GPU:-false} # Skip GPU monitoring
|
||||||
|
- SYSTEM_NAME=${SYSTEM_NAME} # Custom system name
|
||||||
volumes:
|
volumes:
|
||||||
- beszel_agent_data:/var/lib/beszel-agent
|
- beszel_agent_data:/var/lib/beszel-agent
|
||||||
- beszel_socket:/beszel_socket
|
- beszel_socket:/beszel_socket
|
||||||
- '/var/run/docker.sock:/var/run/docker.sock:ro'
|
- '/var/run/docker.sock:/var/run/docker.sock:ro'
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD', '/agent', 'health']
|
||||||
|
interval: 60s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 10
|
||||||
|
start_period: 5s
|
||||||
|
|
@ -9,21 +9,41 @@
|
||||||
# Add the public Key in "Key" env variable and token in the "Token" variable below (These are obtained from Beszel UI)
|
# Add the public Key in "Key" env variable and token in the "Token" variable below (These are obtained from Beszel UI)
|
||||||
services:
|
services:
|
||||||
beszel:
|
beszel:
|
||||||
image: 'henrygd/beszel:0.16.1' # Released on 14 Nov 2025
|
image: 'henrygd/beszel:0.18.4' # Released on 21 Feb 2026
|
||||||
environment:
|
environment:
|
||||||
- SERVICE_URL_BESZEL_8090
|
- SERVICE_URL_BESZEL_8090
|
||||||
|
- CONTAINER_DETAILS=${CONTAINER_DETAILS:-true}
|
||||||
|
- SHARE_ALL_SYSTEMS=${SHARE_ALL_SYSTEMS:-false}
|
||||||
volumes:
|
volumes:
|
||||||
- 'beszel_data:/beszel_data'
|
- 'beszel_data:/beszel_data'
|
||||||
- 'beszel_socket:/beszel_socket'
|
- 'beszel_socket:/beszel_socket'
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD', '/beszel', 'health', '--url', 'http://localhost:8090']
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 10
|
||||||
|
start_period: 5s
|
||||||
beszel-agent:
|
beszel-agent:
|
||||||
image: 'henrygd/beszel-agent:0.16.1' # Released on 14 Nov 2025
|
image: 'henrygd/beszel-agent:0.18.4' # Released on 21 Feb 2026
|
||||||
|
network_mode: host # Network stats graphs won't work if agent cannot access host system network stack
|
||||||
environment:
|
environment:
|
||||||
|
# Required
|
||||||
- LISTEN=/beszel_socket/beszel.sock
|
- LISTEN=/beszel_socket/beszel.sock
|
||||||
- HUB_URL=http://beszel:8090
|
- HUB_URL=$SERVICE_URL_BESZEL
|
||||||
- 'TOKEN=${TOKEN}'
|
- TOKEN=${TOKEN} # From hub token settings
|
||||||
- 'KEY=${KEY}'
|
- KEY=${KEY} # SSH public key(s) from hub
|
||||||
|
# Optional
|
||||||
|
- DISABLE_SSH=${DISABLE_SSH:-false} # Disable SSH
|
||||||
|
- LOG_LEVEL=${LOG_LEVEL:-warn} # Logging level
|
||||||
|
- SKIP_GPU=${SKIP_GPU:-false} # Skip GPU monitoring
|
||||||
|
- SYSTEM_NAME=${SYSTEM_NAME} # Custom system name
|
||||||
volumes:
|
volumes:
|
||||||
- beszel_agent_data:/var/lib/beszel-agent
|
- beszel_agent_data:/var/lib/beszel-agent
|
||||||
- beszel_socket:/beszel_socket
|
- beszel_socket:/beszel_socket
|
||||||
- '/var/run/docker.sock:/var/run/docker.sock:ro'
|
- '/var/run/docker.sock:/var/run/docker.sock:ro'
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD', '/agent', 'health']
|
||||||
|
interval: 60s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 10
|
||||||
|
start_period: 5s
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# ignore: true
|
||||||
# documentation: https://docs.plane.so/self-hosting/methods/docker-compose
|
# documentation: https://docs.plane.so/self-hosting/methods/docker-compose
|
||||||
# slogan: The open source project management tool
|
# slogan: The open source project management tool
|
||||||
# category: productivity
|
# category: productivity
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# ignore: true
|
||||||
# documentation: https://pterodactyl.io/
|
# documentation: https://pterodactyl.io/
|
||||||
# slogan: Pterodactyl is a free, open-source game server management panel
|
# slogan: Pterodactyl is a free, open-source game server management panel
|
||||||
# category: media
|
# category: media
|
||||||
|
|
@ -102,4 +103,4 @@ services:
|
||||||
- MAIL_PORT=$MAIL_PORT
|
- MAIL_PORT=$MAIL_PORT
|
||||||
- MAIL_USERNAME=$MAIL_USERNAME
|
- MAIL_USERNAME=$MAIL_USERNAME
|
||||||
- MAIL_PASSWORD=$MAIL_PASSWORD
|
- MAIL_PASSWORD=$MAIL_PASSWORD
|
||||||
- MAIL_ENCRYPTION=$MAIL_ENCRYPTION
|
- MAIL_ENCRYPTION=$MAIL_ENCRYPTION
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# ignore: true
|
||||||
# documentation: https://pterodactyl.io/
|
# documentation: https://pterodactyl.io/
|
||||||
# slogan: Pterodactyl is a free, open-source game server management panel
|
# slogan: Pterodactyl is a free, open-source game server management panel
|
||||||
# category: media
|
# category: media
|
||||||
|
|
|
||||||
65
tests/Feature/TwoFactorChallengeAccessTest.php
Normal file
65
tests/Feature/TwoFactorChallengeAccessTest.php
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->user = User::factory()->create();
|
||||||
|
$this->team = Team::factory()->personal()->create();
|
||||||
|
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||||
|
session(['currentTeam' => $this->team]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows unauthenticated access to two-factor-challenge page', function () {
|
||||||
|
$response = $this->get('/two-factor-challenge');
|
||||||
|
|
||||||
|
// Fortify returns a redirect to /login if there's no login.id in session,
|
||||||
|
// but the important thing is it does NOT return a 419 or 500
|
||||||
|
expect($response->status())->toBeIn([200, 302]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes two-factor-challenge in allowed paths for unsubscribed accounts', function () {
|
||||||
|
$paths = allowedPathsForUnsubscribedAccounts();
|
||||||
|
|
||||||
|
expect($paths)->toContain('two-factor-challenge');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes two-factor-challenge in allowed paths for invalid accounts', function () {
|
||||||
|
$paths = allowedPathsForInvalidAccounts();
|
||||||
|
|
||||||
|
expect($paths)->toContain('two-factor-challenge');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes two-factor-challenge in allowed paths for boarding accounts', function () {
|
||||||
|
$paths = allowedPathsForBoardingAccounts();
|
||||||
|
|
||||||
|
expect($paths)->toContain('two-factor-challenge');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not redirect authenticated user with force_password_reset from two-factor-challenge', function () {
|
||||||
|
$this->user->update(['force_password_reset' => true]);
|
||||||
|
|
||||||
|
$response = $this->actingAs($this->user)->get('/two-factor-challenge');
|
||||||
|
|
||||||
|
// Should NOT redirect to force-password-reset page
|
||||||
|
if ($response->isRedirect()) {
|
||||||
|
expect($response->headers->get('Location'))->not->toContain('force-password-reset');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders 419 error page with login link instead of previous url', function () {
|
||||||
|
$response = $this->get('/two-factor-challenge', [
|
||||||
|
'X-CSRF-TOKEN' => 'invalid-token',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// The 419 page should exist and contain a link to /login
|
||||||
|
$view = view('errors.419')->render();
|
||||||
|
|
||||||
|
expect($view)->toContain('/login');
|
||||||
|
expect($view)->toContain('Back to Login');
|
||||||
|
expect($view)->toContain('This page is definitely old, not like you!');
|
||||||
|
expect($view)->not->toContain('url()->previous()');
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue