Skip to content

feat(admin): revoke sessions and notify when an admin changes a user's email or phone - #485

Merged
Bccorb merged 3 commits into
mainfrom
feat/admin-identity-change-notice
Oct 23, 2026
Merged

Bccorb merged 3 commits into
mainfrom
feat/admin-identity-change-notice

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Closes #475.

What

When PATCH /admin/users/{userId} changes a user's email or phone:

  • Sessions revoked. Every unrevoked session for the user is revoked (revokedReason: admin_identity_change) in the same transaction as the user update, so a failed revocation cannot leave the new address stored and the change undetectable on a retry. Access tokens already issued stay valid until they expire, the same as the existing admin revoke-all route.
  • Notices sent. The previous and the new address each get a notice: email for an email change, SMS for a phone change (a removed phone notifies only the old number, an added one only the new). A failed notice is recorded and does not fail the request.
  • Audited. The existing internal_user_updated_by_owner event gains identityChanges (field, previousSha256, nextSha256), revokedSessions and notices (per recipient: sent, failed or not_sent_external_delivery). Audit metadata is redacted of addresses before storage, so the values are kept as SHA-256 digests of the normalized form rather than in the clear; an investigator can still confirm whether a given address was the one removed or added. No new event type, so the event type enum is unchanged.

An email that differs only in case is not treated as a change. Changing only emailVerified, phoneVerified or roles does not revoke sessions.

Messaging contract

@seamless-auth/messaging 0.2.0 has templates only for OTP, magic link and enrollment invite, so there is no suitable template for an account notice. Rather than add a contract field, the notice goes through the closest generic path: the configured EmailTransport.send / SmsTransport.send (the transport contract's generic EmailMessage / SmsMessage), with the text written in the API. directMessaging.ts now exposes createDirectEmailTransport / createDirectSmsTransport for that, and messagingService.ts adds sendNoticeEmail / sendNoticeSms, which follow the same development bypass and DeliveryError handling as the other senders.

External delivery: AuthDeliverySchema in @seamless-auth/types has no kind for an account notice, so with x-seamless-auth-delivery-mode: external the API sends nothing itself (the caller owns delivery) and records each notice as not_sent_external_delivery. A follow-up could add an account_notice delivery kind to the types and messaging contracts so adopters who own delivery get these too.

Step-up for this route is #437 and is not part of this change.

Contract

Response shape is unchanged (UserResponseSchema). The only openapi.json / src/generated/api.ts change is a description on the route. Behaviour change worth noting for the admin dashboard: editing a user's email or phone now signs that user out.

Checks

  • npm run typecheck: pass
  • npm run lint: pass
  • npx prettier --check .: pass
  • npm run build: pass
  • npm run coverage: pass, 142 files, 1995 tests passed (1 todo), thresholds met (all files 98.91% statements, 95.4% branches, 99.21% functions, 99.15% lines)

@Bccorb
Bccorb merged commit a722835 into main Oct 23, 2026
9 checks passed
@Bccorb
Bccorb deleted the feat/admin-identity-change-notice branch October 23, 2026 01:54
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.

feat(admin): revoke sessions and notify when an admin changes a user's email or phone

1 participant