@absolutejs/auth 0.58.0 → 0.59.1

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.
@@ -31,7 +31,7 @@ export declare const mfaChallenge: <UserType>({ authSessionStore, challengeRoute
31
31
  headers: unknown;
32
32
  response: {
33
33
  400: string;
34
- 401: "SMS code expired" | "Too many attempts" | "No MFA challenge in progress" | "Invalid MFA code";
34
+ 401: "SMS code expired" | "Too many attempts" | "SMS challenge is no longer active" | "No MFA challenge in progress" | "Invalid MFA code";
35
35
  200: {
36
36
  readonly status: "sent";
37
37
  } | {
@@ -3,6 +3,7 @@ import type { RouteString, UserSessionId } from '../types';
3
3
  import type { MFAStore } from './types';
4
4
  export declare const DEFAULT_BACKUP_CODE_COUNT = 10;
5
5
  export declare const DEFAULT_MFA_ISSUER = "AbsoluteAuth";
6
+ export declare const DEFAULT_MFA_MANAGEMENT_AUTH_MAX_AGE_MS: number;
6
7
  export declare const DEFAULT_MFA_SESSION_TTL_MS: number;
7
8
  export declare const DEFAULT_SMS_CODE_LENGTH = 6;
8
9
  export declare const DEFAULT_SMS_CODE_TTL_MS: number;
@@ -25,6 +26,8 @@ export type MfaConfig<UserType> = {
25
26
  encryptionKey?: string;
26
27
  issuer?: string;
27
28
  managementRoute?: RouteString;
29
+ /** Maximum age of the authentication ceremony used to enroll, replace, or remove MFA. */
30
+ managementAuthMaxAgeMs?: number;
28
31
  onMfaChallengeError?: (context: {
29
32
  error: unknown;
30
33
  userId?: string;
@@ -1,5 +1,5 @@
1
1
  import { Elysia } from 'elysia';
2
- import type { MfaRouteProps } from './config';
2
+ import { type MfaRouteProps } from './config';
3
3
  export type MfaStatus = {
4
4
  backupCodesRemaining: number;
5
5
  enabled: boolean;
@@ -11,7 +11,7 @@ export type MfaStatus = {
11
11
  enabled: boolean;
12
12
  };
13
13
  };
14
- export declare const mfaManagementRoutes: <UserType>({ authSessionStore, getUserId, managementRoute, mfaStore }: MfaRouteProps<UserType>) => Elysia<"", {
14
+ export declare const mfaManagementRoutes: <UserType>({ authSessionStore, getUserId, managementRoute, managementAuthMaxAgeMs, mfaStore }: MfaRouteProps<UserType>) => Elysia<"", {
15
15
  decorator: {};
16
16
  store: {
17
17
  session: import("..").SessionRecord<UserType>;
@@ -69,7 +69,7 @@ export declare const mfaManagementRoutes: <UserType>({ authSessionStore, getUser
69
69
  query: unknown;
70
70
  headers: unknown;
71
71
  response: {
72
- 401: "Authentication required";
72
+ 401: "Recent authentication required" | "Authentication required";
73
73
  200: {
74
74
  readonly status: "disabled";
75
75
  };
@@ -49,6 +49,21 @@ export declare const mfaEnrollmentsTable: import("drizzle-orm/pg-core").PgTableW
49
49
  identity: undefined;
50
50
  generated: undefined;
51
51
  }>;
52
+ sms_challenge_id: import("drizzle-orm/pg-core").PgBuildColumn<"auth_mfa_enrollments", import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>, {
53
+ name: string;
54
+ tableName: "auth_mfa_enrollments";
55
+ dataType: "string";
56
+ data: string;
57
+ driverParam: string;
58
+ notNull: false;
59
+ hasDefault: false;
60
+ isPrimaryKey: false;
61
+ isAutoincrement: false;
62
+ hasRuntimeDefault: false;
63
+ enumValues: undefined;
64
+ identity: undefined;
65
+ generated: undefined;
66
+ }>;
52
67
  sms_code_sent_at_ms: import("drizzle-orm/pg-core").PgBuildColumn<"auth_mfa_enrollments", import("drizzle-orm/pg-core").PgBigInt53Builder, {
53
68
  name: string;
54
69
  tableName: "auth_mfa_enrollments";
@@ -0,0 +1,2 @@
1
+ import type { SessionData } from '../types';
2
+ export declare const hasRecentAuthentication: <UserType>(session: SessionData<UserType>, maxAgeMs: number, now?: number) => boolean;
@@ -58,7 +58,7 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
58
58
  query: unknown;
59
59
  headers: unknown;
60
60
  response: {
61
- 401: "Authentication required";
61
+ 401: "Recent authentication required" | "Authentication required";
62
62
  200: {
63
63
  readonly status: "disabled";
64
64
  };
@@ -82,7 +82,7 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
82
82
  query: unknown;
83
83
  headers: unknown;
84
84
  response: {
85
- 401: "Authentication required";
85
+ 401: "Recent authentication required" | "Authentication required";
86
86
  200: {
87
87
  readonly secret: string;
88
88
  readonly uri: string;
@@ -110,7 +110,7 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
110
110
  headers: unknown;
111
111
  response: {
112
112
  400: "No TOTP enrollment in progress" | "Invalid TOTP code";
113
- 401: "Authentication required";
113
+ 401: "Recent authentication required" | "Authentication required";
114
114
  200: {
115
115
  readonly backupCodes: string[];
116
116
  };
@@ -137,7 +137,7 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
137
137
  headers: unknown;
138
138
  response: {
139
139
  400: string;
140
- 401: "Authentication required";
140
+ 401: "Recent authentication required" | "Authentication required";
141
141
  501: "SMS MFA is not configured";
142
142
  200: {
143
143
  readonly phone: string;
@@ -167,7 +167,7 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
167
167
  headers: unknown;
168
168
  response: {
169
169
  400: string;
170
- 401: "Authentication required";
170
+ 401: "Recent authentication required" | "Authentication required";
171
171
  200: {
172
172
  readonly status: "enrolled";
173
173
  };
@@ -198,7 +198,7 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
198
198
  headers: unknown;
199
199
  response: {
200
200
  400: string;
201
- 401: "SMS code expired" | "Too many attempts" | "No MFA challenge in progress" | "Invalid MFA code";
201
+ 401: "SMS code expired" | "Too many attempts" | "SMS challenge is no longer active" | "No MFA challenge in progress" | "Invalid MFA code";
202
202
  200: {
203
203
  readonly status: "sent";
204
204
  } | {
package/dist/mfa/sms.d.ts CHANGED
@@ -2,6 +2,9 @@ import { Elysia } from 'elysia';
2
2
  import { type VerificationCheckInput, type VerificationProvider } from '../verification/types';
3
3
  import { type MfaRouteProps, type SmsCodeMessage } from './config';
4
4
  import type { MfaEnrollment, MFAStore } from './types';
5
+ export declare class SmsChallengeConflictError extends Error {
6
+ name: string;
7
+ }
5
8
  export declare const checkWithVerificationProvider: (provider: VerificationProvider, input: VerificationCheckInput) => Promise<{
6
9
  result: import("..").VerificationCheckResult;
7
10
  readonly error?: undefined;
@@ -19,14 +22,16 @@ export declare const checkWithVerificationProvider: (provider: VerificationProvi
19
22
  result?: undefined;
20
23
  }>;
21
24
  export declare const isE164Phone: (phone: string) => boolean;
22
- export declare const issueAndStoreSmsCode: ({ codeLength, enrollment, mfaStore, onSendSmsCode, purpose, verificationProvider, ttlMs, userId }: {
25
+ export declare const issueAndStoreSmsCode: ({ codeLength, enrollment, mfaStore, onSendSmsCode, previousEnrollment, purpose, resendCooldownMs, verificationProvider, ttlMs, userId }: {
23
26
  codeLength: number;
24
27
  enrollment: MfaEnrollment;
25
28
  mfaStore: MFAStore;
26
29
  onSendSmsCode?: (message: SmsCodeMessage) => void | Promise<void>;
30
+ previousEnrollment?: MfaEnrollment;
27
31
  verificationProvider?: MfaRouteProps<unknown>["verificationProvider"];
28
32
  userId: string;
29
33
  purpose: "mfa_challenge" | "mfa_enrollment";
34
+ resendCooldownMs: number;
30
35
  ttlMs: number;
31
36
  }) => Promise<number | undefined>;
32
37
  export declare const mapVerificationProviderError: (error: unknown) => {
@@ -40,7 +45,7 @@ export declare const mapVerificationProviderError: (error: unknown) => {
40
45
  status: "Service Unavailable";
41
46
  } | undefined;
42
47
  export declare const maskPhone: (phone: string) => string;
43
- export declare const mfaSmsRoutes: <UserType>({ authSessionStore, getUserId, mfaStore, onMfaEnrolled, onSendSmsCode, verificationProvider, smsCodeLength, smsCodeTtlMs, smsMaxAttempts, smsResendCooldownMs, smsSetupRoute, smsVerifyRoute }: MfaRouteProps<UserType>) => Elysia<"", {
48
+ export declare const mfaSmsRoutes: <UserType>({ authSessionStore, getUserId, mfaStore, managementAuthMaxAgeMs, onMfaEnrolled, onSendSmsCode, verificationProvider, smsCodeLength, smsCodeTtlMs, smsMaxAttempts, smsResendCooldownMs, smsSetupRoute, smsVerifyRoute }: MfaRouteProps<UserType>) => Elysia<"", {
44
49
  decorator: {};
45
50
  store: {
46
51
  session: import("..").SessionRecord<UserType>;
@@ -69,7 +74,7 @@ export declare const mfaSmsRoutes: <UserType>({ authSessionStore, getUserId, mfa
69
74
  headers: unknown;
70
75
  response: {
71
76
  400: string;
72
- 401: "Authentication required";
77
+ 401: "Recent authentication required" | "Authentication required";
73
78
  501: "SMS MFA is not configured";
74
79
  200: {
75
80
  readonly phone: string;
@@ -99,7 +104,7 @@ export declare const mfaSmsRoutes: <UserType>({ authSessionStore, getUserId, mfa
99
104
  headers: unknown;
100
105
  response: {
101
106
  400: string;
102
- 401: "Authentication required";
107
+ 401: "Recent authentication required" | "Authentication required";
103
108
  200: {
104
109
  readonly status: "enrolled";
105
110
  };
@@ -1,6 +1,6 @@
1
1
  import { Elysia } from 'elysia';
2
2
  import { type MfaRouteProps } from './config';
3
- export declare const mfaTotpRoutes: <UserType>({ authSessionStore, backupCodeCount, encryptionKey, getUserId, issuer, mfaStore, onMfaEnrolled, totpSetupRoute, totpVerifyRoute }: MfaRouteProps<UserType>) => Elysia<"", {
3
+ export declare const mfaTotpRoutes: <UserType>({ authSessionStore, backupCodeCount, encryptionKey, getUserId, issuer, managementAuthMaxAgeMs, mfaStore, onMfaEnrolled, totpSetupRoute, totpVerifyRoute }: MfaRouteProps<UserType>) => Elysia<"", {
4
4
  decorator: {};
5
5
  store: {
6
6
  session: import("..").SessionRecord<UserType>;
@@ -26,7 +26,7 @@ export declare const mfaTotpRoutes: <UserType>({ authSessionStore, backupCodeCou
26
26
  query: unknown;
27
27
  headers: unknown;
28
28
  response: {
29
- 401: "Authentication required";
29
+ 401: "Recent authentication required" | "Authentication required";
30
30
  200: {
31
31
  readonly secret: string;
32
32
  readonly uri: string;
@@ -54,7 +54,7 @@ export declare const mfaTotpRoutes: <UserType>({ authSessionStore, backupCodeCou
54
54
  headers: unknown;
55
55
  response: {
56
56
  400: "No TOTP enrollment in progress" | "Invalid TOTP code";
57
- 401: "Authentication required";
57
+ 401: "Recent authentication required" | "Authentication required";
58
58
  200: {
59
59
  readonly backupCodes: string[];
60
60
  };
@@ -10,6 +10,8 @@ export type MfaEnrollment = {
10
10
  smsPendingPurpose?: VerificationPurpose;
11
11
  smsProviderReference?: string;
12
12
  smsCodeSentAt?: number;
13
+ /** Opaque generation binding every update to the currently active challenge. */
14
+ smsChallengeId?: string;
13
15
  smsPhone?: string;
14
16
  smsVerified: boolean;
15
17
  totpFailedAttempts?: number;
@@ -19,9 +21,38 @@ export type MfaEnrollment = {
19
21
  userId: string;
20
22
  };
21
23
  export type MFAStore = {
24
+ /** Atomically reserves the SMS slot only when the resend cooldown has elapsed. */
25
+ claimSmsChallenge: (input: {
26
+ challengeId: string;
27
+ cooldownCutoff: number;
28
+ enrollment: MfaEnrollment;
29
+ }) => Promise<boolean>;
30
+ completeSmsChallenge: (input: {
31
+ challengeId: string;
32
+ lastUsedAt?: number;
33
+ smsVerified: boolean;
34
+ userId: string;
35
+ }) => Promise<boolean>;
36
+ finalizeSmsChallenge: (input: {
37
+ challengeId: string;
38
+ expiresAt: number;
39
+ hash?: string;
40
+ providerReference?: string;
41
+ userId: string;
42
+ }) => Promise<boolean>;
22
43
  getEnrollment: (userId: string) => Promise<MfaEnrollment | undefined>;
23
44
  listEnrollments: () => Promise<MfaEnrollment[]>;
24
45
  removeEnrollment: (userId: string) => Promise<void>;
46
+ recordSmsFailure: (input: {
47
+ challengeId: string;
48
+ maxAttempts: number;
49
+ userId: string;
50
+ }) => Promise<number | undefined>;
51
+ rollbackSmsChallenge: (input: {
52
+ challengeId: string;
53
+ previous?: MfaEnrollment;
54
+ userId: string;
55
+ }) => Promise<void>;
25
56
  saveEnrollment: (enrollment: MfaEnrollment) => Promise<void>;
26
57
  };
27
58
  export declare const isMfaEnrolled: (enrollment: MfaEnrollment | undefined) => boolean;