Skip to content

auth: generalize credential field read-deny to better-auth plugin tables - #1019

Merged
borisno2 merged 2 commits into
mainfrom
claude/focused-rubin-qd5ynq
Aug 22, 2026
Merged

auth: generalize credential field read-deny to better-auth plugin tables#1019
borisno2 merged 2 commits into
mainfrom
claude/focused-rubin-qd5ynq

Conversation

@borisno2

Copy link
Copy Markdown
Member

Summary

  • Widens the ADR-0036 field-level read deny from the four base Auth models to better-auth plugin tables, since plugin tables derive through the same scalar-field-derivation pass (ADR-0034): oauthClient.clientSecret, oauthAccessToken.token, oauthRefreshToken.token (the mcp/oauth-provider plugin), and twoFactor.secret/twoFactor.backupCodes (twoFactor()).
  • Adds authPlugin({ credentialFields }) so an app can mark a credential field on a better-auth plugin the stack doesn't seed a set for — keyed by better-auth's own model key, naming better-auth's own field keys, additive only (can add fields but can never unmark a stack-seeded one).
  • Derivation now throws, naming the model and field, when a credential-field entry (stack-seeded or app-supplied) names a field missing from a model the app actually derives; an entry for a model the app doesn't derive at all is a silent no-op.
  • Deliberately does not condition the deny on better-auth's own storage mode (storeTokens/storeClientSecret) or its returned: false flag — neither is a reliable/complete signal (see the amended ADR-0036).
  • Updates ADR-0036 (with a cross-reference from ADR-0034), packages/auth/CLAUDE.md, and the docs-site auth reference so the credential set is no longer presented as a closed list of six base fields.

Test plan

  • New unit tests in packages/auth/tests/derive-auth-lists.test.ts cover: real mcp/twoFactor() plugin instances denying the expected fields and leaving identifiers open (including twoFactor.userId, which carries returned: false upstream but is a relationship field, not a scalar); a plugin-table modelName + column remap surviving the deny; credentialFields marking a field on a synthetic plugin; credentialFields failing to unmark a seeded field; the throw-on-missing-field and no-op-on-undeployed-model paths (synthetic schemas).
  • New test in packages/auth/tests/plugin-derived-keys.test.ts proves an app that redeclares a plugin-derived list and its credential field key still gets the derived, read-denied field (extendList merge order), while its own access still applies.
  • The six existing base-model credential-deny tests are unchanged and still pass.
  • pnpm test (packages/auth): 295 passed, 4 skipped (opt-in e2e guards, unaffected by this change).
  • pnpm lint / pnpm format / pnpm manypkg fix on the changed files.
  • pnpm build (core, cli, auth) — no type errors.

Closes #1014


Generated by Claude Code

…n tables

Extends the ADR-0036 field-level read-deny from the four base Auth models
to plugin tables derived through the same registry (ADR-0034): the
mcp/oauth-provider plugin's oauthClient.clientSecret/oauthAccessToken.token/
oauthRefreshToken.token, and twoFactor()'s twoFactor.secret/backupCodes.

Adds authPlugin({ credentialFields }) so an app can mark credential fields
on a plugin the stack doesn't seed a set for — additive only, keyed by
better-auth's own model/field keys, and validated (throws on a field name
that doesn't exist on a derived model; no-op for an undeployed plugin).

Closes #1014
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

Deployment failed for project stack-docs with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/open-saas?upgradeToPro=build-rate-limit

@changeset-bot

changeset-bot Bot commented Aug 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e7098ab

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@opensaas/stack-auth Minor
@opensaas/stack-cli Minor
@opensaas/stack-core Minor
@opensaas/stack-rag Minor
@opensaas/stack-storage-s3 Minor
@opensaas/stack-storage-vercel Minor
@opensaas/stack-storage Minor
@opensaas/stack-tiptap Minor
@opensaas/stack-ui Minor

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

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: 0d3dc51b65

ℹ️ 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 thread packages/auth/src/config/derive-auth-lists.ts
An id-referencing field (references.field === 'id') derives to a
relationship(), never a scalar column, so withCredentialAccess — only ever
applied on the scalar-field path — could never actually deny it. Naming
one in credentialFields previously validated (the field exists) and then
silently had no effect. Throw instead, naming the model and field.

Found in code review of #1019.

Copy link
Copy Markdown
Member Author

Review summary

Ran a /code-review pass (medium effort) against this PR.

