@cairs-npm/express-auth-2fa 0.1.2 → 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.cjs CHANGED
@@ -185,6 +185,16 @@ var configSchema = import_zod.z.object({
185
185
  (v) => typeof v === "function"
186
186
  )
187
187
  ]).default(false),
188
+ /**
189
+ * 2-Step Verification switch above each user's own choice. False: password sign-in never
190
+ * asks for a code and the two-step setting can't be turned on. Passwordless sign-in still
191
+ * sends a code — it's the only factor there. Pass a function to decide per user, e.g. from
192
+ * an organization setting.
193
+ */
194
+ twoStepEnabled: import_zod.z.union([
195
+ import_zod.z.boolean(),
196
+ import_zod.z.custom((v) => typeof v === "function")
197
+ ]).default(true),
188
198
  /** 2-Step Verification state for users who have not chosen one yet. */
189
199
  twoStepDefault: import_zod.z.boolean().default(false),
190
200
  limits: limitSchema.default({}),
@@ -385,6 +395,12 @@ async function migrate(db, prefix = "auth_", auditDays = 365) {
385
395
  return c;
386
396
  }
387
397
 
398
+ // src/config/twoStep.ts
399
+ async function twoStepAvailableFor(config, userId) {
400
+ const v = config.twoStepEnabled;
401
+ return typeof v === "function" ? Boolean(await v({ userId })) : v;
402
+ }
403
+
388
404
  // src/services/auth.service.ts
389
405
  init_hash();
390
406
 
@@ -647,11 +663,16 @@ var MethodService = class {
647
663
  const cred = await this.cols.credentials.findOne({ userId });
648
664
  const docs = await this.cols.methods.find({ userId, status: "active" }).toArray();
649
665
  const methods = docs.sort((a, b) => Number(b.isDefault) - Number(a.isDefault) || a.createdAt.getTime() - b.createdAt.getTime()).map(toApiMethod);
650
- return { twoStepEnabled: cred?.twoStepEnabled ?? this.config.twoStepDefault, methods };
666
+ const available = await twoStepAvailableFor(this.config, userId);
667
+ const twoStepEnabled = available && (cred?.twoStepEnabled ?? this.config.twoStepDefault);
668
+ return { twoStepEnabled, twoStepAvailable: available, methods };
651
669
  }
652
670
  /** BR-15 — enable needs ≥1 active method; disable needs step-up + revokes trusted devices. */
653
671
  async setTwoStep(userId, sid, enabled, stepUpToken) {
654
672
  const now = this.config.clock();
673
+ if (enabled && !await twoStepAvailableFor(this.config, userId)) {
674
+ throw apiError(409, "VALIDATION_ERROR", "2-Step Verification is turned off for this application.");
675
+ }
655
676
  if (enabled) {
656
677
  await ensureEmailMethod(this.config, this.cols, userId);
657
678
  const count = await this.cols.methods.countDocuments({ userId, status: "active" });
@@ -891,8 +912,9 @@ var AuthService = class {
891
912
  throw apiError(423, "LOCKED", "Too many attempts. Try again later.", { retryAt: cred.lockedUntil });
892
913
  }
893
914
  await this.resetFailedLogins(cred, now);
894
- const trustedOk = cred.twoStepEnabled && await this.checkTrustedDevice(req, user.id);
895
- if (!cred.twoStepEnabled || trustedOk) {
915
+ const twoStep = cred.twoStepEnabled && await twoStepAvailableFor(this.config, user.id);
916
+ const trustedOk = twoStep && await this.checkTrustedDevice(req, user.id);
917
+ if (!twoStep || trustedOk) {
896
918
  await this.audit.record(user.id, "login_success", ctx);
897
919
  return this.authenticate(user.id, req, res, ["pwd"], { rememberMe: input.rememberMe });
898
920
  }