Skip to content

feat(gateway): thread authenticated user identity into presence#111179

Merged
steipete merged 2 commits into
mainfrom
claude/identity-presence-core
Jul 19, 2026
Merged

feat(gateway): thread authenticated user identity into presence#111179
steipete merged 2 commits into
mainfrom
claude/identity-presence-core

Conversation

@steipete

Copy link
Copy Markdown
Contributor

What Problem This Solves

Trusted-proxy gateway auth verifies a user's email and then drops it: GatewayAuthResult.user is used for the allow/deny decision and never attached to the connection. On shared team gateways this makes people invisible — presence is device-flavored only, and there is no gateway-side audit line tying a connection to the authenticated identity.

Part of the multiplayer-identity program: #111133 (PR 1 of the train).

Why This Change Was Made

This is the keystone the rest of the identity program (viewer avatars, prompt attribution, profiles) builds on: carry the already-verified identity into WS connection state and expose it on the connection's presence entry, strictly optionally.

  • PresenceEntry gains an optional closed user: { id, email?, name?, avatarUrl? } object. id is opaque: the authenticated email today, a durable profile id once the profiles PR lands. Clients group presence entries by user.id (the gateway keeps reporting one truthful entry per connection).
  • The WS connection retains authenticatedUserId when auth carried a user (trusted-proxy and Tailscale modes).
  • Connect and disconnect log the authenticated user id + connection id (operator audit trail).

Identity is purely optional: token/password/no-auth connections carry no identity and behave byte-identically to before.