Finding (fixed): buildCredentialFieldRegistry validated that a credentialFields entry named an existing field, but didn't account for id-referencing fields (references.field === 'id'), which derive to a relationship() rather than a scalar column. withCredentialAccess only ever runs on the scalar-field derivation path, so a credentialFields entry naming a relationship field (e.g. an FK ending Id) would validate successfully and then silently have no effect — no deny, no error.

Fixed in e7098ab: the registry now throws, naming the model and field, when an entry resolves to an id-referencing relationship field, with a regression test (throws, naming the model and field, when credentialFields names an id-referencing relationship field) and an updated TSDoc note on AuthConfig.credentialFields.

No other findings. Test suite: 296 passed, 4 skipped (opt-in e2e guards). pnpm build/lint/format clean.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Note: the Vercel status check is failing with "Deployment rate limited — retry in 24 hours" (api-deployments-free-per-day quota on the stack-docs project) — unrelated to this diff, nothing to fix here. GitGuardian passed; test/e2e are still running.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Core Package Coverage (./packages/core)

Status Category Percentage Covered / Total
🟢 Lines 94.05% (🎯 65%) 1631 / 1734
🟢 Statements 92.32% (🎯 65%) 1768 / 1915
🟢 Functions 97.41% (🎯 62%) 264 / 271
🟢 Branches 86.18% (🎯 50%) 1279 / 1484
File CoverageNo changed files found.
Generated in workflow #1794 for commit e7098ab by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for UI Package Coverage (./packages/ui)

Status Category Percentage Covered / Total
🔵 Lines 78.45% 244 / 311
🔵 Statements 77.95% 251 / 322
🔵 Functions 69.81% 74 / 106
🔵 Branches 66.94% 160 / 239
File CoverageNo changed files found.
Generated in workflow #1794 for commit e7098ab by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for CLI Package Coverage (./packages/cli)

Status Category Percentage Covered / Total
🔵 Lines 79% 1547 / 1958
🔵 Statements 78.81% 1615 / 2049
🔵 Functions 86.11% 217 / 252
🔵 Branches 69.66% 758 / 1088
File CoverageNo changed files found.
Generated in workflow #1794 for commit e7098ab by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Auth Package Coverage (./packages/auth)

Status Category Percentage Covered / Total
🔵 Lines 99.49% 196 / 197
🔵 Statements 98.13% 211 / 215
🔵 Functions 100% 45 / 45
🔵 Branches 91.26% 188 / 206
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/auth/src/config/derive-auth-lists.ts 97.4% 89.47% 100% 99.28% 230, 231, 418-420, 761
packages/auth/src/config/plugin.ts 100% 100% 100% 100%
packages/auth/src/config/types.ts 0% 0% 0% 0%
Generated in workflow #1794 for commit e7098ab by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Package Coverage (./packages/storage)

Status Category Percentage Covered / Total
🔵 Lines 78.57% 220 / 280
🔵 Statements 80.06% 245 / 306
🔵 Functions 86.07% 68 / 79
🔵 Branches 75.88% 214 / 282
File CoverageNo changed files found.
Generated in workflow #1794 for commit e7098ab by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for RAG Package Coverage (./packages/rag)

Status Category Percentage Covered / Total
🔵 Lines 47.97% 355 / 740
🔵 Statements 48.14% 377 / 783
🔵 Functions 54.26% 70 / 129
🔵 Branches 42.55% 180 / 423
File CoverageNo changed files found.
Generated in workflow #1794 for commit e7098ab by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)

Status Category Percentage Covered / Total
🔵 Lines 100% 40 / 40
🔵 Statements 100% 40 / 40
🔵 Functions 100% 9 / 9
🔵 Branches 100% 19 / 19
File CoverageNo changed files found.
Generated in workflow #1794 for commit e7098ab by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)

Status Category Percentage Covered / Total
🔵 Lines 100% 68 / 68
🔵 Statements 100% 71 / 71
🔵 Functions 100% 15 / 15
🔵 Branches 97.87% 46 / 47
File CoverageNo changed files found.
Generated in workflow #1794 for commit e7098ab by the Vitest Coverage Report Action

@borisno2
borisno2 merged commit 77b7314 into main Aug 22, 2026
4 of 5 checks passed
@borisno2
borisno2 deleted the claude/focused-rubin-qd5ynq branch August 22, 2026 10:33
@github-actions github-actions Bot mentioned this pull request Aug 22, 2026
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.

auth: better-auth plugin-table credential fields (e.g. oidc-provider clientSecret, two-factor secret) aren't covered by the ADR-0036 read-deny

2 participants