@opensaas/stack-auth 0.36.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +109 -0
- package/CLAUDE.md +104 -7
- package/README.md +18 -7
- package/dist/config/adopt-better-auth-tables.d.ts +47 -0
- package/dist/config/adopt-better-auth-tables.d.ts.map +1 -1
- package/dist/config/adopt-better-auth-tables.js +29 -1
- package/dist/config/adopt-better-auth-tables.js.map +1 -1
- package/dist/config/derive-auth-lists.d.ts +7 -5
- package/dist/config/derive-auth-lists.d.ts.map +1 -1
- package/dist/config/derive-auth-lists.js +33 -34
- package/dist/config/derive-auth-lists.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +41 -11
- package/dist/config/index.js.map +1 -1
- package/dist/config/plugin.d.ts.map +1 -1
- package/dist/config/plugin.js +39 -27
- package/dist/config/plugin.js.map +1 -1
- package/dist/config/types.d.ts +126 -24
- package/dist/config/types.d.ts.map +1 -1
- package/dist/server/index.d.ts +29 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +193 -66
- package/dist/server/index.js.map +1 -1
- package/package.json +3 -3
- package/src/config/adopt-better-auth-tables.ts +70 -1
- package/src/config/derive-auth-lists.ts +37 -38
- package/src/config/index.ts +47 -12
- package/src/config/plugin.ts +39 -27
- package/src/config/types.ts +127 -21
- package/src/server/index.ts +244 -80
- package/tests/adopt-better-auth-tables.test.ts +99 -0
- package/tests/config.test.ts +66 -8
- package/tests/derive-auth-lists.test.ts +79 -5
- package/tests/generated-fk-shape.test.ts +65 -0
- package/tests/plugin-derived-keys.test.ts +48 -0
- package/tests/server.test.ts +517 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/server/index.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
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
|
}
|
package/dist/server/index.js.map
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
69
|
-
"@opensaas/stack-core": "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 {
|
|
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
|
|
7
|
-
* OpenSaaS Auth lists (user/session/account/verification)
|
|
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`)
|
|
11
|
-
*
|
|
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
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
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 `
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
...(
|
|
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` —
|
|
114
|
-
* `onDelete: Cascade`,
|
|
115
|
-
* better-auth
|
|
116
|
-
*
|
|
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
|
|
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({
|
|
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
|
|
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
|
|
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({
|
|
252
|
+
expiresAt: timestamp({
|
|
253
|
+
db: { isNullable: false, ...fieldDb('expiresAt', f) },
|
|
254
|
+
}),
|
|
256
255
|
},
|
|
257
|
-
db: listDb(model
|
|
256
|
+
db: listDb(model),
|
|
258
257
|
access,
|
|
259
258
|
})
|
|
260
259
|
}
|