@opensaas/stack-auth 0.35.0 → 0.37.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 (38) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +111 -0
  3. package/CLAUDE.md +104 -7
  4. package/README.md +18 -7
  5. package/dist/config/adopt-better-auth-tables.d.ts +47 -0
  6. package/dist/config/adopt-better-auth-tables.d.ts.map +1 -1
  7. package/dist/config/adopt-better-auth-tables.js +29 -1
  8. package/dist/config/adopt-better-auth-tables.js.map +1 -1
  9. package/dist/config/derive-auth-lists.d.ts +7 -5
  10. package/dist/config/derive-auth-lists.d.ts.map +1 -1
  11. package/dist/config/derive-auth-lists.js +33 -34
  12. package/dist/config/derive-auth-lists.js.map +1 -1
  13. package/dist/config/index.d.ts.map +1 -1
  14. package/dist/config/index.js +41 -11
  15. package/dist/config/index.js.map +1 -1
  16. package/dist/config/plugin.d.ts.map +1 -1
  17. package/dist/config/plugin.js +39 -27
  18. package/dist/config/plugin.js.map +1 -1
  19. package/dist/config/types.d.ts +126 -24
  20. package/dist/config/types.d.ts.map +1 -1
  21. package/dist/server/index.d.ts +29 -3
  22. package/dist/server/index.d.ts.map +1 -1
  23. package/dist/server/index.js +193 -66
  24. package/dist/server/index.js.map +1 -1
  25. package/package.json +3 -3
  26. package/src/config/adopt-better-auth-tables.ts +70 -1
  27. package/src/config/derive-auth-lists.ts +37 -38
  28. package/src/config/index.ts +47 -12
  29. package/src/config/plugin.ts +39 -27
  30. package/src/config/types.ts +127 -21
  31. package/src/server/index.ts +244 -80
  32. package/tests/adopt-better-auth-tables.test.ts +99 -0
  33. package/tests/config.test.ts +66 -8
  34. package/tests/derive-auth-lists.test.ts +79 -5
  35. package/tests/generated-fk-shape.test.ts +65 -0
  36. package/tests/plugin-derived-keys.test.ts +48 -0
  37. package/tests/server.test.ts +517 -0
  38. package/tsconfig.tsbuildinfo +1 -1
@@ -24,6 +24,189 @@ function toBetterAuthModelOptions(model) {
24
24
  options.fields = model.fields;
25
25
  return Object.keys(options).length > 0 ? options : undefined;
26
26
  }
