Skip to content

Port Personal Two-Factor Setup to Angular - #80

Open
nllong wants to merge 1 commit into
mainfrom
port-personal-two-factor-setup
Open

Port Personal Two-Factor Setup to Angular#80
nllong wants to merge 1 commit into
mainfrom
port-personal-two-factor-setup

Conversation

@nllong

@nllong nllong commented Aug 4, 2026

Copy link
Copy Markdown
Member

Port Personal Two-Factor Setup to Angular

Ports the legacy AngularJS "Personal Two-Factor Setup" page
(/profile/two_factor_profile, two_factor_profile_controller) to this app, under
profile/two-factor. Org-level 2FA policy was already migrated
(organizations/settings/two-factor) — this is the separate personal device-enrollment flow.

Changes

  • @seed/api/two-factor — new TwoFactorService + types, preserving the four legacy
    /api/v3/two_factor/ endpoints unchanged: set_method, resend_token_email,
    generate_qr_code, verify_code.
  • profile/two-factorProfileTwoFactorComponent:
    • disabled / email / token-generator method selection (reactive form + mat-radio-group)
    • organization-enforced 2FA banner — computed across all orgs the user belongs to (not just
      the active one), matching the legacy orgs_require_2fa check
    • "Resend Test Email Token" (uncommented/wired up from legacy's dead/commented button) with a
      confirmation message
    • "Re-Generate Token" standalone action when the token method is already active
    • save/reload flow, loading state, and error surfacing via the shared SnackBarService
  • profile/two-factor/modalQrCodeModalComponent (MatDialog): QR code display, 6-digit
    verification form (Validators.pattern(/^\d{6}$/), preserving leading zeros — the legacy
    <input type="number"> silently truncated those), re-generate-in-place, and a safe fallback to
    email/disabled on cancel or "select a different method".
  • Bug fix carried over from the legacy port: the legacy qr_code_scan_modal_controller's
    cancel/fallback path referenced $scope.require_2fa, a variable that was never actually
    assigned anywhere in two_factor_profile_controller (only the differently-named
    $scope.orgs_require_2fa was set). That meant canceling out of the QR flow always fell back to
    disabled, silently bypassing organization-enforced 2FA. This port computes the fallback from
    the real cross-org check instead, so canceling correctly falls back to email (never
    disabled) when any org the user belongs to requires 2FA.
  • Added MatRadioModule to the shared MaterialImports barrel (not previously needed elsewhere).
  • New Transloco keys added to public/i18n/en_US.json (English is the key = value convention used
    throughout this app). fr_CA.json/es.json are Lokalise-managed and unchanged here — a
    follow-up pnpm update-translations push is needed for those locales.
  • MIGRATION.md updated: moved personal two-factor setup from "Not yet migrated" to "Already
    migrated".

Validation

  • pnpm lint and pnpm build both pass.
  • Live-tested against a throwaway backend (seedplatform/seed:develop) with seeded data — two
    orgs, one with require_2fa=True — via Playwright:
    • Email method: save → current method updates → resend token → confirmation message with
      correct email interpolation.
    • Token method: save → QR modal opens → QR code decoded and a real TOTP code computed → Verify
      → success snackbar → modal closes → user refreshed → TOTPDevice confirmed in the DB.
    • Invalid code → inline error, no crash.
    • Standalone "Re-Generate Token" → new QR modal.
    • "Select a Different Method" → falls back to email (not disabled) because the user belongs
      to an org requiring 2FA — confirms the bug-fix above.
    • Switching the active org to one that does not require 2FA still shows the enforcement
      banner and keeps "Disabled" locked, because the check is user-wide across all org
      memberships, not just the active org — matching the legacy controller's behavior.
    • No console errors observed in any of the above.
    • Dark mode and a 390×844 mobile viewport both verified for the page and the QR modal.

Screenshots

Two-Factor Authentication page

Authenticator QR Code modal

Not done here

  • Legacy AngularJS code (seed/static/seed/) is untouched — it keeps serving /app/ traffic.
  • fr_CA/es Lokalise sync for the new keys.

- Add TwoFactorService (@seed/api/two-factor) preserving the four legacy
  /api/v3/two_factor/ endpoints: set_method, resend_token_email,
  generate_qr_code, verify_code
- Add ProfileTwoFactorComponent under profile/two-factor: disabled/email/
  token-generator method selection, organization-enforced 2FA banner
  (correctly falls back to email rather than disabled when any org the
  user belongs to requires 2FA, fixing a latent bug in the legacy
  fallback logic that used an always-undefined scope variable), resend
  test email token, save/reload flow
- Add QrCodeModalComponent dialog: QR code display, 6-digit verification
  form, re-generate token, and safe fallback to email/disabled on cancel
  or 'select a different method'
- Add MatRadioModule to the shared MaterialImports barrel (not previously
  needed by any other page)
- Register the profile/two-factor route (before no other dynamic routes
  are affected) and add a Two Factor Profile nav/tab entry
- Add new Transloco keys to public/i18n/en_US.json (Lokalise sync still
  needed for fr_CA/es)
- Update MIGRATION.md: move personal two-factor setup from 'Not yet
  migrated' to 'Already migrated'

Verified against a live throwaway backend + seeded data (an org requiring
2FA and one that doesn't) via Playwright: email method save + resend,
token method save -> QR modal -> decoded QR -> valid TOTP verification
-> device confirmed in DB, invalid code handling, standalone re-generate,
select-a-different-method fallback, dark mode, and mobile viewport. No
console errors observed. pnpm lint and pnpm build both pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The implementation has user-visible behavior that doesn’t match the PR description (missing resend confirmation and no inline error on invalid verification codes), plus a minor copy issue in a newly added translation string.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Ports the legacy AngularJS personal two-factor enrollment flow into the Angular app under profile/two-factor, adding a dedicated API wrapper and UI (including a QR verification modal) while keeping the backend on the existing /api/v3/two_factor/* endpoints.

Changes:

  • Added TwoFactorService + types under src/@seed/api/two-factor and exported it via the API barrel.
  • Introduced ProfileTwoFactorComponent + QrCodeModalComponent UI, and wired the new route/tab into the profile section.
  • Updated shared Material imports (MatRadio), added new i18n keys, and updated MIGRATION.md.
File summaries
File Description
src/app/modules/profile/two-factor/two-factor.component.ts New personal 2FA settings page logic (load org enforcement, save method, resend/regenerate actions, QR modal flow).
src/app/modules/profile/two-factor/two-factor.component.html New personal 2FA settings page template (method selection UI, enforcement banner, actions).
src/app/modules/profile/two-factor/modal/qr-code-modal.component.ts New QR verification modal logic (verify + regenerate QR code).
src/app/modules/profile/two-factor/modal/qr-code-modal.component.html New QR verification modal template (QR display, 6-digit code input, actions).
src/app/modules/profile/profile.routes.ts Adds the profile/two-factor route.
src/app/modules/profile/profile.component.ts Adds a new “Two Factor Profile” tab/navigation item.
src/@seed/materials/material.module.ts Adds MatRadioModule to the shared Material barrel.
src/@seed/api/two-factor/two-factor.types.ts Defines types for personal 2FA endpoints and responses.
src/@seed/api/two-factor/two-factor.service.ts Implements calls to /api/v3/two_factor/* endpoints with shared error handling.
src/@seed/api/two-factor/index.ts Exports the new two-factor service/types from the feature barrel.
src/@seed/api/index.ts Re-exports the two-factor API barrel from the main API entrypoint.
public/i18n/en_US.json Adds/extends translation keys used by the new page/modal.
MIGRATION.md Marks the personal 2FA page as migrated and updates the cross-check references.
Review details
  • Files reviewed: 13/13 changed files
  • Comments generated: 3
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +44 to +51
next: (response) => {
this.verifying = false
if (response.success) {
this._snackBar.success(this._transloco.translate('Authenticator App Verified!'))
this.close(true)
} else {
this._snackBar.alert(response.error ?? this._transloco.translate('Unable to verify code. Please try again.'))
}
Comment thread public/i18n/en_US.json
"Save Settings": "Save Settings",
"Saving access levels": "Saving access levels",
"Saving…": "Saving…",
"Scan the QR code below using your authenticator app of choice (ex: Google Authenticator, Microsoft Authenticator, Authy, etc...)": "Scan the QR code below using your authenticator app of choice (ex: Google Authenticator, Microsoft Authenticator, Authy, etc...)",
Comment on lines +109 to +116
resendTokenEmail(): void {
this._twoFactorService
.resendTokenEmail(this.user.org_id, this.user.email)
.pipe(takeUntil(this._unsubscribeAll$))
.subscribe(() => {
this.emailSent = true
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants