feat(auth): mandatory 2FA (TOTP + WebAuthn passkeys) with hard enrollment gate, AAL2 step-up, and single-use recovery codes

This commit is contained in:
Gerhard Scheikl
2026-05-31 21:38:01 +02:00
parent 129e21529c
commit e14e909700
19 changed files with 1310 additions and 142 deletions
+33
View File
@@ -0,0 +1,33 @@
/**
* Shared MFA constants/helpers used by both server and client code.
*
* The WebAuthn Relying Party ID is fixed to the registrable suffix
* `linumiq.net` so the same passkey works across `app-dev.linumiq.net` and
* `app.linumiq.net`. The RP origin is derived from NEXT_PUBLIC_APP_URL so each
* environment supplies its own concrete origin at challenge time.
*/
export const MFA_RP_ID = 'linumiq.net';
/** Origin (scheme + host) of this environment's app, from NEXT_PUBLIC_APP_URL. */
export function getAppOrigin(): string {
const raw = process.env.NEXT_PUBLIC_APP_URL;
if (raw) {
try {
return new URL(raw).origin;
} catch {
// fall through to the localhost default
}
}
return 'http://localhost:3000';
}
/**
* Whitelist a `next` redirect target to same-origin relative paths only
* (open-redirect guard). Anything else falls back to /dashboard.
*/
export function safeNextPath(raw: string | null | undefined): string {
if (!raw || !raw.startsWith('/') || raw.startsWith('//')) {
return '/dashboard';
}
return raw;
}