Implement end-to-end invitation acceptance: neutral entry route validates token and routes to register (new users), login (existing users), or auto-accepts (authenticated users). Handles 2FA token survival via session, email case-insensitive matching, and dedicated error pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6.7 KiB
6.7 KiB
title, type, created, status, baseline_commit, context
| title | type | created | status | baseline_commit | context | |
|---|---|---|---|---|---|---|
| Team Invitation Acceptance Flow | feature | 2026-03-27 | done | 8f39bd9 |
|
Team Invitation Acceptance Flow
Intent
Problem: Clicking the team invitation email link lands on a standard registration page that ignores the invitation token. New users register without being attached to the inviting workspace; existing users have no acceptance path at all.
Approach: Create a neutral entry route /team/invitation/{token}/accept that validates the token and routes to the correct flow: auto-accept for authenticated users, redirect to login (with message) for existing accounts, or redirect to register (with pre-filled email) for new users. Modify CreateNewUser to process the invitation token after registration. Update both auth Vue pages to pass the token through.
Boundaries & Constraints
Always:
- Validate invitation via
TeamInvitation::isValid()at every entry point - Attach user to workspace using role from invitation + default permissions from
config('permissions.defaults') - Set
current_workspace_idin session after acceptance - Mark invitation
accepted_at = now()in a DB transaction - Show a dedicated error page for invalid/expired tokens
Ask First:
- Any changes to the invitation email subject or body beyond the URL
Never:
- Modify Fortify's core auth pipeline or middleware stack
- Allow accepting an invitation for an email that doesn't match the authenticated user's email
- Allow a user who is already a member of the workspace to re-accept
I/O & Edge-Case Matrix
| Scenario | Input / State | Expected Output / Behavior | Error Handling |
|---|---|---|---|
| New user, valid token | Guest visits /team/invitation/{token}/accept |
Redirect to /register?invitation={token} with email pre-filled and read-only |
N/A |
| New user completes registration | POST register with invitation field |
User created, attached to workspace, session set, redirect to /dashboard |
N/A |
| Existing user, authenticated, valid token | Auth user visits /team/invitation/{token}/accept |
Auto-accept: attach to workspace, set session, redirect to /dashboard with success flash |
N/A |
| Existing user, not authenticated | Guest with existing account visits entry route | Redirect to /login?invitation={token} with status message |
N/A |
| Post-login with invitation token | User logs in with invitation query param |
Process invitation after login, redirect to dashboard | N/A |
| Expired token | Any visitor | Dedicated error page: "Cette invitation a expiré" | Inertia error page |
| Already-accepted token | Any visitor | Dedicated error page: "Cette invitation a deja ete utilisee" | Inertia error page |
| Email mismatch (auth user email != invitation email) | Auth user visits entry route | Error flash: "Cette invitation est destinee a une autre adresse email" + redirect back | Redirect with error |
| Already a workspace member | Auth user visits entry route | Flash "Vous etes deja membre de cet espace de travail" + redirect to dashboard | Redirect with info |
Code Map
app/Http/Controllers/TeamInvitationAcceptController.php-- NEW: entry route controller, handles routing logic and acceptance for authenticated usersapp/Actions/Fortify/CreateNewUser.php-- ADD: invitation processing after user creationapp/Providers/FortifyServiceProvider.php-- MODIFY: pass invitation data to register/login views, add custom registration responseresources/js/pages/auth/Register.vue-- MODIFY: accept invitation props, pre-fill email, add hidden fieldresources/js/pages/auth/Login.vue-- MODIFY: accept invitation prop, pass token through form, show invitation messageroutes/web.php-- ADD:/team/invitation/{token}/acceptrouteapp/Mail/TeamInvitationMail.php-- MODIFY: change URL to new acceptance routeresources/js/pages/auth/InvitationError.vue-- NEW: dedicated error page for invalid/expired tokensapp/Http/Responses/RegisterResponse.php-- NEW: custom Fortify response to redirect to dashboard with workspace session after invitation registration
Tasks & Acceptance
Execution:
app/Http/Controllers/TeamInvitationAcceptController.php-- Create single-action controller with__invoke(): validate token, check auth state, check if email has existing account, route accordinglyroutes/web.php-- AddGET /team/invitation/{token}/acceptroute (no auth middleware, publicly accessible)resources/js/pages/auth/InvitationError.vue-- Create error page withAuthLayout, show message + link to homeapp/Providers/FortifyServiceProvider.php-- UpdateregisterViewto passinvitationquery param and invitation email as props; updateloginViewto passinvitationquery paramresources/js/pages/auth/Register.vue-- AcceptinvitationandinvitationEmailprops, pre-fill email (read-only when invitation present), add hiddeninvitationinputresources/js/pages/auth/Login.vue-- Acceptinvitationprop, add hiddeninvitationinput, show invitation status messageapp/Actions/Fortify/CreateNewUser.php-- After user creation: ifinvitationtoken present, validate, attach workspace, mark accepted, set sessionapp/Http/Responses/RegisterResponse.php-- Create customRegisterResponseimplementingFortify\Contracts\RegisterResponse: if session hasinvitation_workspace_id, redirect to/dashboard; bind inFortifyServiceProviderapp/Mail/TeamInvitationMail.php-- ChangeregisterUrltoroute('team.invitation.accept', $this->invitation->token)
Acceptance Criteria:
- Given a new user clicks the invitation email link, when they complete registration, then they are attached to the workspace with the correct role and land on the workspace dashboard
- Given an authenticated user clicks the invitation link, when the token is valid and email matches, then they are auto-attached and redirected to the dashboard with the new workspace active
- Given a non-authenticated user with an existing account clicks the link, when they log in, then the invitation is processed and they land on the workspace dashboard
- Given an expired or used token, when any user visits the link, then they see a clear French-language error page
- Given an email mismatch between authenticated user and invitation, when they visit the link, then they see an error and are not attached
Verification
Commands:
composer test-- expected: all existing tests pass (no regressions)npm run lint-- expected: no lint errorscomposer lint-- expected: no PHP formatting errors