@opensaas/stack-auth 0.39.2 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +151 -0
  3. package/CLAUDE.md +140 -22
  4. package/dist/client/index.d.ts +0 -16
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/config/adopt-better-auth-tables.d.ts +4 -49
  7. package/dist/config/adopt-better-auth-tables.d.ts.map +1 -1
  8. package/dist/config/adopt-better-auth-tables.js +4 -51
  9. package/dist/config/adopt-better-auth-tables.js.map +1 -1
  10. package/dist/config/derive-auth-lists.d.ts +42 -37
  11. package/dist/config/derive-auth-lists.d.ts.map +1 -1
  12. package/dist/config/derive-auth-lists.js +579 -223
  13. package/dist/config/derive-auth-lists.js.map +1 -1
  14. package/dist/config/index.d.ts +0 -3
  15. package/dist/config/index.d.ts.map +1 -1
  16. package/dist/config/index.js +4 -34
  17. package/dist/config/index.js.map +1 -1
  18. package/dist/config/plugin.d.ts.map +1 -1
  19. package/dist/config/plugin.js +20 -90
  20. package/dist/config/plugin.js.map +1 -1
  21. package/dist/config/types.d.ts +89 -67
  22. package/dist/config/types.d.ts.map +1 -1
  23. package/dist/index.d.ts +1 -9
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -13
  26. package/dist/index.js.map +1 -1
  27. package/dist/lists/index.d.ts +9 -5
  28. package/dist/lists/index.d.ts.map +1 -1
  29. package/dist/lists/index.js +9 -6
  30. package/dist/lists/index.js.map +1 -1
  31. package/dist/mcp/better-auth.d.ts +6 -19
  32. package/dist/mcp/better-auth.d.ts.map +1 -1
  33. package/dist/mcp/better-auth.js +6 -20
  34. package/dist/mcp/better-auth.js.map +1 -1
  35. package/dist/plugins/index.d.ts +4 -4
  36. package/dist/plugins/index.d.ts.map +1 -1
  37. package/dist/plugins/index.js +4 -4
  38. package/dist/plugins/index.js.map +1 -1
  39. package/dist/runtime/types.d.ts +2 -9
  40. package/dist/runtime/types.d.ts.map +1 -1
  41. package/dist/runtime/types.js +0 -4
  42. package/dist/runtime/types.js.map +1 -1
  43. package/dist/server/index.d.ts +5 -12
  44. package/dist/server/index.d.ts.map +1 -1
  45. package/dist/server/index.js +14 -62
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/ui/components/ForgotPasswordForm.d.ts +0 -3
  48. package/dist/ui/components/ForgotPasswordForm.d.ts.map +1 -1
  49. package/dist/ui/components/ForgotPasswordForm.js +0 -3
  50. package/dist/ui/components/ForgotPasswordForm.js.map +1 -1
  51. package/dist/ui/components/ResetPasswordForm.d.ts +0 -3
  52. package/dist/ui/components/ResetPasswordForm.d.ts.map +1 -1
  53. package/dist/ui/components/ResetPasswordForm.js +0 -5
  54. package/dist/ui/components/ResetPasswordForm.js.map +1 -1
  55. package/dist/ui/components/SignInForm.d.ts +0 -3
  56. package/dist/ui/components/SignInForm.d.ts.map +1 -1
  57. package/dist/ui/components/SignInForm.js +0 -4
  58. package/dist/ui/components/SignInForm.js.map +1 -1
  59. package/dist/ui/components/SignUpForm.d.ts +0 -3
  60. package/dist/ui/components/SignUpForm.d.ts.map +1 -1
  61. package/dist/ui/components/SignUpForm.js +0 -5
  62. package/dist/ui/components/SignUpForm.js.map +1 -1
  63. package/dist/ui/lib/clean-error-message.d.ts +3 -6
  64. package/dist/ui/lib/clean-error-message.d.ts.map +1 -1
  65. package/dist/ui/lib/clean-error-message.js +3 -6
  66. package/dist/ui/lib/clean-error-message.js.map +1 -1
  67. package/dist/ui/types.d.ts +2 -15
  68. package/dist/ui/types.d.ts.map +1 -1
  69. package/dist/ui/types.js +2 -7
  70. package/dist/ui/types.js.map +1 -1
  71. package/package.json +12 -5
  72. package/src/client/index.ts +0 -17
  73. package/src/config/adopt-better-auth-tables.ts +4 -51
  74. package/src/config/derive-auth-lists.ts +685 -252
  75. package/src/config/index.ts +4 -34
  76. package/src/config/plugin.ts +21 -91
  77. package/src/config/types.ts +90 -67
  78. package/src/index.ts +3 -14
  79. package/src/lists/index.ts +17 -5
  80. package/src/mcp/better-auth.ts +6 -25
  81. package/src/plugins/index.ts +4 -5
  82. package/src/runtime/types.ts +2 -10
  83. package/src/server/index.ts +17 -70
  84. package/src/ui/components/ForgotPasswordForm.tsx +0 -3
  85. package/src/ui/components/ResetPasswordForm.tsx +0 -5
  86. package/src/ui/components/SignInForm.tsx +0 -4
  87. package/src/ui/components/SignUpForm.tsx +0 -5
  88. package/src/ui/lib/clean-error-message.ts +3 -6
  89. package/src/ui/types.ts +2 -15
  90. package/tests/auth-lists-drift.test.ts +480 -0
  91. package/tests/config.test.ts +3 -0
  92. package/tests/credential-field-read-deny-e2e.test.ts +295 -0
  93. package/tests/derive-auth-lists.test.ts +263 -6
  94. package/tests/generated-fk-shape.test.ts +432 -9
  95. package/tests/mcp-oauth-cascade-e2e.test.ts +267 -0
  96. package/tests/plugin-derived-keys.test.ts +39 -0
  97. package/tests/plugin-table-derivation.test.ts +270 -0
  98. package/tsconfig.tsbuildinfo +1 -1
  99. package/dist/server/schema-converter.d.ts +0 -81
  100. package/dist/server/schema-converter.d.ts.map +0 -1
  101. package/dist/server/schema-converter.js +0 -157
  102. package/dist/server/schema-converter.js.map +0 -1
  103. package/src/server/schema-converter.ts +0 -243
  104. package/tests/schema-converter.test.ts +0 -399
@@ -1,4 +1,4 @@
1
1
 
2
- > @opensaas/stack-auth@0.39.2 build /home/runner/work/stack/stack/packages/auth
2
+ > @opensaas/stack-auth@0.40.0 build /home/runner/work/stack/stack/packages/auth
3
3
  > tsc
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,156 @@
1
1
  # @opensaas/stack-auth
2
2
 
3
+ ## 0.40.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1015](https://github.com/OpenSaasAU/stack/pull/1015) [`72c4ba3`](https://github.com/OpenSaasAU/stack/commit/72c4ba30e9f53762988a822bb7ead7eae0db270c) Thanks [@borisno2](https://github.com/borisno2)! - Bump the `better-auth` dev dependency to `1.7.1` and move the peer range off the stale `^1.3.29` floor to `^1.4.0` (the release line where better-auth's `index: true` flags — the basis for [#937](https://github.com/OpenSaasAU/stack/issues/937)'s index emission — first shipped; below it the generated schema silently omitted indexes better-auth itself declares).
8
+
9
+ **New required `account.issuer` column.** better-auth 1.7 adds a required `issuer` column to its `account` model. Because `deriveAuthLists` derives the Auth lists from better-auth's own `getAuthTables()` ([#987](https://github.com/OpenSaasAU/stack/issues/987)/[#997](https://github.com/OpenSaasAU/stack/issues/997)), this column now appears in the generated schema automatically — no code change was needed, only verification against real generated output. Existing projects upgrading to `better-auth@^1.7` will see a **new NOT NULL column** on their `account` table and need a backfill for existing rows. Following better-auth's own `createLocalAccountIssuer`/`createOAuthAccountIssuer` helpers (`@better-auth/core/db`):
10
+
11
+ ```sql
12
+ -- PostgreSQL / SQLite (|| is string concatenation on both)
13
+ UPDATE "Account" SET issuer = 'local:' || "providerId" WHERE issuer IS NULL AND "providerId" = 'credential';
14
+ UPDATE "Account" SET issuer = 'local:oauth:' || "providerId" WHERE issuer IS NULL AND "providerId" != 'credential';
15
+
16
+ -- MySQL (|| is logical OR by default, NOT concatenation — use CONCAT instead)
17
+ UPDATE `Account` SET issuer = CONCAT('local:', providerId) WHERE issuer IS NULL AND providerId = 'credential';
18
+ UPDATE `Account` SET issuer = CONCAT('local:oauth:', providerId) WHERE issuer IS NULL AND providerId != 'credential';
19
+ ```
20
+
21
+ `"Account"`/`` `Account` `` above is the stack's own greenfield default table name — substitute your project's actual (and, on Postgres, schema-qualified) table name if you renamed it via `authPlugin({ account: { tableName } })` or adopted an existing install with `adoptBetterAuthTables({ useBetterAuthTableNames: true })` (physical table `account`, commonly under a non-`public` schema).
22
+
23
+ URL-encode `providerId` if it can contain characters outside `[A-Za-z0-9_-]`. If you configured a custom OIDC provider with its own issuer URL, use that provider's real issuer instead of the synthetic `local:oauth:` value.
24
+
25
+ **The new `@@unique([issuer, accountId])` constraint is not yet emitted.** better-auth 1.7 also declares this composite unique index at the table level, but the stack only derives _field_-level `unique`/`index` flags today — table-level index derivation is [#985](https://github.com/OpenSaasAU/stack/issues/985), which hasn't landed. This is deliberately out of scope here (per [#986](https://github.com/OpenSaasAU/stack/issues/986)'s own triage note: build on [#985](https://github.com/OpenSaasAU/stack/issues/985) once it lands, don't duplicate it). When it does land, make sure your backfilled `issuer` values don't collide on `(issuer, accountId)` for any account, or the constraint will fail to apply.
26
+
27
+ **Breaking (MCP plugin users only): `@better-auth/mcp` is now a separate package.** better-auth 1.7 split the `mcp` plugin out of `better-auth/plugins` into its own package, rebuilt on the OAuth Provider RFC 8707/9728 resource model. `@opensaas/stack-auth/plugins` now re-exports `mcp` from `@better-auth/mcp` (added as an optional peer — install it if you use MCP). The plugin also now **requires** a `resource` option:
28
+
29
+ ```typescript
30
+ import { mcp } from '@opensaas/stack-auth/plugins'
31
+ import { jwt } from 'better-auth/plugins'
32
+
33
+ authPlugin({
34
+ betterAuthPlugins: [
35
+ // The OAuth Provider mcp() is built on issues JWT-based access tokens
36
+ // and requires better-auth's own jwt() plugin registered alongside it —
37
+ // omitting it throws `BetterAuthError: jwt_config` at init.
38
+ jwt(),
39
+ mcp({
40
+ loginPage: '/sign-in',
41
+ // The page where a user approves/denies an MCP client's requested
42
+ // scopes — also required as of better-auth 1.7.
43
+ consentPage: '/consent',
44
+ // RFC 8707/9728 canonical resource identifier — required as of
45
+ // better-auth 1.7. Must match your `mcp.basePath`. HTTP is only
46
+ // accepted on loopback hosts.
47
+ resource: `${process.env.BETTER_AUTH_URL}/api/mcp`,
48
+ }),
49
+ ],
50
+ })
51
+ ```
52
+
53
+ better-auth 1.7's MCP plugin also declares a substantially different OAuth table set — the old `oauthApplication`/`oauthAccessToken`/`oauthConsent` three became seven tables (`oauthClient`/`oauthAccessToken`/`oauthConsent`/`oauthRefreshToken`/`oauthResource`/`oauthClientResource`/`oauthClientAssertion`). Since the derivation is schema-driven this needed no code changes, but if you have the MCP plugin enabled, running `pnpm generate` will produce a significantly different Prisma schema for these tables (new/renamed models, and `Session` gains reverse relations to the two token tables that now reference it). Review the diff and migrate your database accordingly.
54
+
55
+ **Workspace-wide: pins `@better-auth/utils` to `0.5.0` via a root `pnpm.overrides`.** better-auth 1.7.1's own published packages disagree on this transitive dependency — `better-auth` pins it at exactly `0.4.2` while `better-call` (used by `@better-auth/core`, `@better-auth/oauth-provider`, and `@better-auth/mcp`) requires `^0.5.0` — so pnpm resolves two separate physical instances of `@better-auth/core` depending on which peer chain a given package sits in. That split is invisible at runtime but breaks TypeScript: `jwt()` (from `better-auth/plugins`) and `mcp()` (from `@better-auth/mcp`) end up typed against different `@better-auth/core` instances, so `betterAuthPlugins: [jwt(), mcp(...)]` fails to type-check with a `BetterAuthPlugin` structural-mismatch error even though both plugins are otherwise correctly configured. The override forces one instance workspace-wide. If you hit the same error in your own app, add the equivalent override to your own `package.json`.
56
+
57
+ Also fixes two `deriveAuthLists` gaps surfaced by the MCP plugin's expanded schema:
58
+
59
+ - the scalar-field builder now threads a static `defaultValue` through for `number`-typed fields (`integer()`/`bigInt()`), matching the existing `string`/`boolean` behavior (e.g. `oauthResource.policyVersion`, which defaults to `1`)
60
+ - a plugin table that declares only one of `createdAt`/`updatedAt` upstream (several of the new OAuth tables declare `createdAt` alone) is no longer silently dropped — it derives as an ordinary required column instead. Previously any model with an asymmetric timestamp pair had the field skipped entirely with no replacement, which crashed the first real write that supplied it (better-auth's own OAuth Provider does this at `betterAuth()` init time, seeding an `oauthResource` row)
61
+
62
+ - [#1019](https://github.com/OpenSaasAU/stack/pull/1019) [`77b7314`](https://github.com/OpenSaasAU/stack/commit/77b731452f24045995a1d3a2ffede0246d5743d3) Thanks [@borisno2](https://github.com/borisno2)! - Extend the ADR-0036 credential field read-deny from the four base Auth models to better-auth plugin tables, and add a `credentialFields` config option for plugins the stack doesn't seed a set for.
63
+
64
+ The following fields now ship field-level `read`-denied, on top of the existing `Session.token`/`Verification.value`/`Account.password`/`accessToken`/`refreshToken`/`idToken`:
65
+
66
+ - `oauthClient.clientSecret`, `oauthAccessToken.token`, `oauthRefreshToken.token` (the `mcp`/oauth-provider plugin)
67
+ - `twoFactor.secret`, `twoFactor.backupCodes` (`twoFactor()`)
68
+
69
+ An application opening one of these lists (e.g. declaring `OauthClient` under its own `lists` to grant access) no longer also exposes the credential column — same behavior as the existing base-model deny, `sudo()` still reads it.
70
+
71
+ For a plugin the stack has no seeded credential set for, mark a field yourself:
72
+
73
+ ```typescript
74
+ authPlugin({
75
+ betterAuthPlugins: [passkey()],
76
+ credentialFields: { passkey: ['publicKey'] },
77
+ })
78
+ ```
79
+
80
+ `credentialFields` is additive only — it can add fields to any model (including a seeded one) but can never unmark a seeded field. An entry naming a field missing from a model your app actually derives throws at config time; an entry for a model your app doesn't derive is a no-op.
81
+
82
+ - [#1017](https://github.com/OpenSaasAU/stack/pull/1017) [`b30fa61`](https://github.com/OpenSaasAU/stack/commit/b30fa6135a6acca8c9be99fbdf5ffa7faab1959f) Thanks [@{](https://github.com/{)! - Let an application declare model-level indexes (`db.indexes`) on the derived auth lists (`User`/`Session`/`Account`/`Verification`/`RateLimit`).
83
+
84
+ Each per-model block in `authPlugin()` now accepts `indexes`, in the same shape as a list's own `db.indexes`:
85
+
86
+ ```typescript
87
+ authPlugin({
88
+ // Adopt a live constraint's real name instead of Prisma's derived one.
89
+ indexes: [{ fields: ['email'], unique: true, name: 'user_email_key' }] },
90
+ session: { indexes: [{ fields: ['token'], unique: true, name: 'session_token_key' }] },
91
+ // Extend a derived column into a composite index.
92
+ verification: {
93
+ indexes: [{ fields: ['identifier', { field: 'createdAt', sort: 'desc' }] }],
94
+ },
95
+ })
96
+ ```
97
+
98
+ An entry covering a column the stack already derives an index for (e.g. `User.email`) suppresses that derived index for that column and emits only the app's entry, rather than erroring — the application's declaration wins (ADR-0035). Suppression is per-column: every other derived index on the model is unaffected.
99
+
100
+ This also fixes a related generator gap: a list's `db.indexes` can now reference `createdAt`/`updatedAt` even when the list has no explicit field for them and relies on `db.timestamps` for the auto-injected columns (previously only a list with an explicitly declared `createdAt`/`updatedAt` field could be indexed on it).
101
+
102
+ - [#1005](https://github.com/OpenSaasAU/stack/pull/1005) [`b67fdd1`](https://github.com/OpenSaasAU/stack/commit/b67fdd10d678f9fd209259b063186db9f9aaf20a) Thanks [@borisno2](https://github.com/borisno2)! - Better-auth plugin tables (e.g. the MCP plugin's `oauthApplication`/`oauthAccessToken`/`oauthConsent`) are now derived through the same registry as the four base Auth models, instead of a separate converter that dropped every reference to a bare column. Reference fields now become real `relationship()` foreign keys with the correct `onDelete` cascade, index, uniqueness and nullability, closing a data-integrity defect where deleting a user left their OAuth rows orphaned (neither the database nor better-auth's own `deleteUser` cleaned them up). Plugin-table scalar fields now also honour `fieldName` column maps and `index: true`, and list keys are PascalCased with `db.map` restoring the original physical table name. A reference whose target field isn't the target's `id` (e.g. `oauthAccessToken.clientId` → `oauthApplication.clientId`) is left as a plain scalar column, since `relationship()` only supports `id`-based foreign keys.
103
+
104
+ No config changes are required — `authPlugin()`/`getAuthLists()` are unchanged. If your app already had an MCP-enabled config generated with an older version, regenerate and diff your schema: the OAuth tables' `userId` columns gain a foreign key, cascade and index they didn't have before.
105
+
106
+ - [#1013](https://github.com/OpenSaasAU/stack/pull/1013) [`49687ea`](https://github.com/OpenSaasAU/stack/commit/49687eaf8ad80696d62e2616ba3dfef992985282) Thanks [@borisno2](https://github.com/borisno2)! - BREAKING (pre-1.0): The derived auth lists' credential-bearing fields now ship with a field-level `read` deny, so opening operation-level access to a list no longer exposes them:
107
+
108
+ - `Session.token`
109
+ - `Verification.value`
110
+ - `Account.password`
111
+ - `Account.accessToken`
112
+ - `Account.refreshToken`
113
+ - `Account.idToken`
114
+
115
+ A denied field is stripped from a returned row, not an error — a `context.db` read on an opened list still succeeds and returns every other field, including a `findUnique` lookup that selects the row **by** the denied field itself (e.g. `context.db.session.findUnique({ where: { token } })` still finds the session; the returned `token` comes back stripped). Naming a denied field in `findMany`'s (or `count`'s) `where`/`orderBy` is different: the existing predicate-time read-access check (`validateQueryFieldReadAccess`) throws a `ValidationError` there instead, the same as it already does for any other field-level `read` deny. `sudo()` bypasses both — it's the supported path for an application with a genuine need. Sign-in, session refresh, email verification, and password reset are unaffected — better-auth's own flows write through the raw Prisma adapter, bypassing access control entirely.
116
+
117
+ If your application opens one of these lists today and deliberately reads one of these fields through `context.db` — a returned row, a `findMany`/`count` predicate, or a `findUnique` selector — switch that access to `context.sudo().db...`. See ADR-0036.
118
+
119
+ ### Patch Changes
120
+
121
+ - [#1020](https://github.com/OpenSaasAU/stack/pull/1020) [`8e6707a`](https://github.com/OpenSaasAU/stack/commit/8e6707adcca9d7e062bc1747ec79a29082c09ef9) Thanks [@borisno2](https://github.com/borisno2)! - Read-denied credential fields (ADR-0036) now also declare `ui.listView.defaultColumn: false`, so they're curated out of the admin's default table columns instead of rendering as permanently empty columns.
122
+
123
+ - [#997](https://github.com/OpenSaasAU/stack/pull/997) [`ed6ffcd`](https://github.com/OpenSaasAU/stack/commit/ed6ffcd5cc2b471ea680f75f108596ee6b87d083) Thanks [@borisno2](https://github.com/borisno2)! - `deriveAuthLists` now derives the Auth lists (User/Session/Account/Verification/RateLimit) from better-auth's own `getAuthTables()` output instead of a hand-written transcription, closing the drift class behind [#935](https://github.com/OpenSaasAU/stack/issues/935)/[#937](https://github.com/OpenSaasAU/stack/issues/937)/[#921](https://github.com/OpenSaasAU/stack/issues/921)/[#986](https://github.com/OpenSaasAU/stack/issues/986). Generated schema output is unchanged for existing projects — no migration needed.
124
+
125
+ - [#972](https://github.com/OpenSaasAU/stack/pull/972) [`08c3787`](https://github.com/OpenSaasAU/stack/commit/08c3787a46ead83bbc6a3730dae4d89598fba1b2) Thanks [@borisno2](https://github.com/borisno2)! - Clean up comments in `packages/auth/src` per the CLAUDE.md Comments rule — removed restating/duplicated comments, kept public-API TSDoc and footgun/external-constraint warnings. No behavior changes.
126
+
127
+ - [#996](https://github.com/OpenSaasAU/stack/pull/996) [`cfd366c`](https://github.com/OpenSaasAU/stack/commit/cfd366ccb62c3a858a95d0df859984c08e3b3a5f) Thanks [@borisno2](https://github.com/borisno2)! - Add a test that compares the derived Auth lists against better-auth's own `getAuthTables()` definitions, failing the build on future upstream schema drift instead of relying on a human to notice.
128
+
129
+ - [#990](https://github.com/OpenSaasAU/stack/pull/990) [`37d7905`](https://github.com/OpenSaasAU/stack/commit/37d7905b9b5126e7d7826469af467775f4daab34) Thanks [@borisno2](https://github.com/borisno2)! - The derived `Session.user` / `Account.user` foreign keys are now indexed (`isIndexed: true`, was `false`), and `Verification.identifier` is now indexed too — matching the three indexes better-auth itself declares (`session_userId_idx`, `account_userId_idx`, `verification_identifier_idx`). `prisma migrate diff` against a real better-auth install no longer reads these as three dropped indexes, and `Session`/`Account` lookups by `userId` are no longer unindexed.
130
+
131
+ **Migration note:** existing projects will see a migration on their next `prisma migrate dev`/`db push` adding the three indexes.
132
+
133
+ - [#989](https://github.com/OpenSaasAU/stack/pull/989) [`9cc6f8d`](https://github.com/OpenSaasAU/stack/commit/9cc6f8dcb0ed2958c39da9e7648a7a462c10264a) Thanks [@borisno2](https://github.com/borisno2)! - Fix `Session.user`/`Account.user` foreign key generating a `user` physical column instead of `userId`, mismatching better-auth's own schema and breaking clean-diff adoption (ADR-0007). An explicit `fields: { userId: ... }` override is unaffected.
134
+
135
+ **Migration note:** existing greenfield projects need to rename the column on `Session` and `Account` (e.g. `ALTER TABLE "Session" RENAME COLUMN "user" TO "userId";` and the same for `Account`) to match the new generated schema.
136
+
137
+ - [#1002](https://github.com/OpenSaasAU/stack/pull/1002) [`48d2762`](https://github.com/OpenSaasAU/stack/commit/48d27626dfb636c481301116e46c826ef3156124) Thanks [@borisno2](https://github.com/borisno2)! - Fix admin UI URL round-trip for a list keyed with anything other than strict PascalCase (issue [#991](https://github.com/OpenSaasAU/stack/issues/991)). `getListKeyFromUrl` reconstructs a list key by string transformation, which is lossy for a non-PascalCase key — a real example is a better-auth plugin's derived list (e.g. `oauthApplication`, from the `mcp` plugin's OAuth tables). Such a list appeared in navigation but its own link resolved to a key that did not exist in `config.lists`, rendering "List not found".
138
+
139
+ `@opensaas/stack-core` adds `resolveListKeyFromUrl(urlSegment, listKeys)` alongside the existing `getListKeyFromUrl`, which is unchanged and still exported. The new resolver matches a URL segment against the config's actual list keys via `getUrlKey` — the same helper that builds the URL — instead of reconstructing one, so route lookup and URL generation cannot drift apart. It returns `undefined` for a segment matching no list (so callers keep rendering their existing "not found" state), and throws if two distinct list keys would produce the same URL segment.
140
+
141
+ ```typescript
142
+ import { resolveListKeyFromUrl } from '@opensaas/stack-core'
143
+
144
+ resolveListKeyFromUrl('oauth-application', Object.keys(config.lists)) // 'oauthApplication'
145
+ resolveListKeyFromUrl('does-not-exist', Object.keys(config.lists)) // undefined
146
+ ```
147
+
148
+ `@opensaas/stack-ui`'s `AdminUI` now uses `resolveListKeyFromUrl` for its route resolution, fixing the broken link for any such list.
149
+
150
+ `@opensaas/stack-auth`'s `convertBetterAuthSchema` now PascalCases a better-auth plugin's camelCase `modelName` when deriving a list key (`oauthApplication` → `OauthApplication`, `rateLimit` → `RateLimit`), matching the repo's PascalCase list-key convention and fixing the same round-trip bug at the source for these lists.
151
+
152
+ **Schema-affecting for `@opensaas/stack-auth` users with a better-auth plugin that declares extra tables** (e.g. `mcp`'s OAuth tables, or `rateLimit.storage: 'database'` with no `modelName` remap configured): the generated Prisma **model name** changes to match the new PascalCase list key. The physical **table name** does not change — the previous camelCase name is preserved via `db.map` (`@@map`) — so `prisma db push` / `prisma migrate dev` sees a model rename, not a table rename, and `context.db.oauthApplication` (the camelCase db accessor) keeps working unchanged. Regenerate (`pnpm generate`) and re-run your migration/push step after upgrading.
153
+
3
154
  ## 0.39.2
4
155
 
5
156
  ## 0.39.1
package/CLAUDE.md CHANGED
@@ -49,7 +49,16 @@ Pre-built forms (client components). Each takes **server action** props (not an
49
49
 
50
50
  ### Plugins (`src/plugins/index.ts`)
51
51
 
52
- - Better-auth MCP plugin for OAuth authentication with AI assistants
52
+ - `mcp` - re-exported from the optional `@better-auth/mcp` peer (better-auth
53
+ 1.7 split it out of `better-auth/plugins`) for OAuth authentication with AI
54
+ assistants. Its `resource` option (the RFC 8707/9728 canonical
55
+ protected-resource URL) and `consentPage` (where a user approves/denies an
56
+ MCP client's requested scopes) are both required as of that split, and it
57
+ now needs better-auth's own `jwt()` plugin (from `better-auth/plugins`,
58
+ unaffected by the `@better-auth/mcp` split) registered alongside it — the
59
+ OAuth Provider `mcp()` is built on issues JWT-based access tokens and
60
+ throws `BetterAuthError: jwt_config` at init without it. See the
61
+ `betterAuthPlugins` example in the root `CLAUDE.md`'s MCP section.
53
62
 
54
63
  ## Architecture Patterns
55
64
 
@@ -67,22 +76,65 @@ config({
67
76
 
68
77
  ### Deriving Auth lists from better-auth config
69
78
 
70
- The four Auth lists are **derived** from the better-auth model config the
71
- developer writes not hardcoded. The pure derivation lives in
72
- `src/config/derive-auth-lists.ts` (`deriveAuthLists`), which `getAuthLists`
73
- and the plugin's add-vs-extend logic consume:
74
-
75
- - per-model `modelName` list key (and Prisma model name)
76
- - per-model `tableName` table `@@map`, **independent of `modelName`**
77
- (defaults to `modelName` when it differs from the better-auth default,
78
- otherwise unset i.e. unchanged output when `tableName` isn't set)
79
- - per-model `fields` (better-auth field column) field-level `@map`
80
- - the `userId` column override → the `user` relationship foreign-key `@map`
79
+ The four Auth lists, the conditional `RateLimit` fifth, and every table a
80
+ better-auth plugin declares in its own `schema` (e.g. the `mcp` plugin's
81
+ `oauthClient`/`oauthAccessToken`/`oauthConsent`/`oauthRefreshToken`/
82
+ `oauthResource`/`oauthClientResource`/`oauthClientAssertion` better-auth
83
+ 1.7 split the plugin into `@better-auth/mcp` and rebuilt it on the OAuth
84
+ Provider RFC 8707/9728 resource model, a wider table set than the pre-1.7
85
+ `oauthApplication`/`oauthAccessToken`/`oauthConsent` three) are all **derived**
86
+ from better-auth's own resolved table definitions, not hand-transcribed. The
87
+ pure derivation lives in `src/config/derive-auth-lists.ts` (`deriveAuthLists`),
88
+ which `getAuthLists` and the plugin's `init` consume. It calls `getAuthTables`
89
+ (re-exported from `better-auth/db` the same function better-auth's own
90
+ Kysely migrator, schema generator, and adapter base use to build their
91
+ schemas), passing the app's `betterAuthPlugins` through as `options.plugins`
92
+ so a plugin's own schema (a standalone table, or a schema extension of a base
93
+ model like the `anonymous` plugin's `user.isAnonymous`) is already merged
94
+ into the result — and translates it into stack list configs, so the Auth
95
+ lists cannot silently drift from what better-auth itself declares (issue
96
+ #987, ADR-0033, ADR-0034):
97
+
98
+ - per-model `modelName`/`fields` (the developer's own `authPlugin({ user:
99
+ {...}, session: {...} })` config, already normalized) pass straight
100
+ through to `getAuthTables` as its own options object, so an override path
101
+ (`adoptBetterAuthTables`, a renamed model, a remapped column) is inherited
102
+ for free rather than re-implemented as a parallel normalization
103
+ - `modelName` → list key (and Prisma model name); `tableName` → table
104
+ `@@map`, **independent of `modelName`** (defaults to `modelName` when it
105
+ differs from the better-auth default, otherwise unset — i.e. unchanged
106
+ output when `tableName` isn't set)
107
+ - every scalar field's type, nullability, uniqueness, index, column map, and
108
+ static default value are read from better-auth's own field metadata (a
109
+ fixed type-mapping table: `string`→`text()`, `boolean`→`checkbox()`,
110
+ `date`→`timestamp()`, `number`→`integer()`/`bigInt()` depending on
111
+ better-auth's own `bigint` flag) — not hand-authored per field
112
+ - the `user` relationship foreign key maps to better-auth's own resolved
113
+ column name (`userId` by default; an explicit `fields.userId` override
114
+ takes precedence), and its index/uniqueness and `onDelete` action are read
115
+ from better-auth's own `index`/`unique`/`references.onDelete`, matching a
116
+ live better-auth database on all three dimensions (ADR-0007, ADR-0033)
81
117
  - relationship refs between the Auth lists follow the derived keys
82
- (e.g. `Session.user → AuthUser.sessions`)
83
-
84
- With no `modelName`/`tableName`/`fields` overrides the output is unchanged
85
- (`User`/`Session`/`Account`/`Verification`, original field shapes, no `@@map`).
118
+ (e.g. `Session.user → AuthUser.sessions`) — the reverse relation name
119
+ itself (`sessions`/`accounts`) has no source in better-auth's metadata
120
+ (its FK declaration is one-directional) and is derived by pluralizing the
121
+ child model's own key, with a documented override map in
122
+ `derive-auth-lists.ts` for a collision or bad pluralization
123
+ - a **plugin table**'s list key is PascalCased from better-auth's resolved
124
+ `modelName` (`oauthClient` → `OauthClient`), with `db.map` set
125
+ back to the original whenever the case changed; its scalar/FK fields go
126
+ through the exact same derivation as a base model's, including the reverse
127
+ relation onto whichever list it references (base or another plugin table).
128
+ A reference whose target field isn't the target's `id` (better-auth's own
129
+ oidc-provider schema does this — `oauthAccessToken.clientId` references
130
+ `oauthClient.clientId`, not its `id`) stays a plain scalar column,
131
+ since `relationship()` can only express an `id`-based FK. Plugin tables
132
+ ship closed like the base models, with no `access` passthrough at all —
133
+ see ADR-0034 and "Access control on Auth lists" below
134
+
135
+ With no `modelName`/`tableName`/`fields` overrides the base list/table shape
136
+ is otherwise unchanged (`User`/`Session`/`Account`/`Verification`, original
137
+ field shapes, no table `@@map`).
86
138
 
87
139
  ```typescript
88
140
  // Adopt an existing better-auth installation (Auth lists ≠ app User)
@@ -133,12 +185,78 @@ reason — "who is this session" must not depend on the application's User
133
185
  access policy. `sudo` is a plain second argument, not a method on `context`
134
186
  (`AccessContext`) itself — see `packages/core/CLAUDE.md`.
135
187
 
136
- `convertBetterAuthSchema`/`convertTableToList` (`src/server/schema-converter.ts`),
137
- which handle additional tables a better-auth plugin's own schema declares
138
- (e.g. OAuth client tables from the `mcp` plugin), also ship closed there is
139
- no `access` passthrough for them; an app that needs to grant access declares
140
- the list itself under the same derived key so the plugin's field-only extend
141
- path merges in (its own access then stands, same as any other `extendList`).
188
+ Better-auth plugin tables (e.g. OAuth client tables from the `mcp` plugin)
189
+ are derived by the same `deriveAuthLists` not a separate converter, since
190
+ ADR-0034 and also ship closed: there is no `access` passthrough for them;
191
+ an app that needs to grant access declares the list itself under the same
192
+ derived key so the plugin's field-only extend path merges in (its own access
193
+ then stands, same as any other `extendList`).
194
+
195
+ ### Credential fields are read-denied independent of operation access (ADR-0036)
196
+
197
+ Operation-level access is all-or-nothing at the list, not the column — so
198
+ the deny above (list-level) isn't the whole story for a credential-bearing
199
+ field: `deriveAuthLists` sets a field-level `access: { read: () => false }`
200
+ on each one unconditionally, independent of whatever `accessConfig` an app
201
+ supplies. Opening `query` on `Session` for a "your active sessions" screen no
202
+ longer also exposes `token` — the field is stripped from a returned row (the
203
+ ordinary field-access-denial behavior), the row itself still returns. This
204
+ holds even for a `findUnique` lookup that selects the row BY the denied
205
+ field (`context.db.session.findUnique({ where: { token } })` still finds
206
+ the session; `token` just comes back stripped) — `findUnique`'s `where` is
207
+ a unique selector, not a predicate the read-access check walks. Naming the
208
+ field in `findMany`'s (or `count`'s) `where`/`orderBy` instead takes the
209
+ predicate-time path (`validateQueryFieldReadAccess` in
210
+ `packages/core/src/access/query-validation.ts`) and throws a
211
+ `ValidationError` up front rather than stripping anything; `sudo()` is
212
+ required for that shape too, not only for reading the column back off a
213
+ row fetched another way.
214
+
215
+ This is not a closed list of six base-model fields — it also covers **plugin
216
+ table** credential fields the stack has first-class support for (ADR-0034),
217
+ since a plugin table derives through the same scalar-field derivation pass:
218
+
219
+ | Model (better-auth key) | Field(s) | Source |
220
+ | ----------------------- | ---------------------------------------------------- | ---------------------- |
221
+ | `session` | `token` | base |
222
+ | `verification` | `value` | base |
223
+ | `account` | `password`, `accessToken`, `refreshToken`, `idToken` | base |
224
+ | `oauthClient` | `clientSecret` | `mcp` / oauth-provider |
225
+ | `oauthAccessToken` | `token` | `mcp` / oauth-provider |
226
+ | `oauthRefreshToken` | `token` | `mcp` / oauth-provider |
227
+ | `twoFactor` | `secret`, `backupCodes` | `twoFactor()` |
228
+
229
+ The deny is applied in the scalar-field derivation loop
230
+ (`withCredentialAccess` in `derive-auth-lists.ts`), against a registry built
231
+ by `buildCredentialFieldRegistry` — the stack-seeded `CREDENTIAL_FIELDS` table
232
+ above merged with an app's `authPlugin({ credentialFields })` — keyed by
233
+ better-auth's own model/field key, not the app's list key or column `db.map`,
234
+ so it survives a `modelName` remap or a `fields` column override either way.
235
+ `credentialFields` (`Record<better-auth model key, field key[]>`) is how an
236
+ app marks a credential field on a better-auth plugin the stack doesn't seed a
237
+ set for; it is **additive only** — it can add fields to any model, including a
238
+ seeded one, but can never unmark a seeded field. An entry naming a field
239
+ absent from a model the app actually derives (that plugin is registered)
240
+ throws, naming the model and field; an entry for a model the app doesn't
241
+ derive at all is a silent no-op:
242
+
243
+ ```typescript
244
+ authPlugin({
245
+ betterAuthPlugins: [passkey()],
246
+ credentialFields: { passkey: ['publicKey'] },
247
+ })
248
+ ```
249
+
250
+ `sudo()` still reads every one of these fields either way, unaffected: this
251
+ is the supported path for a genuine need (an admin tool, or an app's own auth
252
+ code verifying a password hash via `HashedPassword.compare()`). See
253
+ `packages/core/CLAUDE.md`'s "Access Control Execution Flow" for how a
254
+ field-level `read` denial is enforced, and ADR-0036 for why the rule is about
255
+ whether reading the value confers a live credential, not, say,
256
+ `Account.providerId` or `Session.ipAddress` (identifying, not authenticating
257
+ — left open) — and it does not condition on better-auth's own storage mode
258
+ (`storeTokens`/`storeClientSecret`) or `returned: false` flag, which is
259
+ neither a reliable nor a complete signal (see ADR-0036).
142
260
 
143
261
  ### Schema placement (relocatable Auth lists)
144
262
 
@@ -18,21 +18,5 @@ import type { Session } from 'better-auth/types';
18
18
  export declare function createClient(options: {
19
19
  baseURL: string;
20
20
  }): ReturnType<typeof createAuthClient>;
21
- /**
22
- * Re-export useful types from better-auth
23
- */
24
21
  export type { Session };
25
- /**
26
- * Note: React hooks (useSession, etc.) are accessed from the client instance
27
- *
28
- * @example
29
- * ```typescript
30
- * import { authClient } from '@/lib/auth-client'
31
- *
32
- * function MyComponent() {
33
- * const { data: session } = authClient.useSession()
34
- * // ...
35
- * }
36
- * ```
37
- */
38
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAEhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAI9F;AAED;;GAEG;AACH,YAAY,EAAE,OAAO,EAAE,CAAA;AAEvB;;;;;;;;;;;;GAYG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAEhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAI9F;AAED,YAAY,EAAE,OAAO,EAAE,CAAA"}
@@ -1,49 +1,8 @@
1
1
  /**
2
- * "Adopt existing better-auth tables" recipe.
3
- *
4
- * A migrating project usually already has a working, hand-wired better-auth
5
- * installation: its tables are `AuthUser`/`AuthSession`/`AuthAccount`/
6
- * `AuthVerification`, mapped into a separate `auth` Postgres schema, and its
7
- * application `User` (`public.User`) is a *different* model. Reconstructing the
8
- * matching {@link AuthConfig} by hand — four `modelName`s plus a `schema` on each
9
- * model — is repetitive and easy to get wrong.
10
- *
11
- * {@link adoptBetterAuthTables} produces that {@link AuthConfig} fragment from a
12
- * couple of options so the migrator doesn't rebuild it from scratch. It only
13
- * sets the *adoption* knobs (per-model `modelName` + the plugin-level `schema`);
14
- * the developer composes it with the rest of their auth config (providers,
15
- * session fields, `extendUserList`, etc.):
16
- *
17
- * ```typescript
18
- * authPlugin({
19
- * ...adoptBetterAuthTables(),
20
- * emailAndPassword: { enabled: true },
21
- * sessionFields: ['userId', 'email', 'name'],
22
- * })
23
- * ```
24
- *
25
- * Combined with the keys/field derivation (`deriveAuthLists`) and schema
26
- * placement, the generated Auth lists reach **Schema parity** with the live
27
- * tables — they are modelled for runtime/types without producing a destructive
28
- * auth migration. The recipe never touches the application's own domain `User`:
29
- * its model names are `Auth`-prefixed by default and the plugin only ever
30
- * adds/extends its *derived* keys.
31
- *
32
- * The single most common adoption shape is a project that ran better-auth
33
- * *before* adding Stack: its live tables are still better-auth's own default
34
- * lowercase names (`user`/`session`/`account`/`verification`), even though the
35
- * derived list keys need the `Auth` prefix to avoid colliding with the app's
36
- * own domain `User`. Pass `useBetterAuthTableNames: true` to point every
37
- * model's physical table at that default while keeping the prefixed list keys
38
- * (or `tableNames` for an explicit per-model override):
39
- *
40
- * ```typescript
41
- * authPlugin({
42
- * ...adoptBetterAuthTables({ useBetterAuthTableNames: true }),
43
- * // AuthUser/AuthSession/AuthAccount/AuthVerification list keys,
44
- * // @@map("user")/@@map("session")/@@map("account")/@@map("verification")
45
- * })
46
- * ```
2
+ * "Adopt existing better-auth tables" recipe: produces the {@link AuthConfig}
3
+ * adoption fragment for a pre-existing better-auth install. See
4
+ * `packages/auth/CLAUDE.md` ("Adopting an existing better-auth install") for
5
+ * the full rationale and examples.
47
6
  */
48
7
  import type { AuthConfig } from './types.js';
49
8
  /**
@@ -165,10 +124,6 @@ export type AdoptBetterAuthTablesConfig = Pick<AuthConfig, 'schema' | 'user' | '
165
124
  *
166
125
  * Returns only the model/schema knobs needed to adopt the live tables; spread it
167
126
  * into {@link authPlugin} alongside your own auth config.
168
- *
169
- * @param options - Adoption conventions (schema, model-name prefix, column maps)
170
- * @returns An {@link AuthConfig} fragment with `schema` + per-model `modelName`
171
- * (and any field column maps) set to match the live tables
172
127
  */
173
128
  export declare function adoptBetterAuthTables(options?: AdoptBetterAuthTablesOptions): AdoptBetterAuthTablesConfig;
174
129
  //# sourceMappingURL=adopt-better-auth-tables.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adopt-better-auth-tables.d.ts","sourceRoot":"","sources":["../../src/config/adopt-better-auth-tables.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,YAAY,CAAA;AAE7D;;;;;;GAMG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAChC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAChC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACrC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACnC,CAAA;IAED;;;;;;;;;;;;;;OAcG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAA;IAEjC;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE;QACX,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;IAED;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAoBD;;;;;;GAMG;AACH,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,UAAU,EACV,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,cAAc,CAC3D,GAAG;IACF,SAAS,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,CAAA;CACpC,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,4BAAiC,GACzC,2BAA2B,CAqC7B"}
1
+ {"version":3,"file":"adopt-better-auth-tables.d.ts","sourceRoot":"","sources":["../../src/config/adopt-better-auth-tables.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,YAAY,CAAA;AAE7D;;;;;;GAMG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAChC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAChC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACrC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACnC,CAAA;IAED;;;;;;;;;;;;;;OAcG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAA;IAEjC;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE;QACX,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;IAED;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAkBD;;;;;;GAMG;AACH,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,UAAU,EACV,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,cAAc,CAC3D,GAAG;IACF,SAAS,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,CAAA;CACpC,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,4BAAiC,GACzC,2BAA2B,CAqC7B"}
@@ -1,51 +1,9 @@
1
1
  /**
2
- * "Adopt existing better-auth tables" recipe.
3
- *
4
- * A migrating project usually already has a working, hand-wired better-auth
5
- * installation: its tables are `AuthUser`/`AuthSession`/`AuthAccount`/
6
- * `AuthVerification`, mapped into a separate `auth` Postgres schema, and its
7
- * application `User` (`public.User`) is a *different* model. Reconstructing the
8
- * matching {@link AuthConfig} by hand — four `modelName`s plus a `schema` on each
9
- * model — is repetitive and easy to get wrong.
10
- *
11
- * {@link adoptBetterAuthTables} produces that {@link AuthConfig} fragment from a
12
- * couple of options so the migrator doesn't rebuild it from scratch. It only
13
- * sets the *adoption* knobs (per-model `modelName` + the plugin-level `schema`);
14
- * the developer composes it with the rest of their auth config (providers,
15
- * session fields, `extendUserList`, etc.):
16
- *
17
- * ```typescript
18
- * authPlugin({
19
- * ...adoptBetterAuthTables(),
20
- * emailAndPassword: { enabled: true },
21
- * sessionFields: ['userId', 'email', 'name'],
22
- * })
23
- * ```
24
- *
25
- * Combined with the keys/field derivation (`deriveAuthLists`) and schema
26
- * placement, the generated Auth lists reach **Schema parity** with the live
27
- * tables — they are modelled for runtime/types without producing a destructive
28
- * auth migration. The recipe never touches the application's own domain `User`:
29
- * its model names are `Auth`-prefixed by default and the plugin only ever
30
- * adds/extends its *derived* keys.
31
- *
32
- * The single most common adoption shape is a project that ran better-auth
33
- * *before* adding Stack: its live tables are still better-auth's own default
34
- * lowercase names (`user`/`session`/`account`/`verification`), even though the
35
- * derived list keys need the `Auth` prefix to avoid colliding with the app's
36
- * own domain `User`. Pass `useBetterAuthTableNames: true` to point every
37
- * model's physical table at that default while keeping the prefixed list keys
38
- * (or `tableNames` for an explicit per-model override):
39
- *
40
- * ```typescript
41
- * authPlugin({
42
- * ...adoptBetterAuthTables({ useBetterAuthTableNames: true }),
43
- * // AuthUser/AuthSession/AuthAccount/AuthVerification list keys,
44
- * // @@map("user")/@@map("session")/@@map("account")/@@map("verification")
45
- * })
46
- * ```
2
+ * "Adopt existing better-auth tables" recipe: produces the {@link AuthConfig}
3
+ * adoption fragment for a pre-existing better-auth install. See
4
+ * `packages/auth/CLAUDE.md` ("Adopting an existing better-auth install") for
5
+ * the full rationale and examples.
47
6
  */
48
- /** The better-auth models and their default (unprefixed) model names. */
49
7
  const MODEL_DEFAULT_NAMES = {
50
8
  user: 'User',
51
9
  session: 'Session',
@@ -53,7 +11,6 @@ const MODEL_DEFAULT_NAMES = {
53
11
  verification: 'Verification',
54
12
  rateLimit: 'RateLimit',
55
13
  };
56
- /** better-auth's own default lowercase table names, per model. */
57
14
  const BETTER_AUTH_DEFAULT_TABLE_NAMES = {
58
15
  user: 'user',
59
16
  session: 'session',
@@ -67,10 +24,6 @@ const BETTER_AUTH_DEFAULT_TABLE_NAMES = {
67
24
  *
68
25
  * Returns only the model/schema knobs needed to adopt the live tables; spread it
69
26
  * into {@link authPlugin} alongside your own auth config.
70
- *
71
- * @param options - Adoption conventions (schema, model-name prefix, column maps)
72
- * @returns An {@link AuthConfig} fragment with `schema` + per-model `modelName`
73
- * (and any field column maps) set to match the live tables
74
27
  */
75
28
  export function adoptBetterAuthTables(options = {}) {
76
29
  const { schema = 'auth', modelNamePrefix = 'Auth', fields = {}, useBetterAuthTableNames = false, tableNames = {}, rateLimit = false, } = options;
@@ -1 +1 @@
1
- {"version":3,"file":"adopt-better-auth-tables.js","sourceRoot":"","sources":["../../src/config/adopt-better-auth-tables.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAiHH,yEAAyE;AACzE,MAAM,mBAAmB,GAAG;IAC1B,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,cAAc;IAC5B,SAAS,EAAE,WAAW;CACd,CAAA;AAEV,kEAAkE;AAClE,MAAM,+BAA+B,GAAG;IACtC,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,cAAc;IAC5B,SAAS,EAAE,WAAW;CACd,CAAA;AAgBV;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAO,GAAiC,EAAE;IAE1C,MAAM,EACJ,MAAM,GAAG,MAAM,EACf,eAAe,GAAG,MAAM,EACxB,MAAM,GAAG,EAAE,EACX,uBAAuB,GAAG,KAAK,EAC/B,UAAU,GAAG,EAAE,EACf,SAAS,GAAG,KAAK,GAClB,GAAG,OAAO,CAAA;IAEX,MAAM,UAAU,GAAG,CAAC,KAAuC,EAAmB,EAAE;QAC9E,MAAM,MAAM,GAAoB;YAC9B,SAAS,EAAE,GAAG,eAAe,GAAG,mBAAmB,CAAC,KAAK,CAAC,EAAE;SAC7D,CAAA;QACD,MAAM,SAAS,GACb,UAAU,CAAC,KAAK,CAAC;YACjB,CAAC,uBAAuB,CAAC,CAAC,CAAC,+BAA+B,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;QAChF,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,CAAC,SAAS,GAAG,SAAS,CAAA;QAC9B,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjD,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAA;QAC1B,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;IAED,OAAO;QACL,MAAM;QACN,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC;QACxB,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC;QAC9B,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC;QAC9B,YAAY,EAAE,UAAU,CAAC,cAAc,CAAC;QACxC,GAAG,CAAC,SAAS;YACX,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAmB,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,EAAE,EAAE;YAC5F,CAAC,CAAC,EAAE,CAAC;KACR,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"adopt-better-auth-tables.js","sourceRoot":"","sources":["../../src/config/adopt-better-auth-tables.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiHH,MAAM,mBAAmB,GAAG;IAC1B,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,cAAc;IAC5B,SAAS,EAAE,WAAW;CACd,CAAA;AAEV,MAAM,+BAA+B,GAAG;IACtC,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,cAAc;IAC5B,SAAS,EAAE,WAAW;CACd,CAAA;AAgBV;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAO,GAAiC,EAAE;IAE1C,MAAM,EACJ,MAAM,GAAG,MAAM,EACf,eAAe,GAAG,MAAM,EACxB,MAAM,GAAG,EAAE,EACX,uBAAuB,GAAG,KAAK,EAC/B,UAAU,GAAG,EAAE,EACf,SAAS,GAAG,KAAK,GAClB,GAAG,OAAO,CAAA;IAEX,MAAM,UAAU,GAAG,CAAC,KAAuC,EAAmB,EAAE;QAC9E,MAAM,MAAM,GAAoB;YAC9B,SAAS,EAAE,GAAG,eAAe,GAAG,mBAAmB,CAAC,KAAK,CAAC,EAAE;SAC7D,CAAA;QACD,MAAM,SAAS,GACb,UAAU,CAAC,KAAK,CAAC;YACjB,CAAC,uBAAuB,CAAC,CAAC,CAAC,+BAA+B,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;QAChF,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,CAAC,SAAS,GAAG,SAAS,CAAA;QAC9B,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjD,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAA;QAC1B,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;IAED,OAAO;QACL,MAAM;QACN,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC;QACxB,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC;QAC9B,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC;QAC9B,YAAY,EAAE,UAAU,CAAC,cAAc,CAAC;QACxC,GAAG,CAAC,SAAS;YACX,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAmB,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,EAAE,EAAE;YAC5F,CAAC,CAAC,EAAE,CAAC;KACR,CAAA;AACH,CAAC"}