@opensaas/stack-auth 0.36.0 → 0.38.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 +146 -0
- package/CLAUDE.md +153 -9
- 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 +143 -28
- package/dist/config/types.d.ts.map +1 -1
- package/dist/server/build-better-auth-options.test.d.ts +2 -0
- package/dist/server/build-better-auth-options.test.d.ts.map +1 -0
- package/dist/server/build-better-auth-options.test.js +29 -0
- package/dist/server/build-better-auth-options.test.js.map +1 -0
- package/dist/server/index.d.ts +112 -8
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +284 -96
- package/dist/server/index.js.map +1 -1
- package/dist/server/schema-converter.d.ts +3 -3
- package/dist/server/schema-converter.d.ts.map +1 -1
- package/package.json +5 -5
- 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 +40 -28
- package/src/config/types.ts +144 -27
- package/src/server/build-better-auth-options.test.ts +59 -0
- package/src/server/index.ts +470 -106
- package/src/server/schema-converter.ts +3 -3
- 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 +723 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +7 -1
|
@@ -76,6 +76,105 @@ describe('adoptBetterAuthTables - recipe defaults', () => {
|
|
|
76
76
|
})
|
|
77
77
|
})
|
|
78
78
|
|
|
79
|
+
describe('adoptBetterAuthTables - table names (issue #862)', () => {
|
|
80
|
+
it('sets no tableName by default (prefixed modelName also pins the table, as before)', () => {
|
|
81
|
+
const fragment = adoptBetterAuthTables()
|
|
82
|
+
|
|
83
|
+
expect(fragment.user).toEqual({ modelName: 'AuthUser' })
|
|
84
|
+
expect(fragment.session).toEqual({ modelName: 'AuthSession' })
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('useBetterAuthTableNames sets every model tableName to the better-auth default lowercase name', () => {
|
|
88
|
+
const fragment = adoptBetterAuthTables({ useBetterAuthTableNames: true })
|
|
89
|
+
|
|
90
|
+
expect(fragment.user).toEqual({ modelName: 'AuthUser', tableName: 'user' })
|
|
91
|
+
expect(fragment.session).toEqual({ modelName: 'AuthSession', tableName: 'session' })
|
|
92
|
+
expect(fragment.account).toEqual({ modelName: 'AuthAccount', tableName: 'account' })
|
|
93
|
+
expect(fragment.verification).toEqual({
|
|
94
|
+
modelName: 'AuthVerification',
|
|
95
|
+
tableName: 'verification',
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('tableNames sets explicit per-model table names', () => {
|
|
100
|
+
const fragment = adoptBetterAuthTables({
|
|
101
|
+
tableNames: { user: 'users', session: 'user_sessions' },
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
expect(fragment.user).toEqual({ modelName: 'AuthUser', tableName: 'users' })
|
|
105
|
+
expect(fragment.session).toEqual({ modelName: 'AuthSession', tableName: 'user_sessions' })
|
|
106
|
+
// Models without an explicit tableName entry stay unset.
|
|
107
|
+
expect(fragment.account).toEqual({ modelName: 'AuthAccount' })
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('tableNames takes precedence over useBetterAuthTableNames for the same model', () => {
|
|
111
|
+
const fragment = adoptBetterAuthTables({
|
|
112
|
+
useBetterAuthTableNames: true,
|
|
113
|
+
tableNames: { user: 'custom_user_table' },
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
expect(fragment.user?.tableName).toBe('custom_user_table')
|
|
117
|
+
// Other models still fall back to the better-auth default.
|
|
118
|
+
expect(fragment.session?.tableName).toBe('session')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('combines tableName with a field column map on the same model', () => {
|
|
122
|
+
const fragment = adoptBetterAuthTables({
|
|
123
|
+
useBetterAuthTableNames: true,
|
|
124
|
+
fields: { user: { name: 'full_name' } },
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
expect(fragment.user).toEqual({
|
|
128
|
+
modelName: 'AuthUser',
|
|
129
|
+
tableName: 'user',
|
|
130
|
+
fields: { name: 'full_name' },
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
describe('adoptBetterAuthTables - clean-diff adoption with better-auth default table names', () => {
|
|
136
|
+
it('produces Auth lists that @@map to the live lowercase tables under prefixed list keys', async () => {
|
|
137
|
+
const result = await generationConfig({
|
|
138
|
+
db: { provider: 'postgresql' },
|
|
139
|
+
plugins: [
|
|
140
|
+
authPlugin({
|
|
141
|
+
...adoptBetterAuthTables({ useBetterAuthTableNames: true }),
|
|
142
|
+
emailAndPassword: { enabled: true },
|
|
143
|
+
}),
|
|
144
|
+
],
|
|
145
|
+
lists: {
|
|
146
|
+
User: list({
|
|
147
|
+
fields: { subjectId: text({ validation: { isRequired: true } }) },
|
|
148
|
+
}),
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// List keys stay prefixed (no collision with the app's own User)...
|
|
153
|
+
expect(result.lists).toHaveProperty('AuthUser')
|
|
154
|
+
// ...but the physical table is better-auth's own default lowercase name.
|
|
155
|
+
expect(result.lists.AuthUser.db).toEqual({ timestamps: true, map: 'user', schema: 'auth' })
|
|
156
|
+
expect(result.lists.AuthSession.db).toEqual({
|
|
157
|
+
timestamps: true,
|
|
158
|
+
map: 'session',
|
|
159
|
+
schema: 'auth',
|
|
160
|
+
})
|
|
161
|
+
expect(result.lists.AuthAccount.db).toEqual({
|
|
162
|
+
timestamps: true,
|
|
163
|
+
map: 'account',
|
|
164
|
+
schema: 'auth',
|
|
165
|
+
})
|
|
166
|
+
expect(result.lists.AuthVerification.db).toEqual({
|
|
167
|
+
timestamps: true,
|
|
168
|
+
map: 'verification',
|
|
169
|
+
schema: 'auth',
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// The app's own domain User is untouched.
|
|
173
|
+
expect(result.lists.User.fields).toHaveProperty('subjectId')
|
|
174
|
+
expect(result.lists.User.fields).not.toHaveProperty('email')
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
79
178
|
describe('adoptBetterAuthTables - composes with authPlugin', () => {
|
|
80
179
|
it('spreads into authPlugin alongside the rest of the auth config', async () => {
|
|
81
180
|
const result = await config({
|
package/tests/config.test.ts
CHANGED
|
@@ -14,9 +14,19 @@ describe('normalizeAuthConfig', () => {
|
|
|
14
14
|
expect(result.emailVerification.enabled).toBe(false)
|
|
15
15
|
expect(result.passwordReset.enabled).toBe(false)
|
|
16
16
|
expect(result.session.expiresIn).toBe(604800) // 7 days
|
|
17
|
+
expect(result.session.updateAge).toBe(86400) // 1 day, matching better-auth's own default
|
|
17
18
|
expect(result.sessionFields).toEqual(['userId', 'email', 'name'])
|
|
18
19
|
expect(result.socialProviders).toEqual({})
|
|
19
20
|
expect(result.betterAuthPlugins).toEqual([])
|
|
21
|
+
expect(result.betterAuthOptions).toEqual({})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should pass through betterAuthOptions unchanged', () => {
|
|
25
|
+
const result = normalizeAuthConfig({
|
|
26
|
+
betterAuthOptions: { baseURL: 'https://example.com' },
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
expect(result.betterAuthOptions).toEqual({ baseURL: 'https://example.com' })
|
|
20
30
|
})
|
|
21
31
|
|
|
22
32
|
it('should normalize email and password config', () => {
|
|
@@ -81,6 +91,14 @@ describe('normalizeAuthConfig', () => {
|
|
|
81
91
|
expect(result.session.updateAge).toBe(false)
|
|
82
92
|
})
|
|
83
93
|
|
|
94
|
+
it('should normalize a custom numeric session.updateAge', () => {
|
|
95
|
+
const result = normalizeAuthConfig({
|
|
96
|
+
session: { updateAge: 3600 },
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(result.session.updateAge).toBe(3600)
|
|
100
|
+
})
|
|
101
|
+
|
|
84
102
|
it('should normalize custom session fields', () => {
|
|
85
103
|
const result = normalizeAuthConfig({
|
|
86
104
|
sessionFields: ['userId', 'email', 'role'],
|
|
@@ -120,22 +138,24 @@ describe('normalizeAuthConfig', () => {
|
|
|
120
138
|
expect(result.extendUserList.fields).toHaveProperty('role')
|
|
121
139
|
})
|
|
122
140
|
|
|
123
|
-
it('should include custom
|
|
124
|
-
const
|
|
141
|
+
it('should include custom sendResetPassword and sendVerificationEmail functions', () => {
|
|
142
|
+
const mockSendResetPassword = async () => {}
|
|
143
|
+
const mockSendVerificationEmail = async () => {}
|
|
125
144
|
|
|
126
145
|
const result = normalizeAuthConfig({
|
|
127
|
-
|
|
146
|
+
emailAndPassword: { enabled: true, sendResetPassword: mockSendResetPassword },
|
|
147
|
+
emailVerification: { enabled: true, sendVerificationEmail: mockSendVerificationEmail },
|
|
128
148
|
})
|
|
129
149
|
|
|
130
|
-
expect(result.
|
|
150
|
+
expect(result.emailAndPassword.sendResetPassword).toBe(mockSendResetPassword)
|
|
151
|
+
expect(result.emailVerification.sendVerificationEmail).toBe(mockSendVerificationEmail)
|
|
131
152
|
})
|
|
132
153
|
|
|
133
|
-
it('should provide default
|
|
154
|
+
it('should provide default sendResetPassword and sendVerificationEmail that log to console', () => {
|
|
134
155
|
const result = normalizeAuthConfig({})
|
|
135
156
|
|
|
136
|
-
expect(typeof result.
|
|
137
|
-
|
|
138
|
-
expect(result.sendEmail.length).toBe(1)
|
|
157
|
+
expect(typeof result.emailAndPassword.sendResetPassword).toBe('function')
|
|
158
|
+
expect(typeof result.emailVerification.sendVerificationEmail).toBe('function')
|
|
139
159
|
})
|
|
140
160
|
|
|
141
161
|
it('should include betterAuthPlugins', () => {
|
|
@@ -165,6 +185,44 @@ describe('normalizeAuthConfig', () => {
|
|
|
165
185
|
expect(result.access.user).toBe(userAccess)
|
|
166
186
|
expect(result.access.session).toBe(sessionAccess)
|
|
167
187
|
})
|
|
188
|
+
|
|
189
|
+
describe('models.tableName (issue #862)', () => {
|
|
190
|
+
it('defaults tableName to undefined for unrenamed models', () => {
|
|
191
|
+
const result = normalizeAuthConfig({})
|
|
192
|
+
|
|
193
|
+
expect(result.models.user.tableName).toBeUndefined()
|
|
194
|
+
expect(result.models.session.tableName).toBeUndefined()
|
|
195
|
+
expect(result.models.account.tableName).toBeUndefined()
|
|
196
|
+
expect(result.models.verification.tableName).toBeUndefined()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('defaults tableName to the renamed modelName, matching pre-#862 behaviour', () => {
|
|
200
|
+
const result = normalizeAuthConfig({
|
|
201
|
+
user: { modelName: 'AuthUser' },
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
expect(result.models.user.modelName).toBe('AuthUser')
|
|
205
|
+
expect(result.models.user.tableName).toBe('AuthUser')
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('resolves an explicit tableName independent of modelName', () => {
|
|
209
|
+
const result = normalizeAuthConfig({
|
|
210
|
+
user: { modelName: 'AuthUser', tableName: 'user' },
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
expect(result.models.user.modelName).toBe('AuthUser')
|
|
214
|
+
expect(result.models.user.tableName).toBe('user')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('honours an explicit tableName even when modelName is left at its default', () => {
|
|
218
|
+
const result = normalizeAuthConfig({
|
|
219
|
+
session: { tableName: 'sessions' },
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
expect(result.models.session.modelName).toBe('Session')
|
|
223
|
+
expect(result.models.session.tableName).toBe('sessions')
|
|
224
|
+
})
|
|
225
|
+
})
|
|
168
226
|
})
|
|
169
227
|
|
|
170
228
|
describe('authPlugin', () => {
|
|
@@ -44,6 +44,13 @@ describe('deriveAuthLists - default behaviour (no overrides)', () => {
|
|
|
44
44
|
expect(lists.User.fields.accounts.ref).toBe('Account.user')
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
+
it('marks Session/Verification expiresAt as DB-required, matching better-auth (issue #863)', () => {
|
|
48
|
+
const { lists } = deriveAuthLists(defaultModels)
|
|
49
|
+
|
|
50
|
+
expect(lists.Session.fields.expiresAt.db?.isNullable).toBe(false)
|
|
51
|
+
expect(lists.Verification.fields.expiresAt.db?.isNullable).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
|
|
47
54
|
it('emits no table @@map and no scalar @map for default keys', () => {
|
|
48
55
|
const { lists } = deriveAuthLists(defaultModels)
|
|
49
56
|
|
|
@@ -108,6 +115,13 @@ describe('deriveAuthLists - user FK shape mirrors better-auth (issue #679)', ()
|
|
|
108
115
|
}
|
|
109
116
|
})
|
|
110
117
|
|
|
118
|
+
it('marks the user FK non-nullable, matching better-auth (issue #863)', () => {
|
|
119
|
+
const { lists } = deriveAuthLists(defaultModels)
|
|
120
|
+
|
|
121
|
+
expect(lists.Session.fields.user.db?.isNullable).toBe(false)
|
|
122
|
+
expect(lists.Account.fields.user.db?.isNullable).toBe(false)
|
|
123
|
+
})
|
|
124
|
+
|
|
111
125
|
it('keeps the cascade extendPrismaSchema alongside a userId column override', () => {
|
|
112
126
|
const models: NormalizedAuthModels = {
|
|
113
127
|
user: { modelName: 'User', fields: {} },
|
|
@@ -126,11 +140,16 @@ describe('deriveAuthLists - user FK shape mirrors better-auth (issue #679)', ()
|
|
|
126
140
|
})
|
|
127
141
|
|
|
128
142
|
describe('deriveAuthLists - custom modelName overrides', () => {
|
|
143
|
+
// `deriveAuthLists` is the pure derivation step: it consumes an already-
|
|
144
|
+
// resolved `tableName` rather than re-deriving one from `modelName`. That
|
|
145
|
+
// default derivation (tableName follows modelName when it differs from the
|
|
146
|
+
// better-auth default) lives in `normalizeModelConfig` (config/index.ts) —
|
|
147
|
+
// mirror its output here since these fixtures bypass normalization.
|
|
129
148
|
const customModels: NormalizedAuthModels = {
|
|
130
|
-
user: { modelName: 'AuthUser', fields: {} },
|
|
131
|
-
session: { modelName: 'AuthSession', fields: {} },
|
|
132
|
-
account: { modelName: 'AuthAccount', fields: {} },
|
|
133
|
-
verification: { modelName: 'AuthVerification', fields: {} },
|
|
149
|
+
user: { modelName: 'AuthUser', tableName: 'AuthUser', fields: {} },
|
|
150
|
+
session: { modelName: 'AuthSession', tableName: 'AuthSession', fields: {} },
|
|
151
|
+
account: { modelName: 'AuthAccount', tableName: 'AuthAccount', fields: {} },
|
|
152
|
+
verification: { modelName: 'AuthVerification', tableName: 'AuthVerification', fields: {} },
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
it('derives list keys from modelName', () => {
|
|
@@ -179,6 +198,61 @@ describe('deriveAuthLists - custom modelName overrides', () => {
|
|
|
179
198
|
})
|
|
180
199
|
})
|
|
181
200
|
|
|
201
|
+
describe('deriveAuthLists - tableName independent of modelName (issue #862)', () => {
|
|
202
|
+
it('emits @@map from tableName even though modelName is unchanged', () => {
|
|
203
|
+
const models: NormalizedAuthModels = {
|
|
204
|
+
user: { modelName: 'User', tableName: 'users', fields: {} },
|
|
205
|
+
session: { modelName: 'Session', fields: {} },
|
|
206
|
+
account: { modelName: 'Account', fields: {} },
|
|
207
|
+
verification: { modelName: 'Verification', fields: {} },
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const { keys, lists } = deriveAuthLists(models)
|
|
211
|
+
|
|
212
|
+
// The list key follows modelName, unaffected by tableName.
|
|
213
|
+
expect(keys.user).toBe('User')
|
|
214
|
+
expect(lists.User.db?.map).toBe('users')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('adopts a better-auth default lowercase table under a prefixed list key', () => {
|
|
218
|
+
// The shape issue #862 exists for: a prefixed list key (to avoid
|
|
219
|
+
// colliding with the app's own domain User) whose live table is still
|
|
220
|
+
// better-auth's own default lowercase name.
|
|
221
|
+
const models: NormalizedAuthModels = {
|
|
222
|
+
user: { modelName: 'AuthUser', tableName: 'user', fields: {} },
|
|
223
|
+
session: { modelName: 'AuthSession', tableName: 'session', fields: {} },
|
|
224
|
+
account: { modelName: 'AuthAccount', tableName: 'account', fields: {} },
|
|
225
|
+
verification: { modelName: 'AuthVerification', tableName: 'verification', fields: {} },
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const { keys, lists } = deriveAuthLists(models)
|
|
229
|
+
|
|
230
|
+
expect(keys).toEqual({
|
|
231
|
+
user: 'AuthUser',
|
|
232
|
+
session: 'AuthSession',
|
|
233
|
+
account: 'AuthAccount',
|
|
234
|
+
verification: 'AuthVerification',
|
|
235
|
+
})
|
|
236
|
+
expect(lists.AuthUser.db?.map).toBe('user')
|
|
237
|
+
expect(lists.AuthSession.db?.map).toBe('session')
|
|
238
|
+
expect(lists.AuthAccount.db?.map).toBe('account')
|
|
239
|
+
expect(lists.AuthVerification.db?.map).toBe('verification')
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('emits no @@map when tableName is unset, even with a renamed modelName', () => {
|
|
243
|
+
const models: NormalizedAuthModels = {
|
|
244
|
+
user: { modelName: 'AuthUser', fields: {} },
|
|
245
|
+
session: { modelName: 'Session', fields: {} },
|
|
246
|
+
account: { modelName: 'Account', fields: {} },
|
|
247
|
+
verification: { modelName: 'Verification', fields: {} },
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const { lists } = deriveAuthLists(models)
|
|
251
|
+
|
|
252
|
+
expect(lists.AuthUser.db?.map).toBeUndefined()
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
|
|
182
256
|
describe('deriveAuthLists - custom field column maps', () => {
|
|
183
257
|
const models: NormalizedAuthModels = {
|
|
184
258
|
user: { modelName: 'AuthUser', fields: { name: 'full_name', emailVerified: 'is_verified' } },
|
|
@@ -229,7 +303,7 @@ describe('deriveAuthLists - schema placement', () => {
|
|
|
229
303
|
|
|
230
304
|
it('carries both @@map and @@schema for renamed + relocated lists', () => {
|
|
231
305
|
const models: NormalizedAuthModels = {
|
|
232
|
-
user: { modelName: 'AuthUser', fields: {}, schema: 'auth' },
|
|
306
|
+
user: { modelName: 'AuthUser', tableName: 'AuthUser', fields: {}, schema: 'auth' },
|
|
233
307
|
session: { modelName: 'AuthSession', fields: {}, schema: 'auth' },
|
|
234
308
|
account: { modelName: 'AuthAccount', fields: {}, schema: 'auth' },
|
|
235
309
|
verification: { modelName: 'AuthVerification', fields: {}, schema: 'auth' },
|
|
@@ -79,3 +79,68 @@ describe('generated auth schema — Session/Account user FK mirrors better-auth
|
|
|
79
79
|
expect(widget).toContain('@@index([ownerId])')
|
|
80
80
|
})
|
|
81
81
|
})
|
|
82
|
+
|
|
83
|
+
describe('generated auth schema — required columns mirror better-auth (issue #863)', () => {
|
|
84
|
+
it('emits a required (non-nullable) userId FK and user relation on Session and Account', async () => {
|
|
85
|
+
const schema = await generateSchema({
|
|
86
|
+
db: { provider: 'sqlite' },
|
|
87
|
+
plugins: [authPlugin({ emailAndPassword: { enabled: true } })],
|
|
88
|
+
lists: {},
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
for (const model of ['Session', 'Account']) {
|
|
92
|
+
const block = modelBlock(schema, model)
|
|
93
|
+
|
|
94
|
+
expect(block).toMatch(/userId\s+String\s/)
|
|
95
|
+
expect(block).not.toMatch(/userId\s+String\?/)
|
|
96
|
+
expect(block).toMatch(/user\s+User\s+@relation/)
|
|
97
|
+
expect(block).not.toMatch(/user\s+User\?/)
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('emits a required (non-nullable) expiresAt on Session and Verification', async () => {
|
|
102
|
+
const schema = await generateSchema({
|
|
103
|
+
db: { provider: 'sqlite' },
|
|
104
|
+
plugins: [authPlugin({ emailAndPassword: { enabled: true } })],
|
|
105
|
+
lists: {},
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
for (const model of ['Session', 'Verification']) {
|
|
109
|
+
const block = modelBlock(schema, model)
|
|
110
|
+
|
|
111
|
+
expect(block).toMatch(/expiresAt\s+DateTime\s/)
|
|
112
|
+
expect(block).not.toMatch(/expiresAt\s+DateTime\?/)
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('generated auth schema — tableName independent of modelName (issue #862)', () => {
|
|
118
|
+
it('emits a prefixed model name with a @@map to a better-auth default lowercase table', async () => {
|
|
119
|
+
const schema = await generateSchema({
|
|
120
|
+
db: { provider: 'postgresql' },
|
|
121
|
+
plugins: [
|
|
122
|
+
authPlugin({
|
|
123
|
+
user: { modelName: 'AuthUser', tableName: 'user' },
|
|
124
|
+
session: { modelName: 'AuthSession', tableName: 'session' },
|
|
125
|
+
account: { modelName: 'AuthAccount', tableName: 'account' },
|
|
126
|
+
verification: { modelName: 'AuthVerification', tableName: 'verification' },
|
|
127
|
+
emailAndPassword: { enabled: true },
|
|
128
|
+
}),
|
|
129
|
+
],
|
|
130
|
+
lists: {},
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
for (const [model, table] of [
|
|
134
|
+
['AuthUser', 'user'],
|
|
135
|
+
['AuthSession', 'session'],
|
|
136
|
+
['AuthAccount', 'account'],
|
|
137
|
+
['AuthVerification', 'verification'],
|
|
138
|
+
]) {
|
|
139
|
+
const block = modelBlock(schema, model)
|
|
140
|
+
// The generated model keeps the prefixed name but maps to the live
|
|
141
|
+
// lowercase table — no DROP/CREATE rename against a real better-auth
|
|
142
|
+
// install using its own default table names.
|
|
143
|
+
expect(block).toContain(`@@map("${table}")`)
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
})
|
|
@@ -161,6 +161,54 @@ describe('authPlugin - add-vs-extend with derived keys', () => {
|
|
|
161
161
|
expect(appUser.access?.operation?.update?.({} as any)).toBe(false)
|
|
162
162
|
expect(appUser.access?.operation?.update).toBe(hostUserUpdate)
|
|
163
163
|
})
|
|
164
|
+
|
|
165
|
+
it('preserves the derived Auth list db (map/schema/timestamps) and access when a better-auth plugin extends a base model (#861)', async () => {
|
|
166
|
+
// A better-auth provider plugin (e.g. the `anonymous` plugin) that ships a
|
|
167
|
+
// schema extension for the base `user` model — the exact shape adoptBetterAuthTables()
|
|
168
|
+
// + betterAuthPlugins produces in the field. Before the #861 fix, the
|
|
169
|
+
// plugin's schema loop ran BEFORE the derived Auth lists were added, so it
|
|
170
|
+
// pre-empted `AuthUser` with a bare `list({ fields })` (no `db`, no
|
|
171
|
+
// `access`) — and the real derived list then lost to the field-only
|
|
172
|
+
// `extendList` merge, silently dropping `@@map`/`@@schema`/timestamps/access.
|
|
173
|
+
const anonymousBetterAuthPlugin = {
|
|
174
|
+
id: 'anonymous',
|
|
175
|
+
schema: {
|
|
176
|
+
user: {
|
|
177
|
+
fields: {
|
|
178
|
+
isAnonymous: { type: 'boolean', required: false },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const userQuery = vi.fn(() => true)
|
|
185
|
+
const result = await config({
|
|
186
|
+
db: { provider: 'postgresql' },
|
|
187
|
+
plugins: [
|
|
188
|
+
authPlugin({
|
|
189
|
+
schema: 'auth',
|
|
190
|
+
user: { modelName: 'AuthUser' },
|
|
191
|
+
session: { modelName: 'AuthSession' },
|
|
192
|
+
account: { modelName: 'AuthAccount' },
|
|
193
|
+
verification: { modelName: 'AuthVerification' },
|
|
194
|
+
betterAuthPlugins: [anonymousBetterAuthPlugin],
|
|
195
|
+
access: { user: { operation: { query: userQuery } } },
|
|
196
|
+
}),
|
|
197
|
+
],
|
|
198
|
+
lists: {},
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const authUser = result.lists.AuthUser
|
|
202
|
+
|
|
203
|
+
// The plugin's schema extension is merged in...
|
|
204
|
+
expect(authUser.fields).toHaveProperty('isAnonymous')
|
|
205
|
+
// ...without displacing the derived list's own db config...
|
|
206
|
+
expect(authUser.db?.map).toBe('AuthUser')
|
|
207
|
+
expect(authUser.db?.schema).toBe('auth')
|
|
208
|
+
expect(authUser.db?.timestamps).toBe(true)
|
|
209
|
+
// ...or its access config.
|
|
210
|
+
expect(authUser.access?.operation?.query).toBe(userQuery)
|
|
211
|
+
})
|
|
164
212
|
})
|
|
165
213
|
|
|
166
214
|
describe('authPlugin - runtime user-key resolution', () => {
|