diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 2add824e2..071f575e5 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.58.1-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' 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 });