Skip to content

fix: prune auth event types nothing emits and fix their consumers - #127

Merged
Bccorb merged 1 commit into
mainfrom
fix/prune-dead-auth-events
Jul 29, 2026
Merged

fix: prune auth event types nothing emits and fix their consumers#127
Bccorb merged 1 commit into
mainfrom
fix/prune-dead-auth-events

Conversation

@Bccorb

@Bccorb Bccorb commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Closes #125

The audit found more than stale entries

The issue expected a few dead names left behind by removed features. The actual result: 15 of 74 declared types have no emit site anywhere in src/, and several of them are things other code actively searches for. So this was not just dead weight in an enum, it was a monitoring gap.

getSecurityAnomalies searched for five types nothing writes:

Searched Emitted? Reality
bearer_token_failed no nothing in the bearer_token_* family is ever emitted
jwks_failed no same for jwks_*
otp_failed no the real name is verify_otp_failed
recovery_otp_failed no recovery emits admin_device_replacement_recovery
user_data_failed no only user_data_suspicious is emitted

Meanwhile verify_otp_failed, totp_failed, magic_link_failed, and logout_failed are emitted and were never searched for. The practical effect: OTP verification failures did not show up in anomaly detection at all.

expandType in the admin controller had the same drift. The otp filter expanded to ['otp_success', 'otp_failed'], so it missed every verify_otp_* and mfa_otp_* event, and suspicious listed webauthn_login_suspicious and service_token_suspicious, neither of which is ever written.

Change

Removed (per your call): the bearer_token_* and jwks_* families, recovery_otp_*, user_data_failed, user_data_success, and the four remaining never-emitted stragglers otp_failed, mfa_otp_suspicious, service_token_suspicious, webauthn_login_suspicious. 74 to 59.

The groupings are now derived from AUTH_EVENT_TYPES rather than hand-listed:

  • FAILURE_EVENT_TYPES is everything ending in _failed
  • SUSPICIOUS_EVENT_TYPES is everything ending in _suspicious
  • authEventTypesFor(...prefixes) groups a flow by prefix

Both consumers now use those, so the copies cannot drift apart again. Prefix matching is anchored on _ so authEventTypesFor('otp') does not sweep in the unrelated totp_* family, which a naive includes would have done, and there is a test for exactly that.

Guard

A test asserts every declared type has a type: '...' emit site in src/, excluding the declaration itself and src/generated/ (both list every name verbatim and would make it pass vacuously). This is what would have caught the bootstrap_admin_* pair when that feature was removed.

Verified it is not vacuous by adding a never_emitted_probe entry and confirming the test failed naming it, then reverting.

Compatibility

Reading historical rows is unaffected. InternalAuthEventSchema.type is z.string(), and AuthEventQuerySchema.type is a union with z.string(), so an event row written under a removed name is still returned and still filterable. The enum is a compile-time constraint on emit sites plus an OpenAPI annotation, not a runtime gate on stored data. Removing a member makes emitting it a type error, which is the point.

Relevant to fells-code/seamless-auth-types#4: the consolidated list there still carries these 15. Worth pruning before that release is adopted in #124, otherwise adopting it reintroduces them.

Checks

npm run typecheck, npm run lint, npm run format:check, and the suite pass (985 passed, 1 skipped). openapi.json and the generated types were regenerated; the drift test from #119 caught the stale spec on the first run, which is a nice confirmation it works.

15 of the 74 declared types had no emit site anywhere in src. Five were
actively queried by the security anomaly detector (bearer_token_failed,
jwks_failed, otp_failed, recovery_otp_failed, user_data_failed), so those
failure categories always came back empty, while verify_otp_failed,
totp_failed, magic_link_failed, and logout_failed were emitted and never
searched for. OTP verification failures were therefore invisible to anomaly
detection.

The admin event filter had the same drift: otp expanded to a type nothing
emits and missed every verify_otp_* and mfa_otp_* event, and suspicious named
two types that are never written.

Removes the bearer_token_* and jwks_* families, recovery_otp_*,
user_data_failed, user_data_success, otp_failed, mfa_otp_suspicious,
service_token_suspicious, and webauthn_login_suspicious.

The failure and suspicious groupings are now derived from AUTH_EVENT_TYPES
rather than hand-copied, so a new event type files itself in the right bucket.
A test asserts every declared type has an emit site, which is what would have
caught the bootstrap_admin_* entries when that feature was removed.

Reading historical rows is unaffected: stored events are returned as z.string()
and the query filter accepts any string, so events written under a removed name
still appear.

Closes #125
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.

Audit AUTH_EVENT_TYPES for events that are no longer emitted

1 participant