Skip to content

feat(auth-server): tag account.login events with the login method - #21038

Open
vbudhram wants to merge 1 commit into
mainfrom
fxa-14325
Open

feat(auth-server): tag account.login events with the login method#21038
vbudhram wants to merge 1 commit into
mainfrom
fxa-14325

Conversation

@vbudhram

Copy link
Copy Markdown
Contributor

Because

  • account.login is a catch-all. A password sign-in and a passkey sign-in write the same row, so security history cannot tell them apart.
  • Passwordless OTP writes only account.passwordless_login_otp_verified, and third party auth writes no security event at all. Those two sign-ins are invisible in security history today.

This pull request

  • Adds method to SecurityEventAdditionalInfo in account-events.ts, as the union password | passwordless.otp | passwordless.thirdParty | passkey.
  • Passes method through the shared recordSecurityEvent helper in routes/utils/security-event.ts.
  • Sets the method on the password flow (routes/utils/signin.js, which builds its own additionalInfo) and on the passkey flow (routes/passkeys.ts).
  • Writes an account.login event for passwordless OTP (routes/passwordless.ts) and for third party auth (routes/linked-accounts.ts). Neither wrote one before.
  • Adds a co-located spec per flow, and two for the helper.

additionalInfo is already a JSON column, so this needs no new event name, no securityEventNames row, and no migration. The passwordless OTP account.login is in addition to account.passwordless_login_otp_verified, not a replacement. Nothing mutates or deletes an existing row.

Issue that this pull request solves

Closes: FXA-14325

Checklist

Put an x in the boxes that apply

  • My commit is GPG signed.
  • If applicable, I have modified or added tests which pass locally.
  • I have added necessary documentation (if appropriate).
  • I have verified that my changes render correctly in RTL (if appropriate).
  • I have manually reviewed all AI generated code.

How to review (Optional)

  • Key files/areas to focus on: there are two write paths that do not share code. routes/utils/security-event.ts is the shared helper, used by passkeys and passwordless. routes/utils/signin.js calls accountEventsManager.recordSecurityEvent directly with its own additionalInfo literal, so the helper change does nothing for it.
  • Suggested review order: account-events.ts, then routes/utils/security-event.ts, then the four call sites.
  • Risky or complex parts:
    • sendSigninNotifications is shared by /account/login and /session/reauth. Both are password authentication, so password is correct for both.
    • Nothing is backfilled. Existing rows have no method. A consumer must read a missing method as unknown, never as password. There is deliberately no default in the type and none at read time.

Screenshots (Optional)

No user interface change.

Other information (Optional)

This is item 2 from the ticket only. Item 1, keeping the unverified to verified transition instead of overwriting the row, needs its own ticket. It is riskier: it touches the logic that skips login confirmation emails.

Out of scope on purpose: the Amplitude emitMetricsEvent('account.login') calls are a different pipeline and are unchanged. Rendering method in the admin panel or in Settings is not part of this change.

Verified locally: the fxa-auth-server Jest unit suite and nx lint fxa-auth-server. Functional tests were not run.

@vbudhram
vbudhram requested a review from a team as a code owner August 14, 2026 15:13
Copilot AI balanced review requested due to automatic review settings August 14, 2026 15:13
@vbudhram vbudhram added the auto label Aug 14, 2026

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.

Pull request overview

Adds authentication-method metadata to account.login security events and records previously missing passwordless login events.

Changes:

  • Adds password, OTP, third-party, and passkey method tags.
  • Records login events for OTP and third-party authentication.
  • Expands unit coverage across affected flows.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
lib/account-events.ts Defines login method metadata.
lib/routes/utils/security-event.ts Passes method metadata to event storage.
lib/routes/utils/security-event.spec.ts Tests method forwarding and omission.
lib/routes/utils/signin.js Tags password authentication.
lib/routes/utils/signin.spec.ts Verifies password tagging.
lib/routes/passwordless.ts Records OTP login events.
lib/routes/passwordless.spec.ts Tests OTP login events.
lib/routes/passkeys.ts Tags passkey authentication.
lib/routes/passkeys.spec.ts Verifies passkey tagging.
lib/routes/linked-accounts.ts Records third-party login events.
lib/routes/linked-accounts.spec.ts Tests third-party login events.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +349 to +354
await recordSecurityEvent('account.login', {
db: this.db,
request,
account: { uid: account.uid },
method: 'passwordless.otp',
});
Comment on lines +634 to +639
await recordSecurityEvent('account.login', {
db: this.db,
request,
account: { uid: accountRecord.uid },
method: 'passwordless.thirdParty',
});
location: opts?.request.app.geo.location,
...(clientId && { client_id: clientId }),
...(service && { service }),
...(opts?.method && { method: opts.method }),
## Because

- `account.login` is a catch-all. A password sign-in and a passkey sign-in write the same row, so security history cannot tell them apart. This slowed the recent attack investigation.
- Passwordless OTP writes only `account.passwordless_login_otp_verified`, and third party auth writes no security event at all. Those two sign-ins are invisible in security history today.

## This pull request

- Adds `method` to `SecurityEventAdditionalInfo` in `account-events.ts`, as the union `password | passwordless.otp | passwordless.thirdParty | passkey`.
- Types the options of the shared `recordSecurityEvent` helper in `routes/utils/security-event.ts`, so a misspelled `method` fails the build instead of reaching the database.
- Sets the method on the password flow (`routes/utils/signin.js`, which builds its own `additionalInfo`) and on the passkey flow (`routes/passkeys.ts`).
- Writes an `account.login` event for passwordless OTP (`routes/passwordless.ts`) and for third party auth (`routes/linked-accounts.ts`). Neither wrote one before.
- Lets a caller pass `tokenId` to the helper, and passes the new session token id from the two flows that create a session while unauthenticated.
- Adds a co-located spec per flow, and two for the helper.

`additionalInfo` is already a JSON column, so this needs no new event name, no `securityEventNames` row, and no migration. The passwordless OTP `account.login` is in addition to `account.passwordless_login_otp_verified`, not a replacement. Nothing mutates or deletes an existing row.

## Issue that this pull request solves

Closes: FXA-14325
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants