Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e5e4a73
Introduce new config setting
duncanmcclean Feb 27, 2023
895bbcf
Only register login/reset password routes when auth is enabled
duncanmcclean Feb 27, 2023
3b0249d
`abort(401)` when CP Auth is disabled
duncanmcclean Feb 27, 2023
546ed77
Add test to ensure front-end login form still works
duncanmcclean Feb 27, 2023
66abbd1
Apply StyleCI changes
duncanmcclean Feb 27, 2023
0caa8cf
Merge branch '3.4' into feature/disable-login-and-reset-password-pages
duncanmcclean Apr 25, 2023
638e304
StyleCI fixes
duncanmcclean Apr 25, 2023
977f830
Merge branch '4.x' into feature/disable-login-and-reset-password-pages
edalzell May 20, 2023
0b2bd55
Merge branch '4.x' into pr/7617
duncanmcclean Nov 9, 2023
fc66e42
Pint
duncanmcclean Nov 9, 2023
8bffc14
Move logout route out of if statement
duncanmcclean Nov 9, 2023
e656efc
Ensure users can register via user:register_form tag when CP auth is …
duncanmcclean Nov 9, 2023
029ac6c
Ensure users can reset password via tags when CP auth is disabled
duncanmcclean Nov 9, 2023
80ccbf5
Allow specifying a redirect URL for when authentication is disabled
duncanmcclean Nov 9, 2023
b54cb0a
Reference updated config key
duncanmcclean Feb 24, 2024
e39db24
Merge branch '4.x' into pr/7617
duncanmcclean Feb 24, 2024
bc10aa2
Make sure `intended` is set
edalzell Apr 21, 2024
4711a50
use the helper instead
duncanmcclean Apr 22, 2024
3a3c403
Merge remote-tracking branch 'origin/5.x' into pr/7617
duncanmcclean May 13, 2024
54a3e83
Redirect to login page after session expires
duncanmcclean May 13, 2024
1f2fc1b
Merge branch '5.x' into pr/7617
jasonvarga Jun 27, 2024
047c97d
Dont redirect at zero ...
jasonvarga Jun 27, 2024
a5198ed
these tests dont really prove anything
jasonvarga Jun 27, 2024
d402365
words
jasonvarga Jun 27, 2024
710a191
add tests
jasonvarga Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand Down
10 changes: 7 additions & 3 deletions resources/js/components/SessionExpiry.vue
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,8 @@ export default {
warnAt: Number,
lifetime: Number,
email: String,
oauthProvider: String
oauthProvider: String,
auth: Object,
},

data() {
Expand DownExpand Up@@ -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.
Expand DownExpand Up@@ -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;
Expand Down
3 changes: 2 additions & 1 deletion resources/views/partials/session-expiry.blade.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,4 +3,5 @@
:warn-at="{{ $warnAt }}"
:lifetime="{{ $lifetime }}"
:oauth-provider="{{ json_encode($oauth) }}"
></session-expiry>
:auth="{{ json_encode($auth) }}"
></session-expiry>
17 changes: 10 additions & 7 deletions routes/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
Expand Down
13 changes: 12 additions & 1 deletion src/Exceptions/AuthenticationException.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
}
}
1 change: 1 addition & 0 deletions src/Http/View/Composers/SessionExpiryComposer.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ public function compose(View $view)
'lifetime' => config('session.lifetime') * 60,
'warnAt' => 60,
'oauth' => $this->oauth(),
'auth' => config('statamic.cp.auth'),
]);
}

Expand Down
37 changes: 37 additions & 0 deletions tests/Feature/AuthenticationTest.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
<?php

