Merge pull request #7432 from pixelfed/fix/pulse-horizon-admin-routes

pulse and horizon - Moved both to admin/horizon and admin/pulse (and fix pulse)
pull/7439/head
Shlee 1 day ago committed by GitHub
commit 7bc28bc740
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -112,7 +112,9 @@ class AppServiceProvider extends ServiceProvider
Event::listen(Failed::class, LogFailedLogin::class);
Gate::define('viewPulse', function (User $user) {
return $user->is_admin === 1;
// is_admin is cast to bool on the User model, so a strict `=== 1`
// never matches. Use a boolean check, consistent with viewHorizon.
return (bool) $user->is_admin === true;
});
if (config('pulse.enabled', false)) {
@ -230,6 +232,7 @@ class AppServiceProvider extends ServiceProvider
public function register()
{
Passport::ignoreRoutes();
Pulse::ignoreRoutes();
$this->app->bind(UserOidcService::class, function () {
return UserOidcService::build();

@ -3,6 +3,7 @@
namespace App\Providers;
use App\Services\Captcha\CaptchaManager;
use Illuminate\Container\Container;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;
@ -11,7 +12,12 @@ class CaptchaServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton('captcha.manager', fn ($app): CaptchaManager => new CaptchaManager($app));
// Resolve the current container via Container::getInstance() rather than
// capturing the $app injected at boot time, so the manager never holds a
// stale container instance across requests under Octane.
$this->app->singleton('captcha.manager', function (): CaptchaManager {
return new CaptchaManager(Container::getInstance());
});
$this->app->alias('captcha.manager', CaptchaManager::class);
}

@ -99,9 +99,6 @@ class RestrictedNames
'mix-manifest.json',
'robots.txt',
// Laravel Horizon
'horizon',
// Reserved routes
'a',
'app',

@ -24,7 +24,7 @@
"laravel/framework": "^13.0",
"laravel/horizon": "^5.0",
"laravel/passport": "^13.4.4",
"laravel/pulse": "^1.3",
"laravel/pulse": "^1.8",
"laravel/sanctum": "^4.3",
"laravel/tinker": "^3.0",
"laravel/ui": "^4.2",

211
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "03d710d525be0612fac492c42e960daa",
"content-hash": "af40964f6e2cbfc1eb1aadef670e4c5c",
"packages": [
{
"name": "aws/aws-crt-php",
@ -62,16 +62,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.394.5",
"version": "3.395.7",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "12c80170a12c9e9e5d9057bff4d197979631624f"
"reference": "575425a1c7c1dabb808badc7995e67874cba102a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/12c80170a12c9e9e5d9057bff4d197979631624f",
"reference": "12c80170a12c9e9e5d9057bff4d197979631624f",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/575425a1c7c1dabb808badc7995e67874cba102a",
"reference": "575425a1c7c1dabb808badc7995e67874cba102a",
"shasum": ""
},
"require": {
@ -153,9 +153,9 @@
"support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.394.5"
"source": "https://github.com/aws/aws-sdk-php/tree/3.395.7"
},
"time": "2026-08-31T22:10:11+00:00"
"time": "2026-09-21T18:22:34+00:00"
},
{
"name": "bacon/bacon-qr-code",
@ -938,16 +938,16 @@
},
{
"name": "ezyang/htmlpurifier",
"version": "v4.19.0",
"version": "v4.19.1",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf"
"reference": "5e0539132d934f936fbae2c88b55d58f9438623f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf",
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/5e0539132d934f936fbae2c88b55d58f9438623f",
"reference": "5e0539132d934f936fbae2c88b55d58f9438623f",
"shasum": ""
},
"require": {
@ -993,32 +993,32 @@
],
"support": {
"issues": "https://github.com/ezyang/htmlpurifier/issues",
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0"
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.1"
},
"time": "2025-10-17T16:34:55+00:00"
"time": "2026-09-15T15:42:51+00:00"
},
{
"name": "firebase/php-jwt",
"version": "v7.1.0",
"version": "v7.2.0",
"source": {
"type": "git",
"url": "https://github.com/googleapis/php-jwt.git",
"reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0"
"reference": "f502cdbf279cd7532060b041f7c22a05208c0b93"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/googleapis/php-jwt/zipball/b374a5d1a4f1f67fadc2165cdb284645945e2fc0",
"reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0",
"url": "https://api.github.com/repos/googleapis/php-jwt/zipball/f502cdbf279cd7532060b041f7c22a05208c0b93",
"reference": "f502cdbf279cd7532060b041f7c22a05208c0b93",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.4",
"guzzlehttp/guzzle": "^7.4||^8.0",
"phpfastcache/phpfastcache": "^9.2",
"phpseclib/phpseclib": "~3.0",
"phpspec/prophecy-phpunit": "^2.0",
"phpspec/prophecy-phpunit": "^2.2",
"phpunit/phpunit": "^9.5",
"psr/cache": "^2.0||^3.0",
"psr/http-client": "^1.0",
@ -1030,6 +1030,14 @@
"phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures"
},
"type": "library",
"extra": {
"component": {
"id": "jwt",
"path": "Jwt",
"entry": "README.md",
"target": "googleapis/php-jwt.git"
}
},
"autoload": {
"psr-4": {
"Firebase\\JWT\\": "src"
@ -1059,9 +1067,9 @@
],
"support": {
"issues": "https://github.com/googleapis/php-jwt/issues",
"source": "https://github.com/googleapis/php-jwt/tree/v7.1.0"
"source": "https://github.com/googleapis/php-jwt/tree/v7.2.0"
},
"time": "2026-06-11T17:54:14+00:00"
"time": "2026-09-21T19:29:58+00:00"
},
{
"name": "fruitcake/php-cors",
@ -3031,16 +3039,16 @@
},
{
"name": "league/commonmark",
"version": "2.10.1",
"version": "2.10.3",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "9d489ab67a02960fd8ffe624d93f751daf95439e"
"reference": "6efbd9c472b91db0a3350fcd601c8332c2382e1f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/9d489ab67a02960fd8ffe624d93f751daf95439e",
"reference": "9d489ab67a02960fd8ffe624d93f751daf95439e",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6efbd9c472b91db0a3350fcd601c8332c2382e1f",
"reference": "6efbd9c472b91db0a3350fcd601c8332c2382e1f",
"shasum": ""
},
"require": {
@ -3134,7 +3142,7 @@
"type": "tidelift"
}
],
"time": "2026-09-07T13:44:26+00:00"
"time": "2026-09-21T13:07:34+00:00"
},
{
"name": "league/config",
@ -3934,16 +3942,16 @@
},
{
"name": "livewire/livewire",
"version": "v4.4.3",
"version": "v4.4.6",
"source": {
"type": "git",
"url": "https://github.com/livewire/livewire.git",
"reference": "92f1427022714b34024459167aa6a85d4fb4af44"
"reference": "c6b7db7a92103ac2738e8fdc926cecc93eb67853"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/livewire/livewire/zipball/92f1427022714b34024459167aa6a85d4fb4af44",
"reference": "92f1427022714b34024459167aa6a85d4fb4af44",
"url": "https://api.github.com/repos/livewire/livewire/zipball/c6b7db7a92103ac2738e8fdc926cecc93eb67853",
"reference": "c6b7db7a92103ac2738e8fdc926cecc93eb67853",
"shasum": ""
},
"require": {
@ -3998,7 +4006,7 @@
"description": "A front-end framework for Laravel.",
"support": {
"issues": "https://github.com/livewire/livewire/issues",
"source": "https://github.com/livewire/livewire/tree/v4.4.3"
"source": "https://github.com/livewire/livewire/tree/v4.4.6"
},
"funding": [
{
@ -4006,7 +4014,7 @@
"type": "github"
}
],
"time": "2026-08-31T15:40:58+00:00"
"time": "2026-09-21T18:51:23+00:00"
},
{
"name": "matomo/device-detector",
@ -4646,16 +4654,16 @@
},
{
"name": "nikic/php-parser",
"version": "v5.8.0",
"version": "v5.9.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f"
"reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f",
"reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def",
"reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def",
"shasum": ""
},
"require": {
@ -4697,9 +4705,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0"
},
"time": "2026-07-04T14:30:18+00:00"
"time": "2026-09-13T18:51:52+00:00"
},
{
"name": "nunomaduro/termwind",
@ -7300,20 +7308,20 @@
},
{
"name": "spomky-labs/pki-framework",
"version": "1.6.2",
"version": "1.6.3",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/pki-framework.git",
"reference": "8f333bebe104ea24f1a160a6fb69e2dead4f1882"
"reference": "792e909d4e387adffe3c4f404451c7d57a3d2022"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/8f333bebe104ea24f1a160a6fb69e2dead4f1882",
"reference": "8f333bebe104ea24f1a160a6fb69e2dead4f1882",
"url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/792e909d4e387adffe3c4f404451c7d57a3d2022",
"reference": "792e909d4e387adffe3c4f404451c7d57a3d2022",
"shasum": ""
},
"require": {
"brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17|^0.18|^0.19|^0.20",
"brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17|^0.18|^0.19|^0.20|^1.0",
"ext-mbstring": "*",
"php": ">=8.1"
},
@ -7394,7 +7402,7 @@
],
"support": {
"issues": "https://github.com/Spomky-Labs/pki-framework/issues",
"source": "https://github.com/Spomky-Labs/pki-framework/tree/1.6.2"
"source": "https://github.com/Spomky-Labs/pki-framework/tree/1.6.3"
},
"funding": [
{
@ -7406,7 +7414,7 @@
"type": "patreon"
}
],
"time": "2026-09-08T07:15:08+00:00"
"time": "2026-09-12T19:02:49+00:00"
},
{
"name": "stevebauman/purify",
@ -7476,16 +7484,16 @@
},
{
"name": "symfony/cache",
"version": "v7.4.18",
"version": "v7.4.19",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
"reference": "6c521e19a99e8ae57e22aa85b9271534513ee850"
"reference": "e037fd41e9f8ec9ac270e2a4c76a1992d18d808c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/cache/zipball/6c521e19a99e8ae57e22aa85b9271534513ee850",
"reference": "6c521e19a99e8ae57e22aa85b9271534513ee850",
"url": "https://api.github.com/repos/symfony/cache/zipball/e037fd41e9f8ec9ac270e2a4c76a1992d18d808c",
"reference": "e037fd41e9f8ec9ac270e2a4c76a1992d18d808c",
"shasum": ""
},
"require": {
@ -7555,7 +7563,7 @@
"psr6"
],
"support": {
"source": "https://github.com/symfony/cache/tree/v7.4.18"
"source": "https://github.com/symfony/cache/tree/v7.4.19"
},
"funding": [
{
@ -7575,7 +7583,7 @@
"type": "tidelift"
}
],
"time": "2026-08-30T20:10:52+00:00"
"time": "2026-09-08T13:28:43+00:00"
},
{
"name": "symfony/cache-contracts",
@ -8360,16 +8368,16 @@
},
{
"name": "symfony/http-client",
"version": "v7.4.18",
"version": "v7.4.19",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "68d81f78d127984ff2e741f0e0d9cb1078f8f3ea"
"reference": "3a523f38dc399337ec45b2ab9628e8c37ceb23c7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/68d81f78d127984ff2e741f0e0d9cb1078f8f3ea",
"reference": "68d81f78d127984ff2e741f0e0d9cb1078f8f3ea",
"url": "https://api.github.com/repos/symfony/http-client/zipball/3a523f38dc399337ec45b2ab9628e8c37ceb23c7",
"reference": "3a523f38dc399337ec45b2ab9628e8c37ceb23c7",
"shasum": ""
},
"require": {
@ -8437,7 +8445,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.4.18"
"source": "https://github.com/symfony/http-client/tree/v7.4.19"
},
"funding": [
{
@ -8457,7 +8465,7 @@
"type": "tidelift"
}
],
"time": "2026-08-30T13:49:59+00:00"
"time": "2026-09-08T13:28:43+00:00"
},
{
"name": "symfony/http-client-contracts",
@ -10956,20 +10964,20 @@
},
{
"name": "web-token/jwt-library",
"version": "4.2.2",
"version": "4.2.3",
"source": {
"type": "git",
"url": "https://github.com/web-token/jwt-library.git",
"reference": "ae642340ee2ca91ca0c37edd72a9d06302651ca9"
"reference": "db962c3ad8bc91c531a49dd373f0a641fc507bfd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-token/jwt-library/zipball/ae642340ee2ca91ca0c37edd72a9d06302651ca9",
"reference": "ae642340ee2ca91ca0c37edd72a9d06302651ca9",
"url": "https://api.github.com/repos/web-token/jwt-library/zipball/db962c3ad8bc91c531a49dd373f0a641fc507bfd",
"reference": "db962c3ad8bc91c531a49dd373f0a641fc507bfd",
"shasum": ""
},
"require": {
"brick/math": "^0.12|^0.13|^0.14|^0.15|^0.16|^0.17|^0.18|^0.19|^0.20",
"brick/math": "^0.12|^0.13|^0.14|^0.15|^0.16|^0.17|^0.18|^0.19|^0.20|^1.0",
"php": ">=8.2",
"psr/clock": "^1.0",
"spomky-labs/pki-framework": "^1.2.1",
@ -11030,7 +11038,7 @@
],
"support": {
"issues": "https://github.com/web-token/jwt-library/issues",
"source": "https://github.com/web-token/jwt-library/tree/4.2.2"
"source": "https://github.com/web-token/jwt-library/tree/4.2.3"
},
"funding": [
{
@ -11042,7 +11050,7 @@
"type": "patreon"
}
],
"time": "2026-08-30T13:53:36+00:00"
"time": "2026-09-12T18:50:35+00:00"
}
],
"packages-dev": [
@ -11671,16 +11679,16 @@
},
{
"name": "filp/whoops",
"version": "2.18.4",
"version": "2.18.5",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d"
"reference": "baec851ad6ae00db8bd0fdd30d208afc1b71c56e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d",
"reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d",
"url": "https://api.github.com/repos/filp/whoops/zipball/baec851ad6ae00db8bd0fdd30d208afc1b71c56e",
"reference": "baec851ad6ae00db8bd0fdd30d208afc1b71c56e",
"shasum": ""
},
"require": {
@ -11689,8 +11697,8 @@
},
"require-dev": {
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
"symfony/var-dumper": "^4.0 || ^5.0"
"phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3 || ^10.5.58",
"symfony/var-dumper": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
@ -11730,7 +11738,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.18.4"
"source": "https://github.com/filp/whoops/tree/2.18.5"
},
"funding": [
{
@ -11738,7 +11746,7 @@
"type": "github"
}
],
"time": "2025-08-08T12:00:00+00:00"
"time": "2026-09-18T12:00:00+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@ -11898,16 +11906,16 @@
},
{
"name": "larastan/larastan",
"version": "v3.12.1",
"version": "v3.12.2",
"source": {
"type": "git",
"url": "https://github.com/larastan/larastan.git",
"reference": "168fd3b955e723c51dbf81709849b0c3ac4ac5f6"
"reference": "c328727e6103c1147d1c64cc96b3aedfda26bc20"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/larastan/larastan/zipball/168fd3b955e723c51dbf81709849b0c3ac4ac5f6",
"reference": "168fd3b955e723c51dbf81709849b0c3ac4ac5f6",
"url": "https://api.github.com/repos/larastan/larastan/zipball/c328727e6103c1147d1c64cc96b3aedfda26bc20",
"reference": "c328727e6103c1147d1c64cc96b3aedfda26bc20",
"shasum": ""
},
"require": {
@ -11975,7 +11983,7 @@
],
"support": {
"issues": "https://github.com/larastan/larastan/issues",
"source": "https://github.com/larastan/larastan/tree/v3.12.1"
"source": "https://github.com/larastan/larastan/tree/v3.12.2"
},
"funding": [
{
@ -11983,7 +11991,7 @@
"type": "github"
}
],
"time": "2026-09-13T15:17:35+00:00"
"time": "2026-09-19T10:46:58+00:00"
},
{
"name": "laravel/pint",
@ -12924,16 +12932,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "5.6.7",
"version": "6.0.3",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "31a105931bc8ffa3a123383829772e832fd8d903"
"reference": "7bae67520aa9f5ecc506d646810bd40d9da54582"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/31a105931bc8ffa3a123383829772e832fd8d903",
"reference": "31a105931bc8ffa3a123383829772e832fd8d903",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582",
"reference": "7bae67520aa9f5ecc506d646810bd40d9da54582",
"shasum": ""
},
"require": {
@ -12941,8 +12949,8 @@
"ext-filter": "*",
"php": "^7.4 || ^8.0",
"phpdocumentor/reflection-common": "^2.2",
"phpdocumentor/type-resolver": "^1.7",
"phpstan/phpdoc-parser": "^1.7|^2.0",
"phpdocumentor/type-resolver": "^2.0",
"phpstan/phpdoc-parser": "^2.0",
"webmozart/assert": "^1.9.1 || ^2"
},
"require-dev": {
@ -12952,7 +12960,8 @@
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-webmozart-assert": "^1.2",
"phpunit/phpunit": "^9.5",
"psalm/phar": "^5.26"
"psalm/phar": "^5.26",
"shipmonk/dead-code-detector": "^0.5.1"
},
"type": "library",
"extra": {
@ -12982,44 +12991,44 @@
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.7"
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3"
},
"time": "2026-03-18T20:47:46+00:00"
"time": "2026-03-18T20:49:53+00:00"
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.12.0",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "92a98ada2b93d9b201a613cb5a33584dde25f195"
"reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195",
"reference": "92a98ada2b93d9b201a613cb5a33584dde25f195",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9",
"reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9",
"shasum": ""
},
"require": {
"doctrine/deprecations": "^1.0",
"php": "^7.3 || ^8.0",
"php": "^7.4 || ^8.0",
"phpdocumentor/reflection-common": "^2.0",
"phpstan/phpdoc-parser": "^1.18|^2.0"
"phpstan/phpdoc-parser": "^2.0"
},
"require-dev": {
"ext-tokenizer": "*",
"phpbench/phpbench": "^1.2",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-phpunit": "^1.1",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^9.5",
"rector/rector": "^0.13.9",
"vimeo/psalm": "^4.25"
"psalm/phar": "^4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-1.x": "1.x-dev"
"dev-1.x": "1.x-dev",
"dev-2.x": "2.x-dev"
}
},
"autoload": {
@ -13040,9 +13049,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0"
},
"time": "2025-11-21T15:09:14+00:00"
"time": "2026-01-06T21:53:42+00:00"
},
{
"name": "phpstan/phpdoc-parser",

@ -26,7 +26,7 @@ return [
|
*/
'path' => 'horizon',
'path' => env('HORIZON_PATH', 'admin/horizon'),
/*
|--------------------------------------------------------------------------

@ -30,7 +30,7 @@ return [
|
*/
'path' => env('PULSE_PATH', 'pulse'),
'path' => env('PULSE_PATH', 'admin/pulse'),
/*
|--------------------------------------------------------------------------

@ -146,12 +146,21 @@
</li>
<li class="nav-item">
<a class="nav-link" href="/horizon">
<a class="nav-link" href="/admin/horizon">
<i class="ni ni-bold-right text-primary"></i>
<span class="nav-link-text">Horizon</span>
</a>
</li>
@if(config('pulse.enabled'))
<li class="nav-item">
<a class="nav-link" href="/{{ config('pulse.path', 'admin/pulse') }}">
<i class="ni ni-bold-right text-primary"></i>
<span class="nav-link-text">Pulse</span>
</a>
</li>
@endif
<li class="nav-item">
<a class="nav-link {{request()->is('*site-news*')?'active':''}}" href="/i/admin/site-news">

@ -67,7 +67,10 @@
<a class="dropdown-item font-weight-bold {{request()->is('*site-news*')?'active':''}}" href="/i/admin/site-news">Newsroom</a>
<a class="dropdown-item font-weight-bold {{request()->is('*profiles*')?'active':''}}" href="/i/admin/profiles">Profiles</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item font-weight-bold" href="/horizon">Horizon</a>
<a class="dropdown-item font-weight-bold" href="/admin/horizon">Horizon</a>
@if(config('pulse.enabled'))
<a class="dropdown-item font-weight-bold" href="/{{ config('pulse.path', 'admin/pulse') }}">Pulse</a>
@endif
</div>
</li>
</ul>

@ -5,6 +5,21 @@ use App\Http\Controllers\AdminController;
use App\Http\Controllers\AdminCuratedRegisterController;
use App\Http\Controllers\AdminShadowFilterController;
use App\Http\Controllers\PageController;
use Illuminate\Contracts\View\Factory;
use Laravel\Horizon\Http\Controllers\HomeController as HorizonHomeController;
use Laravel\Pulse\Pulse;
// Laravel Pulse + Horizon dashboards, kept under `admin/*` so their routes can
// never collide with the `{username}` profile catch-all in routes/web.php
Route::domain(config('pixelfed.domain.app'))->middleware(['localization'])->group(function () {
Route::get(config('pulse.path', 'admin/pulse'), function (Pulse $pulse, Factory $view) {
return $view->make('pulse::dashboard');
})->middleware('pulse')->name('pulse');
Route::get(config('horizon.path'), [HorizonHomeController::class, 'index'])
->middleware('horizon')
->name('horizon.base');
});
Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->middleware(['localization'])->group(function () {
Route::redirect('/', '/dashboard');

@ -500,10 +500,6 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['localization'])->grou
Route::get('auth/invite/a/{code}', [AdminInviteController::class, 'index']);
Route::post('api/v1.1/auth/invite/admin/re', [AdminInviteController::class, 'apiRegister'])->middleware('throttle:5,1440');
// Laravel 13's Horizon no longer redirects the base path to its dashboard,
// so /horizon 404s by default. Redirect admins from /horizon to /horizon/dashboard.
Route::redirect('horizon', '/horizon/dashboard')->middleware('admin');
Route::redirect('groups/', '/groups/home');
Route::redirect('groups/home', '/groups/feed');

@ -0,0 +1,53 @@
<?php
use App\Models\User;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
uses(LazilyRefreshDatabase::class);
/*
|--------------------------------------------------------------------------
| Horizon base-path dashboard
|--------------------------------------------------------------------------
|
| Horizon lives under `admin/horizon` (config('horizon.path')) so its routes can
| never collide with the `{username}` profile catch-all. Horizon's own optional
| catch-all `GET {view?}` serves the base path directly by rendering the dashboard
| SPA, so there is no separate base-path redirect. Access is gated by Horizon's
| own auth middleware + the `viewHorizon` gate (admins only).
|
*/
it('serves the Horizon dashboard at the base path for an admin', function () {
$path = config('horizon.path');
$admin = User::factory()->admin()->create();
$admin->refresh();
// The base path is handled by Horizon (not a redirect) and renders the SPA.
$this->actingAs($admin)
->get('/'.$path)
->assertOk()
->assertSee('Horizon', false);
});
it('serves the Horizon dashboard at the /dashboard subpath for an admin', function () {
$path = config('horizon.path');
$admin = User::factory()->admin()->create();
$admin->refresh();
$this->actingAs($admin)
->get('/'.$path.'/dashboard')
->assertOk();
});
it('does not allow a non-admin to access the Horizon dashboard', function () {
$user = User::factory()->create(['is_admin' => false]);
$user->refresh();
// Horizon's authorization gate (viewHorizon) forbids non-admins.
$this->actingAs($user)
->get('/'.config('horizon.path'))
->assertForbidden();
});

@ -1,57 +0,0 @@
<?php
use App\Models\User;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
uses(LazilyRefreshDatabase::class);
/*
|--------------------------------------------------------------------------
| /horizon base-path redirect
|--------------------------------------------------------------------------
|
| Laravel 13's Horizon no longer redirects its base path, so routes/web.php
| adds an admin-only redirect from /horizon to the dashboard. The destination
| must be ABSOLUTE (leading slash): a relative "horizon/dashboard" Location is
| resolved by the browser against a trailing-slash request path /horizon/ to
| /horizon/horizon/dashboard (a doubled path served as HTTP 200 by Horizon's
| SPA catch-all), silently missing the dashboard.
|
*/
it('redirects an admin from /horizon to an absolute /horizon/dashboard', function () {
$admin = User::factory()->admin()->create();
$admin->refresh();
$response = $this->actingAs($admin)->get('/horizon');
$response->assertRedirect('/horizon/dashboard');
// The Location header must be absolute so a trailing-slash request cannot
// resolve it into a doubled /horizon/horizon/dashboard path.
expect($response->headers->get('Location'))->toEndWith('/horizon/dashboard');
expect(parse_url($response->headers->get('Location'), PHP_URL_PATH))
->toBe('/horizon/dashboard');
});
it('redirects with an absolute Location for a trailing-slash /horizon/ request', function () {
$admin = User::factory()->admin()->create();
$admin->refresh();
$response = $this->actingAs($admin)->get('/horizon/');
// Regardless of the trailing slash, the redirect path is the absolute
// dashboard path, never a relative reference that would double up.
expect(parse_url($response->headers->get('Location'), PHP_URL_PATH))
->toBe('/horizon/dashboard');
});
it('does not allow a non-admin to use the /horizon redirect', function () {
$user = User::factory()->create(['is_admin' => false]);
$user->refresh();
// The admin middleware bounces non-admins to the app root, not the dashboard.
$this->actingAs($user)
->get('/horizon')
->assertRedirect(config('app.url'));
});
Loading…
Cancel
Save