@korajs/auth 0.5.0 → 1.0.0-beta.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.
package/dist/server.d.cts CHANGED
@@ -3602,6 +3602,13 @@ interface TotpSecret {
3602
3602
  createdAt: number;
3603
3603
  /** When MFA was verified (confirmed with first valid code) */
3604
3604
  verifiedAt: number | null;
3605
+ /**
3606
+ * The most recent TOTP time-step counter that was successfully consumed
3607
+ * during authentication. Used to enforce single-use of codes (RFC 6238 §5.2):
3608
+ * a code at or before this time-step is rejected as a replay. `undefined`
3609
+ * until the first code is consumed.
3610
+ */
3611
+ lastUsedTimeStep?: number;
3605
3612
  }
3606
3613
  /**
3607
3614
  * Setup result returned when enabling TOTP MFA.
@@ -3702,6 +3709,11 @@ declare class TotpManager {
3702
3709
  /**
3703
3710
  * Verify a TOTP code during login.
3704
3711
  * Returns true if the code is valid, false otherwise.
3712
+ *
3713
+ * Enforces single-use: a code is rejected if its time-step has already
3714
+ * been consumed (or predates a previously consumed time-step). This
3715
+ * prevents an attacker who observes one valid code from replaying it
3716
+ * within the acceptance window (RFC 6238 §5.2).
3705
3717
  */
3706
3718
  verify(userId: string, code: string): Promise<boolean>;
3707
3719
  /**
@@ -3728,6 +3740,18 @@ declare class TotpManager {
3728
3740
  */
3729
3741
  remainingRecoveryCodes(userId: string): Promise<number>;
3730
3742
  private validateCode;
3743
+ /**
3744
+ * Find the TOTP time-step counter (within the acceptance window) whose
3745
+ * generated code matches `code`, or null if none matches. Comparison is
3746
+ * timing-safe.
3747
+ */
3748
+ private matchTimeStep;
3749
+ /**
3750
+ * Validate a code AND consume its time-step so it (and any earlier code in
3751
+ * the window) cannot be reused. Returns false for an invalid code or a
3752
+ * replay of an already-consumed time-step. Persists the consumed time-step.
3753
+ */
3754
+ private consumeCode;
3731
3755
  }
3732
3756
  /**
3733
3757
  * Encode bytes to base32 string (RFC 4648, no padding).
package/dist/server.d.ts CHANGED
@@ -3602,6 +3602,13 @@ interface TotpSecret {
3602
3602
  createdAt: number;
3603
3603
  /** When MFA was verified (confirmed with first valid code) */
3604
3604
  verifiedAt: number | null;
3605
+ /**
3606
+ * The most recent TOTP time-step counter that was successfully consumed
3607
+ * during authentication. Used to enforce single-use of codes (RFC 6238 §5.2):
3608
+ * a code at or before this time-step is rejected as a replay. `undefined`
3609
+ * until the first code is consumed.
3610
+ */
3611
+ lastUsedTimeStep?: number;
3605
3612
  }
3606
3613
  /**
3607
3614
  * Setup result returned when enabling TOTP MFA.
@@ -3702,6 +3709,11 @@ declare class TotpManager {
3702
3709
  /**
3703
3710
  * Verify a TOTP code during login.
3704
3711
  * Returns true if the code is valid, false otherwise.
3712
+ *
3713
+ * Enforces single-use: a code is rejected if its time-step has already
3714
+ * been consumed (or predates a previously consumed time-step). This
3715
+ * prevents an attacker who observes one valid code from replaying it
3716
+ * within the acceptance window (RFC 6238 §5.2).
3705
3717
  */
3706
3718
  verify(userId: string, code: string): Promise<boolean>;
3707
3719
  /**
@@ -3728,6 +3740,18 @@ declare class TotpManager {
3728
3740
  */
3729
3741
  remainingRecoveryCodes(userId: string): Promise<number>;
3730
3742
  private validateCode;
3743
+ /**
3744
+ * Find the TOTP time-step counter (within the acceptance window) whose
3745
+ * generated code matches `code`, or null if none matches. Comparison is
3746
+ * timing-safe.
3747
+ */
3748
+ private matchTimeStep;
3749
+ /**
3750
+ * Validate a code AND consume its time-step so it (and any earlier code in
3751
+ * the window) cannot be reused. Returns false for an invalid code or a
3752
+ * replay of an already-consumed time-step. Persists the consumed time-step.
3753
+ */
3754
+ private consumeCode;
3731
3755
  }
3732
3756
  /**
3733
3757
  * Encode bytes to base32 string (RFC 4648, no padding).
package/dist/server.js CHANGED
@@ -3783,6 +3783,13 @@ var OrgRoutes = class {
3783
3783
  body: { error: "Cannot assign owner role directly. Use ownership transfer." }
3784
3784
  };
3785
3785
  }
3786
+ const org = await this.store.getOrg(orgId);
3787
+ if (org && org.ownerId === params.targetUserId) {
3788
+ return {
3789
+ status: 403,
3790
+ body: { error: "The organization owner's role cannot be changed. Use ownership transfer." }
3791
+ };
3792
+ }
3786
3793
  const callerMembership = await this.store.getMembership(orgId, userId);
3787
3794
  if (callerMembership && callerMembership.role !== "owner" && params.role === "admin") {
3788
3795
  return { status: 403, body: { error: "Only the owner can assign admin role." } };
@@ -5358,9 +5365,9 @@ var TotpManager = class {
5358
5365
  throw new TotpNotEnabledError(userId);
5359
5366
  }
5360
5367
  if (stored.verified) {
5361
- return this.validateCode(stored.secret, code);
5368
+ return this.consumeCode(stored, code);
5362
5369
  }
5363
- const valid = this.validateCode(stored.secret, code);
5370
+ const valid = await this.consumeCode(stored, code);
5364
5371
  if (!valid) {
5365
5372
  throw new TotpInvalidCodeError();
5366
5373
  }
@@ -5372,6 +5379,11 @@ var TotpManager = class {
5372
5379
  /**
5373
5380
  * Verify a TOTP code during login.
5374
5381
  * Returns true if the code is valid, false otherwise.
5382
+ *
5383
+ * Enforces single-use: a code is rejected if its time-step has already
5384
+ * been consumed (or predates a previously consumed time-step). This
5385
+ * prevents an attacker who observes one valid code from replaying it
5386
+ * within the acceptance window (RFC 6238 §5.2).
5375
5387
  */
5376
5388
  async verify(userId, code) {
5377
5389
  const stored = await this.store.getByUserId(userId);
@@ -5381,7 +5393,7 @@ var TotpManager = class {
5381
5393
  if (!stored.verified) {
5382
5394
  throw new TotpNotVerifiedError(userId);
5383
5395
  }
5384
- return this.validateCode(stored.secret, code);
5396
+ return this.consumeCode(stored, code);
5385
5397
  }
5386
5398
  /**
5387
5399
  * Verify a recovery code as an alternative to TOTP.
@@ -5465,16 +5477,41 @@ var TotpManager = class {
5465
5477
  }
5466
5478
  // --- Private ---
5467
5479
  validateCode(base32Secret, code) {
5480
+ return this.matchTimeStep(base32Secret, code) !== null;
5481
+ }
5482
+ /**
5483
+ * Find the TOTP time-step counter (within the acceptance window) whose
5484
+ * generated code matches `code`, or null if none matches. Comparison is
5485
+ * timing-safe.
5486
+ */
5487
+ matchTimeStep(base32Secret, code) {
5468
5488
  const secretBytes = base32Decode(base32Secret);
5469
5489
  const now = Math.floor(Date.now() / 1e3);
5470
5490
  for (let offset = -this.window; offset <= this.window; offset++) {
5471
5491
  const timeCounter = Math.floor((now + offset * this.period) / this.period);
5472
5492
  const expected = generateTotpCode(secretBytes, timeCounter, this.digits, this.algorithm);
5473
5493
  if (timingSafeEqual(code, expected)) {
5474
- return true;
5494
+ return timeCounter;
5475
5495
  }
5476
5496
  }
5477
- return false;
5497
+ return null;
5498
+ }
5499
+ /**
5500
+ * Validate a code AND consume its time-step so it (and any earlier code in
5501
+ * the window) cannot be reused. Returns false for an invalid code or a
5502
+ * replay of an already-consumed time-step. Persists the consumed time-step.
5503
+ */
5504
+ async consumeCode(stored, code) {
5505
+ const matched = this.matchTimeStep(stored.secret, code);
5506
+ if (matched === null) {
5507
+ return false;
5508
+ }
5509
+ if (stored.lastUsedTimeStep !== void 0 && matched <= stored.lastUsedTimeStep) {
5510
+ return false;
5511
+ }
5512
+ stored.lastUsedTimeStep = matched;
5513
+ await this.store.save(stored);
5514
+ return true;
5478
5515
  }
5479
5516
  };
5480
5517
  function generateTotpCode(secret, counter, digits, algorithm) {