@opensaas/stack-auth 0.21.0 → 0.23.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 +92 -0
- package/CLAUDE.md +98 -0
- package/README.md +33 -0
- package/dist/config/adopt-better-auth-tables.d.ts +107 -0
- package/dist/config/adopt-better-auth-tables.d.ts.map +1 -0
- package/dist/config/adopt-better-auth-tables.js +70 -0
- package/dist/config/adopt-better-auth-tables.js.map +1 -0
- package/dist/config/derive-auth-lists.d.ts +50 -0
- package/dist/config/derive-auth-lists.d.ts.map +1 -0
- package/dist/config/derive-auth-lists.js +274 -0
- package/dist/config/derive-auth-lists.js.map +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +43 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/plugin.d.ts.map +1 -1
- package/dist/config/plugin.js +52 -9
- package/dist/config/plugin.js.map +1 -1
- package/dist/config/types.d.ts +130 -3
- package/dist/config/types.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/lists/index.d.ts +17 -11
- package/dist/lists/index.d.ts.map +1 -1
- package/dist/lists/index.js +34 -208
- package/dist/lists/index.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +28 -7
- package/dist/server/index.js.map +1 -1
- package/package.json +2 -2
- package/src/config/adopt-better-auth-tables.ts +146 -0
- package/src/config/derive-auth-lists.ts +323 -0
- package/src/config/index.ts +58 -0
- package/src/config/plugin.ts +66 -9
- package/src/config/types.ts +146 -3
- package/src/index.ts +13 -0
- package/src/lists/index.ts +42 -202
- package/src/server/index.ts +31 -9
- package/tests/adopt-better-auth-tables.test.ts +183 -0
- package/tests/derive-auth-lists.test.ts +232 -0
- package/tests/plugin-derived-keys.test.ts +138 -0
- package/tests/plugin-schema-placement.test.ts +121 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/config/types.ts
CHANGED
|
@@ -82,6 +82,55 @@ export type SessionConfig = {
|
|
|
82
82
|
updateAge?: boolean
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Per-model better-auth configuration block.
|
|
87
|
+
*
|
|
88
|
+
* Mirrors better-auth's own `BetterAuthDBOptions` (the `user`/`session`/
|
|
89
|
+
* `account`/`verification` config a developer already writes): `modelName`
|
|
90
|
+
* renames the table/list and `fields` maps individual better-auth field names
|
|
91
|
+
* to database column names. The auth plugin derives its Auth lists from this
|
|
92
|
+
* config so the generated lists carry the same keys and column maps as the
|
|
93
|
+
* developer's live better-auth tables.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* authPlugin({
|
|
98
|
+
* user: { modelName: 'AuthUser', fields: { name: 'full_name' } },
|
|
99
|
+
* session: { modelName: 'AuthSession' },
|
|
100
|
+
* })
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export type AuthModelConfig = {
|
|
104
|
+
/**
|
|
105
|
+
* The table/list name for this model.
|
|
106
|
+
* Becomes the OpenSaaS list key (and Prisma model name) and the table `@@map`.
|
|
107
|
+
* @default the default better-auth model name (e.g. 'User', 'Session')
|
|
108
|
+
*/
|
|
109
|
+
modelName?: string
|
|
110
|
+
/**
|
|
111
|
+
* Map better-auth field names to database column names.
|
|
112
|
+
* Each entry generates a `@map("column")` on the derived field.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* fields: { name: 'full_name', emailVerified: 'email_verified' }
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
fields?: Record<string, string>
|
|
120
|
+
/**
|
|
121
|
+
* Database schema (Postgres) for this auth model.
|
|
122
|
+
* Generates a `@@schema("...")` on the derived list, overriding the
|
|
123
|
+
* plugin-level {@link AuthConfig.schema} for this one model.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* // Place the verification table in a different schema from the rest
|
|
128
|
+
* verification: { schema: 'auth_internal' }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
schema?: string
|
|
132
|
+
}
|
|
133
|
+
|
|
85
134
|
/**
|
|
86
135
|
* Auth configuration options
|
|
87
136
|
*/
|
|
@@ -107,9 +156,60 @@ export type AuthConfig = {
|
|
|
107
156
|
socialProviders?: SocialProvidersConfig
|
|
108
157
|
|
|
109
158
|
/**
|
|
110
|
-
* Session configuration
|
|
159
|
+
* Session configuration.
|
|
160
|
+
*
|
|
161
|
+
* Carries session expiry settings as well as the better-auth `session` model
|
|
162
|
+
* config (`modelName` + field column `fields` maps) used to derive the Auth
|
|
163
|
+
* session list.
|
|
164
|
+
*/
|
|
165
|
+
session?: SessionConfig & AuthModelConfig
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* better-auth `user` model configuration (modelName + field column maps).
|
|
169
|
+
* Used to derive the Auth user list's key, table `@@map`, and field `@map`s.
|
|
170
|
+
*
|
|
171
|
+
* Custom fields beyond the better-auth basics are added via `extendUserList`.
|
|
172
|
+
*/
|
|
173
|
+
user?: AuthModelConfig
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* better-auth `account` model configuration (modelName + field column maps).
|
|
177
|
+
*/
|
|
178
|
+
account?: AuthModelConfig
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* better-auth `verification` model configuration (modelName + field column maps).
|
|
182
|
+
*/
|
|
183
|
+
verification?: AuthModelConfig
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Database schema (Postgres) for the generated Auth lists.
|
|
187
|
+
*
|
|
188
|
+
* When set, all four Auth lists (user/session/account/verification) are placed
|
|
189
|
+
* in this schema via `@@schema(...)`, and the stack's multi-schema support is
|
|
190
|
+
* wired automatically: the datasource `schemas` array gains this schema (plus
|
|
191
|
+
* `public`) and the `multiSchema` preview feature is enabled. A per-model
|
|
192
|
+
* {@link AuthModelConfig.schema} overrides this for an individual list.
|
|
193
|
+
*
|
|
194
|
+
* Useful for adopting an existing separate-schema better-auth installation
|
|
195
|
+
* (e.g. an `auth` Postgres schema) so the generated lists diff clean against
|
|
196
|
+
* the live tables. When unset, the Auth lists stay in the default `public`
|
|
197
|
+
* schema and no `@@schema` is emitted (greenfield default unchanged).
|
|
198
|
+
*
|
|
199
|
+
* Only applies to the `postgresql` provider.
|
|
200
|
+
*
|
|
201
|
+
* @example Adopt an `auth`-schema better-auth install
|
|
202
|
+
* ```typescript
|
|
203
|
+
* authPlugin({
|
|
204
|
+
* schema: 'auth',
|
|
205
|
+
* user: { modelName: 'AuthUser' },
|
|
206
|
+
* session: { modelName: 'AuthSession' },
|
|
207
|
+
* account: { modelName: 'AuthAccount' },
|
|
208
|
+
* verification: { modelName: 'AuthVerification' },
|
|
209
|
+
* })
|
|
210
|
+
* ```
|
|
111
211
|
*/
|
|
112
|
-
|
|
212
|
+
schema?: string
|
|
113
213
|
|
|
114
214
|
/**
|
|
115
215
|
* Which fields to include in the session object
|
|
@@ -202,6 +302,30 @@ export type AuthConfig = {
|
|
|
202
302
|
}
|
|
203
303
|
}
|
|
204
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Resolved per-model auth configuration after normalization.
|
|
307
|
+
* Always carries a concrete `modelName` (the developer's override or the
|
|
308
|
+
* better-auth default) and a (possibly empty) `fields` column map. `schema`
|
|
309
|
+
* carries the resolved Postgres schema for the model (per-model override, else
|
|
310
|
+
* the plugin-level schema, else `undefined` for the default `public` schema).
|
|
311
|
+
*/
|
|
312
|
+
export type NormalizedAuthModelConfig = {
|
|
313
|
+
modelName: string
|
|
314
|
+
fields: Record<string, string>
|
|
315
|
+
schema?: string
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Resolved auth model configuration for all four better-auth models.
|
|
320
|
+
* Consumed by the Auth-list derivation and the runtime user-key resolution.
|
|
321
|
+
*/
|
|
322
|
+
export type NormalizedAuthModels = {
|
|
323
|
+
user: NormalizedAuthModelConfig
|
|
324
|
+
session: NormalizedAuthModelConfig
|
|
325
|
+
account: NormalizedAuthModelConfig
|
|
326
|
+
verification: NormalizedAuthModelConfig
|
|
327
|
+
}
|
|
328
|
+
|
|
205
329
|
/**
|
|
206
330
|
* Internal normalized auth configuration
|
|
207
331
|
* Used after parsing user config
|
|
@@ -209,12 +333,31 @@ export type AuthConfig = {
|
|
|
209
333
|
export type NormalizedAuthConfig = Required<
|
|
210
334
|
Omit<
|
|
211
335
|
AuthConfig,
|
|
212
|
-
|
|
336
|
+
| 'emailAndPassword'
|
|
337
|
+
| 'emailVerification'
|
|
338
|
+
| 'passwordReset'
|
|
339
|
+
| 'betterAuthPlugins'
|
|
340
|
+
| 'rateLimit'
|
|
341
|
+
| 'session'
|
|
342
|
+
| 'user'
|
|
343
|
+
| 'account'
|
|
344
|
+
| 'verification'
|
|
345
|
+
| 'schema'
|
|
213
346
|
>
|
|
214
347
|
> & {
|
|
215
348
|
emailAndPassword: Required<EmailPasswordConfig>
|
|
216
349
|
emailVerification: Required<EmailVerificationConfig>
|
|
217
350
|
passwordReset: Required<PasswordResetConfig>
|
|
351
|
+
/** Resolved session expiry settings (model config lives under `models.session`). */
|
|
352
|
+
session: Required<SessionConfig>
|
|
353
|
+
/** Resolved better-auth model config (modelName + field column maps + schema) for all auth models. */
|
|
354
|
+
models: NormalizedAuthModels
|
|
355
|
+
/**
|
|
356
|
+
* Plugin-level Postgres schema for the Auth lists, if any. Resolved per-model
|
|
357
|
+
* schemas live on `models.<model>.schema`; this is the unresolved plugin-level
|
|
358
|
+
* default (used to wire the datasource `schemas` array during generation).
|
|
359
|
+
*/
|
|
360
|
+
schema?: string
|
|
218
361
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Better Auth plugin types are not exposed, must use any
|
|
219
362
|
betterAuthPlugins: any[]
|
|
220
363
|
rateLimit?: {
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,19 @@ export { authPlugin } from './config/plugin.js'
|
|
|
34
34
|
export type { AuthConfig, NormalizedAuthConfig } from './config/index.js'
|
|
35
35
|
export type * from './config/types.js'
|
|
36
36
|
|
|
37
|
+
// Pure better-auth config -> Auth lists derivation (advanced use cases)
|
|
38
|
+
export { deriveAuthLists } from './config/derive-auth-lists.js'
|
|
39
|
+
export type { DerivedAuthLists } from './config/derive-auth-lists.js'
|
|
40
|
+
|
|
41
|
+
// "Adopt existing better-auth tables" recipe — sets the model/schema knobs that
|
|
42
|
+
// match a pre-existing separate-schema better-auth install so a migrating
|
|
43
|
+
// project reaches Schema parity without rebuilding the config by hand.
|
|
44
|
+
export { adoptBetterAuthTables } from './config/adopt-better-auth-tables.js'
|
|
45
|
+
export type {
|
|
46
|
+
AdoptBetterAuthTablesOptions,
|
|
47
|
+
AdoptBetterAuthTablesConfig,
|
|
48
|
+
} from './config/adopt-better-auth-tables.js'
|
|
49
|
+
|
|
37
50
|
// Runtime type exports
|
|
38
51
|
export type { AuthRuntimeServices } from './runtime/types.js'
|
|
39
52
|
|
package/src/lists/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { list } from '@opensaas/stack-core'
|
|
2
|
-
import { text, timestamp, checkbox, relationship } from '@opensaas/stack-core/fields'
|
|
3
1
|
import type { ListConfig, FieldConfig } from '@opensaas/stack-core'
|
|
2
|
+
import type { NormalizedAuthModels } from '../config/types.js'
|
|
3
|
+
import { deriveAuthLists } from '../config/derive-auth-lists.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Configuration for extending the auto-generated User list
|
|
@@ -25,229 +25,69 @@ export type ExtendUserListConfig = {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
28
|
+
* The default better-auth model config (no `modelName`/`fields` overrides).
|
|
29
|
+
* Produces the historical `User`/`Session`/`Account`/`Verification` keys with
|
|
30
|
+
* their original field shapes. Used by the backwards-compatible
|
|
31
|
+
* `createUserList`/`getAuthLists` helpers.
|
|
32
|
+
*/
|
|
33
|
+
const DEFAULT_MODELS: NormalizedAuthModels = {
|
|
34
|
+
user: { modelName: 'User', fields: {} },
|
|
35
|
+
session: { modelName: 'Session', fields: {} },
|
|
36
|
+
account: { modelName: 'Account', fields: {} },
|
|
37
|
+
verification: { modelName: 'Verification', fields: {} },
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create the base User list with better-auth required fields.
|
|
42
|
+
*
|
|
43
|
+
* Backwards-compatible helper: derives the default `User` list (keyed `User`,
|
|
44
|
+
* default field shapes) via {@link deriveAuthLists}.
|
|
30
45
|
*/
|
|
31
46
|
export function createUserList(
|
|
32
|
-
config?: ExtendUserListConfig,
|
|
47
|
+
config?: ExtendUserListConfig,
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
33
49
|
): ListConfig<any> {
|
|
34
|
-
return
|
|
35
|
-
fields: {
|
|
36
|
-
// Better-auth required fields
|
|
37
|
-
name: text({
|
|
38
|
-
validation: { isRequired: true },
|
|
39
|
-
}),
|
|
40
|
-
email: text({
|
|
41
|
-
validation: { isRequired: true },
|
|
42
|
-
isIndexed: 'unique',
|
|
43
|
-
}),
|
|
44
|
-
emailVerified: checkbox({
|
|
45
|
-
defaultValue: false,
|
|
46
|
-
}),
|
|
47
|
-
image: text(),
|
|
48
|
-
|
|
49
|
-
// Relationships to other auth tables
|
|
50
|
-
sessions: relationship({
|
|
51
|
-
ref: 'Session.user',
|
|
52
|
-
many: true,
|
|
53
|
-
}),
|
|
54
|
-
accounts: relationship({
|
|
55
|
-
ref: 'Account.user',
|
|
56
|
-
many: true,
|
|
57
|
-
}),
|
|
58
|
-
|
|
59
|
-
// Custom fields from user config
|
|
60
|
-
...(config?.fields || {}),
|
|
61
|
-
},
|
|
62
|
-
access: config?.access || {
|
|
63
|
-
operation: {
|
|
64
|
-
// Anyone can query users (for displaying names, etc.)
|
|
65
|
-
query: () => true,
|
|
66
|
-
// Anyone can create a user (sign up)
|
|
67
|
-
create: () => true,
|
|
68
|
-
// Only update your own user record
|
|
69
|
-
update: ({ session, item }) => {
|
|
70
|
-
if (!session) return false
|
|
71
|
-
const userId = (session as { userId?: string }).userId
|
|
72
|
-
const itemId = (item as { id?: string })?.id
|
|
73
|
-
return userId === itemId
|
|
74
|
-
},
|
|
75
|
-
// Only delete your own user record
|
|
76
|
-
delete: ({ session, item }) => {
|
|
77
|
-
if (!session) return false
|
|
78
|
-
const userId = (session as { userId?: string }).userId
|
|
79
|
-
const itemId = (item as { id?: string })?.id
|
|
80
|
-
return userId === itemId
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
hooks: config?.hooks,
|
|
85
|
-
})
|
|
50
|
+
return deriveAuthLists(DEFAULT_MODELS, config).lists.User
|
|
86
51
|
}
|
|
87
52
|
|
|
88
53
|
/**
|
|
89
|
-
* Create the Session list for better-auth
|
|
90
|
-
* Stores active user sessions
|
|
54
|
+
* Create the Session list for better-auth (default `Session` key).
|
|
91
55
|
*/
|
|
92
56
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
93
57
|
export function createSessionList(): ListConfig<any> {
|
|
94
|
-
return
|
|
95
|
-
fields: {
|
|
96
|
-
// Session token (stored in cookie, used as primary key)
|
|
97
|
-
token: text({
|
|
98
|
-
validation: { isRequired: true },
|
|
99
|
-
isIndexed: 'unique',
|
|
100
|
-
}),
|
|
101
|
-
// Expiration timestamp
|
|
102
|
-
expiresAt: timestamp(),
|
|
103
|
-
// Optional: IP address for security
|
|
104
|
-
ipAddress: text(),
|
|
105
|
-
// Optional: User agent for security
|
|
106
|
-
userAgent: text(),
|
|
107
|
-
// Relationship to user (userId will be auto-generated)
|
|
108
|
-
user: relationship({
|
|
109
|
-
ref: 'User.sessions',
|
|
110
|
-
}),
|
|
111
|
-
},
|
|
112
|
-
access: {
|
|
113
|
-
operation: {
|
|
114
|
-
// Only the session owner can query their sessions
|
|
115
|
-
query: ({ session }) => {
|
|
116
|
-
if (!session) return false
|
|
117
|
-
const userId = (session as { userId?: string }).userId
|
|
118
|
-
if (!userId) return false
|
|
119
|
-
// Return Prisma filter for nested relationship
|
|
120
|
-
return {
|
|
121
|
-
user: {
|
|
122
|
-
id: { equals: userId },
|
|
123
|
-
},
|
|
124
|
-
} as Record<string, unknown>
|
|
125
|
-
},
|
|
126
|
-
// Better-auth handles session creation
|
|
127
|
-
create: () => true,
|
|
128
|
-
// No manual updates
|
|
129
|
-
update: () => false,
|
|
130
|
-
// Better-auth handles session deletion (logout)
|
|
131
|
-
delete: ({ session, item }) => {
|
|
132
|
-
if (!session) return false
|
|
133
|
-
const userId = (session as { userId?: string }).userId
|
|
134
|
-
const itemUserId = (item as { user?: { id?: string } })?.user?.id
|
|
135
|
-
return userId === itemUserId
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
})
|
|
58
|
+
return deriveAuthLists(DEFAULT_MODELS).lists.Session
|
|
140
59
|
}
|
|
141
60
|
|
|
142
61
|
/**
|
|
143
|
-
* Create the Account list for better-auth
|
|
144
|
-
* Stores OAuth provider accounts and credentials
|
|
62
|
+
* Create the Account list for better-auth (default `Account` key).
|
|
145
63
|
*/
|
|
146
64
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
147
65
|
export function createAccountList(): ListConfig<any> {
|
|
148
|
-
return
|
|
149
|
-
fields: {
|
|
150
|
-
// Account identifier from provider
|
|
151
|
-
accountId: text({
|
|
152
|
-
validation: { isRequired: true },
|
|
153
|
-
}),
|
|
154
|
-
// Provider identifier (e.g., 'github', 'google', 'credentials')
|
|
155
|
-
providerId: text({
|
|
156
|
-
validation: { isRequired: true },
|
|
157
|
-
}),
|
|
158
|
-
// Relationship to user (userId will be auto-generated)
|
|
159
|
-
user: relationship({
|
|
160
|
-
ref: 'User.accounts',
|
|
161
|
-
}),
|
|
162
|
-
// OAuth tokens
|
|
163
|
-
accessToken: text(),
|
|
164
|
-
refreshToken: text(),
|
|
165
|
-
accessTokenExpiresAt: timestamp(),
|
|
166
|
-
refreshTokenExpiresAt: timestamp(),
|
|
167
|
-
scope: text(),
|
|
168
|
-
idToken: text(),
|
|
169
|
-
// Password hash for credential provider (better-auth stores in account table)
|
|
170
|
-
password: text(),
|
|
171
|
-
},
|
|
172
|
-
access: {
|
|
173
|
-
operation: {
|
|
174
|
-
// Only the account owner can query their accounts
|
|
175
|
-
query: ({ session }) => {
|
|
176
|
-
if (!session) return false
|
|
177
|
-
const userId = (session as { userId?: string }).userId
|
|
178
|
-
if (!userId) return false
|
|
179
|
-
// Return Prisma filter for nested relationship
|
|
180
|
-
return {
|
|
181
|
-
user: {
|
|
182
|
-
id: { equals: userId },
|
|
183
|
-
},
|
|
184
|
-
} as Record<string, unknown>
|
|
185
|
-
},
|
|
186
|
-
// Better-auth handles account creation
|
|
187
|
-
create: () => true,
|
|
188
|
-
// Better-auth handles account updates (token refresh)
|
|
189
|
-
update: ({ session, item }) => {
|
|
190
|
-
if (!session) return false
|
|
191
|
-
const userId = (session as { userId?: string }).userId
|
|
192
|
-
const itemUserId = (item as { user?: { id?: string } })?.user?.id
|
|
193
|
-
return userId === itemUserId
|
|
194
|
-
},
|
|
195
|
-
// Account owner can delete their accounts
|
|
196
|
-
delete: ({ session, item }) => {
|
|
197
|
-
if (!session) return false
|
|
198
|
-
const userId = (session as { userId?: string }).userId
|
|
199
|
-
const itemUserId = (item as { user?: { id?: string } })?.user?.id
|
|
200
|
-
return userId === itemUserId
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
})
|
|
66
|
+
return deriveAuthLists(DEFAULT_MODELS).lists.Account
|
|
205
67
|
}
|
|
206
68
|
|
|
207
69
|
/**
|
|
208
|
-
* Create the Verification list for better-auth
|
|
209
|
-
* Stores email verification tokens, password reset tokens, etc.
|
|
70
|
+
* Create the Verification list for better-auth (default `Verification` key).
|
|
210
71
|
*/
|
|
211
72
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
212
73
|
export function createVerificationList(): ListConfig<any> {
|
|
213
|
-
return
|
|
214
|
-
fields: {
|
|
215
|
-
// Identifier (e.g., email address)
|
|
216
|
-
identifier: text({
|
|
217
|
-
validation: { isRequired: true },
|
|
218
|
-
}),
|
|
219
|
-
// Token value
|
|
220
|
-
value: text({
|
|
221
|
-
validation: { isRequired: true },
|
|
222
|
-
}),
|
|
223
|
-
// Expiration timestamp
|
|
224
|
-
expiresAt: timestamp(),
|
|
225
|
-
},
|
|
226
|
-
access: {
|
|
227
|
-
operation: {
|
|
228
|
-
// No public querying (better-auth handles verification internally)
|
|
229
|
-
query: () => false,
|
|
230
|
-
// Better-auth creates verification tokens
|
|
231
|
-
create: () => true,
|
|
232
|
-
// No updates
|
|
233
|
-
update: () => false,
|
|
234
|
-
// Better-auth deletes used/expired tokens
|
|
235
|
-
delete: () => true,
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
})
|
|
74
|
+
return deriveAuthLists(DEFAULT_MODELS).lists.Verification
|
|
239
75
|
}
|
|
240
76
|
|
|
241
77
|
/**
|
|
242
|
-
* Get all auth lists required by better-auth
|
|
243
|
-
*
|
|
78
|
+
* Get all auth lists required by better-auth.
|
|
79
|
+
*
|
|
80
|
+
* Derives the Auth lists from the resolved better-auth model config. When no
|
|
81
|
+
* `models` are supplied (or none carry overrides), the result is the historical
|
|
82
|
+
* default set keyed `User`/`Session`/`Account`/`Verification`.
|
|
83
|
+
*
|
|
84
|
+
* @param userConfig - Extra User-list fields/access/hooks (from `extendUserList`)
|
|
85
|
+
* @param models - Resolved better-auth model config; defaults to the better-auth defaults
|
|
244
86
|
*/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
Verification: createVerificationList(),
|
|
252
|
-
}
|
|
87
|
+
export function getAuthLists(
|
|
88
|
+
userConfig?: ExtendUserListConfig,
|
|
89
|
+
models: NormalizedAuthModels = DEFAULT_MODELS,
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
91
|
+
): Record<string, ListConfig<any>> {
|
|
92
|
+
return deriveAuthLists(models, userConfig || {}).lists
|
|
253
93
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { prismaAdapter } from 'better-auth/adapters/prisma'
|
|
|
3
3
|
import type { BetterAuthOptions } from 'better-auth'
|
|
4
4
|
import type { OpenSaasConfig, AccessContext } from '@opensaas/stack-core'
|
|
5
5
|
import type { DatabaseConfig } from '@opensaas/stack-core/internal'
|
|
6
|
-
import type { NormalizedAuthConfig } from '../config/types.js'
|
|
6
|
+
import type { NormalizedAuthConfig, NormalizedAuthModelConfig } from '../config/types.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Get better-auth database configuration from OpenSaas config
|
|
@@ -17,6 +17,22 @@ function getDatabaseConfig(
|
|
|
17
17
|
})
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Translate a normalized OpenSaaS auth model config into the better-auth
|
|
22
|
+
* per-model options (`modelName` + `fields` column map). Returns `undefined`
|
|
23
|
+
* when there is nothing to override so the running auth instance keeps
|
|
24
|
+
* better-auth's own defaults untouched.
|
|
25
|
+
*/
|
|
26
|
+
function toBetterAuthModelOptions(
|
|
27
|
+
model: NormalizedAuthModelConfig,
|
|
28
|
+
): { modelName?: string; fields?: Record<string, string> } | undefined {
|
|
29
|
+
const hasFields = Object.keys(model.fields).length > 0
|
|
30
|
+
const options: { modelName?: string; fields?: Record<string, string> } = {}
|
|
31
|
+
if (model.modelName) options.modelName = model.modelName
|
|
32
|
+
if (hasFields) options.fields = model.fields
|
|
33
|
+
return Object.keys(options).length > 0 ? options : undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
/**
|
|
21
37
|
* Create a better-auth instance from OpenSaas config
|
|
22
38
|
* This should be called once at app startup
|
|
@@ -64,6 +80,20 @@ export function createAuth(
|
|
|
64
80
|
const betterAuthConfig: BetterAuthOptions = {
|
|
65
81
|
database: getDatabaseConfig(resolvedConfig.db, resolvedContext),
|
|
66
82
|
|
|
83
|
+
// Mirror the per-model config (modelName + field column maps) back to
|
|
84
|
+
// better-auth so the running auth instance reads/writes the same
|
|
85
|
+
// tables/columns the OpenSaaS Auth lists were derived from.
|
|
86
|
+
user: toBetterAuthModelOptions(authConfig.models.user),
|
|
87
|
+
session: {
|
|
88
|
+
...toBetterAuthModelOptions(authConfig.models.session),
|
|
89
|
+
expiresIn: authConfig.session.expiresIn || 604800,
|
|
90
|
+
updateAge: authConfig.session.updateAge
|
|
91
|
+
? (authConfig.session.expiresIn || 604800) / 10
|
|
92
|
+
: 0,
|
|
93
|
+
},
|
|
94
|
+
account: toBetterAuthModelOptions(authConfig.models.account),
|
|
95
|
+
verification: toBetterAuthModelOptions(authConfig.models.verification),
|
|
96
|
+
|
|
67
97
|
// Enable email and password if configured
|
|
68
98
|
emailAndPassword: authConfig.emailAndPassword.enabled
|
|
69
99
|
? {
|
|
@@ -72,14 +102,6 @@ export function createAuth(
|
|
|
72
102
|
}
|
|
73
103
|
: undefined,
|
|
74
104
|
|
|
75
|
-
// Configure session
|
|
76
|
-
session: {
|
|
77
|
-
expiresIn: authConfig.session.expiresIn || 604800,
|
|
78
|
-
updateAge: authConfig.session.updateAge
|
|
79
|
-
? (authConfig.session.expiresIn || 604800) / 10
|
|
80
|
-
: 0,
|
|
81
|
-
},
|
|
82
|
-
|
|
83
105
|
// Trust host (required for production)
|
|
84
106
|
trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS?.split(',') || [],
|
|
85
107
|
|