namespace Tests\Feature;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class AuthenticationTest extends TestCase
{
#[Test]
public function it_responds_with_a_401_when_requesting_json()
{
$this->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);
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e5e4a73
Introduce new config setting
duncanmcclean Feb 27, 2023
895bbcf
Only register login/reset password routes when auth is enabled
duncanmcclean Feb 27, 2023
3b0249d
`abort(401)` when CP Auth is disabled
duncanmcclean Feb 27, 2023
546ed77
Add test to ensure front-end login form still works
duncanmcclean Feb 27, 2023
66abbd1
Apply StyleCI changes
duncanmcclean Feb 27, 2023
0caa8cf
Merge branch '3.4' into feature/disable-login-and-reset-password-pages
duncanmcclean Apr 25, 2023
638e304
StyleCI fixes
duncanmcclean Apr 25, 2023
977f830
Merge branch '4.x' into feature/disable-login-and-reset-password-pages
edalzell May 20, 2023
0b2bd55
Merge branch '4.x' into pr/7617
duncanmcclean Nov 9, 2023
fc66e42
Pint
duncanmcclean Nov 9, 2023
8bffc14
Move logout route out of if statement
duncanmcclean Nov 9, 2023
e656efc
Ensure users can register via user:register_form tag when CP auth is …
duncanmcclean Nov 9, 2023
029ac6c
Ensure users can reset password via tags when CP auth is disabled
duncanmcclean Nov 9, 2023
80ccbf5
Allow specifying a redirect URL for when authentication is disabled
duncanmcclean Nov 9, 2023
b54cb0a
Reference updated config key
duncanmcclean Feb 24, 2024
e39db24
Merge branch '4.x' into pr/7617
duncanmcclean Feb 24, 2024
bc10aa2
Make sure `intended` is set
edalzell Apr 21, 2024
4711a50
use the helper instead
duncanmcclean Apr 22, 2024
3a3c403
Merge remote-tracking branch 'origin/5.x' into pr/7617
duncanmcclean May 13, 2024
54a3e83
Redirect to login page after session expires
duncanmcclean May 13, 2024
1f2fc1b
Merge branch '5.x' into pr/7617
jasonvarga Jun 27, 2024
047c97d
Dont redirect at zero ...
jasonvarga Jun 27, 2024
a5198ed
these tests dont really prove anything
jasonvarga Jun 27, 2024
d402365
words
jasonvarga Jun 27, 2024
710a191
add tests
jasonvarga Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand Down
10 changes: 7 additions & 3 deletions resources/js/components/SessionExpiry.vue
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,8 @@ export default {
warnAt: Number,
lifetime: Number,
email: String,
oauthProvider: String
oauthProvider: String,
auth: Object,
},

data() {
Expand DownExpand Up@@ -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.
Expand DownExpand Up@@ -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;
Expand Down
3 changes: 2 additions & 1 deletion resources/views/partials/session-expiry.blade.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,4 +3,5 @@
:warn-at="{{ $warnAt }}"
:lifetime="{{ $lifetime }}"
:oauth-provider="{{ json_encode($oauth) }}"
></session-expiry>
:auth="{{ json_encode($auth) }}"
></session-expiry>
17 changes: 10 additions & 7 deletions routes/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
Expand Down
13 changes: 12 additions & 1 deletion src/Exceptions/AuthenticationException.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
}
}
1 change: 1 addition & 0 deletions src/Http/View/Composers/SessionExpiryComposer.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ public function compose(View $view)
'lifetime' => config('session.lifetime') * 60,
'warnAt' => 60,
'oauth' => $this->oauth(),
'auth' => config('statamic.cp.auth'),
]);
}

Expand Down
37 changes: 37 additions & 0 deletions tests/Feature/AuthenticationTest.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
<?php

namespace Tests\Feature;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class AuthenticationTest extends TestCase
{
#[Test]
public function it_responds_with_a_401_when_requesting_json()
{
$this->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);
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e5e4a73
Introduce new config setting
duncanmcclean Feb 27, 2023
895bbcf
Only register login/reset password routes when auth is enabled
duncanmcclean Feb 27, 2023
3b0249d
`abort(401)` when CP Auth is disabled
duncanmcclean Feb 27, 2023
546ed77
Add test to ensure front-end login form still works
duncanmcclean Feb 27, 2023
66abbd1
Apply StyleCI changes
duncanmcclean Feb 27, 2023
0caa8cf
Merge branch '3.4' into feature/disable-login-and-reset-password-pages
duncanmcclean Apr 25, 2023
638e304
StyleCI fixes
duncanmcclean Apr 25, 2023
977f830
Merge branch '4.x' into feature/disable-login-and-reset-password-pages
edalzell May 20, 2023
0b2bd55
Merge branch '4.x' into pr/7617
duncanmcclean Nov 9, 2023
fc66e42
Pint
duncanmcclean Nov 9, 2023
8bffc14
Move logout route out of if statement
duncanmcclean Nov 9, 2023
e656efc
Ensure users can register via user:register_form tag when CP auth is …
duncanmcclean Nov 9, 2023
029ac6c
Ensure users can reset password via tags when CP auth is disabled
duncanmcclean Nov 9, 2023
80ccbf5
Allow specifying a redirect URL for when authentication is disabled
duncanmcclean Nov 9, 2023
b54cb0a
Reference updated config key
duncanmcclean Feb 24, 2024
e39db24
Merge branch '4.x' into pr/7617
duncanmcclean Feb 24, 2024
bc10aa2
Make sure `intended` is set
edalzell Apr 21, 2024
4711a50
use the helper instead
duncanmcclean Apr 22, 2024
3a3c403
Merge remote-tracking branch 'origin/5.x' into pr/7617
duncanmcclean May 13, 2024
54a3e83
Redirect to login page after session expires
duncanmcclean May 13, 2024
1f2fc1b
Merge branch '5.x' into pr/7617
jasonvarga Jun 27, 2024
047c97d
Dont redirect at zero ...
jasonvarga Jun 27, 2024
a5198ed
these tests dont really prove anything
jasonvarga Jun 27, 2024
d402365
words
jasonvarga Jun 27, 2024
710a191
add tests
jasonvarga Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand Down
10 changes: 7 additions & 3 deletions resources/js/components/SessionExpiry.vue
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,8 @@ export default {
warnAt: Number,
lifetime: Number,
email: String,
oauthProvider: String
oauthProvider: String,
auth: Object,
},

data() {
Expand DownExpand Up@@ -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.
Expand DownExpand Up@@ -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;
Expand Down
3 changes: 2 additions & 1 deletion resources/views/partials/session-expiry.blade.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,4 +3,5 @@
:warn-at="{{ $warnAt }}"
:lifetime="{{ $lifetime }}"
:oauth-provider="{{ json_encode($oauth) }}"
></session-expiry>
:auth="{{ json_encode($auth) }}"
></session-expiry>
17 changes: 10 additions & 7 deletions routes/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
Expand Down
13 changes: 12 additions & 1 deletion src/Exceptions/AuthenticationException.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
}
}
1 change: 1 addition & 0 deletions src/Http/View/Composers/SessionExpiryComposer.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ public function compose(View $view)
'lifetime' => config('session.lifetime') * 60,
'warnAt' => 60,
'oauth' => $this->oauth(),
'auth' => config('statamic.cp.auth'),
]);
}

Expand Down
37 changes: 37 additions & 0 deletions tests/Feature/AuthenticationTest.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
<?php

namespace Tests\Feature;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class AuthenticationTest extends TestCase
{
#[Test]
public function it_responds_with_a_401_when_requesting_json()
{
$this->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);
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e5e4a73
Introduce new config setting
duncanmcclean Feb 27, 2023
895bbcf
Only register login/reset password routes when auth is enabled
duncanmcclean Feb 27, 2023
3b0249d
`abort(401)` when CP Auth is disabled
duncanmcclean Feb 27, 2023
546ed77
Add test to ensure front-end login form still works
duncanmcclean Feb 27, 2023
66abbd1
Apply StyleCI changes
duncanmcclean Feb 27, 2023
0caa8cf
Merge branch '3.4' into feature/disable-login-and-reset-password-pages
duncanmcclean Apr 25, 2023
638e304
StyleCI fixes
duncanmcclean Apr 25, 2023
977f830
Merge branch '4.x' into feature/disable-login-and-reset-password-pages
edalzell May 20, 2023
0b2bd55
Merge branch '4.x' into pr/7617
duncanmcclean Nov 9, 2023
fc66e42
Pint
duncanmcclean Nov 9, 2023
8bffc14
Move logout route out of if statement
duncanmcclean Nov 9, 2023
e656efc
Ensure users can register via user:register_form tag when CP auth is …
duncanmcclean Nov 9, 2023
029ac6c
Ensure users can reset password via tags when CP auth is disabled
duncanmcclean Nov 9, 2023
80ccbf5
Allow specifying a redirect URL for when authentication is disabled
duncanmcclean Nov 9, 2023
b54cb0a
Reference updated config key
duncanmcclean Feb 24, 2024
e39db24
Merge branch '4.x' into pr/7617
duncanmcclean Feb 24, 2024
bc10aa2
Make sure `intended` is set
edalzell Apr 21, 2024
4711a50
use the helper instead
duncanmcclean Apr 22, 2024
3a3c403
Merge remote-tracking branch 'origin/5.x' into pr/7617
duncanmcclean May 13, 2024
54a3e83
Redirect to login page after session expires
duncanmcclean May 13, 2024
1f2fc1b
Merge branch '5.x' into pr/7617
jasonvarga Jun 27, 2024
047c97d
Dont redirect at zero ...
jasonvarga Jun 27, 2024
a5198ed
these tests dont really prove anything
jasonvarga Jun 27, 2024
d402365
words
jasonvarga Jun 27, 2024
710a191
add tests
jasonvarga Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand Down
10 changes: 7 additions & 3 deletions resources/js/components/SessionExpiry.vue
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,8 @@ export default {
warnAt: Number,
lifetime: Number,
email: String,
oauthProvider: String
oauthProvider: String,
auth: Object,
},

data() {
Expand DownExpand Up@@ -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.
Expand DownExpand Up@@ -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;
Expand Down
3 changes: 2 additions & 1 deletion resources/views/partials/session-expiry.blade.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,4 +3,5 @@
:warn-at="{{ $warnAt }}"
:lifetime="{{ $lifetime }}"
:oauth-provider="{{ json_encode($oauth) }}"
></session-expiry>
:auth="{{ json_encode($auth) }}"
></session-expiry>
17 changes: 10 additions & 7 deletions routes/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
Expand Down
13 changes: 12 additions & 1 deletion src/Exceptions/AuthenticationException.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
}
}
1 change: 1 addition & 0 deletions src/Http/View/Composers/SessionExpiryComposer.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ public function compose(View $view)
'lifetime' => config('session.lifetime') * 60,
'warnAt' => 60,
'oauth' => $this->oauth(),
'auth' => config('statamic.cp.auth'),
]);
}

Expand Down
37 changes: 37 additions & 0 deletions tests/Feature/AuthenticationTest.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
<?php

namespace Tests\Feature;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class AuthenticationTest extends TestCase
{
#[Test]
public function it_responds_with_a_401_when_requesting_json()
{
$this->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);
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e5e4a73
Introduce new config setting
duncanmcclean Feb 27, 2023
895bbcf
Only register login/reset password routes when auth is enabled
duncanmcclean Feb 27, 2023
3b0249d
`abort(401)` when CP Auth is disabled
duncanmcclean Feb 27, 2023
546ed77
Add test to ensure front-end login form still works
duncanmcclean Feb 27, 2023
66abbd1
Apply StyleCI changes
duncanmcclean Feb 27, 2023
0caa8cf
Merge branch '3.4' into feature/disable-login-and-reset-password-pages
duncanmcclean Apr 25, 2023
638e304
StyleCI fixes
duncanmcclean Apr 25, 2023
977f830
Merge branch '4.x' into feature/disable-login-and-reset-password-pages
edalzell May 20, 2023
0b2bd55
Merge branch '4.x' into pr/7617
duncanmcclean Nov 9, 2023
fc66e42
Pint
duncanmcclean Nov 9, 2023
8bffc14
Move logout route out of if statement
duncanmcclean Nov 9, 2023
e656efc
Ensure users can register via user:register_form tag when CP auth is …
duncanmcclean Nov 9, 2023
029ac6c
Ensure users can reset password via tags when CP auth is disabled
duncanmcclean Nov 9, 2023
80ccbf5
Allow specifying a redirect URL for when authentication is disabled
duncanmcclean Nov 9, 2023
b54cb0a
Reference updated config key
duncanmcclean Feb 24, 2024
e39db24
Merge branch '4.x' into pr/7617
duncanmcclean Feb 24, 2024
bc10aa2
Make sure `intended` is set
edalzell Apr 21, 2024
4711a50
use the helper instead
duncanmcclean Apr 22, 2024
3a3c403
Merge remote-tracking branch 'origin/5.x' into pr/7617
duncanmcclean May 13, 2024
54a3e83
Redirect to login page after session expires
duncanmcclean May 13, 2024
1f2fc1b
Merge branch '5.x' into pr/7617
jasonvarga Jun 27, 2024
047c97d
Dont redirect at zero ...
jasonvarga Jun 27, 2024
a5198ed
these tests dont really prove anything
jasonvarga Jun 27, 2024
d402365
words
jasonvarga Jun 27, 2024
710a191
add tests
jasonvarga Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand Down
10 changes: 7 additions & 3 deletions resources/js/components/SessionExpiry.vue
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,8 @@ export default {
warnAt: Number,
lifetime: Number,
email: String,
oauthProvider: String
oauthProvider: String,
auth: Object,
},

data() {
Expand DownExpand Up@@ -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.
Expand DownExpand Up@@ -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;
Expand Down
3 changes: 2 additions & 1 deletion resources/views/partials/session-expiry.blade.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,4 +3,5 @@
:warn-at="{{ $warnAt }}"
:lifetime="{{ $lifetime }}"
:oauth-provider="{{ json_encode($oauth) }}"
></session-expiry>
:auth="{{ json_encode($auth) }}"
></session-expiry>
17 changes: 10 additions & 7 deletions routes/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
Expand Down
13 changes: 12 additions & 1 deletion src/Exceptions/AuthenticationException.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
}
}
1 change: 1 addition & 0 deletions src/Http/View/Composers/SessionExpiryComposer.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ public function compose(View $view)
'lifetime' => config('session.lifetime') * 60,
'warnAt' => 60,
'oauth' => $this->oauth(),
'auth' => config('statamic.cp.auth'),
]);
}

Expand Down
37 changes: 37 additions & 0 deletions tests/Feature/AuthenticationTest.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
<?php

namespace Tests\Feature;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class AuthenticationTest extends TestCase
{
#[Test]
public function it_responds_with_a_401_when_requesting_json()
{
$this->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);
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e5e4a73
Introduce new config setting
duncanmcclean Feb 27, 2023
895bbcf
Only register login/reset password routes when auth is enabled
duncanmcclean Feb 27, 2023
3b0249d
`abort(401)` when CP Auth is disabled
duncanmcclean Feb 27, 2023
546ed77
Add test to ensure front-end login form still works
duncanmcclean Feb 27, 2023
66abbd1
Apply StyleCI changes
duncanmcclean Feb 27, 2023
0caa8cf
Merge branch '3.4' into feature/disable-login-and-reset-password-pages
duncanmcclean Apr 25, 2023
638e304
StyleCI fixes
duncanmcclean Apr 25, 2023
977f830
Merge branch '4.x' into feature/disable-login-and-reset-password-pages
edalzell May 20, 2023
0b2bd55
Merge branch '4.x' into pr/7617
duncanmcclean Nov 9, 2023
fc66e42
Pint
duncanmcclean Nov 9, 2023
8bffc14
Move logout route out of if statement
duncanmcclean Nov 9, 2023
e656efc
Ensure users can register via user:register_form tag when CP auth is …
duncanmcclean Nov 9, 2023
029ac6c
Ensure users can reset password via tags when CP auth is disabled
duncanmcclean Nov 9, 2023
80ccbf5
Allow specifying a redirect URL for when authentication is disabled
duncanmcclean Nov 9, 2023
b54cb0a
Reference updated config key
duncanmcclean Feb 24, 2024
e39db24
Merge branch '4.x' into pr/7617
duncanmcclean Feb 24, 2024
bc10aa2
Make sure `intended` is set
edalzell Apr 21, 2024
4711a50
use the helper instead
duncanmcclean Apr 22, 2024
3a3c403
Merge remote-tracking branch 'origin/5.x' into pr/7617
duncanmcclean May 13, 2024
54a3e83
Redirect to login page after session expires
duncanmcclean May 13, 2024
1f2fc1b
Merge branch '5.x' into pr/7617
jasonvarga Jun 27, 2024
047c97d
Dont redirect at zero ...
jasonvarga Jun 27, 2024
a5198ed
these tests dont really prove anything
jasonvarga Jun 27, 2024
d402365
words
jasonvarga Jun 27, 2024
710a191
add tests
jasonvarga Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand Down
10 changes: 7 additions & 3 deletions resources/js/components/SessionExpiry.vue
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,8 @@ export default {
warnAt: Number,
lifetime: Number,
email: String,
oauthProvider: String
oauthProvider: String,
auth: Object,
},

data() {
Expand DownExpand Up@@ -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.
Expand DownExpand Up@@ -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;
Expand Down
3 changes: 2 additions & 1 deletion resources/views/partials/session-expiry.blade.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,4 +3,5 @@
:warn-at="{{ $warnAt }}"
:lifetime="{{ $lifetime }}"
:oauth-provider="{{ json_encode($oauth) }}"
></session-expiry>
:auth="{{ json_encode($auth) }}"
></session-expiry>
17 changes: 10 additions & 7 deletions routes/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
Expand Down
13 changes: 12 additions & 1 deletion src/Exceptions/AuthenticationException.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
}
}
1 change: 1 addition & 0 deletions src/Http/View/Composers/SessionExpiryComposer.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ public function compose(View $view)
'lifetime' => config('session.lifetime') * 60,
'warnAt' => 60,
'oauth' => $this->oauth(),
'auth' => config('statamic.cp.auth'),
]);
}

Expand Down
37 changes: 37 additions & 0 deletions tests/Feature/AuthenticationTest.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
<?php

namespace Tests\Feature;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class AuthenticationTest extends TestCase
{
#[Test]
public function it_responds_with_a_401_when_requesting_json()
{
$this->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);
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e5e4a73
Introduce new config setting
duncanmcclean Feb 27, 2023
895bbcf
Only register login/reset password routes when auth is enabled
duncanmcclean Feb 27, 2023
3b0249d
`abort(401)` when CP Auth is disabled
duncanmcclean Feb 27, 2023
546ed77
Add test to ensure front-end login form still works
duncanmcclean Feb 27, 2023
66abbd1
Apply StyleCI changes
duncanmcclean Feb 27, 2023
0caa8cf
Merge branch '3.4' into feature/disable-login-and-reset-password-pages
duncanmcclean Apr 25, 2023
638e304
StyleCI fixes
duncanmcclean Apr 25, 2023
977f830
Merge branch '4.x' into feature/disable-login-and-reset-password-pages
edalzell May 20, 2023
0b2bd55
Merge branch '4.x' into pr/7617
duncanmcclean Nov 9, 2023
fc66e42
Pint
duncanmcclean Nov 9, 2023
8bffc14
Move logout route out of if statement
duncanmcclean Nov 9, 2023
e656efc
Ensure users can register via user:register_form tag when CP auth is …
duncanmcclean Nov 9, 2023
029ac6c
Ensure users can reset password via tags when CP auth is disabled
duncanmcclean Nov 9, 2023
80ccbf5
Allow specifying a redirect URL for when authentication is disabled
duncanmcclean Nov 9, 2023
b54cb0a
Reference updated config key
duncanmcclean Feb 24, 2024
e39db24
Merge branch '4.x' into pr/7617
duncanmcclean Feb 24, 2024
bc10aa2
Make sure `intended` is set
edalzell Apr 21, 2024
4711a50
use the helper instead
duncanmcclean Apr 22, 2024
3a3c403
Merge remote-tracking branch 'origin/5.x' into pr/7617
duncanmcclean May 13, 2024
54a3e83
Redirect to login page after session expires
duncanmcclean May 13, 2024
1f2fc1b
Merge branch '5.x' into pr/7617
jasonvarga Jun 27, 2024
047c97d
Dont redirect at zero ...
jasonvarga Jun 27, 2024
a5198ed
these tests dont really prove anything
jasonvarga Jun 27, 2024
d402365
words
jasonvarga Jun 27, 2024
710a191
add tests
jasonvarga Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand Down
10 changes: 7 additions & 3 deletions resources/js/components/SessionExpiry.vue
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,8 @@ export default {
warnAt: Number,
lifetime: Number,
email: String,
oauthProvider: String
oauthProvider: String,
auth: Object,
},

data() {
Expand DownExpand Up@@ -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.
Expand DownExpand Up@@ -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;
Expand Down
3 changes: 2 additions & 1 deletion resources/views/partials/session-expiry.blade.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,4 +3,5 @@
:warn-at="{{ $warnAt }}"
:lifetime="{{ $lifetime }}"
:oauth-provider="{{ json_encode($oauth) }}"
></session-expiry>
:auth="{{ json_encode($auth) }}"
></session-expiry>
17 changes: 10 additions & 7 deletions routes/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
Expand Down
13 changes: 12 additions & 1 deletion src/Exceptions/AuthenticationException.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
}
}
1 change: 1 addition & 0 deletions src/Http/View/Composers/SessionExpiryComposer.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ public function compose(View $view)
'lifetime' => config('session.lifetime') * 60,
'warnAt' => 60,
'oauth' => $this->oauth(),
'auth' => config('statamic.cp.auth'),
]);
}

