Skip to content

Auth & Session

This area covers 19 features. Watch the walkthrough, then use the reference below — each feature links to the exact moment it appears (▶).

Who this is for: Project lead / owner. Each feature below lists the role/permission it requires.

Features

Sign-up with username, email, password ▶ 00:06

As a new user, I want to create an account with a username, email, and password, so that I can access the app.

How it works. User can access sign-up form via 'Create an account' link in AccountSwitcher dialog. Form accepts username (3-50 chars), email, and password (≥8 chars). Form displays password requirements: min 8 chars and does not contain email. Password strength indicator shows weak/medium/strong based on length + digits + symbols. Submit button disabled until all fields valid and user online. On success, user signs in and dialog closes.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/git-import/FrontierSignupForm.tsx:1-180
src/lib/frontier/auth.ts:56-75
src/hooks/useFrontierSession.ts:32-39

Login with username or email + password ▶ 00:06

As an existing user, I want to sign in with my username or email and password, so that I can access my projects.

How it works. User navigates to /login or clicks 'Add another account' in AccountSwitcher. Login form accepts username/email and password. Show/hide password toggle (eye icon) reveals plaintext. Submit button disabled until both fields filled. On invalid credentials, shows 401 error 'Invalid username or password'. On success, user authenticated and redirected to ?next parameter or /. Offline state detected and submit disabled with message.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/pages/Login.tsx:1-137
src/components/git-import/FrontierLoginForm.tsx:1-104
src/lib/frontier/auth.ts:40-54
src/hooks/useFrontierSession.ts:24-30

Show/hide password toggle on login ▶ 00:00

As a user logging in, I want to verify my password by toggling visibility, so that I can confirm I typed it correctly.

How it works. Login form password input type='password' by default. Eye icon button toggles to show password as plaintext (type='text'). Button aria-label changes from 'Show password' to 'Hide password'. Icon switches from Eye to EyeOff. Toggle can be clicked repeatedly.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/pages/Login.tsx:89-108
src/components/git-import/FrontierLoginForm.tsx:79-96

Forgot password flow ▶ 00:00

As a user who forgot my password, I want to request a reset link, so that I can set a new password.

How it works. From login form, user clicks 'Forgot password?' link. Dialog switches to FrontierForgotPasswordForm with title 'Reset your password'. Form has email input (must match /.+@.+\.+/ pattern). 'Send reset link' button disabled until valid email. On submit, calls POST /api/v2/auth/password-reset/request { email }. Shows success message with email display and 'Back to login' button. Users can click 'Back to login' to return to login form without submitting.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/components/git-import/FrontierForgotPasswordForm.tsx:1-73
src/lib/frontier/auth.ts:99-115
src/components/AccountSwitcher.tsx:72-76

Password reset token verification ▶ 00:00

As a user with a reset link, I want to verify the link is valid before entering a new password, so that I know my reset request was legitimate.

How it works. User navigates to /reset-password?token=<token>&username=<username>. Page calls POST /api/v2/auth/password-reset/verify { token, username }. While verifying, shows 'Verifying link…' loading state. On 401/invalid: shows TokenExpiredView with form to request new link. On 200/valid: shows new password form. Form displays password checklist (8+ chars, not contain email). Submit button disabled until password valid.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/pages/ResetPassword.tsx:1-282
src/lib/frontier/auth.ts:125-141

Password reset execution ▶ 00:00

As a user resetting my password, I want to set a new password and be automatically signed in, so that I can continue without clicking login again.

How it works. On /reset-password with valid token, user enters new password (≥8 chars). Form validates and shows checklist. On submit, calls POST /api/v2/auth/password-reset/reset { token, username, new_password }. On success, automatically calls login(username, new_password) to sign user in, then redirects to /. On 400/500 error, shows message and keeps user on form.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/pages/ResetPassword.tsx:207-223
src/lib/frontier/auth.ts:150-170

Password strength indicator ▶ 00:00

As a user creating or resetting a password, I want to see password strength feedback, so that I can create a secure password.

