mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-18 22:36:19 +00:00
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:
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user