Skip to content

fix(identity): include auth_workos_user_id in audit log details for non-primary bindings#3728

Draft
bokelley wants to merge 1 commit into
mainfrom
claude/issue-3717-audit-log-auth-identity
Draft

fix(identity): include auth_workos_user_id in audit log details for non-primary bindings#3728
bokelley wants to merge 1 commit into
mainfrom
claude/issue-3717-audit-log-auth-identity

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 1, 2026

Closes #3717

Summary

PR #3715 introduced id-swap in the auth middleware: req.user.id is always the canonical workos_user_id, and req.user.authWorkosUserId holds the actual credential when a non-primary binding is signed in. Audit log inserts were recording only the canonical id, losing the forensic trail of which bound email performed each action. Phase 2b (#3722) ships today, creating the first non-singleton bindings — this fix needed to land immediately.

What this PR does:

  • Adds auth_workos_user_id?: string to AuditLogEntry interface; recordAuditLog merges it into the details JSONB automatically (no schema migration — details is already JSONB)
  • Updates all 32 orgDb.recordAuditLog() call sites that use req.user.id as the actor across organizations.ts (23 sites), http.ts (3), accounts-billing.ts (1), accounts.ts (1), billing.ts (2), and brand-enrichment.ts (1 inline SQL)
  • Fixes 2 inline SQL sites in accounts-billing.ts to spread auth_workos_user_id conditionally into their JSON.stringify details
  • Fixes admin/users.ts: the adminUserId passed to mergeUsers now uses authWorkosUserId ?? id so merge_user audit entries record the credential identity
  • Fixes admin/cleanup.ts: same credential-id fix for mergeOrganizations actor
  • Adds auth_user_name display enrichment to the admin audit log API endpoint (resolves the credential user via the existing WorkOS cache, guarded against workos being null in dev mode)

Non-breaking justification: All changes are additive to the details JSONB column. workos_user_id (= canonical) is unchanged for all routes except merge_user / merge_organization, where the actor column now records the credential id directly (consistent with how those two functions have always worked — the actor is passed by the caller, not extracted from the canonical id). No existing query is broken; the new field is absent from records written before this PR.

Note on merge audit entries (merge_user / merge_organization): For these two operations, workos_user_id stores the credential id directly (the caller passes it). This differs from the details.auth_workos_user_id pattern used everywhere else. The auth_user_name display enrichment will not fire for these entries (they don't store auth_workos_user_id in details). A follow-up could unify the pattern by adding an authMergedBy?: string parameter to both merge functions, but that is out of scope here.

Note on companyDb.recordAuditLog sites (http.ts:7962, 8032): These use a different audit table schema (user_id column, metadata JSONB) and are currently dead code. Not updated here; the companyDb audit surface should be handled separately when those endpoints are activated.

Pre-PR review:

  • code-reviewer: approved — no blockers; 3 nits (double cast in http.ts, extract formatWorkOSDisplayName helper, add test case for id-swap scenario). Nits noted; not blocking.
  • internal-tools-strategist: approved — both prior blockers resolved (AuditLogEntry centralization ✓, auth_user_name enrichment ✓); inline SQL sites acceptable due to transaction coupling.

Triage-managed PR. This bot does not currently iterate on
review comments or PR conversation threads (only on the source
issue). To unblock:

  • Push fixup commits directly: gh pr checkout <num>
    fix → push.
  • Or re-trigger: comment /triage execute on the source
    issue.

See #3121
for context.

Session: https://claude.ai/code/session_014nNk64VpVs3hLFS7xSPKdQ


Generated by Claude Code

…on-primary bindings

Audit log inserts recorded req.user.id (canonical identity) as the actor,
losing the credential identity for non-primary bindings. Adds auth_workos_user_id
to AuditLogEntry, centralizes merging into recordAuditLog, and updates all
req.user-driven audit sites across organizations.ts, accounts-billing.ts,
accounts.ts, brand-enrichment.ts, admin/users.ts, billing.ts, and http.ts.
Also adds auth_user_name enrichment to the admin audit log display endpoint.

Refs #3717

https://claude.ai/code/session_014nNk64VpVs3hLFS7xSPKdQ
@bokelley bokelley added the claude-triaged Issue has been triaged by the Claude Code triage routine. Remove to re-triage. label May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude-triaged Issue has been triaged by the Claude Code triage routine. Remove to re-triage.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Identity layer M1: include authWorkosUserId in audit log details

2 participants