How it works. Signup and reset forms display PasswordChecklist when user types. Shows green Check for 'At least 8 characters' when met. Shows Check for 'Does not contain your email' when met. Displays colored progress bar (red=weak 1/3, yellow=medium 2/3, green=strong full). Text label 'Strength: weak|medium|strong'. Strength calculated as: ≥8 chars (+1), ≥12 chars (+1), digits (+1), symbols (+1). Score ≤1=weak, 2=medium, ≥3=strong.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/components/git-import/FrontierSignupForm.tsx:10-77
src/pages/ResetPassword.tsx:40-79

Session persistence to IndexedDB ▶ 00:00

As a user, I want my session to persist across page reloads, so that I don't need to log in repeatedly.

How it works. On successful login/register, FrontierSession (jwt, username, createdAt, email) saved to IndexedDB 'frontier' store with key 'envelope'. Session stored in envelope.sessions[username] and envelope.active set to username. On cold boot, loadActiveSession() retrieves session from IndexedDB. If session exists, user is logged in without re-authenticating. Session cleared only on logout.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/lib/frontier/session-store.ts:1-156
src/lib/frontier/auth.ts:194-209
src/hooks/useAccounts.ts:17-22

Auth hint cookie for edge routing ▶ 00:00

As a returning user, I want the Worker/edge to know I'm logged in without waiting for IndexedDB, so that the SPA loads quickly.

How it works. On successful login, IndexedDB write also sets aq_hint=1 cookie (Max-Age=31536000, Path=/, SameSite=Lax, no Domain). On logout, cookie deleted. On app boot, hasAuthHintCookie() checks if aq_hint=1 present. If no hint and not onboarded, RootRedirect sends hard window.location.replace('/homepage'). Hint scoped to host only (dev.aquilla.app vs aquilla.app vs localhost isolated).

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/lib/frontier/session-store.ts:50-68
src/App.tsx:93-106

Account switcher dropdown ▶ 00:00

As a user with multiple accounts, I want to see all my signed-in accounts in one place, so that I can switch between them or manage them.

How it works. AccountSwitcher button shows active user's avatar + username. Click opens dropdown showing 'Signed in' header, active account entry (with checkmark), list of other accounts (clickable to switch), 'Preferences' link, 'Add another account…' button, 'Log out' button. If >1 account, adds 'Sign out of all accounts' (destructive color). Clicking other account calls activateSession(), clears React Query cache and local project data. Clicking remove button (hover icon) on non-active account removes it.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/components/AccountSwitcher.tsx:1-343
src/hooks/useAccounts.ts:35-45

Single-account logout ▶ 00:00

As a user, I want to sign out, so that my account is no longer active on this device.

How it works. User clicks 'Log out' in AccountSwitcher. If outbox has pending edits, shows warning dialog with edit count and 'Log out anyway' button. On confirm or if no pending edits, clearSession() removes active session from IndexedDB, clears auth hint cookie, clears local project data, and wipes React Query cache. If other accounts exist, next account becomes active. App returns to login state.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/components/AccountSwitcher.tsx:122-144, 234-239
src/lib/frontier/session-store.ts:145-150
src/hooks/useFrontierSession.ts:41-48

Sign out of all accounts ▶ 00:00

As a user with multiple logged-in accounts, I want to sign out globally, so that all my accounts are cleared from this device.

How it works. User clicks 'Sign out of all accounts' (only visible if >1 account). Shows warning about pending edits if any. On confirm, loops through all sessions and calls removeSession() for each, then clearAllLocalData() and query cache clear. All sessions removed from IndexedDB, auth hint cookie deleted. App returns to signed-out state.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/components/AccountSwitcher.tsx:241-249, 132-144
src/lib/frontier/session-store.ts:115-124

Session expiry banner (401 handling) ▶ 00:00

As a user whose session expired while working, I want a non-intrusive notification, so that I can re-sign-in without losing context.

How it works. When any fetch helper receives 401 (expired session), it calls notifySessionExpired(). SessionExpiredBanner subscribes to signal, shows fixed top banner (amber, role=alert, aria-live=assertive) with message 'Your session expired. Sign in again to continue.' Banner has dismiss button (X icon). Banner clears on navigation or user dismiss. Shows link to /login?next=<current-path> to restore context.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/components/SessionExpiredBanner.tsx:1-62
src/lib/errors/session-expired-signal.ts:1-31

Dev-only auto-login route (__dev/login) ▶ 00:00

As a developer testing the app, I want a one-click login shortcut, so that I don't manually enter credentials repeatedly.

How it works. Navigate to /__dev/login. If import.meta.env.DEV false, shows error 'dev login is disabled in production builds'. If DEV true, calls devLogin() which hits POST /__dev__/login on auth-worker. Returns mocked session with seeded dev user. On success, redirects to /project/dev-project. On 404 (WRANGLER_LOCAL not set), shows error 'dev login endpoint unavailable'. Cancellation-safe (cleanup on unmount).

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/components/DevLoginRoute.tsx:1-62
src/lib/frontier/auth.ts:83-97

Dev-only logout route (__dev/logout) ▶ 00:00

As a developer, I want a quick way to clear my session and return to onboarding, so that I can test the sign-in flow without manual cleanup.

How it works. Navigate to /__dev/logout. If import.meta.env.DEV true, calls logout() to clear session and redirect to /onboarding. If DEV false (production build), silently redirects to / with session intact. Dev route is no-op in built apps (verified by e2e in preview mode).

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/components/DevLogoutRoute.tsx:1-35
src/hooks/useFrontierSession.ts:41-48

Private browsing mode banner ▶ 00:00

As a user in private browsing, I want to know storage is unavailable, so that I understand why loads are slow.

How it works. PrivateModeBanner detects if OPFS unavailable (navigator.storage.getDirectory() throws in Safari private, Brave Tor, locked profiles). Shows one-time banner per session: 'Private browsing is restricting local storage…' with icon, message, and dismiss button. Banner stored per username+createdAt in sessionStorage. Dismissal persists for duration of session. Accessible state: role=alert not used (informational, not urgent).

WhoAny user (personal preference) PermissionsAny authenticated user — scoped to own account
Key files

src/components/PrivateModeBanner.tsx:1-83
src/hooks/useOpfsAvailability.ts

JWT token persistence and claims extraction ▶ 00:00

As the app, I want to store the JWT and extract user info without validation, so that I know the canonical username and email.

How it works. On login, server returns access_token. Frontend decodes JWT payload (base64url to base64 conversion, JSON.parse) and extracts 'sub' (username) and 'email' claims without signature verification (server verified password). Falls back to login identifier if decode fails. Session stored with username from JWT sub claim, email from claim if present. Invalid/malformed tokens handled gracefully (returns empty {}).

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/lib/frontier/auth.ts:177-209

PostHog analytics integration with auth ▶ 00:00

As the analytics system, I want to track auth events and attribute them to users, so that we can monitor signup/login patterns.

How it works. On login, computes distinct_id as SHA-256(username), calls posthog.identify(distinctId), and captures 'user logged in' event. On signup, same identify + capture 'user signed up' with email property (only if analytics consent given). On logout, captures 'user logged out' and calls posthog.reset().

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/hooks/useFrontierSession.ts:24-48

Offline detection on login/signup ▶ 00:00

As a user without internet, I want to see a message explaining why I can't sign in, so that I understand the issue.

How it works. FrontierLoginForm and FrontierSignupForm detect navigator.onLine and window online/offline events. When offline, shows alert message 'You're offline — connect to sign in' and disables submit button. When online again, button re-enables. Message dismisses on successful auth or stays on network error.

WhoAny user (unauthenticated → authenticated) PermissionsNo role required — public auth endpoints; account owner only for own session
Key files

src/components/git-import/FrontierLoginForm.tsx:27-38, 57-59, 99
src/components/git-import/FrontierSignupForm.tsx:87-98, 123-127, 105