From 31118ba8b67035ee771ce92d488ffd62da14c0c4 Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Thu, 28 May 2026 13:40:53 +0300 Subject: [PATCH 1/3] ci(frontend): use playwright container, add cache + path filter - Run tests in mcr.microsoft.com/playwright:v1.57.0-noble so the workflow no longer downloads browsers at runtime. - Fix yarn test (watch-mode) -> yarn test:ci. - Add yarn cache via setup-node@v4 and yarn install --immutable. - Path-filter triggers to frontend/** plus this workflow file. - Cancel superseded PR runs via concurrency group. - Bump license job Node 16 -> 24, drop unused setup-just step. - Modernize action versions (checkout@v5, setup-node@v4). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/frontend.yml | 55 ++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 2add824e2..8e175229b 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -1,31 +1,56 @@ name: frontend + on: push: branches: - - main - # Publish semver tags as releases - tags: [ '*.*.*' ] + - main + tags: + - '*.*.*' + paths: + - 'frontend/**' + - '.github/workflows/frontend.yml' pull_request: + paths: + - 'frontend/**' + - '.github/workflows/frontend.yml' + +concurrency: + group: frontend-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + working-directory: frontend + +# TODO: add a lint job once ESLint migration replaces the (already-removed in +# @angular-devkit/build-angular v20) `tslint` builder that `yarn lint` calls. + jobs: test: runs-on: ubuntu-latest + timeout-minutes: 20 + container: + image: mcr.microsoft.com/playwright:v1.57.0-noble steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v4 with: node-version: '24' - - run: cd frontend && yarn install - - name: Install Playwright browsers - run: cd frontend && yarn playwright install - - name: run tests - run: cd frontend && yarn test + cache: 'yarn' + cache-dependency-path: frontend/yarn.lock + - run: yarn install --immutable + - name: Run unit tests + run: yarn test:ci + license: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v4 with: - node-version: '16' - - uses: extractions/setup-just@v1 + node-version: '24' + cache: 'yarn' + cache-dependency-path: frontend/yarn.lock - name: license checker - run: 'cd frontend && npx license-checker --onlyAllow="MIT;ISC;Python-2.0;Apache-2.0;BSD;MPL;CC;Custom: http://github.com/dscape/statsd-parser;" --excludePrivatePackages' + run: 'npx --yes license-checker --onlyAllow="MIT;ISC;Python-2.0;Apache-2.0;BSD;MPL;CC;Custom: http://github.com/dscape/statsd-parser;" --excludePrivatePackages' From b7a1e2532f99425bf199efe0128f8a3e96141a15 Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Thu, 28 May 2026 13:57:12 +0300 Subject: [PATCH 2/3] ci(frontend): bump playwright image tag to v1.58.1-noble The yarn.lock resolves playwright@^1.57.0 to 1.58.1; the container tag must match exactly or Vitest's browser provider refuses to run. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/frontend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 8e175229b..071f575e5 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 container: - image: mcr.microsoft.com/playwright:v1.57.0-noble + image: mcr.microsoft.com/playwright:v1.58.1-noble steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v4 From d01db255e220fbf4ba0fbf039280b2c5c3f984d7 Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Thu, 28 May 2026 14:15:39 +0300 Subject: [PATCH 3/3] fix(2fa): accept legacy 10-byte secrets in otplib v13 verification Users enrolled before the otplib v12 -> v13 upgrade have 10-byte secrets stored, which v13's RFC 4226 guardrail (16-byte minimum) rejects with "Secret must be at least 16 bytes". Pass a relaxed `MIN_SECRET_BYTES: 10` guardrail to verifySync so existing users can still authenticate; new secrets continue to use v13's 20-byte default. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/entities/user/use-cases/disable-otp.use.case.ts | 7 ++++++- backend/src/entities/user/use-cases/otp-login-use.case.ts | 7 ++++++- .../src/entities/user/use-cases/verify-otp-use.case.ts | 7 ++++++- backend/src/entities/user/utils/otp-guardrails.ts | 8 ++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 backend/src/entities/user/utils/otp-guardrails.ts diff --git a/backend/src/entities/user/use-cases/disable-otp.use.case.ts b/backend/src/entities/user/use-cases/disable-otp.use.case.ts index b9f3b8bbe..0d30b89c5 100644 --- a/backend/src/entities/user/use-cases/disable-otp.use.case.ts +++ b/backend/src/entities/user/use-cases/disable-otp.use.case.ts @@ -13,6 +13,7 @@ import { BaseType } from '../../../common/data-injection.tokens.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { OtpDisablingResultDS } from '../application/data-structures/otp-validation-result.ds.js'; import { VerifyOtpDS } from '../application/data-structures/verify-otp.ds.js'; +import { legacyOtpGuardrails } from '../utils/otp-guardrails.js'; import { IDisableOTP } from './user-use-cases.interfaces.js'; @Injectable() @@ -41,7 +42,11 @@ export class DisableOtpUseCase extends AbstractUseCase implem if (!foundUser) { throw new NotFoundException(Messages.USER_NOT_FOUND); } - const isValid = verifySync({ token: otpToken, secret: foundUser.otpSecretKey }).valid; + const isValid = verifySync({ + token: otpToken, + secret: foundUser.otpSecretKey, + guardrails: legacyOtpGuardrails, + }).valid; if (!isValid) { await this.recordSignInAudit( foundUser.email, diff --git a/backend/src/entities/user/use-cases/verify-otp-use.case.ts b/backend/src/entities/user/use-cases/verify-otp-use.case.ts index b0c3b626e..cdef4944b 100644 --- a/backend/src/entities/user/use-cases/verify-otp-use.case.ts +++ b/backend/src/entities/user/use-cases/verify-otp-use.case.ts @@ -6,6 +6,7 @@ import { BaseType } from '../../../common/data-injection.tokens.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { OtpValidationResultDS } from '../application/data-structures/otp-validation-result.ds.js'; import { VerifyOtpDS } from '../application/data-structures/verify-otp.ds.js'; +import { legacyOtpGuardrails } from '../utils/otp-guardrails.js'; import { IVerifyOTP } from './user-use-cases.interfaces.js'; @Injectable() @@ -48,7 +49,11 @@ export class VerifyOtpUseCase extends AbstractUseCase v13 upgrade have 10-byte secrets stored +// (the v12 `authenticator.generateSecret()` default). Relax the guardrail at +// verify time so those users can still authenticate. New secrets use v13's +// 20-byte default and are RFC-compliant. +export const legacyOtpGuardrails = createGuardrails({ MIN_SECRET_BYTES: 10 });