@cairs-npm/express-auth-2fa 0.1.3 → 0.1.4

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/dist/index.d.cts CHANGED
@@ -209,11 +209,16 @@ declare const configSchema: z.ZodObject<{
209
209
  type?: string;
210
210
  }) => boolean | Promise<boolean>>]>>;
211
211
  /**
212
- * Project-wide 2-Step Verification switch. False: password sign-in never asks for a code
213
- * (whatever each user chose) and the two-step setting can't be turned on. Passwordless
214
- * sign-in still sends a code — it's the only factor there.
212
+ * 2-Step Verification switch above each user's own choice. False: password sign-in never
213
+ * asks for a code and the two-step setting can't be turned on. Passwordless sign-in still
214
+ * sends a code — it's the only factor there. Pass a function to decide per user, e.g. from
215
+ * an organization setting.
215
216
  */
216
- twoStepEnabled: z.ZodDefault<z.ZodBoolean>;
217
+ twoStepEnabled: z.ZodDefault<z.ZodUnion<[z.ZodBoolean, z.ZodType<(input: {
218
+ userId: string;
219
+ }) => boolean | Promise<boolean>, z.ZodTypeDef, (input: {
220
+ userId: string;
221
+ }) => boolean | Promise<boolean>>]>>;
217
222
  /** 2-Step Verification state for users who have not chosen one yet. */
218
223
  twoStepDefault: z.ZodDefault<z.ZodBoolean>;
219
224
  limits: z.ZodDefault<z.ZodObject<{
@@ -339,7 +344,9 @@ declare const configSchema: z.ZodObject<{
339
344
  username: string;
340
345
  type?: string;
341
346
  }) => boolean | Promise<boolean>);
342
- twoStepEnabled: boolean;
347
+ twoStepEnabled: boolean | ((input: {
348
+ userId: string;
349
+ }) => boolean | Promise<boolean>);
343
350
  twoStepDefault: boolean;
344
351
  limits: {
345
352
  loginMaxAttempts: number;
@@ -409,7 +416,9 @@ declare const configSchema: z.ZodObject<{
409
416
  username: string;
410
417
  type?: string;
411
418
  }) => boolean | Promise<boolean>) | undefined;
412
- twoStepEnabled?: boolean | undefined;
419
+ twoStepEnabled?: boolean | ((input: {
420
+ userId: string;
421
+ }) => boolean | Promise<boolean>) | undefined;
413
422
  twoStepDefault?: boolean | undefined;
414
423
  limits?: {
415
424
  loginMaxAttempts?: number | undefined;
package/dist/index.d.ts CHANGED
@@ -209,11 +209,16 @@ declare const configSchema: z.ZodObject<{
209
209
  type?: string;
210
210
  }) => boolean | Promise<boolean>>]>>;
211
211
  /**
212
- * Project-wide 2-Step Verification switch. False: password sign-in never asks for a code
213
- * (whatever each user chose) and the two-step setting can't be turned on. Passwordless
214
- * sign-in still sends a code — it's the only factor there.
212
+ * 2-Step Verification switch above each user's own choice. False: password sign-in never
213
+ * asks for a code and the two-step setting can't be turned on. Passwordless sign-in still
214
+ * sends a code — it's the only factor there. Pass a function to decide per user, e.g. from
215
+ * an organization setting.
215
216
  */
216
- twoStepEnabled: z.ZodDefault<z.ZodBoolean>;
217
+ twoStepEnabled: z.ZodDefault<z.ZodUnion<[z.ZodBoolean, z.ZodType<(input: {
218
+ userId: string;
219
+ }) => boolean | Promise<boolean>, z.ZodTypeDef, (input: {
220
+ userId: string;
221
+ }) => boolean | Promise<boolean>>]>>;
217
222
  /** 2-Step Verification state for users who have not chosen one yet. */
218
223
  twoStepDefault: z.ZodDefault<z.ZodBoolean>;
219
224
  limits: z.ZodDefault<z.ZodObject<{
@@ -339,7 +344,9 @@ declare const configSchema: z.ZodObject<{
339
344
  username: string;
340
345
  type?: string;
341
346
  }) => boolean | Promise<boolean>);
342
- twoStepEnabled: boolean;
347
+ twoStepEnabled: boolean | ((input: {
348
+ userId: string;
349
+ }) => boolean | Promise<boolean>);
343
350
  twoStepDefault: boolean;
