@absolutejs/auth 0.75.3 → 0.76.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.
@@ -1,4 +1,13 @@
1
1
  import { type MfaRouteProps } from './config';
2
+ export type MfaChallengeOptions = {
3
+ backupCodesAvailable: boolean;
4
+ factors: Array<{
5
+ id: string;
6
+ label: string;
7
+ phone: string | null;
8
+ type: 'sms' | 'totp';
9
+ }>;
10
+ };
2
11
  export declare const mfaChallenge: <UserType>({ authSessionStore, challengeRoute, cookieSecure, encryptionKey, getChallengeUser, getUserId, mfaStore, onMfaChallengeError, onMfaChallengeSuccess, onSendSmsCode, verificationProvider, sessionDurationMs, smsCodeLength, smsCodeTtlMs, smsMaxAttempts, smsResendCooldownMs, totpMaxAttempts }: MfaRouteProps<UserType>) => import("elysia/types").AddRoute<"", "local", {
3
12
  decorator: {};
4
13
  store: {
@@ -9,7 +18,39 @@ export declare const mfaChallenge: <UserType>({ authSessionStore, challengeRoute
9
18
  }, {
10
19
  typebox: {};
11
20
  error: [];
12
- }, import("elysia/types").DefaultMetadata, {}, import("elysia/types").DefaultEphemeral, {
21
+ }, import("elysia/types").DefaultMetadata, {
22
+ [x: string]: {
23
+ get: {
24
+ body: unknown;
25
+ params: {};
26
+ query: unknown;
27
+ headers: unknown;
28
+ response: {
29
+ 401: "No MFA challenge in progress";
30
+ 200: {
31
+ backupCodesAvailable: boolean;
32
+ factors: Array<{
33
+ id: string;
34
+ label: string;
35
+ phone: string | null;
36
+ type: "sms" | "totp";
37
+ }>;
38
+ };
39
+ 422: {
40
+ type: "validation";
41
+ title: "Validation Error";
42
+ status: 422;
43
+ detail?: string;
44
+ on: string;
45
+ found?: unknown;
46
+ property?: string;
47
+ expected?: string;
48
+ };
49
+ };
50
+ error: never;
51
+ };
52
+ };
53
+ }, import("elysia/types").DefaultEphemeral, {
13
54
  derive: {};
14
55
  schema: {};
15
56
  schemas: {};
@@ -19,16 +60,18 @@ export declare const mfaChallenge: <UserType>({ authSessionStore, challengeRoute
19
60
  body: import("typebox").TObject<{
20
61
  action: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"send">, import("typebox").TLiteral<"verify">]>>;
21
62
  code: import("typebox").TOptional<import("typebox").TString>;
22
- factor: import("typebox").TOptional<import("typebox").TLiteral<"sms">>;
63
+ factor: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"backup_codes">, import("typebox").TLiteral<"sms">, import("typebox").TLiteral<"totp">]>>;
64
+ factorId: import("typebox").TOptional<import("typebox").TString>;
23
65
  }>;
24
66
  cookie: import("elysia").TCookieObject<{
25
67
  user_session_id: import("typebox").TOptional<import("typebox").TTemplateLiteral<"^.*-.*-.*-.*-.*$">>;
26
68
  }>;
27
- }, {}, `/${string}`>, import("elysia/types").MergeScopedSchemas<{}, {}, {}>>, {}, ({ body: { action, code, factor }, cookie: { user_session_id }, status, store: { session, unregisteredSession } }: {
69
+ }, {}, `/${string}`>, import("elysia/types").MergeScopedSchemas<{}, {}, {}>>, {}, ({ body: { action, code, factor, factorId }, cookie: { user_session_id }, status, store: { session, unregisteredSession } }: {
28
70
  body: {
29
71
  code?: string | undefined;
30
72
  action?: "verify" | "send" | undefined;
31
- factor?: "sms" | undefined;
73
+ factorId?: string | undefined;
74
+ factor?: "sms" | "backup_codes" | "totp" | undefined;
32
75
  };
33
76
  query: Record<string, string | undefined>;
34
77
  params: {};
@@ -172,7 +215,8 @@ export declare const mfaChallenge: <UserType>({ authSessionStore, challengeRoute
172
215
  body: import("typebox").TObject<{
173
216
  action: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"send">, import("typebox").TLiteral<"verify">]>>;
174
217
  code: import("typebox").TOptional<import("typebox").TString>;
175
- factor: import("typebox").TOptional<import("typebox").TLiteral<"sms">>;
218
+ factor: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"backup_codes">, import("typebox").TLiteral<"sms">, import("typebox").TLiteral<"totp">]>>;
219
+ factorId: import("typebox").TOptional<import("typebox").TString>;
176
220
  }>;
177
221
  cookie: import("elysia").TCookieObject<{
178
222
  user_session_id: import("typebox").TOptional<import("typebox").TTemplateLiteral<"^.*-.*-.*-.*-.*$">>;
@@ -195,8 +239,10 @@ export declare const mfaChallenge: <UserType>({ authSessionStore, challengeRoute
195
239
  session: import("..").SessionRecord<UserType>;
196
240
  unregisteredSession: import("..").UnregisteredSessionRecord;
197
241
  };
198
- }) => Promise<import("elysia").ElysiaStatus<"Too Many Requests", "SMS resend cooldown active", 429> | import("elysia").ElysiaStatus<"Bad Request" | "Too Many Requests" | "Service Unavailable", string, 400 | 429 | 503> | import("elysia").ElysiaStatus<"Unauthorized", "No MFA challenge in progress", 401> | import("elysia").ElysiaStatus<"OK", {
242
+ }) => Promise<import("elysia").ElysiaStatus<"Too Many Requests", "SMS resend cooldown active", 429> | import("elysia").ElysiaStatus<"Bad Request" | "Too Many Requests" | "Service Unavailable", string, 400 | 429 | 503> | import("elysia").ElysiaStatus<"Unauthorized", "No MFA challenge in progress", 401> | import("elysia").ElysiaStatus<"Bad Request", "No SMS code in progress", 400> | import("elysia").ElysiaStatus<"Bad Request", "SMS factor not found", 400> | import("elysia").ElysiaStatus<"OK", {
243
+ readonly factorId: string;
244
+ readonly phone: string;
199
245
  readonly status: "sent";
200
- }, 200> | import("elysia").ElysiaStatus<"Bad Request", "SMS code required", 400> | import("elysia").ElysiaStatus<"Bad Request", "No SMS code in progress", 400> | import("elysia").ElysiaStatus<"Unauthorized", "SMS code expired", 401> | import("elysia").ElysiaStatus<"Unauthorized", "Too many attempts", 401> | import("elysia").ElysiaStatus<"Unauthorized" | "Too Many Requests", "SMS code expired" | "Invalid MFA code", 401 | 429> | import("elysia").ElysiaStatus<"Unauthorized", "SMS challenge is no longer active", 401> | import("elysia").ElysiaStatus<"OK", {
246
+ }, 200> | import("elysia").ElysiaStatus<"Bad Request", "SMS code required", 400> | import("elysia").ElysiaStatus<"Unauthorized", "SMS code expired", 401> | import("elysia").ElysiaStatus<"Unauthorized", "Too many attempts", 401> | import("elysia").ElysiaStatus<"Unauthorized" | "Too Many Requests", "SMS code expired" | "Invalid MFA code", 401 | 429> | import("elysia").ElysiaStatus<"Unauthorized", "SMS challenge is no longer active", 401> | import("elysia").ElysiaStatus<"OK", {
201
247
  readonly status: "authenticated";
202
248
  }, 200> | import("elysia").ElysiaStatus<"Unauthorized", "Invalid MFA code", 401>>>;
@@ -1,7 +1,15 @@
1
1
  import { type MfaRouteProps } from './config';
2
+ import { type MfaFactorType } from './types';
3
+ export type MfaPublicFactor = {
4
+ id: string;
5
+ label: string;
6
+ phone: string | null;
7
+ type: Exclude<MfaFactorType, 'backup_codes'>;
8
+ };
2
9
  export type MfaStatus = {
3
10
  backupCodesRemaining: number;
4
11
  enabled: boolean;
12
+ factors: MfaPublicFactor[];
5
13
  smsBackup: {
6
14
  enabled: boolean;
7
15
  phone: string | null;
@@ -32,6 +40,7 @@ export declare const mfaManagementRoutes: <UserType>({ authSessionStore, getUser
32
40
  200: {
33
41
  backupCodesRemaining: number;
34
42
  enabled: boolean;
43
+ factors: MfaPublicFactor[];
35
44
  smsBackup: {
36
45
  enabled: boolean;
37
46
  phone: string | null;
@@ -54,20 +63,51 @@ export declare const mfaManagementRoutes: <UserType>({ authSessionStore, getUser
54
63
  error: never;
55
64
  };
56
65
  };
66
+ } & {
67
+ [x: string]: {
68
+ delete: {
69
+ body: unknown;
70
+ params: {};
71
+ query: unknown;
72
+ headers: unknown;
73
+ response: {
74
+ 401: "Recent authentication required" | "Authentication required";
75
+ 200: {
76
+ readonly status: "disabled";
77
+ };
78
+ 422: {
79
+ type: "validation";
80
+ title: "Validation Error";
81
+ status: 422;
82
+ detail?: string;
83
+ on: string;
84
+ found?: unknown;
85
+ property?: string;
86
+ expected?: string;
87
+ };
88
+ };
89
+ error: never;
90
+ };
91
+ };
57
92
  }, import("elysia/types").DefaultEphemeral, {
58
93
  derive: {};
59
94
  schema: {};
60
95
  schemas: {};
61
96
  response: {};
62
97
  error: [];
63
- }, "delete", `/${string}`, import("elysia/types").IntersectIfObjectSchema<import("elysia").UnwrapRoute<{
98
+ }, "delete", `/${string}/factors/:factorId`, import("elysia/types").IntersectIfObjectSchema<import("elysia").UnwrapRoute<{
64
99
  cookie: import("elysia").TCookieObject<{
65
100
  user_session_id: import("typebox").TOptional<import("typebox").TTemplateLiteral<"^.*-.*-.*-.*-.*$">>;
66
101
  }>;
67
- }, {}, `/${string}`>, import("elysia/types").MergeScopedSchemas<{}, {}, {}>>, {}, ({ cookie: { user_session_id }, status, store: { session } }: {
102
+ params: import("typebox").TObject<{
103
+ factorId: import("typebox").TString;
104
+ }>;
105
+ }, {}, `/${string}/factors/:factorId`>, import("elysia/types").MergeScopedSchemas<{}, {}, {}>>, {}, ({ cookie: { user_session_id }, params: { factorId }, status, store: { session } }: {
68
106
  body: unknown;
69
107
  query: Record<string, string | undefined>;
70
- params: {};
108
+ params: {
109
+ factorId: string;
110
+ };
71
111
  headers: Record<string, string | undefined>;
72
112
  cookie: Record<string, import("elysia").Cookie<unknown>> & {
73
113
  user_session_id: import("elysia").Cookie<`${string}-${string}-${string}-${string}-${string}` | undefined>;
@@ -208,7 +248,10 @@ export declare const mfaManagementRoutes: <UserType>({ authSessionStore, getUser
208
248
  cookie: import("elysia").TCookieObject<{
209
249
  user_session_id: import("typebox").TOptional<import("typebox").TTemplateLiteral<"^.*-.*-.*-.*-.*$">>;
210
250
  }>;
211
- }, {}, `/${string}`>, import("elysia/types").MergeScopedSchemas<{}, {}, {}>>>, NoInfer<{
251
+ params: import("typebox").TObject<{
252
+ factorId: import("typebox").TString;
253
+ }>;
254
+ }, {}, `/${string}/factors/:factorId`>, import("elysia/types").MergeScopedSchemas<{}, {}, {}>>>, NoInfer<{
212
255
  decorator: {};
213
256
  store: {
214
257
  session: import("..").SessionRecord<UserType>;
@@ -226,6 +269,6 @@ export declare const mfaManagementRoutes: <UserType>({ authSessionStore, getUser
226
269
  session: import("..").SessionRecord<UserType>;
227
270
  unregisteredSession: import("..").UnregisteredSessionRecord;
228
271
  };
229
- }) => Promise<import("elysia").ElysiaStatus<"Unauthorized", "Recent authentication required", 401> | import("elysia").ElysiaStatus<"Unauthorized", "Authentication required", 401> | import("elysia").ElysiaStatus<"OK", {
230
- readonly status: "disabled";
272
+ }) => Promise<import("elysia").ElysiaStatus<"Unauthorized", "Recent authentication required", 401> | import("elysia").ElysiaStatus<"Unauthorized", "Authentication required", 401> | import("elysia").ElysiaStatus<"Not Found", "MFA factor not found", 404> | import("elysia").ElysiaStatus<"OK", {
273
+ readonly status: "removed";
231
274
  }, 200>>>;
@@ -1,5 +1,5 @@
1
1
  import { type AnyPgDatabase } from '../stores/postgres';
2
- import type { MFAStore } from './types';
2
+ import type { MfaFactor, MFAStore } from './types';
3
3
  export declare const mfaEnrollmentsTable: import("drizzle-orm/pg-core").PgTableWithColumns<{
4
4
  name: "auth_mfa_enrollments";
5
5
  schema: undefined;
@@ -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
+ mfa_factors: import("drizzle-orm/pg-core").PgBuildColumn<"auth_mfa_enrollments", import("drizzle-orm/pg-core").Set$Type<import("drizzle-orm/pg-core").PgJsonbBuilder, MfaFactor[]>, {
53
+ name: string;
54
+ tableName: "auth_mfa_enrollments";
55
+ dataType: "object json";
56
+ data: MfaFactor[];
57
+ driverParam: unknown;
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_challenge_id: import("drizzle-orm/pg-core").PgBuildColumn<"auth_mfa_enrollments", import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>, {
53
68
  name: string;
54
69
  tableName: "auth_mfa_enrollments";
@@ -124,6 +139,21 @@ export declare const mfaEnrollmentsTable: import("drizzle-orm/pg-core").PgTableW
124
139
  identity: undefined;
125
140
  generated: undefined;
126
141
  }>;
142
+ sms_pending_factor_id: import("drizzle-orm/pg-core").PgBuildColumn<"auth_mfa_enrollments", import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>, {
143
+ name: string;
144
+ tableName: "auth_mfa_enrollments";
145
+ dataType: "string";
146
+ data: string;
147
+ driverParam: string;
148
+ notNull: false;
149
+ hasDefault: false;
150
+ isPrimaryKey: false;
151
+ isAutoincrement: false;
152
+ hasRuntimeDefault: false;
153
+ enumValues: undefined;
154
+ identity: undefined;
155
+ generated: undefined;
156
+ }>;
127
157
  sms_pending_purpose: import("drizzle-orm/pg-core").PgBuildColumn<"auth_mfa_enrollments", import("drizzle-orm/pg-core").Set$Type<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>, import("..").VerificationPurpose | undefined>, {
128
158
  name: string;
129
159
  tableName: "auth_mfa_enrollments";
@@ -1,4 +1,4 @@
1
- import type { MFAStore } from './types';
1
+ import { type MFAStore } from './types';
2
2
  export type MfaKeyRotationResult = {
3
3
  alreadyRotated: number;
4
4
  rotated: number;
@@ -22,6 +22,7 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
22
22
  200: {
23
23
  backupCodesRemaining: number;
24
24
  enabled: boolean;
25
+ factors: import("./management").MfaPublicFactor[];
25
26
  smsBackup: {
26
27
  enabled: boolean;
27
28
  phone: string | null;
@@ -70,16 +71,52 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
70
71
  error: never;
71
72
  };
72
73
  };
74
+ } & {
75
+ [x: string]: {
76
+ factors: {
77
+ ":factorId": {
78
+ delete: {
79
+ body: unknown;
80
+ params: {
81
+ factorId: string;
82
+ };
83
+ query: unknown;
84
+ headers: unknown;
85
+ response: {
86
+ 401: "Recent authentication required" | "Authentication required";
87
+ 404: "MFA factor not found";
88
+ 200: {
89
+ readonly status: "removed";
90
+ };
91
+ 422: {
92
+ type: "validation";
93
+ title: "Validation Error";
94
+ status: 422;
95
+ detail?: string;
96
+ on: string;
97
+ found?: unknown;
98
+ property?: string;
99
+ expected?: string;
100
+ };
101
+ };
102
+ error: never;
103
+ };
104
+ };
105
+ };
106
+ };
73
107
  } & {
74
108
  [x: string]: {
75
109
  post: {
76
- body: unknown;
110
+ body: {
111
+ label?: string | undefined;
112
+ };
77
113
  params: {};
78
114
  query: unknown;
79
115
  headers: unknown;
80
116
  response: {
81
117
  401: "Recent authentication required" | "Authentication required";
82
118
  200: {
119
+ readonly factorId: string;
83
120
  readonly secret: string;
84
121
  readonly uri: string;
85
122
  };
@@ -101,6 +138,7 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
101
138
  [x: string]: {
102
139
  post: {
103
140
  body: {
141
+ factorId?: string | undefined;
104
142
  code: string;
105
143
  };
106
144
  params: {};
@@ -130,6 +168,7 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
130
168
  [x: string]: {
131
169
  post: {
132
170
  body: {
171
+ label?: string | undefined;
133
172
  phone: string;
134
173
  };
135
174
  params: {};
@@ -140,6 +179,7 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
140
179
  401: "Recent authentication required" | "Authentication required";
141
180
  501: "SMS MFA is not configured";
142
181
  200: {
182
+ readonly factorId: string;
143
183
  readonly phone: string;
144
184
  };
145
185
  422: {
@@ -162,6 +202,7 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
162
202
  [x: string]: {
163
203
  post: {
164
204
  body: {
205
+ factorId?: string | undefined;
165
206
  code: string;
166
207
  };
167
208
  params: {};
@@ -189,13 +230,46 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
189
230
  error: never;
190
231
  };
191
232
  };
233
+ } & {
234
+ [x: string]: {
235
+ get: {
236
+ body: unknown;
237
+ params: {};
238
+ query: unknown;
239
+ headers: unknown;
240
+ response: {
241
+ 401: "No MFA challenge in progress";
242
+ 200: {
243
+ backupCodesAvailable: boolean;
244
+ factors: Array<{
245
+ id: string;
246
+ label: string;
247
+ phone: string | null;
248
+ type: "sms" | "totp";
249
+ }>;
250
+ };
251
+ 422: {
252
+ type: "validation";
253
+ title: "Validation Error";
254
+ status: 422;
255
+ detail?: string;
256
+ on: string;
257
+ found?: unknown;
258
+ property?: string;
259
+ expected?: string;
260
+ };
261
+ };
262
+ error: never;
263
+ };
264
+ };
192
265
  } & {
193
266
  [x: string]: {
194
267
  post: {
195
268
  body: {
196
269
  code?: string | undefined;
197
270
  action?: "verify" | "send" | undefined;
198
- factor?: "sms" | undefined;
271
+ factorId?: string | undefined;
272
+ factor?: "sms" | "backup_codes" | "totp" | undefined;
199
273
  };
200
274
  params: {};
201
275
  query: unknown;
@@ -204,6 +278,8 @@ export declare const mfaRoutes: <UserType>(config: MfaRouteProps<UserType>) => E
204
278
  400: string;
205
279
  401: "SMS code expired" | "Too many attempts" | "SMS challenge is no longer active" | "No MFA challenge in progress" | "Invalid MFA code";
206
280
  200: {
281
+ readonly factorId: string;
282
+ readonly phone: string;
207
283
  readonly status: "sent";
208
284
  } | {
209
285
  readonly status: "authenticated";
package/dist/mfa/sms.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type VerificationCheckInput, type VerificationProvider } from '../verification/types';
2
2
  import { type MfaRouteProps, type SmsCodeMessage } from './config';
3
- import type { MfaEnrollment, MFAStore } from './types';
3
+ import { type MfaEnrollment, type MFAStore } from './types';
4
4
  export declare class SmsChallengeConflictError extends Error {
5
5
  name: string;
6
6
  }
@@ -58,6 +58,7 @@ export declare const mfaSmsRoutes: <UserType>({ authSessionStore, getUserId, mfa
58
58
  [x: string]: {
59
59
  post: {
60
60
  body: {
61
+ label?: string | undefined;
61
62
  phone: string;
62
63
  };
63
64
  params: {};
@@ -68,6 +69,7 @@ export declare const mfaSmsRoutes: <UserType>({ authSessionStore, getUserId, mfa
68
69
  401: "Recent authentication required" | "Authentication required";
69
70
  501: "SMS MFA is not configured";
70
71
  200: {
72
+ readonly factorId: string;
71
73
  readonly phone: string;
72
74
  };
73
75
  422: {
@@ -95,12 +97,14 @@ export declare const mfaSmsRoutes: <UserType>({ authSessionStore, getUserId, mfa
95
97
  }, "post", `/${string}`, import("elysia/types").IntersectIfObjectSchema<import("elysia").UnwrapRoute<{
96
98
  body: import("typebox").TObject<{
97
99
  code: import("typebox").TString;
100
+ factorId: import("typebox").TOptional<import("typebox").TString>;
98
101
  }>;
99
102
  cookie: import("elysia").TCookieObject<{
100
103
  user_session_id: import("typebox").TOptional<import("typebox").TTemplateLiteral<"^.*-.*-.*-.*-.*$">>;
101
104
  }>;
102
- }, {}, `/${string}`>, import("elysia/types").MergeScopedSchemas<{}, {}, {}>>, {}, ({ body: { code }, cookie: { user_session_id }, status, store: { session } }: {
105
+ }, {}, `/${string}`>, import("elysia/types").MergeScopedSchemas<{}, {}, {}>>, {}, ({ body: { code, factorId }, cookie: { user_session_id }, status, store: { session } }: {
103
106
  body: {
107
+ factorId?: string | undefined;
104
108
  code: string;
105
109
  };
106
110
  query: Record<string, string | undefined>;
@@ -244,6 +248,7 @@ export declare const mfaSmsRoutes: <UserType>({ authSessionStore, getUserId, mfa
244
248
  defer: (fn: import("elysia/types").AfterResponseHandler<NoInfer<import("elysia/types").IntersectIfObjectSchema<import("elysia").UnwrapRoute<{
245
249
  body: import("typebox").TObject<{
246
250
  code: import("typebox").TString;
251
+ factorId: import("typebox").TOptional<import("typebox").TString>;
247
252
  }>;
248
253
  cookie: import("elysia").TCookieObject<{
249
254
  user_session_id: import("typebox").TOptional<import("typebox").TTemplateLiteral<"^.*-.*-.*-.*-.*$">>;
@@ -12,13 +12,16 @@ export declare const mfaTotpRoutes: <UserType>({ authSessionStore, backupCodeCou
12
12
  }, import("elysia/types").DefaultMetadata, {
13
13
  [x: string]: {
14
14
  post: {
15
- body: unknown;
15
+ body: {
16
+ label?: string | undefined;
17
+ };
16
18
  params: {};
17
19
  query: unknown;
18
20
  headers: unknown;
19
21
  response: {
20
22
  401: "Recent authentication required" | "Authentication required";
21
23
  200: {
24
+ readonly factorId: string;
22
25
  readonly secret: string;
23
26
  readonly uri: string;
24
27
  };
@@ -45,12 +48,14 @@ export declare const mfaTotpRoutes: <UserType>({ authSessionStore, backupCodeCou
45
48
  }, "post", `/${string}`, import("elysia/types").IntersectIfObjectSchema<import("elysia").UnwrapRoute<{
46
49
  body: import("typebox").TObject<{
47
50
  code: import("typebox").TString;
51
+ factorId: import("typebox").TOptional<import("typebox").TString>;
48
52
  }>;
49
53
  cookie: import("elysia").TCookieObject<{
50
54
  user_session_id: import("typebox").TOptional<import("typebox").TTemplateLiteral<"^.*-.*-.*-.*-.*$">>;
51
55
  }>;
52
- }, {}, `/${string}`>, import("elysia/types").MergeScopedSchemas<{}, {}, {}>>, {}, ({ body: { code }, cookie: { user_session_id }, status, store: { session } }: {
56
+ }, {}, `/${string}`>, import("elysia/types").MergeScopedSchemas<{}, {}, {}>>, {}, ({ body: { code, factorId }, cookie: { user_session_id }, status, store: { session } }: {
53
57
  body: {
58
+ factorId?: string | undefined;
54
59
  code: string;
55
60
  };
56
61
  query: Record<string, string | undefined>;
@@ -194,6 +199,7 @@ export declare const mfaTotpRoutes: <UserType>({ authSessionStore, backupCodeCou
194
199
  defer: (fn: import("elysia/types").AfterResponseHandler<NoInfer<import("elysia/types").IntersectIfObjectSchema<import("elysia").UnwrapRoute<{
195
200
  body: import("typebox").TObject<{
196
201
  code: import("typebox").TString;
202
+ factorId: import("typebox").TOptional<import("typebox").TString>;
197
203
  }>;
198
204
  cookie: import("elysia").TCookieObject<{
199
205
  user_session_id: import("typebox").TOptional<import("typebox").TTemplateLiteral<"^.*-.*-.*-.*-.*$">>;
@@ -1,13 +1,33 @@
1
1
  import type { VerificationPurpose } from '../verification/types';
2
2
  export type MfaFactorType = 'backup_codes' | 'sms' | 'totp';
3
+ export type SmsMfaFactor = {
4
+ id: string;
5
+ label: string;
6
+ phone: string;
7
+ type: 'sms';
8
+ verified: boolean;
9
+ };
10
+ export type TotpMfaFactor = {
11
+ id: string;
12
+ label: string;
13
+ secretCiphertext: string;
14
+ type: 'totp';
15
+ verified: boolean;
16
+ };
17
+ export type MfaFactor = SmsMfaFactor | TotpMfaFactor;
3
18
  export type MfaEnrollment = {
4
19
  backupCodeHashes: string[];
5
20
  createdAt: number;
21
+ /** Labeled factor collection. Undefined means a legacy single-factor row that
22
+ * has not yet been rewritten by a management operation. */
23
+ factors?: MfaFactor[];
6
24
  lastUsedAt?: number;
7
25
  smsFailedAttempts?: number;
8
26
  smsPendingCodeHash?: string;
9
27
  smsPendingCodeExpiresAt?: number;
10
28
  smsPendingPurpose?: VerificationPurpose;
29
+ /** Factor selected for the currently active SMS enrollment/challenge. */
30
+ smsPendingFactorId?: string;
11
31
  smsProviderReference?: string;
12
32
  smsCodeSentAt?: number;
13
33
  /** Opaque generation binding every update to the currently active challenge. */
@@ -29,6 +49,7 @@ export type MFAStore = {
29
49
  }) => Promise<boolean>;
30
50
  completeSmsChallenge: (input: {
31
51
  challengeId: string;
52
+ factors?: MfaFactor[];
32
53
  lastUsedAt?: number;
33
54
  smsVerified: boolean;
34
55
  userId: string;
@@ -55,4 +76,30 @@ export type MFAStore = {
55
76
  }) => Promise<void>;
56
77
  saveEnrollment: (enrollment: MfaEnrollment) => Promise<void>;
57
78
  };
79
+ export declare const getMfaFactors: (enrollment: MfaEnrollment) => MfaFactor[];
58
80
  export declare const isMfaEnrolled: (enrollment: MfaEnrollment | undefined) => boolean;
81
+ /** Keep the historical single-factor columns synchronized for consumers that
82
+ * have not moved to the factor collection yet. */
83
+ export declare const withMfaFactors: (enrollment: MfaEnrollment, factors: MfaFactor[]) => {
84
+ factors: MfaFactor[];
85
+ smsPhone: string | undefined;
86
+ smsVerified: boolean;
87
+ totpSecretCiphertext: string | undefined;
88
+ totpVerified: boolean;
89
+ backupCodeHashes: string[];
90
+ createdAt: number;
91
+ lastUsedAt?: number;
92
+ smsFailedAttempts?: number;
93
+ smsPendingCodeHash?: string;
94
+ smsPendingCodeExpiresAt?: number;
95
+ smsPendingPurpose?: VerificationPurpose;
96
+ /** Factor selected for the currently active SMS enrollment/challenge. */
97
+ smsPendingFactorId?: string;
98
+ smsProviderReference?: string;
99
+ smsCodeSentAt?: number;
100
+ /** Opaque generation binding every update to the currently active challenge. */
101
+ smsChallengeId?: string;
102
+ totpFailedAttempts?: number;
103
+ updatedAt: number;
104
+ userId: string;
105
+ };