fix(auth): project sessionFields from the resolved session, not just user - #889
Conversation
…user getSessionFromAuth now reads sessionFields off the resolved better-auth session (customSession-aware, with a defined top-level/user/session precedence) instead of only the user object, so a customSession shape with no `user` key is no longer misreported as anonymous. Errors from the underlying session lookup now propagate instead of becoming a silent null, and an unresolvable field warns once instead of vanishing. The scaffolded getSession() (CLI generator template, starter-auth, auth-demo) now calls this single helper with sessionFields read from the resolved config at runtime, fixing auth-demo's truthy-anonymous-session bug along the way. Closes #881 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3AJScedpPQTTZbJfbNqfZ
🦋 Changeset detectedLatest commit: 35c75f6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Self-reviewRan an independent review pass against the diff (fresh context, not the authoring session) covering the fix in Verdict: no correctness issues found.
Test coverage maps 1:1 to the issue's acceptance criteria (no- Generated by Claude Code |
| const authConfig = resolvedConfig._pluginData?.auth as NormalizedAuthConfig | undefined | ||
| const sessionFields = authConfig?.sessionFields ?? ['userId', 'email', 'name'] | ||
| const session = await getSessionFromAuth(auth, sessionFields, await headers()) | ||
| return session as unknown as Session | null |
There was a problem hiding this comment.
a consumer shouldn't need to cast as unknown for the types to be happen - infact a consumer shouldn't need to cast at all
There was a problem hiding this comment.
Good catch, fixed in 35c75f6 — the cast wasn't actually necessary. getSessionFromAuth (packages/auth/src/server/index.ts) now declares its return type as Session | null (from @opensaas/stack-core) instead of Record<string, unknown> | null. Within packages/auth's own build Session is unaugmented, so the internal Record<string, unknown> result satisfies it directly with no cast there either — and because the exported signature is already pinned to Session, every consumer (this file, auth-demo, and the CLI generator's template) now just does return getSessionFromAuth(...) with zero casts, regardless of whatever fields a consuming app's own types/session.d.ts augmentation adds.
Generated by Claude Code
Coverage Report for Core Package Coverage (./packages/core)
File CoverageNo changed files found. |
Coverage Report for UI Package Coverage (./packages/ui)
File CoverageNo changed files found. |
Coverage Report for CLI Package Coverage (./packages/cli)
File CoverageNo changed files found. |
Coverage Report for Auth Package Coverage (./packages/auth)
File Coverage
|
||||||||||||||||||||||||||||||||||||||
Coverage Report for Storage Package Coverage (./packages/storage)
File CoverageNo changed files found. |
Coverage Report for RAG Package Coverage (./packages/rag)
File CoverageNo changed files found. |
Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)
File CoverageNo changed files found. |
Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)
File CoverageNo changed files found. |
…r casts Per review feedback on #889: type getSessionFromAuth's return as `Session | null` (from @opensaas/stack-core) instead of `Record<string, unknown> | null`. Session is module-augmentable and unaugmented within packages/auth's own build, so the internal Record<string, unknown> result satisfies it with no cast there — and because the return type is already pinned to Session, every consumer (the CLI generator template, starter-auth, auth-demo) can now forward it straight through with zero casts instead of `as unknown as Session | null`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3AJScedpPQTTZbJfbNqfZ
Summary
getSessionFromAuthnow projectssessionFieldsoff the resolved better-auth session (whateverauth.api.getSession()returns), not just itsusersub-object. Resolution precedence for a non-userIdfield: a top-level key on the resolved session, thenuser, thensession.userIdstays special-cased to the authenticated user'sid.customSessionshape with no top-leveluserkey (see auth: buildBetterAuthOptions returns the widened BetterAuthOptions, erasing better-auth's plugin/session type inference #876) is now correctly treated as a signed-in session instead of being misreported as anonymous —nullmeans "genuinely no session," nothing else.auth.api.getSession()call now propagate instead of being swallowed intonull— collapsing a lookup failure into "anonymous" is indistinguishable from a mass sign-out under fail-closed access control.sessionFieldsentry that can't be resolved is omitted and logs a warning (once per field, per process) naming what was checked, instead of silently surfacing later as an access-control function readingundefined.getSession()— the CLI feature generator'slib/auth.tstemplate, andexamples/starter-auth/examples/auth-demo— now call this single shared helper, readingsessionFieldsfrom the resolved config at runtime instead of baking a field list in at generation time (so editingsessionFieldsafter scaffolding takes effect without regenerating).examples/auth-demo'sgetSession(), which previously returned a truthy object ofundefinedvalues for an anonymous visitor instead ofnull.sessionFieldsJSDoc and the auth reference doc (docs/content/reference/auth.md) to document the flattened-projection contract, the resolution precedence, and that reconciling acustomSession's nested shape is the application's job.Test plan
packages/auth/tests/server.test.tscover: the no-user-keycustomSessioncase, a session-sub-object field, a deliberate top-level/user/session precedence collision, warn-once-per-field behavior, error propagation vs. genuinenull, and the unchanged happy path — 214/214 auth tests passpackages/clitest suite passes (346/346)packages/coretest suite passes (948/948)examples/starter-authandexamples/auth-demoregenerate and typecheck cleanly against the updatedlib/auth.tspnpm lint,pnpm manypkg fix,pnpm format@opensaas/stack-authand@opensaas/stack-cli(minor)Closes #881
Generated by Claude Code