27
+ const MODELS_WITH_NO_ADDITIONAL_FIELDS_PASSTHROUGH = [
28
+ 'user',
29
+ 'session',
30
+ 'account',
31
+ 'verification',
32
+ ];
33
+ /**
34
+ * Reject `betterAuthOptions` keys that already have a dedicated, non-passthrough
35
+ * seam — accepting them here would create two unranked ways to set the same
36
+ * thing, or (for `additionalFields`) silently diverge from the generated
37
+ * Prisma schema. See the `betterAuthOptions` doc comment on `AuthConfig`.
38
+ */
39
+ function assertNoUnsupportedPassthroughKeys(betterAuthOptions) {
40
+ if ('database' in betterAuthOptions) {
41
+ throw new Error('[@opensaas/stack-auth] `betterAuthOptions.database` is not supported — the stack ' +
42
+ 'derives the database adapter from your `db` config and the running context. ' +
43
+ 'Configure the database through `db` in `opensaas.config.ts` instead.');
44
+ }
45
+ if ('plugins' in betterAuthOptions) {
46
+ throw new Error('[@opensaas/stack-auth] `betterAuthOptions.plugins` is not supported — better-auth ' +
47
+ 'plugins are added through `authPlugin({ betterAuthPlugins: [...] })`, which the stack ' +
48
+ 'appends `nextCookies()` after. Use `betterAuthPlugins` instead.');
49
+ }
50
+ for (const model of MODELS_WITH_NO_ADDITIONAL_FIELDS_PASSTHROUGH) {
51
+ const modelOptions = betterAuthOptions[model];
52
+ if (modelOptions &&
53
+ typeof modelOptions === 'object' &&
54
+ !Array.isArray(modelOptions) &&
55
+ 'additionalFields' in modelOptions) {
56
+ throw new Error(`[@opensaas/stack-auth] \`betterAuthOptions.${model}.additionalFields\` is not ` +
57
+ 'supported — it adds columns that would not be reflected in the generated Prisma ' +
58
+ 'schema. Add fields to the derived list instead: ' +
59
+ (model === 'user'
60
+ ? '`extendUserList`, or declare the list yourself in your own `lists` config.'
61
+ : 'declare the derived list yourself in your own `lists` config (the auth plugin ' +
62
+ 'merges in field additions for the models it derives).'));
63
+ }
64
+ }
65
+ }
66
+ function isPlainObject(value) {
67
+ return (typeof value === 'object' &&
68
+ value !== null &&
69
+ !Array.isArray(value) &&
70
+ Object.getPrototypeOf(value) === Object.prototype);
71
+ }
72
+ /**
73
+ * Deep-merge `overrides` onto `base`, recursing into plain-object values so a
74
+ * nested addition (one database hook, one session sub-option) merges
75
+ * alongside sibling keys the stack already set there rather than replacing
76
+ * the whole branch. Arrays and any other value type replace outright.
77
+ * `overrides` wins on every key collision.
78
+ */
79
+ function mergeBetterAuthOptions(base, overrides) {
80
+ const result = { ...base };
81
+ for (const [key, value] of Object.entries(overrides)) {
82
+ const baseValue = result[key];
83
+ result[key] =
84
+ isPlainObject(baseValue) && isPlainObject(value)
85
+ ? mergeBetterAuthOptions(baseValue, value)
86
+ : value;
87
+ }
88
+ return result;
89
+ }
90
+ /**
91
+ * Build the `BetterAuthOptions` a better-auth instance for this OpenSaas
92
+ * config should be constructed with — the same options `createAuth()` uses
93
+ * internally, available standalone for an app that still needs to hand-wire
94
+ * its own `betterAuth()` instance (e.g. a third-party contract that requires
95
+ * a resolved instance at module-init time). Keeps the auth plugin
96
+ * authoritative for everything it models; the app's additions on top become
97
+ * an explicit, reviewable diff instead of a parallel, hand-duplicated config.
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * import { betterAuth } from 'better-auth'
102
+ * import { buildBetterAuthOptions } from '@opensaas/stack-auth/server'
103
+ *
104
+ * export const auth = betterAuth({
105
+ * ...(await buildBetterAuthOptions(config, context)),
106
+ * databaseHooks: { user: { create: { after: syncDomainUser } } },
107
+ * })
108
+ * ```
109
+ */
110
+ export async function buildBetterAuthOptions(opensaasConfig, context) {
111
+ const resolvedConfig = await Promise.resolve(opensaasConfig);
112
+ const resolvedContext = await Promise.resolve(context);
113
+ // Extract auth config from plugin data
114
+ const authConfig = resolvedConfig._pluginData?.auth;
115
+ if (!authConfig) {
116
+ throw new Error('Auth config not found. Make sure to use authPlugin() in your opensaas.config.ts');
117
+ }
118
+ // `requireConfirmation` has no better-auth equivalent — it's a UI-only
119
+ // concern the pre-built forms already take as their own
120
+ // `requirePasswordConfirmation` prop. Warn rather than silently drop it,
121
+ // since setting it here looks like it should do something.
122
+ if (authConfig.emailAndPassword.enabled &&
123
+ authConfig.emailAndPassword.requireConfirmation !== true) {
124
+ console.warn('[@opensaas/stack-auth] `emailAndPassword.requireConfirmation` has no effect here — ' +
125
+ 'createAuth() has no better-auth option to forward it to. Pass ' +
126
+ '`requirePasswordConfirmation` directly to <SignUpForm> / <ResetPasswordForm> instead.');
127
+ }
128
+ // `passwordReset` is wired through better-auth's `emailAndPassword` config
129
+ // (there's no password to reset without a password-based account), so it
130
+ // silently has no effect if email/password auth itself isn't enabled.
131
+ if (authConfig.passwordReset.enabled && !authConfig.emailAndPassword.enabled) {
132
+ console.warn('[@opensaas/stack-auth] `passwordReset.enabled` has no effect here — ' +
133
+ '`emailAndPassword.enabled` is false, so there is no password-based account to reset.');
134
+ }
135
+ assertNoUnsupportedPassthroughKeys(authConfig.betterAuthOptions);
136
+ // Build better-auth configuration
137
+ const betterAuthConfig = {
138
+ database: getDatabaseConfig(resolvedConfig.db, resolvedContext),
139
+ // Mirror the per-model config (modelName + field column maps) back to
140
+ // better-auth so the running auth instance reads/writes the same
141
+ // tables/columns the OpenSaaS Auth lists were derived from.
142
+ user: toBetterAuthModelOptions(authConfig.models.user),
143
+ session: {
144
+ ...toBetterAuthModelOptions(authConfig.models.session),
145
+ expiresIn: authConfig.session.expiresIn || 604800,
146
+ // better-auth treats `updateAge: 0` as "refresh on every request", not
147
+ // "never refresh" — disabling refresh entirely requires its separate
148
+ // `disableSessionRefresh` flag regardless of `updateAge`.
149
+ ...(authConfig.session.updateAge === false
150
+ ? { disableSessionRefresh: true }
151
+ : { updateAge: authConfig.session.updateAge }),
152
+ },
153
+ account: toBetterAuthModelOptions(authConfig.models.account),
154
+ verification: toBetterAuthModelOptions(authConfig.models.verification),
155
+ // Enable email and password if configured
156
+ emailAndPassword: authConfig.emailAndPassword.enabled
157
+ ? {
158
+ enabled: true,
159
+ requireEmailVerification: authConfig.emailVerification.enabled,
160
+ minPasswordLength: authConfig.emailAndPassword.minPasswordLength,
161
+ ...(authConfig.passwordReset.enabled
162
+ ? {
163
+ sendResetPassword: authConfig.emailAndPassword.sendResetPassword,
164
+ resetPasswordTokenExpiresIn: authConfig.passwordReset.tokenExpiration,
165
+ }
166
+ : {}),
167
+ }
168
+ : undefined,
169
+ // Email verification (independent of emailAndPassword — also covers
170
+ // e.g. a social-provider account whose email isn't yet verified)
171
+ emailVerification: authConfig.emailVerification.enabled
172
+ ? {
173
+ sendVerificationEmail: authConfig.emailVerification.sendVerificationEmail,
174
+ sendOnSignUp: authConfig.emailVerification.sendOnSignUp,
175
+ expiresIn: authConfig.emailVerification.tokenExpiration,
176
+ }
177
+ : undefined,
178
+ // Trust host (required for production)
179
+ trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS?.split(',') || [],
180
+ // Social providers
181
+ socialProviders: Object.entries(authConfig.socialProviders)
182
+ .filter(([_, config]) => config?.enabled !== false)
183
+ .reduce((acc, [provider, config]) => {
184
+ if (config) {
185
+ acc[provider] = {
186
+ clientId: config.clientId,
187
+ clientSecret: config.clientSecret,
188
+ };
189
+ }
190
+ return acc;
191
+ }, {}),
192
+ // Rate limiting configuration
193
+ rateLimit: authConfig.rateLimit
194
+ ? {
195
+ enabled: authConfig.rateLimit.enabled,
196
+ window: authConfig.rateLimit.window,
197
+ max: authConfig.rateLimit.max,
198
+ }
199
+ : undefined,
200
+ // Pass through any additional Better Auth plugins, then append
201
+ // nextCookies LAST so it can write the Set-Cookie headers produced by
202
+ // any auth.api.* call made inside a Next.js server action into Next's
203
+ // cookie store. This is what makes the server-action auth forms (which
204
+ // call auth.api.signInEmail/signUpEmail/etc. server-side) actually
205
+ // persist a session. It must be the final plugin in the array.
206
+ plugins: [...(authConfig.betterAuthPlugins || []), nextCookies()],
207
+ };
208
+ return mergeBetterAuthOptions(betterAuthConfig, authConfig.betterAuthOptions);
209
+ }
27
210
  /**
28
211
  * Create a better-auth instance from OpenSaas config
29
212
  * This should be called once at app startup
@@ -50,66 +233,7 @@ export function createAuth(opensaasConfig, context) {
50
233
  return authInstance;
51
234
  if (!authPromise) {
52
235
  authPromise = (async () => {
53
- const resolvedConfig = await configPromise;
54
- const resolvedContext = await contextPromise;
55
- // Extract auth config from plugin data
56
- const authConfig = resolvedConfig._pluginData?.auth;
57
- if (!authConfig) {
58
- throw new Error('Auth config not found. Make sure to use authPlugin() in your opensaas.config.ts');
59
- }
60
- // Build better-auth configuration
61
- const betterAuthConfig = {
62
- database: getDatabaseConfig(resolvedConfig.db, resolvedContext),
63
- // Mirror the per-model config (modelName + field column maps) back to
64
- // better-auth so the running auth instance reads/writes the same
65
- // tables/columns the OpenSaaS Auth lists were derived from.
66
- user: toBetterAuthModelOptions(authConfig.models.user),
67
- session: {
68
- ...toBetterAuthModelOptions(authConfig.models.session),
69
- expiresIn: authConfig.session.expiresIn || 604800,
70
- updateAge: authConfig.session.updateAge
71
- ? (authConfig.session.expiresIn || 604800) / 10
72
- : 0,
73
- },
74
- account: toBetterAuthModelOptions(authConfig.models.account),
75
- verification: toBetterAuthModelOptions(authConfig.models.verification),
76
- // Enable email and password if configured
77
- emailAndPassword: authConfig.emailAndPassword.enabled
78
- ? {
79
- enabled: true,
80
- requireEmailVerification: authConfig.emailVerification.enabled,
81
- }
82
- : undefined,
83
- // Trust host (required for production)
84
- trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS?.split(',') || [],
85
- // Social providers
86
- socialProviders: Object.entries(authConfig.socialProviders)
87
- .filter(([_, config]) => config?.enabled !== false)
88
- .reduce((acc, [provider, config]) => {
89
- if (config) {
90
- acc[provider] = {
91
- clientId: config.clientId,
92
- clientSecret: config.clientSecret,
93
- };
94
- }
95
- return acc;
96
- }, {}),
97
- // Rate limiting configuration
98
- rateLimit: authConfig.rateLimit
99
- ? {
100
- enabled: authConfig.rateLimit.enabled,
101
- window: authConfig.rateLimit.window,
102
- max: authConfig.rateLimit.max,
103
- }
104
- : undefined,
105
- // Pass through any additional Better Auth plugins, then append
106
- // nextCookies LAST so it can write the Set-Cookie headers produced by
107
- // any auth.api.* call made inside a Next.js server action into Next's
108
- // cookie store. This is what makes the server-action auth forms (which
109
- // call auth.api.signInEmail/signUpEmail/etc. server-side) actually
110
- // persist a session. It must be the final plugin in the array.
111
- plugins: [...(authConfig.betterAuthPlugins || []), nextCookies()],
112
- };
236
+ const betterAuthConfig = await buildBetterAuthOptions(configPromise, contextPromise);
113
237
  authInstance = betterAuth(betterAuthConfig);
114
238
  return authInstance;
115
239
  })();
@@ -158,14 +282,17 @@ export function createAuth(opensaasConfig, context) {
158
282
  });
159
283
  }
160
284
  /**
161
- * Get session from better-auth and transform it to OpenSaas session format
162
- * This is used internally by the generated context
285
+ * Get session from better-auth and transform it to OpenSaas session format.
286
+ *
287
+ * Not called by any generated code today — apps currently hand-roll this same
288
+ * transform against `auth.api.getSession({ headers: await headers() })` (see
289
+ * `examples/starter-auth/lib/auth.ts`). Exported as a reusable helper for that
290
+ * pattern; pass the caller's request headers (e.g. Next.js `await headers()`
291
+ * in a Server Component/action) so a session cookie can actually be resolved.
163
292
  */
164
- export async function getSessionFromAuth(auth, sessionFields) {
293
+ export async function getSessionFromAuth(auth, sessionFields, headers) {
165
294
  try {
166
- const session = await auth.api.getSession({
167
- headers: new Headers(),
168
- });
295
+ const session = await auth.api.getSession({ headers });
169
296
  if (!session?.user) {
170
297
  return null;
171
298
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAMjD;;GAEG;AACH,SAAS,iBAAiB,CACxB,QAAwB,EACxB,OAAsB;IAEtB,OAAO,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE;QACnC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;KAC5B,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,wBAAwB,CAC/B,KAAgC;IAEhC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;IACtD,MAAM,OAAO,GAA4D,EAAE,CAAA;IAC3E,IAAI,KAAK,CAAC,SAAS;QAAE,OAAO,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;IACxD,IAAI,SAAS;QAAE,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;IAC5C,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;AAC9D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,UAAU,CACxB,cAAwD,EACxD,OAA+C;IAE/C,4CAA4C;IAC5C,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;IACrD,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAE/C,0CAA0C;IAC1C,IAAI,YAAY,GAAyC,IAAI,CAAA;IAC7D,IAAI,WAAW,GAAkD,IAAI,CAAA;IAErE,KAAK,UAAU,eAAe;QAC5B,IAAI,YAAY;YAAE,OAAO,YAAY,CAAA;QAErC,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,WAAW,GAAG,CAAC,KAAK,IAAI,EAAE;gBACxB,MAAM,cAAc,GAAG,MAAM,aAAa,CAAA;gBAC1C,MAAM,eAAe,GAAG,MAAM,cAAc,CAAA;gBAE5C,uCAAuC;gBACvC,MAAM,UAAU,GAAG,cAAc,CAAC,WAAW,EAAE,IAAwC,CAAA;gBAEvF,IAAI,CAAC,UAAU,EAAE,CAAC;oBAChB,MAAM,IAAI,KAAK,CACb,iFAAiF,CAClF,CAAA;gBACH,CAAC;gBAED,kCAAkC;gBAClC,MAAM,gBAAgB,GAAsB;oBAC1C,QAAQ,EAAE,iBAAiB,CAAC,cAAc,CAAC,EAAE,EAAE,eAAe,CAAC;oBAE/D,sEAAsE;oBACtE,iEAAiE;oBACjE,4DAA4D;oBAC5D,IAAI,EAAE,wBAAwB,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC;oBACtD,OAAO,EAAE;wBACP,GAAG,wBAAwB,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC;wBACtD,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC,SAAS,IAAI,MAAM;wBACjD,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC,SAAS;4BACrC,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,GAAG,EAAE;4BAC/C,CAAC,CAAC,CAAC;qBACN;oBACD,OAAO,EAAE,wBAAwB,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC;oBAC5D,YAAY,EAAE,wBAAwB,CAAC,UAAU,CAAC,MAAM,CAAC,YAAY,CAAC;oBAEtE,0CAA0C;oBAC1C,gBAAgB,EAAE,UAAU,CAAC,gBAAgB,CAAC,OAAO;wBACnD,CAAC,CAAC;4BACE,OAAO,EAAE,IAAI;4BACb,wBAAwB,EAAE,UAAU,CAAC,iBAAiB,CAAC,OAAO;yBAC/D;wBACH,CAAC,CAAC,SAAS;oBAEb,uCAAuC;oBACvC,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE;oBAEzE,mBAAmB;oBACnB,eAAe,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC;yBACxD,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,KAAK,CAAC;yBAClD,MAAM,CACL,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE;wBAC1B,IAAI,MAAM,EAAE,CAAC;4BACX,GAAG,CAAC,QAAQ,CAAC,GAAG;gCACd,QAAQ,EAAE,MAAM,CAAC,QAAQ;gCACzB,YAAY,EAAE,MAAM,CAAC,YAAY;6BAClC,CAAA;wBACH,CAAC;wBACD,OAAO,GAAG,CAAA;oBACZ,CAAC,EACD,EAAgE,CACjE;oBAEH,8BAA8B;oBAC9B,SAAS,EAAE,UAAU,CAAC,SAAS;wBAC7B,CAAC,CAAC;4BACE,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC,OAAO;4BACrC,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,MAAM;4BACnC,GAAG,EAAE,UAAU,CAAC,SAAS,CAAC,GAAG;yBAC9B;wBACH,CAAC,CAAC,SAAS;oBAEb,+DAA+D;oBAC/D,sEAAsE;oBACtE,sEAAsE;oBACtE,uEAAuE;oBACvE,mEAAmE;oBACnE,+DAA+D;oBAC/D,OAAO,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,iBAAiB,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC;iBAClE,CAAA;gBAED,YAAY,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAA;gBAC3C,OAAO,YAAY,CAAA;YACrB,CAAC,CAAC,EAAE,CAAA;QACN,CAAC;QAED,OAAO,WAAW,CAAA;IACpB,CAAC;IAED,2DAA2D;IAC3D,OAAO,IAAI,KAAK,CAAC,EAAmC,EAAE;QACpD,GAAG,CAAC,CAAC,EAAE,IAAI;YACT,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;gBACpB,oCAAoC;gBACpC,OAAO,SAAS,CAAA;YAClB,CAAC;YAED,iCAAiC;YACjC,MAAM,WAAW,GAAG,KAAK,EAAE,GAAG,IAAe,EAAE,EAAE;gBAC/C,MAAM,QAAQ,GAAG,MAAM,eAAe,EAAE,CAAA;gBACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAA6B,CAAC,CAAA;gBACrD,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;oBAChC,OAAQ,KAAyC,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;gBACzE,CAAC;gBACD,OAAO,KAAK,CAAA;YACd,CAAC,CAAA;YAED,4EAA4E;YAC5E,OAAO,IAAI,KAAK,CAAC,WAAW,EAAE;gBAC5B,GAAG,CAAC,MAAM,EAAE,OAAO;oBACjB,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;wBACvB,qCAAqC;wBACrC,OAAO,SAAS,CAAA;oBAClB,CAAC;oBACD,4DAA4D;oBAC5D,OAAO,KAAK,EAAE,GAAG,IAAe,EAAE,EAAE;wBAClC,MAAM,QAAQ,GAAG,MAAM,eAAe,EAAE,CAAA;wBACxC,MAAM,WAAW,GAAG,QAAQ,CAAC,IAA6B,CAAC,CAAA;wBAC3D,IAAI,WAAW,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;4BACnD,MAAM,UAAU,GAAI,WAAuC,CAAC,OAAiB,CAAC,CAAA;4BAC9E,IAAI,OAAO,UAAU,KAAK,UAAU,EAAE,CAAC;gCACrC,OAAQ,UAA8C,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;4BACjF,CAAC;4BACD,OAAO,UAAU,CAAA;wBACnB,CAAC;wBACD,MAAM,IAAI,KAAK,CACb,YAAY,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,6BAA6B,CACzE,CAAA;oBACH,CAAC,CAAA;gBACH,CAAC;aACF,CAAC,CAAA;QACJ,CAAC;KACF,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAmC,EACnC,aAAuB;IAEvB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;YACxC,OAAO,EAAE,IAAI,OAAO,EAAE;SACvB,CAAC,CAAA;QAEF,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;YACnB,OAAO,IAAI,CAAA;QACb,CAAC;QAED,6CAA6C;QAC7C,MAAM,MAAM,GAA4B,EAAE,CAAA;QAE1C,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;YAClC,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACvB,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAA;YACjC,CAAC;iBAAM,IAAI,KAAK,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAkC,CAAC,CAAA;YAClE,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAA;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensaas/stack-auth",
3
- "version": "0.35.0",
3
+ "version": "0.37.0",
4
4
  "description": "Better-auth integration for OpenSaas Stack",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -65,8 +65,8 @@
65
65
  "react": "^19.2.4",
66
66
  "typescript": "npm:@typescript/typescript6@^6.0.2",
67
67
  "vitest": "^4.1.10",
68
- "@opensaas/stack-cli": "0.35.0",
69
- "@opensaas/stack-core": "0.35.0"
68
+ "@opensaas/stack-cli": "0.37.0",
69
+ "@opensaas/stack-core": "0.37.0"
70
70
  },
71
71
  "scripts": {
72
72
  "build": "tsc",
@@ -28,6 +28,22 @@
28
28
  * auth migration. The recipe never touches the application's own domain `User`:
29
29
  * its model names are `Auth`-prefixed by default and the plugin only ever
30
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
+ * ```
31
47
  */
32
48
 
33
49
  import type { AuthConfig, AuthModelConfig } from './types.js'
@@ -89,6 +105,39 @@ export type AdoptBetterAuthTablesOptions = {
89
105
  account?: Record<string, string>
90
106
  verification?: Record<string, string>
91
107
  }
108
+
109
+ /**
110
+ * Set every model's physical table name to better-auth's own default
111
+ * lowercase table name (`user`, `session`, `account`, `verification`) —
112
+ * independent of the prefixed list key/`modelName`.
113
+ *
114
+ * This is the single most common adoption shape: a project that ran
115
+ * better-auth before adding the stack has exactly these tables, and the
116
+ * default `modelNamePrefix: 'Auth'` alone would otherwise pin the table
117
+ * name to the prefixed model name (`AuthUser`, ...), which `prisma migrate
118
+ * diff` reads as a rename against the live `user` table.
119
+ *
120
+ * Ignored for a model with an explicit entry in {@link tableNames}.
121
+ *
122
+ * @default false
123
+ */
124
+ useBetterAuthTableNames?: boolean
125
+
126
+ /**
127
+ * Per-model explicit table name overrides, keyed by model. Takes
128
+ * precedence over `useBetterAuthTableNames` for that model.
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * adoptBetterAuthTables({ tableNames: { user: 'users' } })
133
+ * ```
134
+ */
135
+ tableNames?: {
136
+ user?: string
137
+ session?: string
138
+ account?: string
139
+ verification?: string
140
+ }
92
141
  }
93
142
 
94
143
  /** The four better-auth models and their default (unprefixed) model names. */
@@ -99,6 +148,14 @@ const MODEL_DEFAULT_NAMES = {
99
148
  verification: 'Verification',
100
149
  } as const
101
150
 
151
+ /** better-auth's own default lowercase table names, per model. */
152
+ const BETTER_AUTH_DEFAULT_TABLE_NAMES = {
153
+ user: 'user',
154
+ session: 'session',
155
+ account: 'account',
156
+ verification: 'verification',
157
+ } as const
158
+
102
159
  /**
103
160
  * The adoption-relevant slice of {@link AuthConfig}: the plugin-level `schema`
104
161
  * and the per-model `modelName`/`fields`. Returned (not the full `AuthConfig`)
@@ -123,12 +180,24 @@ export type AdoptBetterAuthTablesConfig = Pick<
123
180
  export function adoptBetterAuthTables(
124
181
  options: AdoptBetterAuthTablesOptions = {},
125
182
  ): AdoptBetterAuthTablesConfig {
126
- const { schema = 'auth', modelNamePrefix = 'Auth', fields = {} } = options
183
+ const {
184
+ schema = 'auth',
185
+ modelNamePrefix = 'Auth',
186
+ fields = {},
187
+ useBetterAuthTableNames = false,
188
+ tableNames = {},
189
+ } = options
127
190
 
128
191
  const buildModel = (model: keyof typeof MODEL_DEFAULT_NAMES): AuthModelConfig => {
129
192
  const config: AuthModelConfig = {
130
193
  modelName: `${modelNamePrefix}${MODEL_DEFAULT_NAMES[model]}`,
131
194
  }
195
+ const tableName =
196
+ tableNames[model] ??
197
+ (useBetterAuthTableNames ? BETTER_AUTH_DEFAULT_TABLE_NAMES[model] : undefined)
198
+ if (tableName !== undefined) {
199
+ config.tableName = tableName
200
+ }
132
201
  const fieldMap = fields[model]
133
202
  if (fieldMap && Object.keys(fieldMap).length > 0) {
134
203
  config.fields = fieldMap
@@ -2,13 +2,15 @@
2
2
  * Pure `better-auth config → Auth lists` derivation.
3
3
  *
4
4
  * This module is intentionally free of side effects and plugin/runtime
5
- * concerns: given the resolved better-auth model config (per-model `modelName`
6
- * and `fields` column maps) plus any custom User fields, it produces the four
7
- * OpenSaaS Auth lists (user/session/account/verification) with:
5
+ * concerns: given the resolved better-auth model config (per-model `modelName`,
6
+ * `tableName`, and `fields` column maps) plus any custom User fields, it
7
+ * produces the four OpenSaaS Auth lists (user/session/account/verification)
8
+ * with:
8
9
  *
9
10
  * - list keys taken from each model's `modelName`
10
- * - a table `@@map` (list-level `db.map`) when the key differs from the
11
- * default better-auth model name
11
+ * - a table `@@map` (list-level `db.map`) taken from each model's resolved
12
+ * `tableName` independent of `modelName`, so a renamed list key can still
13
+ * adopt a differently-named live table
12
14
  * - field-level `@map` (`db.map`) for any better-auth field → column override
13
15
  * - relationship refs between the auth lists wired to the *derived* keys
14
16
  * (e.g. `Session.user → AuthUser.sessions`)
@@ -31,17 +33,6 @@ import type { RelationshipField } from '@opensaas/stack-core/fields'
31
33
  import type { ExtendUserListConfig } from '../lists/index.js'
32
34
  import type { AuthAccessConfig, NormalizedAuthModelConfig, NormalizedAuthModels } from './types.js'
33
35
 
34
- /**
35
- * Default better-auth model names — used to decide whether a `@@map` is needed
36
- * (only when the configured `modelName` differs from the default).
37
- */
38
- const DEFAULT_MODEL_NAMES = {
39
- user: 'User',
40
- session: 'Session',
41
- account: 'Account',
42
- verification: 'Verification',
43
- } as const
44
-
45
36
  /**
46
37
  * The derived Auth list set together with the keys each list was placed under.
47
38
  * Keys are surfaced separately so callers (plugin add-vs-extend logic, runtime
@@ -71,24 +62,24 @@ export type DerivedAuthLists = {
71
62
  * Auth list must opt back in so the generated models keep those columns and
72
63
  * better-auth keeps working.
73
64
  *
74
- * When the developer renames the model (e.g. `modelName: 'AuthUser'`), we also
75
- * pin the physical table name to that model name via `@@map("AuthUser")` so the
76
- * generated list adopts the developer's live table exactly. When a `schema` is
77
- * configured (plugin-level or per-model), the list is placed in that Postgres
78
- * schema via `@@schema(...)`.
65
+ * The physical table name (`@@map`) comes from the model's resolved
66
+ * `tableName` independent of the list key/`modelName` so a renamed list
67
+ * key can still adopt a differently-named live table (e.g. better-auth's own
68
+ * default lowercase table names). When a `schema` is configured (plugin-level
69
+ * or per-model), the list is placed in that Postgres schema via `@@schema(...)`.
79
70
  *
80
- * With no `modelName`/`schema` overrides we emit only `timestamps: true`,
71
+ * With no `tableName`/`schema` overrides we emit only `timestamps: true`,
81
72
  * leaving the default `User`/`Session`/... table/schema output unchanged.
82
73
  */
83
- function listDb(
84
- model: NormalizedAuthModelConfig,
85
- defaultModelName: string,
86
- ): { timestamps: true; map?: string; schema?: string } {
87
- const map = model.modelName !== defaultModelName ? model.modelName : undefined
74
+ function listDb(model: NormalizedAuthModelConfig): {
75
+ timestamps: true
76
+ map?: string
77
+ schema?: string
78
+ } {
88
79
  const schema = model.schema
89
80
  return {
90
81
  timestamps: true,
91
- ...(map !== undefined ? { map } : {}),
82
+ ...(model.tableName !== undefined ? { map: model.tableName } : {}),
92
83
  ...(schema !== undefined ? { schema } : {}),
93
84
  }
94
85
  }
@@ -110,14 +101,18 @@ function fieldDb(fieldName: string, fields: Record<string, string>): { map: stri
110
101
  * Build the `db` config for a `user` relationship (`Session.user` /
111
102
  * `Account.user`), honouring a `userId` column override from the better-auth
112
103
  * `fields` map and mirroring better-auth's own FK shape: no separate FK index
113
- * — the index is applied at the field level via `isIndexed: false` — and
114
- * `onDelete: Cascade`, so a generated Auth schema diffs clean against a live
115
- * better-auth database on both dimensions instead of showing a spurious index
116
- * drop and a referential-action change (issue #679).
104
+ * — the index is applied at the field level via `isIndexed: false` —
105
+ * `onDelete: Cascade`, and a required (non-nullable) foreign key, since
106
+ * better-auth's adapter always writes a `userId` on every session/account row
107
+ * it creates. This means a generated Auth schema diffs clean against a live
108
+ * better-auth database on all three dimensions instead of showing a spurious
109
+ * index drop, a referential-action change, and a `DROP NOT NULL` (issues #679,
110
+ * #863).
117
111
  */
118
112
  function userRelationshipDb(fields: Record<string, string>): NonNullable<RelationshipField['db']> {
119
113
  const column = fields.userId
120
114
  return {
115
+ isNullable: false,
121
116
  ...(column ? { foreignKey: { map: column } } : {}),
122
117
  extendPrismaSchema: ({ fkLine, relationLine }) => ({
123
118
  fkLine,
@@ -161,7 +156,7 @@ function createUserList(
161
156
  // Custom fields from user config
162
157
  ...(userConfig.fields || {}),
163
158
  },
164
- db: listDb(model, DEFAULT_MODEL_NAMES.user),
159
+ db: listDb(model),
165
160
  access: userConfig.access || access,
166
161
  hooks: userConfig.hooks,
167
162
  })
@@ -187,7 +182,9 @@ function createSessionList(
187
182
  isIndexed: 'unique',
188
183
  db: fieldDb('token', f),
189
184
  }),
190
- expiresAt: timestamp({ db: fieldDb('expiresAt', f) }),
185
+ expiresAt: timestamp({
186
+ db: { isNullable: false, ...fieldDb('expiresAt', f) },
187
+ }),
191
188
  ipAddress: text({ db: fieldDb('ipAddress', f) }),
192
189
  userAgent: text({ db: fieldDb('userAgent', f) }),
193
190
  user: relationship({
@@ -196,7 +193,7 @@ function createSessionList(
196
193
  db: userRelationshipDb(f),
197
194
  }),
198
195
  },
199
- db: listDb(model, DEFAULT_MODEL_NAMES.session),
196
+ db: listDb(model),
200
197
  access,
201
198
  })
202
199
  }
@@ -231,7 +228,7 @@ function createAccountList(
231
228
  idToken: text({ db: fieldDb('idToken', f) }),
232
229
  password: text({ db: fieldDb('password', f) }),
233
230
  },
234
- db: listDb(model, DEFAULT_MODEL_NAMES.account),
231
+ db: listDb(model),
235
232
  access,
236
233
  })
237
234
  }
@@ -252,9 +249,11 @@ function createVerificationList(
252
249
  fields: {
253
250
  identifier: text({ validation: { isRequired: true }, db: fieldDb('identifier', f) }),
254
251
  value: text({ validation: { isRequired: true }, db: fieldDb('value', f) }),
255
- expiresAt: timestamp({ db: fieldDb('expiresAt', f) }),
252
+ expiresAt: timestamp({
253
+ db: { isNullable: false, ...fieldDb('expiresAt', f) },
254
+ }),
256
255
  },
257
- db: listDb(model, DEFAULT_MODEL_NAMES.verification),
256
+ db: listDb(model),
258
257
  access,
259
258
  })
260
259
  }