diff --git a/apps/mana/apps/web/src/lib/api/credits.ts b/apps/mana/apps/web/src/lib/api/credits.ts index 3daf227c5..b1425caf9 100644 --- a/apps/mana/apps/web/src/lib/api/credits.ts +++ b/apps/mana/apps/web/src/lib/api/credits.ts @@ -11,6 +11,14 @@ export interface CreditBalance { balance: number; totalEarned: number; totalSpent: number; + /** + * Daily-free-credit accounting. Optional because the backend only + * returns these fields when the user has a free-tier allowance + * configured (paying users get them too but with `dailyFreeCredits = 0`). + * Settings UIs render the "free today" tile only when both are present. + */ + freeCreditsRemaining?: number; + dailyFreeCredits?: number; } export interface CreditTransaction { diff --git a/apps/mana/apps/web/src/routes/(app)/settings/+page.svelte b/apps/mana/apps/web/src/routes/(app)/settings/+page.svelte index 0c8ac30ab..720c4f05a 100644 --- a/apps/mana/apps/web/src/routes/(app)/settings/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/settings/+page.svelte @@ -274,7 +274,7 @@

Deine eindeutige Kennung

- {authStore.user?.sub?.slice(0, 8) || '...'}... + {authStore.user?.id?.slice(0, 8) || '...'}... diff --git a/packages/shared-auth-ui/src/stores/createManaAuthStore.svelte.ts b/packages/shared-auth-ui/src/stores/createManaAuthStore.svelte.ts index 0ec2436d8..db6606983 100644 --- a/packages/shared-auth-ui/src/stores/createManaAuthStore.svelte.ts +++ b/packages/shared-auth-ui/src/stores/createManaAuthStore.svelte.ts @@ -314,6 +314,71 @@ export function createManaAuthStore(config: ManaAuthStoreConfig = {}) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } }, + + // Passkey CRUD — thin passthroughs to authService. The settings page + // (and any other consumer) needs these to render the PasskeyManager + // UI. Each method swallows the SSR/no-service case the same way the + // rest of the wrapper does. + async listPasskeys() { + const authService = getAuthService(); + if (!authService) return [] as unknown[]; + return authService.listPasskeys(); + }, + async registerPasskey(friendlyName?: string) { + const authService = getAuthService(); + if (!authService) return { success: false, error: 'Auth not available on server' }; + return authService.registerPasskey(friendlyName); + }, + async deletePasskey(passkeyId: string) { + const authService = getAuthService(); + if (!authService) return { success: false, error: 'Auth not available on server' }; + return authService.deletePasskey(passkeyId); + }, + async renamePasskey(passkeyId: string, friendlyName: string) { + const authService = getAuthService(); + if (!authService) return { success: false, error: 'Auth not available on server' }; + return authService.renamePasskey(passkeyId, friendlyName); + }, + + // Two-factor passthroughs. enableTwoFactor refreshes the local user + // snapshot on success because the JWT issued post-enrollment carries + // the new flag and downstream UI gates on it. + async enableTwoFactor(password: string) { + const authService = getAuthService(); + if (!authService) return { success: false, error: 'Auth not available on server' }; + const result = await authService.enableTwoFactor(password); + if (result.success) user = await authService.getUserFromToken(); + return result; + }, + async disableTwoFactor(password: string) { + const authService = getAuthService(); + if (!authService) return { success: false, error: 'Auth not available on server' }; + const result = await authService.disableTwoFactor(password); + if (result.success) user = await authService.getUserFromToken(); + return result; + }, + async generateBackupCodes(password: string) { + const authService = getAuthService(); + if (!authService) return { success: false, error: 'Auth not available on server' }; + return authService.generateBackupCodes(password); + }, + + // Sessions + audit log passthroughs. + async listSessions() { + const authService = getAuthService(); + if (!authService) return [] as unknown[]; + return authService.listSessions(); + }, + async revokeSession(sessionId: string) { + const authService = getAuthService(); + if (!authService) return { success: false, error: 'Auth not available on server' }; + return authService.revokeSession(sessionId); + }, + async getSecurityEvents(limit?: number) { + const authService = getAuthService(); + if (!authService) return [] as unknown[]; + return authService.getSecurityEvents(limit); + }, }; } diff --git a/packages/shared-auth/src/types/index.ts b/packages/shared-auth/src/types/index.ts index f57bf412b..515ca0c82 100644 --- a/packages/shared-auth/src/types/index.ts +++ b/packages/shared-auth/src/types/index.ts @@ -52,6 +52,13 @@ export interface UserData { email: string; role: string; tier: string; + /** + * Whether 2FA is enrolled. Optional because the field is only present + * when the JWT was minted with the `twofa` claim — guest tokens and + * legacy sessions omit it. Settings UIs that gate the "enable 2FA" + * button on this should default to `false` when undefined. + */ + twoFactorEnabled?: boolean; } /**