From 74bd88d3d63ea6de1eb996032666324533034f39 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Tue, 19 May 2026 11:45:16 -0300 Subject: [PATCH 1/3] feat(auth): self-hosted registration gate + admin seeder (closes #46) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-hosted installs (SELF_HOSTED=true, the default) now close /register to the public. Workspace invites still work — the AcceptInvite page links into /register with ?invite={id}, the middleware persists that into the session, and POST /register passes through. - EnsureRegistrationEnabled middleware gates GET/POST /register. Accepts ?invite=… (URL) or pending_invite_id (session) as the pass. - RegisteredUserController::store clears the marker after signup. - AcceptInvite.vue passes invite.id in the register link's query string. - Login.vue hides the "Sign up" link when self_hosted. - UserSeeder bootstraps a single admin (admin@trypost.it / password). Idempotent; not wired into DatabaseSeeder — operator runs `php artisan db:seed --class=UserSeeder` per the install docs. - Tests cover both flag values for every changed surface. Docs PR: see trypost-docs self-hosting/installation.mdx step 3. --- .../Auth/RegisteredUserController.php | 2 + .../App/EnsureRegistrationEnabled.php | 27 +++++++ bootstrap/app.php | 2 + database/seeders/UserSeeder.php | 36 +++++++++ resources/js/pages/auth/AcceptInvite.vue | 2 +- resources/js/pages/auth/Login.vue | 8 +- routes/auth.php | 6 +- tests/Feature/AcceptInviteControllerTest.php | 24 +++++- tests/Feature/Auth/RegistrationTest.php | 73 +++++++++++++++++++ tests/Feature/Auth/SignupUtmTrackingTest.php | 2 + tests/Feature/GitHubAuthToggleTest.php | 15 ++++ tests/Feature/GoogleAuthToggleTest.php | 15 ++++ 12 files changed, 206 insertions(+), 6 deletions(-) create mode 100644 app/Http/Middleware/App/EnsureRegistrationEnabled.php create mode 100644 database/seeders/UserSeeder.php diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index b030065a..dd13f0f5 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -57,6 +57,8 @@ public function store(Request $request): RedirectResponse Auth::login($user); + $request->session()->forget('pending_invite_id'); + if ($redirect = $request->input('redirect')) { if (str_starts_with($redirect, '/') && ! str_starts_with($redirect, '//')) { return redirect($redirect); diff --git a/app/Http/Middleware/App/EnsureRegistrationEnabled.php b/app/Http/Middleware/App/EnsureRegistrationEnabled.php new file mode 100644 index 00000000..82d04c4c --- /dev/null +++ b/app/Http/Middleware/App/EnsureRegistrationEnabled.php @@ -0,0 +1,27 @@ +query('invite') ?? $request->session()->get('pending_invite_id')) { + $request->session()->put('pending_invite_id', $inviteId); + + return $next($request); + } + + throw new NotFoundHttpException; + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 12e6c57c..03691e8c 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,6 +1,7 @@ alias([ 'workspace.token' => LoadWorkspaceFromToken::class, + 'registration.enabled' => EnsureRegistrationEnabled::class, ]); $middleware->preventRequestForgery(except: [ diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php new file mode 100644 index 00000000..a3a28b5d --- /dev/null +++ b/database/seeders/UserSeeder.php @@ -0,0 +1,36 @@ +exists()) { + $this->command->info('UserSeeder: a user already exists, skipping.'); + + return; + } + + $user = CreateUser::execute([ + 'name' => 'Admin', + 'email' => 'admin@trypost.it', + 'password' => 'password', + 'email_verified_at' => now(), + 'timezone' => 'UTC', + ]); + + CreateWorkspace::execute($user, ['name' => 'My Workspace']); + + $this->command->info('Admin account created — change the password on first login:'); + $this->command->line(' email: admin@trypost.it'); + $this->command->line(' password: password'); + } +} diff --git a/resources/js/pages/auth/AcceptInvite.vue b/resources/js/pages/auth/AcceptInvite.vue index 278ee462..d168ef32 100644 --- a/resources/js/pages/auth/AcceptInvite.vue +++ b/resources/js/pages/auth/AcceptInvite.vue @@ -96,7 +96,7 @@ const inviteUrl = computed(() => `/invites/${props.invite.id}`); diff --git a/resources/js/pages/auth/Login.vue b/resources/js/pages/auth/Login.vue index 6a3376f7..3b25e1d9 100644 --- a/resources/js/pages/auth/Login.vue +++ b/resources/js/pages/auth/Login.vue @@ -1,5 +1,6 @@