feat(auth): mandatory 2FA (TOTP + WebAuthn passkeys) with hard enrollment gate, AAL2 step-up, and single-use recovery codes
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user