diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 9f97dd0d4..302054b7f 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -35,6 +35,14 @@ public function create(array $input): User ])->validate(); if (User::count() == 0) { + // MapleDeploy: validate setup token for first user registration + if ($settings->setup_token) { + $providedToken = $input['setup_token'] ?? null; + if (! $providedToken || ! hash_equals($settings->setup_token, $providedToken)) { + abort(403); + } + } + // If this is the first user, make them the root user // Team is already created in the database/seeders/ProductionSeeder.php $user = User::create([ diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index 85f38b967..159611caf 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -44,15 +44,24 @@ public function boot(): void { Fortify::createUsersUsing(CreateNewUser::class); Fortify::registerView(function () { - $isFirstUser = User::count() === 0; - $settings = instanceSettings(); if (! $settings->is_registration_enabled) { return redirect()->route('login'); } + $isFirstUser = User::count() === 0; + + // MapleDeploy: token-gated registration for first user + if ($isFirstUser && $settings->setup_token) { + $token = request()->query('setup_token'); + if (! $token || ! hash_equals($settings->setup_token, $token)) { + abort(403); + } + } + return view('auth.register', [ 'isFirstUser' => $isFirstUser, + 'setupToken' => request()->query('setup_token'), ]); }); @@ -61,7 +70,15 @@ public function boot(): void $enabled_oauth_providers = OauthSetting::where('enabled', true)->get(); $users = User::count(); if ($users == 0) { - // If there are no users, redirect to registration + // MapleDeploy: don't redirect to register if setup token is required + if ($settings->setup_token) { + return view('auth.login', [ + 'setup_pending' => true, + 'is_registration_enabled' => false, + 'enabled_oauth_providers' => collect(), + ]); + } + return redirect()->route('register'); } diff --git a/config/constants.php b/config/constants.php index 417079de1..064564ff7 100644 --- a/config/constants.php +++ b/config/constants.php @@ -3,7 +3,7 @@ return [ // MapleDeploy branding: registry pointed to Forgejo, auto-update disabled by default 'coolify' => [ - 'version' => '4.0.0-beta.463.9', + 'version' => '4.0.0-beta.463.10', 'helper_version' => '1.0.12', 'realtime_version' => '1.0.10', 'self_hosted' => env('SELF_HOSTED', true), diff --git a/database/migrations/2026_02_21_000001_add_setup_token_to_instance_settings_table.php b/database/migrations/2026_02_21_000001_add_setup_token_to_instance_settings_table.php new file mode 100644 index 000000000..b5fa1c930 --- /dev/null +++ b/database/migrations/2026_02_21_000001_add_setup_token_to_instance_settings_table.php @@ -0,0 +1,28 @@ +string('setup_token')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('instance_settings', function (Blueprint $table) { + $table->dropColumn('setup_token'); + }); + } +}; diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 21f7c2ec2..d15d89339 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -10,6 +10,26 @@
+ @if (!empty($setup_pending)) + {{-- MapleDeploy: setup token required but not provided --}} +
+
+ + + +
+

Setup pending

+

+ Initial setup has not been completed. Please use the setup link from your + MapleDeploy dashboard. +

+
+
+
+ @else @if (session('status'))

{{ session('status') }}

@@ -96,6 +116,7 @@ class="block w-full text-center py-3 px-4 rounded-lg border border-neutral-300 d @endforeach
@endif + @endif {{-- end setup_pending --}}
diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index 9b861b629..c5dc3988c 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -51,6 +51,9 @@ function getOldOrLocal($key, $localValue)
@csrf + @if (isset($setupToken)) + + @endif