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:
@@ -1,14 +1,34 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Title, Text, Card, Stack, Group, SimpleGrid, Badge, ThemeIcon, Divider,
|
||||
Tabs, Button,
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
IconBuilding, IconUser, IconUsers, IconSettings, IconShieldLock,
|
||||
IconCalendar,
|
||||
IconBuilding, IconUser, IconSettings, IconShieldLock,
|
||||
IconFingerprint, IconLink, IconLogout,
|
||||
} from '@tabler/icons-react';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { useAuthStore } from '../../stores/authStore';
|
||||
import { MfaSettings } from './MfaSettings';
|
||||
import { PasskeySettings } from './PasskeySettings';
|
||||
import { LinkedAccounts } from './LinkedAccounts';
|
||||
import api from '../../services/api';
|
||||
|
||||
export function SettingsPage() {
|
||||
const { user, currentOrg } = useAuthStore();
|
||||
const [loggingOutAll, setLoggingOutAll] = useState(false);
|
||||
|
||||
const handleLogoutEverywhere = async () => {
|
||||
setLoggingOutAll(true);
|
||||
try {
|
||||
await api.post('/auth/logout-everywhere');
|
||||
notifications.show({ message: 'All other sessions have been logged out', color: 'green' });
|
||||
} catch {
|
||||
notifications.show({ message: 'Failed to log out other sessions', color: 'red' });
|
||||
} finally {
|
||||
setLoggingOutAll(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
@@ -68,33 +88,6 @@ export function SettingsPage() {
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Security */}
|
||||
<Card withBorder padding="lg">
|
||||
<Group mb="md">
|
||||
<ThemeIcon color="red" variant="light" size={40} radius="md">
|
||||
<IconShieldLock size={24} />
|
||||
</ThemeIcon>
|
||||
<div>
|
||||
<Text fw={600} size="lg">Security</Text>
|
||||
<Text c="dimmed" size="sm">Authentication and access</Text>
|
||||
</div>
|
||||
</Group>
|
||||
<Stack gap="xs">
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c="dimmed">Authentication</Text>
|
||||
<Badge color="green" variant="light">Active Session</Badge>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c="dimmed">Two-Factor Auth</Text>
|
||||
<Badge color="gray" variant="light">Not Configured</Badge>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c="dimmed">OAuth Providers</Text>
|
||||
<Badge color="gray" variant="light">None Linked</Badge>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* System Info */}
|
||||
<Card withBorder padding="lg">
|
||||
<Group mb="md">
|
||||
@@ -113,7 +106,7 @@ export function SettingsPage() {
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c="dimmed">Version</Text>
|
||||
<Badge variant="light">2026.03.10</Badge>
|
||||
<Badge variant="light">2026.03.17</Badge>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c="dimmed">API</Text>
|
||||
@@ -121,7 +114,71 @@ export function SettingsPage() {
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Sessions */}
|
||||
<Card withBorder padding="lg">
|
||||
<Group mb="md">
|
||||
<ThemeIcon color="orange" variant="light" size={40} radius="md">
|
||||
<IconLogout size={24} />
|
||||
</ThemeIcon>
|
||||
<div>
|
||||
<Text fw={600} size="lg">Sessions</Text>
|
||||
<Text c="dimmed" size="sm">Manage active sessions</Text>
|
||||
</div>
|
||||
</Group>
|
||||
<Stack gap="xs">
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c="dimmed">Current Session</Text>
|
||||
<Badge color="green" variant="light">Active</Badge>
|
||||
</Group>
|
||||
<Button
|
||||
variant="light"
|
||||
color="orange"
|
||||
size="sm"
|
||||
leftSection={<IconLogout size={16} />}
|
||||
onClick={handleLogoutEverywhere}
|
||||
loading={loggingOutAll}
|
||||
mt="xs"
|
||||
>
|
||||
Log Out All Other Sessions
|
||||
</Button>
|
||||
</Stack>
|
||||
</Card>
|
||||
</SimpleGrid>
|
||||
|
||||
<Divider my="md" />
|
||||
|
||||
{/* Security Settings */}
|
||||
<div>
|
||||
<Title order={3} mb="sm">Security</Title>
|
||||
<Text c="dimmed" size="sm" mb="md">Manage authentication methods and security settings</Text>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="mfa">
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="mfa" leftSection={<IconShieldLock size={16} />}>
|
||||
Two-Factor Auth
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="passkeys" leftSection={<IconFingerprint size={16} />}>
|
||||
Passkeys
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="linked" leftSection={<IconLink size={16} />}>
|
||||
Linked Accounts
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="mfa" pt="md">
|
||||
<MfaSettings />
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="passkeys" pt="md">
|
||||
<PasskeySettings />
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="linked" pt="md">
|
||||
<LinkedAccounts />
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user