diff --git a/config/cp.php b/config/cp.php index b1717d474ef..be058d6e9a2 100644 --- a/config/cp.php +++ b/config/cp.php @@ -15,6 +15,21 @@ 'route' => env('CP_ROUTE', 'cp'), + /* + |-------------------------------------------------------------------------- + | Authentication + |-------------------------------------------------------------------------- + | + | Whether the Control Panel's authentication pages should be enabled, + | and where users should be redirected in order to authenticate. + | + */ + + 'auth' => [ + 'enabled' => true, + 'redirect_to' => null, + ], + /* |-------------------------------------------------------------------------- | Start Page diff --git a/resources/js/components/SessionExpiry.vue b/resources/js/components/SessionExpiry.vue index 19d2959b3cc..d932542894e 100644 --- a/resources/js/components/SessionExpiry.vue +++ b/resources/js/components/SessionExpiry.vue @@ -59,7 +59,8 @@ export default { warnAt: Number, lifetime: Number, email: String, - oauthProvider: String + oauthProvider: String, + auth: Object, }, data() { @@ -102,7 +103,7 @@ export default { watch: { count(count) { - this.isShowingLogin = this.remaining <= 0; + this.isShowingLogin = this.auth.enabled && this.remaining <= 0; // While we're in the warning period, we'll check every second so that any // activity in another tab is picked up and the count will get restarted. @@ -145,7 +146,10 @@ export default { return this.$axios.get(cp_url('session-timeout')).then(response => { this.count = this.remaining = response.data; }).catch(e => { - if (e.response.status === 401) this.remaining = 0; + if (e.response.status === 401) { + this.remaining = 0; + if (!this.auth.enabled) window.location = this.auth.redirect_to || '/'; + } throw e; }).finally(response => { this.pinging = false; diff --git a/resources/views/partials/session-expiry.blade.php b/resources/views/partials/session-expiry.blade.php index de483bb6167..29488f33105 100644 --- a/resources/views/partials/session-expiry.blade.php +++ b/resources/views/partials/session-expiry.blade.php @@ -3,4 +3,5 @@ :warn-at="{{ $warnAt }}" :lifetime="{{ $lifetime }}" :oauth-provider="{{ json_encode($oauth) }}" -> \ No newline at end of file + :auth="{{ json_encode($auth) }}" +> diff --git a/routes/cp.php b/routes/cp.php index 536ab6b926a..959e0f63c1a 100644 --- a/routes/cp.php +++ b/routes/cp.php @@ -101,14 +101,17 @@ use Statamic\Statamic; Route::group(['prefix' => 'auth'], function () { - Route::get('login', [LoginController::class, 'showLoginForm'])->name('login'); - Route::post('login', [LoginController::class, 'login']); - Route::get('logout', [LoginController::class, 'logout'])->name('logout'); + if (config('statamic.cp.auth.enabled', true)) { + Route::get('login', [LoginController::class, 'showLoginForm'])->name('login'); + Route::post('login', [LoginController::class, 'login']); + + Route::get('password/reset', [ForgotPasswordController::class, 'showLinkRequestForm'])->name('password.request'); + Route::post('password/email', [ForgotPasswordController::class, 'sendResetLinkEmail'])->name('password.email'); + Route::get('password/reset/{token}', [ResetPasswordController::class, 'showResetForm'])->name('password.reset'); + Route::post('password/reset', [ResetPasswordController::class, 'reset'])->name('password.reset.action'); + } - Route::get('password/reset', [ForgotPasswordController::class, 'showLinkRequestForm'])->name('password.request'); - Route::post('password/email', [ForgotPasswordController::class, 'sendResetLinkEmail'])->name('password.email'); - Route::get('password/reset/{token}', [ResetPasswordController::class, 'showResetForm'])->name('password.reset'); - Route::post('password/reset', [ResetPasswordController::class, 'reset'])->name('password.reset.action'); + Route::get('logout', [LoginController::class, 'logout'])->name('logout'); Route::get('token', CsrfTokenController::class)->name('token'); Route::get('extend', ExtendSessionController::class)->name('extend'); diff --git a/src/Exceptions/AuthenticationException.php b/src/Exceptions/AuthenticationException.php index f7566d07770..35f36e90806 100644 --- a/src/Exceptions/AuthenticationException.php +++ b/src/Exceptions/AuthenticationException.php @@ -11,6 +11,17 @@ public function toResponse($request) { return $request->expectsJson() ? response()->json(['message' => $this->getMessage()], 401) - : redirect()->route('statamic.cp.login'); + : $this->handleRedirect(); + } + + protected function handleRedirect() + { + if (! config('statamic.cp.auth.enabled', true)) { + return config('statamic.cp.auth.redirect_to') + ? redirect()->guest(config('statamic.cp.auth.redirect_to')) + : abort(401); + } + + return redirect()->route('statamic.cp.login'); } } diff --git a/src/Http/View/Composers/SessionExpiryComposer.php b/src/Http/View/Composers/SessionExpiryComposer.php index 1f7a5cd3f64..45d81ecf86d 100644 --- a/src/Http/View/Composers/SessionExpiryComposer.php +++ b/src/Http/View/Composers/SessionExpiryComposer.php @@ -17,6 +17,7 @@ public function compose(View $view) 'lifetime' => config('session.lifetime') * 60, 'warnAt' => 60, 'oauth' => $this->oauth(), + 'auth' => config('statamic.cp.auth'), ]); } diff --git a/tests/Feature/AuthenticationTest.php b/tests/Feature/AuthenticationTest.php new file mode 100644 index 00000000000..c416ffd1a33 --- /dev/null +++ b/tests/Feature/AuthenticationTest.php @@ -0,0 +1,37 @@ +getJson('/cp/anything')->assertStatus(401)->assertJson(['message' => 'Unauthenticated.']); + } + + #[Test] + public function redirects_to_login_page() + { + $this->get('/cp/anything')->assertRedirect('/cp/auth/login'); + } + + #[Test] + public function redirects_to_defined_login_page_when_auth_is_disabled() + { + config(['statamic.cp.auth' => ['enabled' => false, 'redirect_to' => '/my-login-page']]); + + $this->get('/cp/anything')->assertRedirect('/my-login-page'); + } + + #[Test] + public function responds_with_401_when_auth_is_disabled_and_no_redirect_is_defined() + { + config(['statamic.cp.auth' => ['enabled' => false]]); + + $this->get('/cp/anything')->assertStatus(401); + } +}