Skip to content

feat(auth): invitation redemption — admin and facility staff signup from an invite link - #158

Open
aojomo wants to merge 1 commit into
DocForum:mainfrom
aojomo:feat/invitation-redemption-admin-and-facility-staff
Open

aojomo wants to merge 1 commit into
DocForum:mainfrom
aojomo:feat/invitation-redemption-admin-and-facility-staff

Conversation

@aojomo

@aojomo aojomo commented Oct 19, 2026

Copy link
Copy Markdown

feat(auth): invitation redemption — admin and facility staff signup from an invite link

Closes #81

Admins and facility staff can't self-register (PRD OQ-2) — they join through an invite. This PR adds the invite landing route, the redemption flow, and the tests for it.

Summary

  • /#/invite?token=… (src/pages/InvitePage.tsx, route in App.tsx, feature code in src/features/invite/): validates the token against GET /auth/invites/:token, then shows the inviting organisation, the role the invite grants, who invited them, and the invited email.
  • Two acceptance paths. A brand-new user gets an account-creation form (full name + password; the email is locked to the invite). An already-signed-in user accepts with one click; a signed-out existing user follows "Log in to accept this invitation" and lands back on the invite after logging in — the token travels in React Router state only, never the URL, never storage.
  • Clear states for expired, revoked, already-used (status: 'accepted'), and unknown/used-up tokens (404), plus a slow-server hint (Render free-tier cold start) and a retry for transient failures.
  • Role is server-derived, always. The role is rendered from the invite response and is never a form field or a request field. Three layers of defence for the reviewer check ("the role can't be tampered with client-side"): no role input exists in the UI; AcceptInviteInput has no role property (TypeScript makes it unrepresentable); docforum-core derives the role from the token server-side. A test appends ?role=admin to the invite link and asserts neither the shown role nor the accept payload changes.
  • Token hygiene. The token is read from the URL once and immediately stripped (replace), so the credential doesn't linger in the address bar or browser history. Nothing is written to localStorage/sessionStorage (hard rule) — reloading /invite shows the invalid-link state and the person simply reopens the link from their email.
  • Public signup unchanged. The signup test now asserts both facility and admin are never offered as self-serve roles.

API contract (assumed — doc-update)

docforum-core#39 (admin invites) and docforum-core#75 (facility staff invites) haven't landed, so the contract is assumed and documented in ARCHITECTURE_ESSENTIALS.md "Known assumptions — Invitation redemption":

  • GET /auth/invites/:token (unauthenticated — the link itself is the credential) → AccountInvite = {id, email, role: 'facility'|'admin', organisationName, organisationType?, invitedBy?, status: 'pending'|'accepted'|'revoked'|'expired', invitedAt, expiresAt}; 404 = unknown token.
  • POST /auth/invites/:token/accept (body {email, fullName?, password?}, plus Authorization: Bearer when an existing user accepts) → {user, accessToken}, the same session envelope as signup/login.
  • The role is never in the request.

Mirrored in src/types/models.ts (AccountInvite / InviteRole / AccountInviteStatus). MSW mock handlers with demo tokens (admin-demo, expired-demo, revoked-demo, used-demo, missing) keep npm run dev:mock working end-to-end.

Screenshots

Taken against npm run dev:mock (MSW fake data), same convention as #147 — the invite endpoints don't exist in docforum-core yet.

View Desktop Mobile
Invite landing (create-account form) screenshots/invite-landing-desktop.png screenshots/invite-landing-mobile.png
Expired invite screenshots/invite-expired-desktop.png screenshots/invite-expired-mobile.png
Invite already used screenshots/invite-used-desktop.png —
Token not found / already used up screenshots/invite-not-found-desktop.png —
Signed-in user, one-click accept screenshots/invite-accept-signed-in-desktop.png screenshots/invite-accept-signed-in-mobile.png

Checks

npm run typecheck   ✓ no errors
npm test            Test Files  42 passed (42)
                    Tests       387 passed (387)   (+13 new: invite detail/state screens,
                    URL-role tampering, login round-trip, bearer token on existing-user
                    accept, signup offers neither facility nor admin)
npm run build       ✓ built
npm run check:dist  ✓ no mock code in production bundle

Implements the PRD §4 admin/facility onboarding flow via PRD OQ-2 (invite-only accounts). ROADMAP.md updated (item status + changelog line) and ARCHITECTURE_ESSENTIALS.md "Known assumptions" updated for the assumed API shape, per the definition of done.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff noreply@codebuff.com

…om an invite link

Admins and facility staff can't self-register (PRD OQ-2) — they join
through an invite. Adds /#/invite?token=… (InvitePage + src/features/invite/):
validates the token, shows the inviting organisation, role and inviter,
and creates the account with the invite's role (never a form field, never
in the request payload); an already-signed-in user accepts with one click,
and a signed-out existing user goes through /login and straight back to
the invite via router state. Clear expired / revoked / already-used /
not-found states. The token is read from the URL once and immediately
stripped, and nothing is persisted to storage. Built against assumed
shapes pending docforum-core#39/DocForum#75 (documented in
ARCHITECTURE_ESSENTIALS.md "Known assumptions"); MSW mock handlers with
demo tokens included.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@aojomo Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Phase W1] Invitation redemption: admin and facility staff signup from an invite link

1 participant