344
351
  limits: {
345
352
  loginMaxAttempts: number;
@@ -409,7 +416,9 @@ declare const configSchema: z.ZodObject<{
409
416
  username: string;
410
417
  type?: string;
411
418
  }) => boolean | Promise<boolean>) | undefined;
412
- twoStepEnabled?: boolean | undefined;
419
+ twoStepEnabled?: boolean | ((input: {
420
+ userId: string;
421
+ }) => boolean | Promise<boolean>) | undefined;
413
422
  twoStepDefault?: boolean | undefined;
414
423
  limits?: {
415
424
  loginMaxAttempts?: number | undefined;
package/dist/index.js CHANGED
@@ -100,11 +100,15 @@ var configSchema = z.object({
100
100
  )
101
101
  ]).default(false),
102
102
  /**
103
- * Project-wide 2-Step Verification switch. False: password sign-in never asks for a code
104
- * (whatever each user chose) and the two-step setting can't be turned on. Passwordless
105
- * sign-in still sends a code — it's the only factor there.
103
+ * 2-Step Verification switch above each user's own choice. False: password sign-in never
104
+ * asks for a code and the two-step setting can't be turned on. Passwordless sign-in still
105
+ * sends a code — it's the only factor there. Pass a function to decide per user, e.g. from
106
+ * an organization setting.
106
107
  */
107
- twoStepEnabled: z.boolean().default(true),
108
+ twoStepEnabled: z.union([
109
+ z.boolean(),
110
+ z.custom((v) => typeof v === "function")
111
+ ]).default(true),
108
112
  /** 2-Step Verification state for users who have not chosen one yet. */
109
113
  twoStepDefault: z.boolean().default(false),
110
114
  limits: limitSchema.default({}),
@@ -305,6 +309,12 @@ async function migrate(db, prefix = "auth_", auditDays = 365) {
305
309
  return c;
306
310
  }
307
311
 
312
+ // src/config/twoStep.ts
313
+ async function twoStepAvailableFor(config, userId) {
314
+ const v = config.twoStepEnabled;
315
+ return typeof v === "function" ? Boolean(await v({ userId })) : v;
316
+ }
317
+
308
318
  // src/middleware/errors.ts
309
319
  import { ZodError } from "zod";
310
320
  var ApiErrorException = class extends Error {
@@ -561,14 +571,14 @@ var MethodService = class {
561
571
  const cred = await this.cols.credentials.findOne({ userId });
562
572
  const docs = await this.cols.methods.find({ userId, status: "active" }).toArray();
563
573
  const methods = docs.sort((a, b) => Number(b.isDefault) - Number(a.isDefault) || a.createdAt.getTime() - b.createdAt.getTime()).map(toApiMethod);
564
- const available = this.config.twoStepEnabled;
574
+ const available = await twoStepAvailableFor(this.config, userId);
565
575
  const twoStepEnabled = available && (cred?.twoStepEnabled ?? this.config.twoStepDefault);
566
576
  return { twoStepEnabled, twoStepAvailable: available, methods };
567
577
  }
568
578
  /** BR-15 — enable needs ≥1 active method; disable needs step-up + revokes trusted devices. */
569
579
  async setTwoStep(userId, sid, enabled, stepUpToken) {
570
580
  const now = this.config.clock();
571
- if (enabled && !this.config.twoStepEnabled) {
581
+ if (enabled && !await twoStepAvailableFor(this.config, userId)) {
572
582
  throw apiError(409, "VALIDATION_ERROR", "2-Step Verification is turned off for this application.");
573
583
  }
574
584
  if (enabled) {
@@ -810,7 +820,7 @@ var AuthService = class {
810
820
  throw apiError(423, "LOCKED", "Too many attempts. Try again later.", { retryAt: cred.lockedUntil });
811
821
  }
812
822
  await this.resetFailedLogins(cred, now);
813
- const twoStep = this.config.twoStepEnabled && cred.twoStepEnabled;
823
+ const twoStep = cred.twoStepEnabled && await twoStepAvailableFor(this.config, user.id);
814
824
  const trustedOk = twoStep && await this.checkTrustedDevice(req, user.id);
815
825
  if (!twoStep || trustedOk) {
816
826
  await this.audit.record(user.id, "login_success", ctx);