@puzzle-ui/auth 0.0.0-stage → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +301 -2
- package/dist/LICENSE +21 -0
- package/dist/auth.lifecycle.manager.cjs +835 -0
- package/dist/auth.lifecycle.manager.cjs.map +1 -0
- package/dist/auth.lifecycle.manager.d.cts +115 -0
- package/dist/auth.lifecycle.manager.d.ts +115 -0
- package/dist/auth.lifecycle.manager.js +835 -0
- package/dist/auth.lifecycle.manager.js.map +1 -0
- package/dist/auth.manager.cjs +454 -0
- package/dist/auth.manager.cjs.map +1 -0
- package/dist/auth.manager.d.cts +54 -0
- package/dist/auth.manager.d.ts +54 -0
- package/dist/auth.manager.js +454 -0
- package/dist/auth.manager.js.map +1 -0
- package/dist/auth.mfa.manager.cjs +1488 -0
- package/dist/auth.mfa.manager.cjs.map +1 -0
- package/dist/auth.mfa.manager.d.cts +100 -0
- package/dist/auth.mfa.manager.d.ts +100 -0
- package/dist/auth.mfa.manager.js +1488 -0
- package/dist/auth.mfa.manager.js.map +1 -0
- package/dist/auth.rate-limit.cjs +78 -0
- package/dist/auth.rate-limit.cjs.map +1 -0
- package/dist/auth.rate-limit.d.cts +24 -0
- package/dist/auth.rate-limit.d.ts +24 -0
- package/dist/auth.rate-limit.js +78 -0
- package/dist/auth.rate-limit.js.map +1 -0
- package/dist/auth.step-up.manager.cjs +269 -0
- package/dist/auth.step-up.manager.cjs.map +1 -0
- package/dist/auth.step-up.manager.d.cts +74 -0
- package/dist/auth.step-up.manager.d.ts +74 -0
- package/dist/auth.step-up.manager.js +268 -0
- package/dist/auth.step-up.manager.js.map +1 -0
- package/dist/auth.storage.cjs +164 -0
- package/dist/auth.storage.cjs.map +1 -0
- package/dist/auth.storage.d.cts +29 -0
- package/dist/auth.storage.d.ts +29 -0
- package/dist/auth.storage.js +148 -0
- package/dist/auth.storage.js.map +1 -0
- package/dist/errors/auth.errors.cjs +101 -0
- package/dist/errors/auth.errors.cjs.map +1 -0
- package/dist/errors/auth.errors.d.cts +62 -0
- package/dist/errors/auth.errors.d.ts +62 -0
- package/dist/errors/auth.errors.js +90 -0
- package/dist/errors/auth.errors.js.map +1 -0
- package/dist/index.cjs +54 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +13 -0
- package/dist/providers/mfa-provider.types.d.cts +66 -0
- package/dist/providers/mfa-provider.types.d.ts +66 -0
- package/dist/providers/provider.types.d.cts +34 -0
- package/dist/providers/provider.types.d.ts +34 -0
- package/dist/providers/simplewebauthn-provider.cjs +185 -0
- package/dist/providers/simplewebauthn-provider.cjs.map +1 -0
- package/dist/providers/simplewebauthn-provider.d.cts +35 -0
- package/dist/providers/simplewebauthn-provider.d.ts +35 -0
- package/dist/providers/simplewebauthn-provider.js +185 -0
- package/dist/providers/simplewebauthn-provider.js.map +1 -0
- package/dist/providers/webcrypto-secret-protector.cjs +118 -0
- package/dist/providers/webcrypto-secret-protector.cjs.map +1 -0
- package/dist/providers/webcrypto-secret-protector.d.cts +14 -0
- package/dist/providers/webcrypto-secret-protector.d.ts +14 -0
- package/dist/providers/webcrypto-secret-protector.js +118 -0
- package/dist/providers/webcrypto-secret-protector.js.map +1 -0
- package/dist/types/auth-lifecycle.types.d.cts +53 -0
- package/dist/types/auth-lifecycle.types.d.ts +53 -0
- package/dist/types/auth-manager.types.d.cts +106 -0
- package/dist/types/auth-manager.types.d.ts +106 -0
- package/dist/types/credential.types.d.cts +9 -0
- package/dist/types/credential.types.d.ts +9 -0
- package/dist/types/mfa.types.d.cts +244 -0
- package/dist/types/mfa.types.d.ts +244 -0
- package/dist/types/state.types.d.cts +33 -0
- package/dist/types/state.types.d.ts +33 -0
- package/dist/types/user.types.d.cts +9 -0
- package/dist/types/user.types.d.ts +9 -0
- package/dist/utils/auth-crypto.utils.cjs +38 -0
- package/dist/utils/auth-crypto.utils.cjs.map +1 -0
- package/dist/utils/auth-crypto.utils.d.cts +4 -0
- package/dist/utils/auth-crypto.utils.d.ts +4 -0
- package/dist/utils/auth-crypto.utils.js +35 -0
- package/dist/utils/auth-crypto.utils.js.map +1 -0
- package/dist/utils/email.utils.cjs +20 -0
- package/dist/utils/email.utils.cjs.map +1 -0
- package/dist/utils/email.utils.d.cts +2 -0
- package/dist/utils/email.utils.d.ts +2 -0
- package/dist/utils/email.utils.js +19 -0
- package/dist/utils/email.utils.js.map +1 -0
- package/dist/utils/totp.utils.cjs +145 -0
- package/dist/utils/totp.utils.cjs.map +1 -0
- package/dist/utils/totp.utils.d.cts +26 -0
- package/dist/utils/totp.utils.d.ts +26 -0
- package/dist/utils/totp.utils.js +140 -0
- package/dist/utils/totp.utils.js.map +1 -0
- package/package.json +45 -4
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { PasswordHasher, RateLimitProvider } from "../providers/provider.types.cjs";
|
|
2
|
+
import { AuthMfaEnrollmentPendingResult, AuthMfaGateway, AuthMfaPendingResult } from "./mfa.types.cjs";
|
|
3
|
+
import { AuthState } from "./state.types.cjs";
|
|
4
|
+
import { AuthUser } from "./user.types.cjs";
|
|
5
|
+
import { StorageAdapter } from "@puzzle-ui/storage";
|
|
6
|
+
import { SessionManager } from "@puzzle-ui/session";
|
|
7
|
+
export type AuthRateLimitRule = {
|
|
8
|
+
readonly limit: number;
|
|
9
|
+
readonly windowSeconds: number;
|
|
10
|
+
};
|
|
11
|
+
export type AuthRateLimitScopePolicy = {
|
|
12
|
+
readonly email: AuthRateLimitRule;
|
|
13
|
+
readonly ip: AuthRateLimitRule;
|
|
14
|
+
};
|
|
15
|
+
export type AuthRateLimitPolicies = {
|
|
16
|
+
readonly registration: AuthRateLimitScopePolicy;
|
|
17
|
+
readonly signIn: AuthRateLimitScopePolicy;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Determines which users must authenticate with MFA.
|
|
21
|
+
*
|
|
22
|
+
* When required is true, MFA is required for every user.
|
|
23
|
+
* requiredFor can additionally enforce MFA for selected users.
|
|
24
|
+
*
|
|
25
|
+
* When neither option is configured, MFA remains optional for
|
|
26
|
+
* users without enrolled factors. Existing enrolled factors are
|
|
27
|
+
* still challenged during sign-in.
|
|
28
|
+
*/
|
|
29
|
+
export type AuthMfaPolicy = {
|
|
30
|
+
readonly required?: boolean;
|
|
31
|
+
readonly requiredFor?: (user: AuthUser) => boolean | Promise<boolean>;
|
|
32
|
+
};
|
|
33
|
+
export type AuthManagerOptions = {
|
|
34
|
+
readonly rateLimitKeySecret: string;
|
|
35
|
+
readonly usersCollection?: string;
|
|
36
|
+
readonly credentialsCollection?: string;
|
|
37
|
+
readonly auditCollection?: string;
|
|
38
|
+
readonly minimumPasswordLength?: number;
|
|
39
|
+
readonly maximumPasswordLength?: number;
|
|
40
|
+
readonly requireVerifiedEmail?: boolean;
|
|
41
|
+
readonly mfaPolicy?: AuthMfaPolicy;
|
|
42
|
+
readonly rateLimits?: {
|
|
43
|
+
readonly registration?: Partial<AuthRateLimitScopePolicy>;
|
|
44
|
+
readonly signIn?: Partial<AuthRateLimitScopePolicy>;
|
|
45
|
+
};
|
|
46
|
+
readonly clock?: () => Date;
|
|
47
|
+
};
|
|
48
|
+
export type AuthManagerDependencies = {
|
|
49
|
+
readonly storage: StorageAdapter;
|
|
50
|
+
readonly passwordHasher: PasswordHasher;
|
|
51
|
+
readonly rateLimitProvider: RateLimitProvider;
|
|
52
|
+
readonly sessions: SessionManager;
|
|
53
|
+
readonly mfa?: AuthMfaGateway;
|
|
54
|
+
};
|
|
55
|
+
export type AuthSession = {
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly userId: string;
|
|
58
|
+
readonly kind: "authenticated";
|
|
59
|
+
readonly createdAt: Date;
|
|
60
|
+
readonly lastSeenAt: Date;
|
|
61
|
+
readonly expiresAt: Date;
|
|
62
|
+
readonly revokedAt: Date | null;
|
|
63
|
+
};
|
|
64
|
+
export type AuthResult = {
|
|
65
|
+
readonly user: AuthUser;
|
|
66
|
+
readonly session: AuthSession;
|
|
67
|
+
/** Server-only. Deliver exclusively through a secure HttpOnly cookie. */
|
|
68
|
+
readonly token: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Registration can complete immediately when MFA is optional.
|
|
72
|
+
*
|
|
73
|
+
* When MFA is required, registration creates the account but returns
|
|
74
|
+
* an enrollment challenge instead of an authenticated session.
|
|
75
|
+
*/
|
|
76
|
+
export type AuthRegisterResult = AuthResult | AuthMfaEnrollmentPendingResult;
|
|
77
|
+
/**
|
|
78
|
+
* A successful password check can result in:
|
|
79
|
+
*
|
|
80
|
+
* 1. A fully authenticated session.
|
|
81
|
+
* 2. An MFA sign-in challenge.
|
|
82
|
+
* 3. A mandatory first-factor enrollment challenge.
|
|
83
|
+
*/
|
|
84
|
+
export type AuthSignInResult = AuthResult | AuthMfaPendingResult | AuthMfaEnrollmentPendingResult;
|
|
85
|
+
export type AuthRegisterInput = {
|
|
86
|
+
readonly email: string;
|
|
87
|
+
readonly password: string;
|
|
88
|
+
readonly name?: string | null;
|
|
89
|
+
readonly ipAddress: string;
|
|
90
|
+
readonly sessionMetadata?: Readonly<Record<string, unknown>>;
|
|
91
|
+
};
|
|
92
|
+
export type AuthSignInInput = {
|
|
93
|
+
readonly email: string;
|
|
94
|
+
readonly password: string;
|
|
95
|
+
readonly ipAddress: string;
|
|
96
|
+
readonly sessionMetadata?: Readonly<Record<string, unknown>>;
|
|
97
|
+
};
|
|
98
|
+
export interface AuthManager {
|
|
99
|
+
initialize(): Promise<void>;
|
|
100
|
+
register(input: AuthRegisterInput): Promise<AuthRegisterResult>;
|
|
101
|
+
signIn(input: AuthSignInInput): Promise<AuthSignInResult>;
|
|
102
|
+
getCurrentUser(token: string): Promise<AuthUser | null>;
|
|
103
|
+
getAuthState(token: string): Promise<AuthState>;
|
|
104
|
+
signOut(token: string): Promise<void>;
|
|
105
|
+
signOutEverywhere(userId: string): Promise<void>;
|
|
106
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { PasswordHasher, RateLimitProvider } from "../providers/provider.types.js";
|
|
2
|
+
import { AuthMfaEnrollmentPendingResult, AuthMfaGateway, AuthMfaPendingResult } from "./mfa.types.js";
|
|
3
|
+
import { AuthState } from "./state.types.js";
|
|
4
|
+
import { AuthUser } from "./user.types.js";
|
|
5
|
+
import { StorageAdapter } from "@puzzle-ui/storage";
|
|
6
|
+
import { SessionManager } from "@puzzle-ui/session";
|
|
7
|
+
export type AuthRateLimitRule = {
|
|
8
|
+
readonly limit: number;
|
|
9
|
+
readonly windowSeconds: number;
|
|
10
|
+
};
|
|
11
|
+
export type AuthRateLimitScopePolicy = {
|
|
12
|
+
readonly email: AuthRateLimitRule;
|
|
13
|
+
readonly ip: AuthRateLimitRule;
|
|
14
|
+
};
|
|
15
|
+
export type AuthRateLimitPolicies = {
|
|
16
|
+
readonly registration: AuthRateLimitScopePolicy;
|
|
17
|
+
readonly signIn: AuthRateLimitScopePolicy;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Determines which users must authenticate with MFA.
|
|
21
|
+
*
|
|
22
|
+
* When required is true, MFA is required for every user.
|
|
23
|
+
* requiredFor can additionally enforce MFA for selected users.
|
|
24
|
+
*
|
|
25
|
+
* When neither option is configured, MFA remains optional for
|
|
26
|
+
* users without enrolled factors. Existing enrolled factors are
|
|
27
|
+
* still challenged during sign-in.
|
|
28
|
+
*/
|
|
29
|
+
export type AuthMfaPolicy = {
|
|
30
|
+
readonly required?: boolean;
|
|
31
|
+
readonly requiredFor?: (user: AuthUser) => boolean | Promise<boolean>;
|
|
32
|
+
};
|
|
33
|
+
export type AuthManagerOptions = {
|
|
34
|
+
readonly rateLimitKeySecret: string;
|
|
35
|
+
readonly usersCollection?: string;
|
|
36
|
+
readonly credentialsCollection?: string;
|
|
37
|
+
readonly auditCollection?: string;
|
|
38
|
+
readonly minimumPasswordLength?: number;
|
|
39
|
+
readonly maximumPasswordLength?: number;
|
|
40
|
+
readonly requireVerifiedEmail?: boolean;
|
|
41
|
+
readonly mfaPolicy?: AuthMfaPolicy;
|
|
42
|
+
readonly rateLimits?: {
|
|
43
|
+
readonly registration?: Partial<AuthRateLimitScopePolicy>;
|
|
44
|
+
readonly signIn?: Partial<AuthRateLimitScopePolicy>;
|
|
45
|
+
};
|
|
46
|
+
readonly clock?: () => Date;
|
|
47
|
+
};
|
|
48
|
+
export type AuthManagerDependencies = {
|
|
49
|
+
readonly storage: StorageAdapter;
|
|
50
|
+
readonly passwordHasher: PasswordHasher;
|
|
51
|
+
readonly rateLimitProvider: RateLimitProvider;
|
|
52
|
+
readonly sessions: SessionManager;
|
|
53
|
+
readonly mfa?: AuthMfaGateway;
|
|
54
|
+
};
|
|
55
|
+
export type AuthSession = {
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly userId: string;
|
|
58
|
+
readonly kind: "authenticated";
|
|
59
|
+
readonly createdAt: Date;
|
|
60
|
+
readonly lastSeenAt: Date;
|
|
61
|
+
readonly expiresAt: Date;
|
|
62
|
+
readonly revokedAt: Date | null;
|
|
63
|
+
};
|
|
64
|
+
export type AuthResult = {
|
|
65
|
+
readonly user: AuthUser;
|
|
66
|
+
readonly session: AuthSession;
|
|
67
|
+
/** Server-only. Deliver exclusively through a secure HttpOnly cookie. */
|
|
68
|
+
readonly token: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Registration can complete immediately when MFA is optional.
|
|
72
|
+
*
|
|
73
|
+
* When MFA is required, registration creates the account but returns
|
|
74
|
+
* an enrollment challenge instead of an authenticated session.
|
|
75
|
+
*/
|
|
76
|
+
export type AuthRegisterResult = AuthResult | AuthMfaEnrollmentPendingResult;
|
|
77
|
+
/**
|
|
78
|
+
* A successful password check can result in:
|
|
79
|
+
*
|
|
80
|
+
* 1. A fully authenticated session.
|
|
81
|
+
* 2. An MFA sign-in challenge.
|
|
82
|
+
* 3. A mandatory first-factor enrollment challenge.
|
|
83
|
+
*/
|
|
84
|
+
export type AuthSignInResult = AuthResult | AuthMfaPendingResult | AuthMfaEnrollmentPendingResult;
|
|
85
|
+
export type AuthRegisterInput = {
|
|
86
|
+
readonly email: string;
|
|
87
|
+
readonly password: string;
|
|
88
|
+
readonly name?: string | null;
|
|
89
|
+
readonly ipAddress: string;
|
|
90
|
+
readonly sessionMetadata?: Readonly<Record<string, unknown>>;
|
|
91
|
+
};
|
|
92
|
+
export type AuthSignInInput = {
|
|
93
|
+
readonly email: string;
|
|
94
|
+
readonly password: string;
|
|
95
|
+
readonly ipAddress: string;
|
|
96
|
+
readonly sessionMetadata?: Readonly<Record<string, unknown>>;
|
|
97
|
+
};
|
|
98
|
+
export interface AuthManager {
|
|
99
|
+
initialize(): Promise<void>;
|
|
100
|
+
register(input: AuthRegisterInput): Promise<AuthRegisterResult>;
|
|
101
|
+
signIn(input: AuthSignInInput): Promise<AuthSignInResult>;
|
|
102
|
+
getCurrentUser(token: string): Promise<AuthUser | null>;
|
|
103
|
+
getAuthState(token: string): Promise<AuthState>;
|
|
104
|
+
signOut(token: string): Promise<void>;
|
|
105
|
+
signOutEverywhere(userId: string): Promise<void>;
|
|
106
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PasswordHash } from "../providers/provider.types.cjs";
|
|
2
|
+
import { StorageRecord } from "@puzzle-ui/storage";
|
|
3
|
+
export type PasswordCredentialStorageRecord = StorageRecord & {
|
|
4
|
+
readonly userId: string;
|
|
5
|
+
readonly kind: "password";
|
|
6
|
+
readonly passwordHash: PasswordHash;
|
|
7
|
+
readonly passwordUpdatedAt: Date;
|
|
8
|
+
readonly revokedAt: Date | null;
|
|
9
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PasswordHash } from "../providers/provider.types.js";
|
|
2
|
+
import { StorageRecord } from "@puzzle-ui/storage";
|
|
3
|
+
export type PasswordCredentialStorageRecord = StorageRecord & {
|
|
4
|
+
readonly userId: string;
|
|
5
|
+
readonly kind: "password";
|
|
6
|
+
readonly passwordHash: PasswordHash;
|
|
7
|
+
readonly passwordUpdatedAt: Date;
|
|
8
|
+
readonly revokedAt: Date | null;
|
|
9
|
+
};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { AuthResult } from "./auth-manager.types.cjs";
|
|
2
|
+
import { ProtectedAuthSecret } from "../providers/mfa-provider.types.cjs";
|
|
3
|
+
import { AuthState } from "./state.types.cjs";
|
|
4
|
+
import { StorageRecord } from "@puzzle-ui/storage";
|
|
5
|
+
export type AuthMfaFactorType = "totp" | "passkey";
|
|
6
|
+
export type AuthMfaFactorStatus = "pending" | "active";
|
|
7
|
+
export type AuthMfaAvailableFactor = "totp" | "passkey" | "recovery-code";
|
|
8
|
+
export type AuthMfaEnrollmentFactor = "totp" | "passkey";
|
|
9
|
+
export type AuthMfaEnrollmentPendingResult = {
|
|
10
|
+
readonly status: "mfa-enrollment-pending";
|
|
11
|
+
readonly challengeId: string;
|
|
12
|
+
/**
|
|
13
|
+
* Server-only temporary credential.
|
|
14
|
+
*
|
|
15
|
+
* Deliver exclusively through a short-lived HttpOnly cookie.
|
|
16
|
+
* Never serialize this token into browser JSON or browser storage.
|
|
17
|
+
*/
|
|
18
|
+
readonly challengeToken: string;
|
|
19
|
+
readonly expiresAt: Date;
|
|
20
|
+
readonly availableFactors: readonly AuthMfaEnrollmentFactor[];
|
|
21
|
+
};
|
|
22
|
+
export type AuthMfaEnrollmentCompleted = {
|
|
23
|
+
/**
|
|
24
|
+
* The authenticated result is only created after the first factor
|
|
25
|
+
* has been successfully verified and the enrollment transaction commits.
|
|
26
|
+
*/
|
|
27
|
+
readonly auth: AuthResult;
|
|
28
|
+
readonly factor: AuthMfaFactor;
|
|
29
|
+
readonly recoveryCodes: readonly string[];
|
|
30
|
+
};
|
|
31
|
+
export type BeginRequiredMfaEnrollmentInput = {
|
|
32
|
+
readonly challengeToken: string;
|
|
33
|
+
readonly ipAddress: string;
|
|
34
|
+
readonly label?: string;
|
|
35
|
+
};
|
|
36
|
+
export type VerifyRequiredTotpEnrollmentInput = {
|
|
37
|
+
readonly challengeToken: string;
|
|
38
|
+
readonly factorId: string;
|
|
39
|
+
readonly code: string;
|
|
40
|
+
readonly ipAddress: string;
|
|
41
|
+
};
|
|
42
|
+
export type FinishRequiredPasskeyEnrollmentInput = {
|
|
43
|
+
readonly challengeToken: string;
|
|
44
|
+
readonly response: unknown;
|
|
45
|
+
readonly ipAddress: string;
|
|
46
|
+
};
|
|
47
|
+
export type AuthMfaFactor = {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly type: AuthMfaFactorType;
|
|
50
|
+
readonly status: AuthMfaFactorStatus;
|
|
51
|
+
readonly label: string;
|
|
52
|
+
readonly createdAt: Date;
|
|
53
|
+
readonly lastUsedAt: Date | null;
|
|
54
|
+
};
|
|
55
|
+
export type AuthMfaFactorStorageRecord = StorageRecord & {
|
|
56
|
+
readonly userId: string;
|
|
57
|
+
/**
|
|
58
|
+
* Globally unique key.
|
|
59
|
+
* TOTP: totp:<factor-id>
|
|
60
|
+
* Passkey: passkey:<credential-id>
|
|
61
|
+
*/
|
|
62
|
+
readonly factorKey: string;
|
|
63
|
+
readonly type: AuthMfaFactorType;
|
|
64
|
+
readonly status: AuthMfaFactorStatus;
|
|
65
|
+
readonly label: string;
|
|
66
|
+
/** Encrypted TOTP secret; never populated for passkeys. */
|
|
67
|
+
readonly secret: ProtectedAuthSecret | null;
|
|
68
|
+
/** WebAuthn credential data; never populated for TOTP. */
|
|
69
|
+
readonly credentialId: string | null;
|
|
70
|
+
readonly publicKey: string | null;
|
|
71
|
+
readonly counter: number;
|
|
72
|
+
readonly transports: readonly string[];
|
|
73
|
+
/** Prevents reuse of an accepted TOTP time step. */
|
|
74
|
+
readonly lastUsedStep: number | null;
|
|
75
|
+
readonly lastUsedAt: Date | null;
|
|
76
|
+
/** Pending enrollments expire automatically at the domain level. */
|
|
77
|
+
readonly expiresAt: Date | null;
|
|
78
|
+
};
|
|
79
|
+
export type AuthMfaChallengePurpose = "sign-in" | "enrollment" | "passkey-registration" | "passkey-sign-in";
|
|
80
|
+
export type AuthMfaChallengeStorageRecord = StorageRecord & {
|
|
81
|
+
readonly purpose: AuthMfaChallengePurpose;
|
|
82
|
+
/** SHA-256 hash of the temporary challenge token. */
|
|
83
|
+
readonly tokenHash: string;
|
|
84
|
+
/** Null for discoverable passkey sign-in. */
|
|
85
|
+
readonly userId: string | null;
|
|
86
|
+
/** Factors accepted by the current MFA challenge. */
|
|
87
|
+
readonly factorIds: readonly string[];
|
|
88
|
+
/** Passkeys allowed for an in-progress WebAuthn ceremony. */
|
|
89
|
+
readonly webAuthnFactorIds: readonly string[];
|
|
90
|
+
/** WebAuthn challenge, bound to this temporary record. */
|
|
91
|
+
readonly webAuthnChallenge: string | null;
|
|
92
|
+
/** Label selected when a passkey registration was initiated. */
|
|
93
|
+
readonly factorLabel: string | null;
|
|
94
|
+
/**
|
|
95
|
+
* Account-state snapshot for password-backed sign-in challenges.
|
|
96
|
+
* A challenge becomes invalid if either record changes.
|
|
97
|
+
*/
|
|
98
|
+
readonly userRevision: number | null;
|
|
99
|
+
readonly credentialRevision: number | null;
|
|
100
|
+
readonly attemptCount: number;
|
|
101
|
+
readonly maximumAttempts: number;
|
|
102
|
+
readonly expiresAt: Date;
|
|
103
|
+
readonly consumedAt: Date | null;
|
|
104
|
+
};
|
|
105
|
+
export type AuthRecoveryCodeStorageRecord = StorageRecord & {
|
|
106
|
+
readonly userId: string;
|
|
107
|
+
readonly factorId: string;
|
|
108
|
+
readonly codeHash: string;
|
|
109
|
+
readonly consumedAt: Date | null;
|
|
110
|
+
};
|
|
111
|
+
export type AuthMfaPendingResult = {
|
|
112
|
+
readonly status: "mfa-pending";
|
|
113
|
+
readonly challengeId: string;
|
|
114
|
+
/**
|
|
115
|
+
* Server-only temporary credential.
|
|
116
|
+
*
|
|
117
|
+
* Deliver this through a short-lived HttpOnly cookie.
|
|
118
|
+
* Never serialize it into browser JSON or browser storage.
|
|
119
|
+
*/
|
|
120
|
+
readonly challengeToken: string;
|
|
121
|
+
readonly expiresAt: Date;
|
|
122
|
+
readonly availableFactors: readonly AuthMfaAvailableFactor[];
|
|
123
|
+
};
|
|
124
|
+
export type AuthMfaGateway = {
|
|
125
|
+
initialize(): Promise<void>;
|
|
126
|
+
createSignInChallenge(input: {
|
|
127
|
+
readonly userId: string;
|
|
128
|
+
readonly ipAddress: string;
|
|
129
|
+
}): Promise<AuthMfaPendingResult | null>;
|
|
130
|
+
createEnrollmentChallenge(input: {
|
|
131
|
+
readonly userId: string;
|
|
132
|
+
readonly ipAddress: string;
|
|
133
|
+
}): Promise<AuthMfaEnrollmentPendingResult>;
|
|
134
|
+
/**
|
|
135
|
+
* Returns whether the user currently has at least one active MFA factor.
|
|
136
|
+
* Used by AuthManager to enforce policy against existing sessions.
|
|
137
|
+
*/
|
|
138
|
+
hasActiveFactor(userId: string): Promise<boolean>;
|
|
139
|
+
getPendingAuthState(token: string): Promise<AuthState | null>;
|
|
140
|
+
revokeChallenges(userId: string): Promise<void>;
|
|
141
|
+
};
|
|
142
|
+
export type AuthMfaTotpOptions = {
|
|
143
|
+
readonly digits: number;
|
|
144
|
+
readonly periodSeconds: number;
|
|
145
|
+
/** Number of adjacent time steps accepted on either side. */
|
|
146
|
+
readonly window: number;
|
|
147
|
+
};
|
|
148
|
+
export type AuthMfaWebAuthnOptions = {
|
|
149
|
+
readonly rpId: string;
|
|
150
|
+
readonly rpName: string;
|
|
151
|
+
/** Exact expected browser origin, including scheme and port. */
|
|
152
|
+
readonly origin: string;
|
|
153
|
+
};
|
|
154
|
+
export type AuthMfaManagerOptions = {
|
|
155
|
+
readonly rateLimitKeySecret: string;
|
|
156
|
+
readonly usersCollection?: string;
|
|
157
|
+
readonly credentialsCollection?: string;
|
|
158
|
+
readonly auditCollection?: string;
|
|
159
|
+
readonly factorsCollection?: string;
|
|
160
|
+
readonly challengesCollection?: string;
|
|
161
|
+
readonly recoveryCodesCollection?: string;
|
|
162
|
+
readonly challengeLifetimeSeconds?: number;
|
|
163
|
+
readonly recoveryCodeCount?: number;
|
|
164
|
+
readonly issuer?: string;
|
|
165
|
+
readonly totp?: Partial<AuthMfaTotpOptions>;
|
|
166
|
+
readonly webAuthn?: AuthMfaWebAuthnOptions;
|
|
167
|
+
/** Keep this aligned with AuthManagerOptions.requireVerifiedEmail. */
|
|
168
|
+
readonly requireVerifiedEmail?: boolean;
|
|
169
|
+
readonly clock?: () => Date;
|
|
170
|
+
};
|
|
171
|
+
export type BeginTotpEnrollmentInput = {
|
|
172
|
+
readonly sessionToken: string;
|
|
173
|
+
readonly currentPassword: string;
|
|
174
|
+
readonly ipAddress: string;
|
|
175
|
+
readonly label?: string;
|
|
176
|
+
};
|
|
177
|
+
export type TotpEnrollmentStarted = {
|
|
178
|
+
readonly factorId: string;
|
|
179
|
+
readonly secret: string;
|
|
180
|
+
readonly otpauthUrl: string;
|
|
181
|
+
readonly expiresAt: Date;
|
|
182
|
+
};
|
|
183
|
+
export type VerifyTotpEnrollmentInput = {
|
|
184
|
+
readonly sessionToken: string;
|
|
185
|
+
readonly factorId: string;
|
|
186
|
+
readonly code: string;
|
|
187
|
+
readonly ipAddress: string;
|
|
188
|
+
};
|
|
189
|
+
export type MfaEnrollmentCompleted = {
|
|
190
|
+
readonly factor: AuthMfaFactor;
|
|
191
|
+
readonly recoveryCodes: readonly string[];
|
|
192
|
+
};
|
|
193
|
+
export type BeginPasskeyRegistrationInput = {
|
|
194
|
+
readonly sessionToken: string;
|
|
195
|
+
readonly currentPassword: string;
|
|
196
|
+
readonly ipAddress: string;
|
|
197
|
+
readonly label?: string;
|
|
198
|
+
};
|
|
199
|
+
export type PasskeyRegistrationStarted = {
|
|
200
|
+
readonly challengeId: string;
|
|
201
|
+
readonly challengeToken: string;
|
|
202
|
+
readonly options: Readonly<Record<string, unknown>>;
|
|
203
|
+
readonly expiresAt: Date;
|
|
204
|
+
};
|
|
205
|
+
export type FinishPasskeyRegistrationInput = {
|
|
206
|
+
readonly sessionToken: string;
|
|
207
|
+
readonly challengeToken: string;
|
|
208
|
+
readonly response: unknown;
|
|
209
|
+
readonly ipAddress: string;
|
|
210
|
+
};
|
|
211
|
+
export type BeginPasskeySignInInput = {
|
|
212
|
+
readonly ipAddress: string;
|
|
213
|
+
};
|
|
214
|
+
export type PasskeySignInStarted = {
|
|
215
|
+
readonly challengeId: string;
|
|
216
|
+
readonly challengeToken: string;
|
|
217
|
+
readonly options: Readonly<Record<string, unknown>>;
|
|
218
|
+
readonly expiresAt: Date;
|
|
219
|
+
};
|
|
220
|
+
export type CompleteMfaSignInInput = {
|
|
221
|
+
readonly challengeToken: string;
|
|
222
|
+
readonly code: string;
|
|
223
|
+
readonly ipAddress: string;
|
|
224
|
+
};
|
|
225
|
+
export type PasskeyMfaStarted = {
|
|
226
|
+
readonly options: Readonly<Record<string, unknown>>;
|
|
227
|
+
readonly expiresAt: Date;
|
|
228
|
+
};
|
|
229
|
+
export type PasskeyMfaCompletedInput = {
|
|
230
|
+
readonly challengeToken: string;
|
|
231
|
+
readonly response: unknown;
|
|
232
|
+
readonly ipAddress: string;
|
|
233
|
+
};
|
|
234
|
+
export type RemoveMfaFactorInput = {
|
|
235
|
+
readonly sessionToken: string;
|
|
236
|
+
readonly factorId: string;
|
|
237
|
+
readonly currentPassword: string;
|
|
238
|
+
readonly ipAddress: string;
|
|
239
|
+
};
|
|
240
|
+
export type RegenerateRecoveryCodesInput = {
|
|
241
|
+
readonly sessionToken: string;
|
|
242
|
+
readonly currentPassword: string;
|
|
243
|
+
readonly ipAddress: string;
|
|
244
|
+
};
|