feat: SaaS onboarding, Stripe billing, MFA, SSO, passkeys, refresh tokens

Complete SaaS self-service onboarding sprint:

- Stripe-powered signup flow: pricing page → checkout → provisioning → activation
- Refresh token infrastructure: 1h access tokens + 30-day httpOnly cookie refresh
- TOTP MFA with QR setup, recovery codes, and login challenge flow
- Google + Azure AD SSO (conditional on env vars) with account linking
- WebAuthn passkey registration and passwordless login
- Guided onboarding checklist with server-side progress tracking
- Stubbed email service (console + DB logging, ready for real provider)
- Settings page with tabbed security settings (MFA, passkeys, linked accounts)
- Login page enhanced with MFA verification, SSO buttons, passkey login
- Database migration 015 with all new tables and columns
- Version bump to 2026.03.17

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 21:12:35 -04:00
parent 17bdebfb52
commit dfcd172ef3
39 changed files with 4673 additions and 82 deletions

View File

@@ -33,6 +33,7 @@ interface AuthState {
currentOrg: Organization | null;
impersonationOriginal: ImpersonationOriginal | null;
setAuth: (token: string, user: User, organizations: Organization[]) => void;
setToken: (token: string) => void;
setCurrentOrg: (org: Organization, token?: string) => void;
setUserIntroSeen: () => void;
setOrgSettings: (settings: Record<string, any>) => void;
@@ -60,6 +61,7 @@ export const useAuthStore = create<AuthState>()(
// Don't auto-select org — force user through SelectOrgPage
currentOrg: null,
}),
setToken: (token) => set({ token }),
setCurrentOrg: (org, token) =>
set((state) => ({
currentOrg: org,
@@ -102,14 +104,17 @@ export const useAuthStore = create<AuthState>()(
});
}
},
logout: () =>
logout: () => {
// Fire-and-forget server-side logout to revoke refresh token cookie
fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }).catch(() => {});
set({
token: null,
user: null,
organizations: [],
currentOrg: null,
impersonationOriginal: null,
}),
});
},
}),
{
name: 'ledgeriq-auth',