Expand Down
37 changes: 37 additions & 0 deletions tests/Feature/AuthenticationTest.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
<?php

namespace Tests\Feature;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class AuthenticationTest extends TestCase
{
#[Test]
public function it_responds_with_a_401_when_requesting_json()
{
$this->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);
}
}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e5e4a73
Introduce new config setting
duncanmcclean Feb 27, 2023
895bbcf
Only register login/reset password routes when auth is enabled
duncanmcclean Feb 27, 2023
3b0249d
`abort(401)` when CP Auth is disabled
duncanmcclean Feb 27, 2023
546ed77
Add test to ensure front-end login form still works
duncanmcclean Feb 27, 2023
66abbd1
Apply StyleCI changes
duncanmcclean Feb 27, 2023
0caa8cf
Merge branch '3.4' into feature/disable-login-and-reset-password-pages
duncanmcclean Apr 25, 2023
638e304
StyleCI fixes
duncanmcclean Apr 25, 2023
977f830
Merge branch '4.x' into feature/disable-login-and-reset-password-pages
edalzell May 20, 2023
0b2bd55
Merge branch '4.x' into pr/7617
duncanmcclean Nov 9, 2023
fc66e42
Pint
duncanmcclean Nov 9, 2023
8bffc14
Move logout route out of if statement
duncanmcclean Nov 9, 2023
e656efc
Ensure users can register via user:register_form tag when CP auth is …
duncanmcclean Nov 9, 2023
029ac6c
Ensure users can reset password via tags when CP auth is disabled
duncanmcclean Nov 9, 2023
80ccbf5
Allow specifying a redirect URL for when authentication is disabled
duncanmcclean Nov 9, 2023
b54cb0a
Reference updated config key
duncanmcclean Feb 24, 2024
e39db24
Merge branch '4.x' into pr/7617
duncanmcclean Feb 24, 2024
bc10aa2
Make sure `intended` is set
edalzell Apr 21, 2024
4711a50
use the helper instead
duncanmcclean Apr 22, 2024
3a3c403
Merge remote-tracking branch 'origin/5.x' into pr/7617
duncanmcclean May 13, 2024
54a3e83
Redirect to login page after session expires
duncanmcclean May 13, 2024
1f2fc1b
Merge branch '5.x' into pr/7617
jasonvarga Jun 27, 2024
047c97d
Dont redirect at zero ...
jasonvarga Jun 27, 2024
a5198ed
these tests dont really prove anything
jasonvarga Jun 27, 2024
d402365
words
jasonvarga Jun 27, 2024
710a191
add tests
jasonvarga Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand Down
10 changes: 7 additions & 3 deletions resources/js/components/SessionExpiry.vue
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,8 @@ export default {
warnAt: Number,
lifetime: Number,
email: String,
oauthProvider: String
oauthProvider: String,
auth: Object,
},

data() {
Expand DownExpand Up@@ -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.
Expand DownExpand Up@@ -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;
Expand Down
3 changes: 2 additions & 1 deletion resources/views/partials/session-expiry.blade.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,4 +3,5 @@
:warn-at="{{ $warnAt }}"
:lifetime="{{ $lifetime }}"
:oauth-provider="{{ json_encode($oauth) }}"
></session-expiry>
:auth="{{ json_encode($auth) }}"
></session-expiry>
17 changes: 10 additions & 7 deletions routes/cp.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
Expand Down
13 changes: 12 additions & 1 deletion src/Exceptions/AuthenticationException.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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');
}
}
1 change: 1 addition & 0 deletions src/Http/View/Composers/SessionExpiryComposer.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@ public function compose(View $view)
'lifetime' => config('session.lifetime') * 60,
'warnAt' => 60,
'oauth' => $this->oauth(),
'auth' => config('statamic.cp.auth'),
]);
}

Expand Down
37 changes: 37 additions & 0 deletions tests/Feature/AuthenticationTest.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
<?php

namespace Tests\Feature;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class AuthenticationTest extends TestCase
{
#[Test]
public function it_responds_with_a_401_when_requesting_json()
{
$this->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);
}
}