@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.d.cts CHANGED
@@ -208,6 +208,17 @@ declare const configSchema: z.ZodObject<{
208
208
  username: string;
209
209
  type?: string;
210
210
  }) => boolean | Promise<boolean>>]>>;
211
+ /**
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.
216
+ */
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>>]>>;
211
222
  /** 2-Step Verification state for users who have not chosen one yet. */
212
223
  twoStepDefault: z.ZodDefault<z.ZodBoolean>;
213
224
  limits: z.ZodDefault<z.ZodObject<{
@@ -333,6 +344,9 @@ declare const configSchema: z.ZodObject<{
333
344
  username: string;
334
345
  type?: string;
335
346
  }) => boolean | Promise<boolean>);
347
+ twoStepEnabled: boolean | ((input: {
348
+ userId: string;
349
+ }) => boolean | Promise<boolean>);
336
350
  twoStepDefault: boolean;
337
351
  limits: {
338
352
  loginMaxAttempts: number;
@@ -402,6 +416,9 @@ declare const configSchema: z.ZodObject<{
402
416
  username: string;
403
417
  type?: string;
404
418
  }) => boolean | Promise<boolean>) | undefined;
419
+ twoStepEnabled?: boolean | ((input: {
420
+ userId: string;
421
+ }) => boolean | Promise<boolean>) | undefined;
405
422
  twoStepDefault?: boolean | undefined;
406
423
  limits?: {
407
424
  loginMaxAttempts?: number | undefined;
package/dist/index.d.ts CHANGED
@@ -208,6 +208,17 @@ declare const configSchema: z.ZodObject<{
208
208
  username: string;
209
209
  type?: string;
210
210
  }) => boolean | Promise<boolean>>]>>;
211
+ /**
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.
216
+ */
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>>]>>;
211
222
  /** 2-Step Verification state for users who have not chosen one yet. */
212
223
  twoStepDefault: z.ZodDefault<z.ZodBoolean>;
213
224
  limits: z.ZodDefault<z.ZodObject<{
@@ -333,6 +344,9 @@ declare const configSchema: z.ZodObject<{
333
344
  username: string;
334
345
  type?: string;
335
346
  }) => boolean | Promise<boolean>);
347
+ twoStepEnabled: boolean | ((input: {
348
+ userId: string;
349
+ }) => boolean | Promise<boolean>);
336
350
  twoStepDefault: boolean;
337
351
  limits: {
338
352
  loginMaxAttempts: number;
@@ -402,6 +416,9 @@ declare const configSchema: z.ZodObject<{
402
416
  username: string;
403
417
  type?: string;
404
418
  }) => boolean | Promise<boolean>) | undefined;
419
+ twoStepEnabled?: boolean | ((input: {
420
+ userId: string;
421
+ }) => boolean | Promise<boolean>) | undefined;
405
422
  twoStepDefault?: boolean | undefined;
406
423
  limits?: {
407
424
  loginMaxAttempts?: number | undefined;
package/dist/index.js CHANGED
@@ -99,6 +99,16 @@ var configSchema = z.object({
99
99
  (v) => typeof v === "function"
100
100
  )
101
101
  ]).default(false),
102
+ /**
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.
107
+ */
108
+ twoStepEnabled: z.union([
109
+ z.boolean(),
110
+ z.custom((v) => typeof v === "function")
111
+ ]).default(true),
102
112
  /** 2-Step Verification state for users who have not chosen one yet. */
103
113
  twoStepDefault: z.boolean().default(false),
104
114
  limits: limitSchema.default({}),
@@ -299,6 +309,12 @@ async function migrate(db, prefix = "auth_", auditDays = 365) {
299
309
  return c;
300
310
  }
301
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
+
302
318
  // src/middleware/errors.ts
303
319
  import { ZodError } from "zod";
304
320
  var ApiErrorException = class extends Error {
@@ -555,11 +571,16 @@ var MethodService = class {
555
571
  const cred = await this.cols.credentials.findOne({ userId });
556
572
  const docs = await this.cols.methods.find({ userId, status: "active" }).toArray();
557
573
  const methods = docs.sort((a, b) => Number(b.isDefault) - Number(a.isDefault) || a.createdAt.getTime() - b.createdAt.getTime()).map(toApiMethod);
558
- return { twoStepEnabled: cred?.twoStepEnabled ?? this.config.twoStepDefault, methods };
574
+ const available = await twoStepAvailableFor(this.config, userId);
575
+ const twoStepEnabled = available && (cred?.twoStepEnabled ?? this.config.twoStepDefault);
576
+ return { twoStepEnabled, twoStepAvailable: available, methods };
559
577
  }
560
578
  /** BR-15 — enable needs ≥1 active method; disable needs step-up + revokes trusted devices. */
561
579
  async setTwoStep(userId, sid, enabled, stepUpToken) {
562
580
  const now = this.config.clock();
581
+ if (enabled && !await twoStepAvailableFor(this.config, userId)) {
582
+ throw apiError(409, "VALIDATION_ERROR", "2-Step Verification is turned off for this application.");
583
+ }
563
584
  if (enabled) {
564
585
  await ensureEmailMethod(this.config, this.cols, userId);
565
586
  const count = await this.cols.methods.countDocuments({ userId, status: "active" });
@@ -799,8 +820,9 @@ var AuthService = class {
799
820
  throw apiError(423, "LOCKED", "Too many attempts. Try again later.", { retryAt: cred.lockedUntil });
800
821
  }
801
822
  await this.resetFailedLogins(cred, now);
802
- const trustedOk = cred.twoStepEnabled && await this.checkTrustedDevice(req, user.id);
803
- if (!cred.twoStepEnabled || trustedOk) {
823
+ const twoStep = cred.twoStepEnabled && await twoStepAvailableFor(this.config, user.id);
824
+ const trustedOk = twoStep && await this.checkTrustedDevice(req, user.id);
825
+ if (!twoStep || trustedOk) {
804
826
  await this.audit.record(user.id, "login_success", ctx);
805
827
  return this.authenticate(user.id, req, res, ["pwd"], { rememberMe: input.rememberMe });
806
828
  }