Skip to content

Report the Posit AI Pass account email while setup is pending - #121

Merged
wch merged 2 commits into
mainfrom
positai-account-email
Oct 2, 2026
Merged

wch merged 2 commits into
mainfrom
positai-account-email

Conversation

@wch

@wch wch commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator

This PR makes two changes to how the Posit AI Pass provider fetches its model list:

  1. While the account's setup is pending, the provider looks up the signed-in account's email and reports it alongside the pending status, so that host apps can add it to posit.ai's setup link (/setup?email=...) and posit.ai can show "Wrong Account" when the browser is signed in as someone else.
  2. The provider's cached model list and status now belong to the exact access token that produced them, so that after an account switch or sign-out the app never shows the previous account's models or status.

The email closes a gap in setup: when a user signs in to Posit Assistant as one account while their browser is signed in to posit.ai as another, "Complete setup" finishes setup for the browser's account, and the Assistant's account stays pending. posit.ai now accepts an expected email on its pages (posit-hosted/prism-ui#315); this PR supplies it.

The lookup is off unless the host app supplies a list of candidate login hosts; the Posit Assistant wiring comes in a separate PR. The provider sends the access token only to a host that provably issued it. It checks the token's signature against each candidate host's published public keys and uses the first host whose keys match. It then asks that host for the current user, and accepts the email only if the returned user ID matches the token's.

If anything goes wrong, the provider reports pending status without an email, and the setup link works as it does today. That covers no candidate hosts, an http:// or malformed host, a signature that no host's keys verify, a failed key or user lookup, a redirect to the login page, and a lookup that takes longer than 5 seconds. Failed lookups retry on the next model-list refresh; a successful lookup is reused for the same token and hosts, even across cache clears. The token, the email, and the user response are never logged.

A model-list response that finishes after a sign-out, token change, or cache clear is now discarded instead of overwriting newer state. The cost is one extra model-list request each time the access token refreshes.

Part of posit-dev/assistant#2560. Related to posit-hosted/prism-ui#293

Changes

  • Provider registration accepts an optional getter of candidate login hosts (getPositAiAuthHostCandidates on the registration config, or a fifth argument to registerPositAiProvider). Hosts can be bare (login.posit.cloud) or https:// URLs. Without it, behavior is unchanged apart from the token-keyed cache.
  • The pending status can carry accountEmail. It appears only with agreement_pending, and only when the lookup succeeded.
  • A new or missing access token clears the cached model list and status. Previously, the cache was served regardless of which token filled it, and signing out cleared only the status.

Design decisions

  • Signature check instead of the issuer claim. The token's issuer claim is lucid:services:lucid-auth, which names no host. Staging and production publish the same key ID with different keys, so verifying the signature is what tells them apart. It also works for tokens stored before an auth-host edit, without persisting anything new.
  • Ordered candidates instead of one host. Positron's sign-in and Posit Assistant's configuration can disagree about the login host on older Positron versions. Using the first candidate that verifies the token covers both without version checks.
  • Keyed on the full token, not the user ID. The user ID claim can't be trusted before the signature check, and user IDs can repeat across environments, so a claim-based key could hand one account's email to another.

Not included

  • Supplying the candidate hosts from Posit Assistant (Node and Positron), and showing the email in the setup banner and configuration overlay. Those land in the Posit Assistant repo.

wch added 2 commits October 2, 2026 14:22
The positai fetcher's model cache and fetch state now belong to the exact
access token that filled them: a new or missing token resets both, and a
generation counter (bumped by clearCache() and token changes) drops
/models responses that settle after a reset.

While setup is pending, the fetcher can look up the signed-in account's
email (new PositAiAuthMetadata.accountEmail) so hosts can pass it to
posit.ai /setup?email= for the wrong-account check. The lookup is enabled
by an optional getter of candidate login hosts
(ProviderRegistrationConfig.getPositAiAuthHostCandidates). The token is
sent to /api/users/me only on the first candidate whose public JWKS
verifies its RS256 signature, over HTTPS with redirects refused, and the
response id must match the token's sub. Lookups are memoized per
(token, hosts), survive clearCache(), are bounded at 5 s, and resolve to
no email on any failure. The token, email, and response body are never
logged.
A failed account-email lookup logged the error's message verbatim. JSON
parse errors quote the start of the input, so a malformed /api/users/me
body could put part of the profile (such as the email) in the debug log,
and a custom fetch's rejection can carry request details. Log a fixed
failure category instead, and cover both cases with a test.

Also correct the module doc: the JWKS signature check keeps the token
from being sent to a candidate that doesn't publish its signing key
(misrouting, e.g. staging vs. production), but it doesn't prove the host
issued the token, since public keys can be republished. Candidates must
come from trusted configuration.
@wch
wch force-pushed the positai-account-email branch from 8769a88 to 0ea095f Compare October 2, 2026 19:24
@wch
wch enabled auto-merge (squash) October 2, 2026 19:24
@wch
wch merged commit 4f94420 into main Oct 2, 2026
4 checks passed
@wch
wch deleted the positai-account-email branch October 2, 2026 19:25
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.

1 participant