mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:01:08 +02:00
fix(shared-auth): proxy passkey/2FA/session methods through ManaAuthStore
The settings page in mana/web (and any future consumer that wants to
manage passkeys, 2FA, or sessions from the UI) was calling 11
methods on `authStore` that the wrapper had never exposed:
listPasskeys, registerPasskey, deletePasskey, renamePasskey,
listSessions, revokeSession, getSecurityEvents, enableTwoFactor,
disableTwoFactor, generateBackupCodes — all of which DO exist on
the underlying AuthServiceInterface but were silently dropped by
createManaAuthStore. Result: 17 type errors on settings/+page.svelte
and a complete dead-end for anyone trying to wire up the UI.
Fix: add thin passthrough wrappers in createManaAuthStore that
delegate to authService. Each handles the SSR/no-service case the
same way the existing methods do (return empty array or
{success:false} with a stable error message). enableTwoFactor and
disableTwoFactor additionally refresh the local user snapshot
after success because the JWT issued post-enrollment carries the
new flag and downstream UI gates on it.
Type fixes that fell out of touching settings/+page.svelte:
- UserData.twoFactorEnabled?: boolean — optional flag on the
public user shape. The TwoFactorSetup component reads it via
`authStore.user?.twoFactorEnabled` to gate the enable/disable
button; without the type the call site coerced through `any`.
- CreditBalance.{freeCreditsRemaining,dailyFreeCredits}?: number
— daily-free accounting fields the backend already returns but
the local type was missing. Optional because not every backend
deployment turns them on.
- settings/+page.svelte: `authStore.user?.sub` → `?.id`. The
public UserData shape uses `id`; `sub` is the raw JWT claim
name and never made it onto the consumer type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7ba381fde8
commit
05d9d1962c
4 changed files with 81 additions and 1 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@
|
|||
<p class="text-sm text-muted-foreground">Deine eindeutige Kennung</p>
|
||||
</div>
|
||||
<code class="rounded bg-muted px-2 py-1 text-xs font-mono">
|
||||
{authStore.user?.sub?.slice(0, 8) || '...'}...
|
||||
{authStore.user?.id?.slice(0, 8) || '...'}...
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue