chore(deps): bump the all-npm-backend group across 1 directory with 10 updates (#783)

* chore(deps): bump the all-npm-backend group across 1 directory with 10 updates

Bumps the all-npm-backend group with 10 updates in the /backend directory:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-ecr](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-ecr) | `3.1028.0` | `3.1037.0` |
| [axios](https://github.com/axios/axios) | `1.15.0` | `1.15.2` |
| [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) | `12.8.0` | `12.9.0` |
| [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) | `8.3.2` | `8.4.1` |
| [openid-client](https://github.com/panva/openid-client) | `6.8.2` | `6.8.3` |
| [otplib](https://github.com/yeojz/otplib/tree/HEAD/packages/otplib) | `12.0.1` | `13.4.0` |
| [eslint](https://github.com/eslint/eslint) | `10.2.0` | `10.2.1` |
| [typescript](https://github.com/microsoft/TypeScript) | `6.0.2` | `6.0.3` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.58.1` | `8.59.0` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.4` | `4.1.5` |



Updates `@aws-sdk/client-ecr` from 3.1028.0 to 3.1037.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-ecr/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1037.0/clients/client-ecr)

Updates `axios` from 1.15.0 to 1.15.2
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.15.0...v1.15.2)

Updates `better-sqlite3` from 12.8.0 to 12.9.0
- [Release notes](https://github.com/WiseLibs/better-sqlite3/releases)
- [Commits](https://github.com/WiseLibs/better-sqlite3/compare/v12.8.0...v12.9.0)

Updates `express-rate-limit` from 8.3.2 to 8.4.1
- [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases)
- [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v8.3.2...v8.4.1)

Updates `openid-client` from 6.8.2 to 6.8.3
- [Release notes](https://github.com/panva/openid-client/releases)
- [Changelog](https://github.com/panva/openid-client/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/openid-client/compare/v6.8.2...v6.8.3)

Updates `otplib` from 12.0.1 to 13.4.0
- [Release notes](https://github.com/yeojz/otplib/releases)
- [Commits](https://github.com/yeojz/otplib/commits/v13.4.0/packages/otplib)

Updates `eslint` from 10.2.0 to 10.2.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.2.0...v10.2.1)

Updates `typescript` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/compare/v6.0.2...v6.0.3)

Updates `typescript-eslint` from 8.58.1 to 8.59.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.0/packages/typescript-eslint)

Updates `vitest` from 4.1.4 to 4.1.5
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.5/packages/vitest)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-ecr"
  dependency-version: 3.1037.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-npm-backend
- dependency-name: axios
  dependency-version: 1.15.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-backend
- dependency-name: better-sqlite3
  dependency-version: 12.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-npm-backend
- dependency-name: express-rate-limit
  dependency-version: 8.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-npm-backend
- dependency-name: openid-client
  dependency-version: 6.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-backend
- dependency-name: otplib
  dependency-version: 13.4.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-npm-backend
- dependency-name: eslint
  dependency-version: 10.2.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-npm-backend
- dependency-name: typescript
  dependency-version: 6.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-npm-backend
- dependency-name: typescript-eslint
  dependency-version: 8.59.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: all-npm-backend
- dependency-name: vitest
  dependency-version: 4.1.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-npm-backend
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(mfa): migrate otplib API to v13

The v13 release removed the singleton authenticator export and renamed
HashAlgorithms to a string union type. Switch to the OTP class with
generateSync/verifySync for synchronous operation, passing per-call
options instead of setting global instance state.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: SaelixCode <dev@saelix.com>
This commit is contained in:
dependabot[bot]
2026-04-26 16:24:03 -04:00
committed by GitHub
parent f15d0a94e8
commit 71d164cf9e
4 changed files with 255 additions and 247 deletions
+13 -18
View File
@@ -13,8 +13,7 @@
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import request from 'supertest';
import jwt from 'jsonwebtoken';
import { authenticator } from 'otplib';
import { HashAlgorithms } from '@otplib/core';
import { OTP } from 'otplib';
import {
setupTestDb,
cleanupTestDb,
@@ -25,12 +24,8 @@ import {
// Match the server-side otplib configuration so test-generated OTPs are
// accepted by the verify path.
authenticator.options = {
digits: 6,
step: 30,
algorithm: HashAlgorithms.SHA1,
window: 1,
};
const authenticator = new OTP({ strategy: 'totp' });
const TOTP_PARAMS = { algorithm: 'sha1' as const, digits: 6, period: 30 };
let tmpDir: string;
let app: import('express').Express;
@@ -92,7 +87,7 @@ afterAll(() => {
describe('MfaService', () => {
it('verifyTotp accepts a freshly generated code', () => {
const secret = MfaService.generateSecret();
const code = authenticator.generate(secret);
const code = authenticator.generateSync({ secret, ...TOTP_PARAMS });
expect(MfaService.verifyTotp(secret, code)).toBe(true);
});
@@ -190,7 +185,7 @@ describe('POST /api/auth/login/mfa', () => {
it('accepts a valid TOTP, clears pending cookie, issues session', async () => {
const pendingCookie = await startChallenge();
const code = authenticator.generate(secret);
const code = authenticator.generateSync({ secret, ...TOTP_PARAMS });
const res = await request(app)
.post('/api/auth/login/mfa')
@@ -216,7 +211,7 @@ describe('POST /api/auth/login/mfa', () => {
const login = await request(app).post('/api/auth/login').send({ username: u, password: p });
const pending = findCookie(login.headers, 'sencho_mfa_pending')!;
const code = authenticator.generate(s);
const code = authenticator.generateSync({ secret: s, ...TOTP_PARAMS });
const ok = await request(app).post('/api/auth/login/mfa').set('Cookie', pending).send({ code });
expect(ok.status).toBe(200);
@@ -346,7 +341,7 @@ describe('MFA enrol + confirm', () => {
.send({ code: '000000' });
expect(wrong.status).toBe(401);
const code = authenticator.generate(startRes.body.secret as string);
const code = authenticator.generateSync({ secret: startRes.body.secret as string, ...TOTP_PARAMS });
const confirm = await request(app)
.post('/api/auth/mfa/enroll/confirm')
.set('Authorization', `Bearer ${userToken}`)
@@ -488,7 +483,7 @@ describe('MfaService.verifyTotp drift handling', () => {
vi.useFakeTimers();
try {
vi.setSystemTime(baseMs);
const code = authenticator.generate(secret);
const code = authenticator.generateSync({ secret, ...TOTP_PARAMS });
// Advance three full 30s windows so the code is outside the +-1 tolerance.
vi.setSystemTime(baseMs + 3 * 30_000);
expect(MfaService.verifyTotp(secret, code)).toBe(false);
@@ -499,7 +494,7 @@ describe('MfaService.verifyTotp drift handling', () => {
it('still accepts a fresh code generated in the current window', () => {
const secret = MfaService.generateSecret();
const code = authenticator.generate(secret);
const code = authenticator.generateSync({ secret, ...TOTP_PARAMS });
expect(MfaService.verifyTotp(secret, code)).toBe(true);
});
});
@@ -555,7 +550,7 @@ describe('POST /api/auth/login/mfa edge cases', () => {
const ok = await request(app)
.post('/api/auth/login/mfa')
.set('Cookie', pending)
.send({ code: authenticator.generate(secret) });
.send({ code: authenticator.generateSync({ secret, ...TOTP_PARAMS }) });
expect(ok.status).toBe(200);
const after = db.getUserMfa(userId)!;
@@ -578,7 +573,7 @@ describe('POST /api/auth/login/mfa edge cases', () => {
const ok = await request(app)
.post('/api/auth/login/mfa')
.set('Cookie', pending)
.send({ code: authenticator.generate(secret) });
.send({ code: authenticator.generateSync({ secret, ...TOTP_PARAMS }) });
expect(ok.status).toBe(200);
const after = db.getUserMfa(userId)!;
@@ -618,7 +613,7 @@ describe('MFA enrol/start overwrites a prior pending secret', () => {
expect(secondSecret).not.toBe(firstSecret);
// First secret no longer verifies against the stored (now-overwritten) secret.
const wrongCode = authenticator.generate(firstSecret);
const wrongCode = authenticator.generateSync({ secret: firstSecret, ...TOTP_PARAMS });
const rejected = await request(app)
.post('/api/auth/mfa/enroll/confirm')
.set('Authorization', `Bearer ${token}`)
@@ -637,7 +632,7 @@ describe('MFA enrol/start overwrites a prior pending secret', () => {
const ok = await request(app)
.post('/api/auth/mfa/enroll/confirm')
.set('Authorization', `Bearer ${token}`)
.send({ code: authenticator.generate(secondSecret) });
.send({ code: authenticator.generateSync({ secret: secondSecret, ...TOTP_PARAMS }) });
expect(ok.status).toBe(200);
expect(ok.body.backupCodes).toHaveLength(10);
});
+8 -17
View File
@@ -1,23 +1,14 @@
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import { authenticator } from 'otplib';
import { HashAlgorithms } from '@otplib/core';
import { OTP } from 'otplib';
import { DatabaseService } from './DatabaseService';
import { MFA_REPLAY_TTL_MS, MFA_REPLAY_PURGE_INTERVAL_MS } from '../helpers/constants';
import { isDebugEnabled } from '../utils/debug';
// Configure otplib for the default TOTP contract we present to users:
// - 6 digits
// - 30-second step
// - SHA-1 (the universally supported default for authenticator apps)
// - ±1 step tolerance, so the server accepts the previous, current, and next code
// to cover small clock drift between the device and the server.
authenticator.options = {
digits: 6,
step: 30,
algorithm: HashAlgorithms.SHA1,
window: 1,
};
// TOTP configuration: 6 digits, 30-second step, SHA-1, ±1 step tolerance.
// SHA-1 is the universally supported default for authenticator apps (RFC 6238).
const totp = new OTP({ strategy: 'totp' });
const TOTP_PARAMS = { algorithm: 'sha1' as const, digits: 6, period: 30 };
const BACKUP_CODE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Crockford-like, no 0/O/1/I/L
const BACKUP_CODE_LENGTH = 10;
@@ -70,7 +61,7 @@ export class MfaService {
* `verifyTotp`. Each user should receive a unique secret.
*/
public static generateSecret(): string {
return authenticator.generateSecret();
return totp.generateSecret();
}
/**
@@ -79,7 +70,7 @@ export class MfaService {
* app can label the entry clearly.
*/
public static buildOtpauthUri(secret: string, username: string, issuer = 'Sencho'): string {
return authenticator.keyuri(username, issuer, secret);
return totp.generateURI({ issuer, label: username, secret, ...TOTP_PARAMS });
}
/**
@@ -92,7 +83,7 @@ export class MfaService {
const trimmed = code.trim().replace(/\s+/g, '');
if (!/^\d{6}$/.test(trimmed)) return false;
try {
return authenticator.check(trimmed, secret);
return totp.verifySync({ secret, token: trimmed, ...TOTP_PARAMS, epochTolerance: 30 }).valid;
} catch {
return false;
}