Skip to content

fix(test-apps): key OIDC sign-in records by userId instead of module state - #285

Open
ahmedhamouda78 wants to merge 1 commit into
mainfrom
fix/oidc-signin-shared-state
Open

fix(test-apps): key OIDC sign-in records by userId instead of module state#285
ahmedhamouda78 wants to merge 1 commit into
mainfrom
fix/oidc-signin-shared-state

Conversation

@ahmedhamouda78

Copy link
Copy Markdown
Contributor

What

AuthOIDC's onSignIn hooks in the comprehensive test app recorded the signed-in user into module-level variables:

let lastOidcSignInUser: { userId: string; email: string | null; provider: string } | null = null;

The hook runs while the OIDC callback is being served. The e2e reads the result back over a later, separate request. In a deployed environment those two can land on different Lambda instances, so the reader either sees its own null or the record of whoever signed in last on whichever instance answered.

The second case is the one that bothers me more than a plain failure. The old assertions only checked provider, so a leftover record from the same provider would pass while proving nothing about the sign-in the test had just performed.

Same class as the verification-code flake in #278, different flow. No red builds have been pinned on this one yet, so this is prevention.

How

Sign-in records go to the existing oidc-profiles KVStore under signin:<instance>:<userId>, and userId is now required on oidcGetLastSignInUser and oidcExtrasGetLastSignInUser. "Give me whoever signed in last" no longer type checks:

test/__noarg-probe.ts(3,23): error TS2554: Expected 1 arguments, but got 0.
test/__noarg-probe.ts(4,23): error TS2554: Expected 1 arguments, but got 0.

That is a live gate, not just a convention: test:e2e:local runs tsc --noEmit in its before-hook.

I reused oidc-profiles instead of adding a store. It already holds the per-user profile the second instance upserts, which the code comments call the canonical post-sign-in pattern, and no code scans it, only keyed get/put. The keys are also bounded here, unlike the verification codes: the stub IdP returns a fixed user per provider, so each sign-in overwrites its own record rather than piling up. That is why there is no purge method in this one.

The two consumers now resolve their own userId from the session via requireAuth and poll for that record rather than reading once. The polling lives in test/poll-for-signin.ts, a sibling of the poll-for-code.ts that #278 adds. Worth folding the two into one generic poller once both have landed; I kept them separate here so this PR does not depend on that one.

Tests

Two new tests, both with a control run showing they bite. Restoring module-level semantics (a per-instance Record holding the last sign-in, which is exactly what the old code did) makes both fail:

not ok 1 - sign-in record is keyed per user, not a shared "last sign-in" slot
    unknown userId should read as null, not the most recent sign-in
not ok 2 - each AuthOIDC instance keeps its own sign-in records
    Expected values to be strictly equal:
    - null
# tests 2 / # pass 0 / # fail 2

With the fix both pass, along with the rest of the OIDC suite:

ok 1 - onSignIn fires on successful sign-in
ok 2 - sign-in record is keyed per user, not a shared "last sign-in" slot
ok 3 - each AuthOIDC instance keeps its own sign-in records
ok 1 - sign-in flow works and onSignIn upserts profile
ok 2 - second sign-in updates profile, does not duplicate
ok 14 - AuthOIDC

Also added sign-out cleanup to the onSignIn fires test, which previously left its session open.

Ran locally on Node 22: npm run build, npm run test:unit (0 fail across every workspace), the comprehensive e2e local, check:api, check:exports-consistency, and all three changeset guards. verify-coverage reports no publishable packages changed, so no changeset is needed here.

The e2e shows 372 pass / 2 fail. Both failures are cli-client.test.ts shelling out to npm and hitting my host's CodeArtifact registry with an expired token, E401. They are unrelated to this change and pass in CI.

Fixes #284

The AuthOIDC `onSignIn` hooks recorded the signed-in user into module-level
variables that the e2e then read back over a separate request. The hook runs
while the callback is being served, so in a deployed environment the two
requests can land on different Lambda instances: the reader gets its own
null, or the record of whoever signed in last on that instance. The second
case is the worse one, since the old assertions only checked `provider` and
a leftover record from the same provider would pass while proving nothing.

Same shape as the verification-code flake, different flow.

Sign-in records now go to the existing oidc-profiles KVStore under
`signin:<instance>:<userId>`, and `userId` is required on
oidcGetLastSignInUser / oidcExtrasGetLastSignInUser, so "give me whoever
signed in last" no longer type checks. The e2e before-hook runs
tsc --noEmit, so that is enforced.

Reusing oidc-profiles rather than adding a store: it already holds the
per-user profile that the second instance upserts, and the keys are bounded
here because the stub IdP returns a fixed user per provider, so each
sign-in overwrites its own record.

Tests now resolve their own userId from the session (via requireAuth) and
poll for that record instead of reading once. New coverage: an unknown
userId reads as null rather than the most recent sign-in, and the two
AuthOIDC instances keep separate records. Both fail if the module-level
semantics come back.

Fixes #284
@ahmedhamouda78
ahmedhamouda78 requested a review from a team as a code owner July 30, 2026 16:44
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: ab15e70

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

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.

AuthOIDC onSignIn hook stores user in module-level state, invisible across Lambda instances (test-apps/comprehensive)

1 participant