Compatibility: additive optional field on a closed schema, matching established convention (e.g. appliedConfigHash in #107577). Audit of consumers: Control UI casts presence entries without schema validation; macOS/iOS Swift Codable ignores unknown keys; packages/gateway-client frame guards are explicitly additive-tolerant; SDK exposes hello as unknown; no in-tree consumer validates inbound snapshots against the closed schema, and @openclaw/gateway-protocol is not npm-published.

User Impact

No visible change yet on its own. Shared-gateway operators get audit log lines for identified connects/disconnects; presence snapshots start carrying identity for UIs to render (follow-up PRs).

Evidence

  • node scripts/run-vitest.mjs packages/gateway-protocol/src/schema/snapshot.test.ts — schema round-trip with and without user (2 tests).
  • node scripts/run-vitest.mjs src/gateway/server/ws-connection/message-handler.post-connect-health.test.ts — trusted-proxy connection projects user.id; token connection has no user (36 tests).
  • node scripts/run-vitest.mjs src/gateway/server/ws-connection.test.ts — disconnect audit log (17 tests).
  • Adjacent suites green: auth + auth-context (102 tests), system-presence (13 tests).
  • Structured autoreview (codex/gpt-5.6-sol, xhigh): one P2 suggesting protocol-version gating for the new field — rejected against repo convention of ungated additive optional fields (fix(ui): show restart banner for unapplied config #107577 precedent) and the consumer audit above.

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui gateway Gateway runtime size: M maintainer Maintainer-authored PR labels Jul 19, 2026
@steipete
steipete marked this pull request as ready for review July 19, 2026 03:39
@clawsweeper

clawsweeper Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper status: review started.

I am starting a fresh review of this pull request: feat(gateway): thread authenticated user identity into presence This is item 1/1 in the current shard. Shard 0/1.

This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking.

Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 289095234a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +327 to +329
...(authenticatedUserId
? { user: { id: authenticatedUserId, email: authenticatedUserId } }
: {}),

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.

P2 Badge Clear identity on userless reconnects

When the same presenceKey reconnects through a userless path after identity-bearing auth—for example, a paired Control UI device switches from Tailscale Serve to an explicit token while allowTailscale is enabled—this branch omits user. upsertPresence merges the update over the existing entry (src/infra/system-presence.ts:269-272), so the prior user's identity survives and the new session is falsely attributed to that user in presence snapshots. Explicitly clear user when authenticatedUserId is absent.

Useful? React with 👍 / 👎.

scopes,
instanceId: device?.id ?? instanceId,
...(authenticatedUserId
? { user: { id: authenticatedUserId, email: authenticatedUserId } }

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.

P2 Badge Do not label arbitrary auth identities as email addresses

For trusted-proxy configurations whose identity header contains a username or subject ID (both are allowed because authorizeTrustedProxy only requires a nonblank string), this publishes that value as user.email. The Tailscale auth contract likewise permits identities such as peter. Downstream clients therefore receive a knowingly invalid email field; keep the opaque value in id and populate email only from a contract that actually guarantees an email address.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. labels Jul 19, 2026
@clawsweeper

clawsweeper Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 18, 2026, 11:52 PM ET / July 19, 2026, 03:52 UTC.

Summary
The branch adds optional authenticated-user identity to gateway connection state and presence snapshots, updates Swift protocol bindings, and adds connect/disconnect audit logging and focused tests.

PR surface: Source +34, Tests +185, Other +4. Total +223 across 9 files.

Reproducibility: yes. from source: connect once with an authenticated identity, then reconnect the same presence key through a userless auth path; the merge-based presence update can retain the old user object. This was not run in a live gateway during this read-only review.

Review metrics: none identified.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #111133
Summary: This PR is explicitly the first implementation slice of the open shared-gateway identity program.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🦐 gold shrimp
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Clear user on an identified-to-userless reconnect using the same presence key and add a regression test.
  • Publish only opaque user.id unless the auth result explicitly guarantees an email claim.
  • [P1] Add redacted live trusted-proxy or Tailscale evidence showing the presence payload and connect/disconnect audit lines; updating the PR body should trigger a fresh review, or a maintainer can request @clawsweeper re-review.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR provides focused unit-test claims but no after-fix real gateway connection, presence snapshot, or audit-log proof; add redacted live output or a terminal recording before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] A userless reconnect with the same presence key can retain and expose the preceding session's authenticated identity, causing incorrect viewer attribution.
  • [P1] Trusted-proxy and Tailscale identities are opaque strings; representing all of them as user.email gives downstream consumers an unreliable identity claim.
  • [P1] The new identity field reaches presence consumers and operator logs, so its exact public data contract should be settled before later UI and profile slices build on it.

Maintainer options:

  1. Correct presence replacement semantics (recommended)
    Before merge, ensure a userless update for an existing presence key explicitly clears the previous user value and add the reconnect regression coverage.
  2. Narrow the initial identity payload
    Expose only the opaque authenticated identifier until trusted-proxy and Tailscale paths provide a distinct verified-email contract.
  3. Approve a richer identity contract
    Maintainers may intentionally define and document which auth modes guarantee email before retaining the current field mapping.

Next step before merge

  • [P1] The two code corrections are clear, but the protected maintainer label and the new public identity contract require a maintainer decision on the supported claim shape before an automated repair should proceed.

Maintainer decision needed

  • Question: Should this first identity slice publish only an opaque authenticated-user ID in presence and audit logs, deferring email, name, and avatarUrl until a gateway identity contract guarantees those claims?
  • Rationale: The PR creates a public gateway presence contract that follow-up profile and UI work will depend on; source evidence shows current auth results do not guarantee an email-shaped identity.
  • Likely owner: steipete — They proposed the linked identity program and recently carried the adjacent gateway-protocol compatibility work.
  • Options:
    • Publish opaque IDs only (recommended): Keep user: { id } in this slice, clear it on userless updates, and add richer claims only when their provenance is defined.
    • Define an email-bearing auth contract first: Extend the authentication result to distinguish opaque subject IDs from verified email claims before exposing user.email.

Security
Cleared: No new dependency, execution, permission, or secret-handling path is introduced; the identity field's public semantics are addressed as functional and product-contract findings.

Review findings

  • [P2] Clear identity on userless presence updates — src/gateway/server/ws-connection/connect-session.ts:324-334
  • [P2] Keep opaque auth subjects out of user.emailsrc/gateway/server/ws-connection/connect-session.ts:324-334
Review details

Best possible solution:

Make user a replacement value on every presence update so an unauthenticated reconnect clears it, publish only the opaque id until an email-guaranteeing identity contract exists, and add redacted live gateway proof for identified and userless reconnects.

Do we have a high-confidence way to reproduce the issue?

Yes, from source: connect once with an authenticated identity, then reconnect the same presence key through a userless auth path; the merge-based presence update can retain the old user object. This was not run in a live gateway during this read-only review.

Is this the best way to solve the issue?

No. The intended threading is appropriate, but the patch must clear stale identity on userless reconnects and must not claim opaque auth subjects are email addresses.

Full review comments:

  • [P2] Clear identity on userless presence updates — src/gateway/server/ws-connection/connect-session.ts:324-334
    When a connection reuses a presenceKey after an identified session, omitting user leaves the prior value intact because presence updates merge existing fields. Emit an explicit undefined/clear value for userless connections and cover the identified-to-userless reconnect path.
    Confidence: 0.98
  • [P2] Keep opaque auth subjects out of user.emailsrc/gateway/server/ws-connection/connect-session.ts:324-334
    Trusted-proxy and Tailscale authentication accept nonblank opaque identities, not necessarily email addresses. Publishing the same value as user.email gives clients a false claim; retain it only in user.id until an email-guaranteeing contract exists.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.94

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 63f8b8454b75.

Label changes

Label changes:

  • add P2: This is a bounded gateway identity feature with incorrect presence and auth-claim semantics, but no demonstrated urgent outage or data loss.
  • add merge-risk: 🚨 session-state: Presence updates can preserve a prior connection's identity when the same presence key reconnects without authenticated identity.
  • add merge-risk: 🚨 auth-provider: The patch maps opaque trusted-proxy and Tailscale identities into an email-specific public field without an email guarantee.
  • add rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR provides focused unit-test claims but no after-fix real gateway connection, presence snapshot, or audit-log proof; add redacted live output or a terminal recording before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Label justifications:

  • P2: This is a bounded gateway identity feature with incorrect presence and auth-claim semantics, but no demonstrated urgent outage or data loss.
  • merge-risk: 🚨 session-state: Presence updates can preserve a prior connection's identity when the same presence key reconnects without authenticated identity.
  • merge-risk: 🚨 auth-provider: The patch maps opaque trusted-proxy and Tailscale identities into an email-specific public field without an email guarantee.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR provides focused unit-test claims but no after-fix real gateway connection, presence snapshot, or audit-log proof; add redacted live output or a terminal recording before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +34, Tests +185, Other +4. Total +223 across 9 files.

View PR surface stats
Area Files Added Removed Net
Source 5 35 1 +34
Tests 3 186 1 +185
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 1 5 1 +4
Total 9 226 3 +223

What I checked:

  • Presence merge leaves stale identity: The changed connection finalization emits user only when authenticatedUserId exists, while the existing presence implementation merges updates for the same presence key; a later userless reconnect can therefore retain the prior authenticated identity. (src/gateway/server/ws-connection/connect-session.ts:324, 78cb96fd98d1)
  • Auth identity is not an email contract: The reviewed auth path accepts a nonblank identity string for trusted-proxy and Tailscale authentication, so assigning that opaque value to user.email makes the wire field semantically false for usernames or subject IDs. (src/gateway/auth.ts:525, 78cb96fd98d1)
  • Existing review feedback remains applicable: The supplied PR review comments identify both the stale-presence reconnect path and the opaque-identity/email mismatch; the follow-up commit only regenerates the Swift protocol binding and does not address the affected gateway implementation. (src/gateway/server/ws-connection/connect-session.ts:324, 78cb96fd98d1)
  • Program and adjacent protocol history: The linked, merged additive-protocol work in fix(ui): show restart banner for unapplied config #107577 supplies a relevant compatibility precedent, while the open identity-program issue explicitly describes this PR as its first implementation slice. (packages/gateway-protocol/src/schema/snapshot.ts:28, 2ecc6b6b6b0c)
  • Real behavior proof is not yet supplied: The PR body reports unit and adjacent test suites, but contains no redacted live gateway connection, presence snapshot, or audit-log evidence showing the changed trusted-proxy/Tailscale behavior after the fix. (78cb96fd98d1)

Likely related people:

  • steipete: The author proposed the linked shared-gateway identity program and authored the merged additive gateway-protocol precedent referenced by this PR, making them the strongest available routing candidate for the intended public identity shape. (role: identity-program proposer and recent adjacent protocol contributor; confidence: high; commits: 2ecc6b6b6b0c, 289095234a29, 78cb96fd98d1; files: packages/gateway-protocol/src/schema/snapshot.ts, src/gateway/server/ws-connection/connect-session.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@steipete
steipete merged commit c684b13 into main Jul 19, 2026
157 of 161 checks passed
@steipete
steipete deleted the claude/identity-presence-core branch July 19, 2026 04:06
@steipete

Copy link
Copy Markdown
Contributor Author

Merged via squash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui gateway Gateway runtime maintainer Maintainer-authored PR merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: M status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant