Skip to content

IdP-managed tenancy: no write overwrites a newer change, and every sign-in is capped - #5

Merged
cliftonc merged 3 commits into
mainfrom
review-fixes
Nov 1, 2026
Merged

cliftonc merged 3 commits into
mainfrom
review-fixes

Conversation

@cliftonc

@cliftonc cliftonc commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

These fix the review findings on nearform/agentic-mapper#8 and nearform/unwind-next#9, the two apps that took #4.

  • Reconcile race: upsertIdpMembership updates a membership only if its role and source are still what it read. An owner promotion or an identity event that lands in between wins, and the next sign-in reconciles from there.
  • Stale removal: removeIdpMemberships re-checks idp_synced_at in the DELETE itself. A sign-in that stamps the row after the event's read keeps the membership and its settings. Activity is recorded only for rows actually removed.
  • Slug collision: if two different orgs take the same free slug at the same moment, the loser picks a new slug instead of failing the sign-in with oauth_failed.
  • Uncapped sessions: under managed tenancy, completeLogin caps every sign-in (OIDC, magic link and dev login), not only an OIDC one. The maxAgeMs option is gone.
  • Cookie life: OIDC_SESSION_MAX_AGE_DAYS is now at most 30. The cookie is set once, at sign-in, and lasts 30 days, so a longer cap would have been cut short by the browser.

Tests: a dev login under managed tenancy gets max_expires_at, and the config refuses 31. The two races aren't tested deterministically; the guards are in the SQL.

The local gate passes except the kit-manifest test, which only fails because of untracked local files that CI doesn't see.

🤖 Generated with Claude Code

cliftonc and others added 3 commits November 1, 2026 14:07
…gn-in is capped

Review findings on the apps that took #4:
- The sign-in reconcile updates a membership only as it read it, so an owner promotion or an
  identity event in between wins.
- A removal re-checks idp_synced_at in the DELETE, so a sign-in that stamped the row after the
  event's read keeps it.
- Two different orgs that take the same free slug at once: the loser picks again instead of
  failing the sign-in.
- Under managed tenancy every sign-in (magic link and dev login too) gets max_expires_at, from
  completeLogin.
- OIDC_SESSION_MAX_AGE_DAYS is at most 30, the cookie's own life: the cookie is set once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… row and a slug clash retry

Second review pass:
- A sign-in stamps tenants and memberships with the id_token's iat, and never writes over a row an
  identity event stamped later, so a token issued before an event cannot undo it or make it look
  stale.
- A membership removed between the reconcile's read and its update is inserted again (the claims
  grant it).
- The slug retry also catches createTenantForUser's own slug_taken check, and tries up to five
  times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…one can restore it

A known gap, recorded rather than closed: the id_token is issued at the callback's own code
exchange, so the window is the second or two before a removal event (or a concurrent sign-in that
cleared global admin), and the next sign-in or the session's end heals it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cliftonc
cliftonc merged commit d761532 into main Nov 1, 2026
15 checks passed
@cliftonc
cliftonc deleted the review-fixes branch November 1, 2026 18:28
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