@opensaas/stack-auth 0.39.1 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +153 -0
  3. package/CLAUDE.md +140 -22
  4. package/dist/client/index.d.ts +0 -16
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/config/adopt-better-auth-tables.d.ts +4 -49
  7. package/dist/config/adopt-better-auth-tables.d.ts.map +1 -1
  8. package/dist/config/adopt-better-auth-tables.js +4 -51
  9. package/dist/config/adopt-better-auth-tables.js.map +1 -1
  10. package/dist/config/derive-auth-lists.d.ts +42 -37
  11. package/dist/config/derive-auth-lists.d.ts.map +1 -1
  12. package/dist/config/derive-auth-lists.js +579 -223
  13. package/dist/config/derive-auth-lists.js.map +1 -1
  14. package/dist/config/index.d.ts +0 -3
  15. package/dist/config/index.d.ts.map +1 -1
  16. package/dist/config/index.js +4 -34
  17. package/dist/config/index.js.map +1 -1
  18. package/dist/config/plugin.d.ts.map +1 -1
  19. package/dist/config/plugin.js +20 -90
  20. package/dist/config/plugin.js.map +1 -1
  21. package/dist/config/types.d.ts +89 -67
  22. package/dist/config/types.d.ts.map +1 -1
  23. package/dist/index.d.ts +1 -9
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -13
  26. package/dist/index.js.map +1 -1
  27. package/dist/lists/index.d.ts +9 -5
  28. package/dist/lists/index.d.ts.map +1 -1
  29. package/dist/lists/index.js +9 -6
  30. package/dist/lists/index.js.map +1 -1
  31. package/dist/mcp/better-auth.d.ts +6 -19
  32. package/dist/mcp/better-auth.d.ts.map +1 -1
  33. package/dist/mcp/better-auth.js +6 -20
  34. package/dist/mcp/better-auth.js.map +1 -1
  35. package/dist/plugins/index.d.ts +4 -4
  36. package/dist/plugins/index.d.ts.map +1 -1
  37. package/dist/plugins/index.js +4 -4
  38. package/dist/plugins/index.js.map +1 -1
  39. package/dist/runtime/types.d.ts +2 -9
  40. package/dist/runtime/types.d.ts.map +1 -1
  41. package/dist/runtime/types.js +0 -4
  42. package/dist/runtime/types.js.map +1 -1
  43. package/dist/server/index.d.ts +5 -12
  44. package/dist/server/index.d.ts.map +1 -1
  45. package/dist/server/index.js +14 -62
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/ui/components/ForgotPasswordForm.d.ts +0 -3
  48. package/dist/ui/components/ForgotPasswordForm.d.ts.map +1 -1
  49. package/dist/ui/components/ForgotPasswordForm.js +0 -3
  50. package/dist/ui/components/ForgotPasswordForm.js.map +1 -1
  51. package/dist/ui/components/ResetPasswordForm.d.ts +0 -3
  52. package/dist/ui/components/ResetPasswordForm.d.ts.map +1 -1
  53. package/dist/ui/components/ResetPasswordForm.js +0 -5
  54. package/dist/ui/components/ResetPasswordForm.js.map +1 -1
  55. package/dist/ui/components/SignInForm.d.ts +0 -3
  56. package/dist/ui/components/SignInForm.d.ts.map +1 -1
  57. package/dist/ui/components/SignInForm.js +0 -4
  58. package/dist/ui/components/SignInForm.js.map +1 -1
  59. package/dist/ui/components/SignUpForm.d.ts +0 -3
  60. package/dist/ui/components/SignUpForm.d.ts.map +1 -1
  61. package/dist/ui/components/SignUpForm.js +0 -5
  62. package/dist/ui/components/SignUpForm.js.map +1 -1
  63. package/dist/ui/lib/clean-error-message.d.ts +3 -6
  64. package/dist/ui/lib/clean-error-message.d.ts.map +1 -1
  65. package/dist/ui/lib/clean-error-message.js +3 -6
  66. package/dist/ui/lib/clean-error-message.js.map +1 -1
  67. package/dist/ui/types.d.ts +2 -15
  68. package/dist/ui/types.d.ts.map +1 -1
  69. package/dist/ui/types.js +2 -7
  70. package/dist/ui/types.js.map +1 -1
  71. package/package.json +12 -5
  72. package/src/client/index.ts +0 -17
  73. package/src/config/adopt-better-auth-tables.ts +4 -51
  74. package/src/config/derive-auth-lists.ts +685 -252
  75. package/src/config/index.ts +4 -34
  76. package/src/config/plugin.ts +21 -91
  77. package/src/config/types.ts +90 -67
  78. package/src/index.ts +3 -14
  79. package/src/lists/index.ts +17 -5
  80. package/src/mcp/better-auth.ts +6 -25
  81. package/src/plugins/index.ts +4 -5
  82. package/src/runtime/types.ts +2 -10
  83. package/src/server/index.ts +17 -70
  84. package/src/ui/components/ForgotPasswordForm.tsx +0 -3
  85. package/src/ui/components/ResetPasswordForm.tsx +0 -5
  86. package/src/ui/components/SignInForm.tsx +0 -4
  87. package/src/ui/components/SignUpForm.tsx +0 -5
  88. package/src/ui/lib/clean-error-message.ts +3 -6
  89. package/src/ui/types.ts +2 -15
  90. package/tests/auth-lists-drift.test.ts +480 -0
  91. package/tests/config.test.ts +3 -0
  92. package/tests/credential-field-read-deny-e2e.test.ts +295 -0
  93. package/tests/derive-auth-lists.test.ts +263 -6
  94. package/tests/generated-fk-shape.test.ts +432 -9
  95. package/tests/mcp-oauth-cascade-e2e.test.ts +267 -0
  96. package/tests/plugin-derived-keys.test.ts +39 -0
  97. package/tests/plugin-table-derivation.test.ts +270 -0
  98. package/tsconfig.tsbuildinfo +1 -1
  99. package/dist/server/schema-converter.d.ts +0 -81
  100. package/dist/server/schema-converter.d.ts.map +0 -1
  101. package/dist/server/schema-converter.js +0 -157
  102. package/dist/server/schema-converter.js.map +0 -1
  103. package/src/server/schema-converter.ts +0 -243
  104. package/tests/schema-converter.test.ts +0 -399
@@ -1,399 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { convertTableToList, convertBetterAuthSchema } from '../src/server/schema-converter.js'
3
-
4
- describe('convertTableToList', () => {
5
- it('should convert string fields', () => {
6
- const tableSchema = {
7
- modelName: 'TestTable',
8
- fields: {
9
- name: { type: 'string', required: true },
10
- bio: { type: 'string' },
11
- },
12
- }
13
-
14
- const listConfig = convertTableToList('test_table', tableSchema)
15
-
16
- expect(listConfig.fields).toHaveProperty('name')
17
- expect(listConfig.fields).toHaveProperty('bio')
18
- expect(listConfig.fields.name.type).toBe('text')
19
- expect(listConfig.fields.name.validation?.isRequired).toBe(true)
20
- expect(listConfig.fields.bio.validation?.isRequired).toBeUndefined()
21
- })
22
-
23
- it('should convert number fields', () => {
24
- const tableSchema = {
25
- modelName: 'TestTable',
26
- fields: {
27
- age: { type: 'number', required: true },
28
- score: { type: 'number', defaultValue: 0 },
29
- },
30
- }
31
-
32
- const listConfig = convertTableToList('test_table', tableSchema)
33
-
34
- expect(listConfig.fields.age.type).toBe('integer')
35
- expect(listConfig.fields.age.validation?.isRequired).toBe(true)
36
- expect(listConfig.fields.score.defaultValue).toBe(0)
37
- })
38
-
39
- it('should convert a number field with bigint: true to a bigInt field (issue #917)', () => {
40
- const tableSchema = {
41
- modelName: 'TestTable',
42
- fields: {
43
- lastRequest: { type: 'number', required: true, bigint: true },
44
- },
45
- }
46
-
47
- const listConfig = convertTableToList('test_table', tableSchema)
48
-
49
- expect(listConfig.fields.lastRequest.type).toBe('bigInt')
50
- expect(listConfig.fields.lastRequest.validation?.isRequired).toBe(true)
51
- })
52
-
53
- it('should keep a number field with bigint: false as integer', () => {
54
- const tableSchema = {
55
- modelName: 'TestTable',
56
- fields: {
57
- age: { type: 'number', bigint: false },
58
- },
59
- }
60
-
61
- const listConfig = convertTableToList('test_table', tableSchema)
62
-
63
- expect(listConfig.fields.age.type).toBe('integer')
64
- })
65
-
66
- it('should pass defaultValue through on a bigint number field', () => {
67
- const tableSchema = {
68
- modelName: 'TestTable',
69
- fields: {
70
- lastRequest: { type: 'number', bigint: true, defaultValue: 0 },
71
- },
72
- }
73
-
74
- const listConfig = convertTableToList('test_table', tableSchema)
75
-
76
- expect(listConfig.fields.lastRequest.type).toBe('bigInt')
77
- expect(listConfig.fields.lastRequest.defaultValue).toBe(0)
78
- })
79
-
80
- it('should convert boolean fields', () => {
81
- const tableSchema = {
82
- modelName: 'TestTable',
83
- fields: {
84
- active: { type: 'boolean', defaultValue: true },
85
- verified: { type: 'boolean' },
86
- },
87
- }
88
-
89
- const listConfig = convertTableToList('test_table', tableSchema)
90
-
91
- expect(listConfig.fields.active.type).toBe('checkbox')
92
- expect(listConfig.fields.active.defaultValue).toBe(true)
93
- expect(listConfig.fields.verified.type).toBe('checkbox')
94
- })
95
-
96
- it('should convert date fields', () => {
97
- const tableSchema = {
98
- modelName: 'TestTable',
99
- fields: {
100
- expiresAt: { type: 'date' },
101
- createdOn: { type: 'date', defaultValue: 'now' },
102
- },
103
- }
104
-
105
- const listConfig = convertTableToList('test_table', tableSchema)
106
-
107
- expect(listConfig.fields.expiresAt.type).toBe('timestamp')
108
- expect(listConfig.fields.createdOn.type).toBe('timestamp')
109
- expect(listConfig.fields.createdOn.defaultValue).toEqual({ kind: 'now' })
110
- })
111
-
112
- it('should handle unique fields', () => {
113
- const tableSchema = {
114
- modelName: 'TestTable',
115
- fields: {
116
- email: { type: 'string', unique: true },
117
- },
118
- }
119
-
120
- const listConfig = convertTableToList('test_table', tableSchema)
121
-
122
- expect(listConfig.fields.email.isIndexed).toBe('unique')
123
- })
124
-
125
- it('should skip system fields', () => {
126
- const tableSchema = {
127
- modelName: 'TestTable',
128
- fields: {
129
- id: { type: 'string', required: true },
130
- name: { type: 'string' },
131
- createdAt: { type: 'date' },
132
- updatedAt: { type: 'date' },
133
- },
134
- }
135
-
136
- const listConfig = convertTableToList('test_table', tableSchema)
137
-
138
- // System fields should be skipped
139
- expect(listConfig.fields).not.toHaveProperty('id')
140
- expect(listConfig.fields).not.toHaveProperty('createdAt')
141
- expect(listConfig.fields).not.toHaveProperty('updatedAt')
142
- // Regular fields should be included
143
- expect(listConfig.fields).toHaveProperty('name')
144
- })
145
-
146
- it('should handle reference fields as text', () => {
147
- const tableSchema = {
148
- modelName: 'TestTable',
149
- fields: {
150
- userId: {
151
- type: 'string',
152
- required: true,
153
- references: {
154
- model: 'User',
155
- field: 'id',
156
- },
157
- },
158
- },
159
- }
160
-
161
- const listConfig = convertTableToList('test_table', tableSchema)
162
-
163
- expect(listConfig.fields).toHaveProperty('userId')
164
- expect(listConfig.fields.userId.type).toBe('text')
165
- expect(listConfig.fields.userId.validation?.isRequired).toBe(true)
166
- })
167
-
168
- it('should handle unknown field types', () => {
169
- const tableSchema = {
170
- modelName: 'TestTable',
171
- fields: {
172
- customField: { type: 'unknown-type' },
173
- },
174
- }
175
-
176
- const listConfig = convertTableToList('test_table', tableSchema)
177
-
178
- // Should default to text
179
- expect(listConfig.fields.customField.type).toBe('text')
180
- })
181
-
182
- it('should ship the User table closed (no access control) per ADR-0013', () => {
183
- const tableSchema = {
184
- modelName: 'User',
185
- fields: {
186
- name: { type: 'string' },
187
- },
188
- }
189
-
190
- const listConfig = convertTableToList('user', tableSchema)
191
-
192
- expect(listConfig.access).toBeUndefined()
193
- })
194
-
195
- it('should ship the Session table closed (no access control) per ADR-0013', () => {
196
- const tableSchema = {
197
- modelName: 'Session',
198
- fields: {
199
- token: { type: 'string' },
200
- },
201
- }
202
-
203
- const listConfig = convertTableToList('session', tableSchema)
204
-
205
- expect(listConfig.access).toBeUndefined()
206
- })
207
-
208
- it('should ship the Verification table closed (no access control) per ADR-0013', () => {
209
- const tableSchema = {
210
- modelName: 'Verification',
211
- fields: {
212
- value: { type: 'string' },
213
- },
214
- }
215
-
216
- const listConfig = convertTableToList('verification', tableSchema)
217
-
218
- expect(listConfig.access).toBeUndefined()
219
- })
220
-
221
- it('should ship unknown tables closed (no access control)', () => {
222
- const tableSchema = {
223
- modelName: 'UnknownTable',
224
- fields: {
225
- data: { type: 'string' },
226
- },
227
- }
228
-
229
- const listConfig = convertTableToList('unknown_table', tableSchema)
230
-
231
- expect(listConfig.access).toBeUndefined()
232
- })
233
-
234
- it('should ship OAuth application table closed (no access control)', () => {
235
- const tableSchema = {
236
- modelName: 'OAuthApplication',
237
- fields: {
238
- clientId: { type: 'string', required: true },
239
- userId: { type: 'string', required: true },
240
- },
241
- }
242
-
243
- const listConfig = convertTableToList('oauthapplication', tableSchema)
244
-
245
- expect(listConfig.access).toBeUndefined()
246
- })
247
- })
248
-
249
- describe('convertBetterAuthSchema', () => {
250
- it('should convert multiple tables', () => {
251
- const schema = {
252
- user: {
253
- modelName: 'User',
254
- fields: {
255
- name: { type: 'string' },
256
- },
257
- },
258
- session: {
259
- modelName: 'Session',
260
- fields: {
261
- token: { type: 'string' },
262
- },
263
- },
264
- }
265
-
266
- const lists = convertBetterAuthSchema(schema)
267
-
268
- expect(lists).toHaveProperty('User')
269
- expect(lists).toHaveProperty('Session')
270
- })
271
-
272
- it('should use modelName for list key', () => {
273
- const schema = {
274
- custom_table: {
275
- modelName: 'CustomTable',
276
- fields: {
277
- data: { type: 'string' },
278
- },
279
- },
280
- }
281
-
282
- const lists = convertBetterAuthSchema(schema)
283
-
284
- expect(lists).toHaveProperty('CustomTable')
285
- expect(lists).not.toHaveProperty('custom_table')
286
- })
287
-
288
- it('should convert snake_case to PascalCase if no modelName', () => {
289
- const schema = {
290
- oauth_application: {
291
- modelName: '',
292
- fields: {
293
- data: { type: 'string' },
294
- },
295
- },
296
- }
297
-
298
- const lists = convertBetterAuthSchema(schema)
299
-
300
- expect(lists).toHaveProperty('OauthApplication')
301
- })
302
-
303
- it('should handle empty schema', () => {
304
- const lists = convertBetterAuthSchema({})
305
-
306
- expect(lists).toEqual({})
307
- })
308
-
309
- it('should resolve a base-model table against the configured baseModelKeys remap instead of the table name', () => {
310
- const schema = {
311
- user: {
312
- modelName: '',
313
- fields: {
314
- isAnonymous: { type: 'boolean' },
315
- },
316
- },
317
- }
318
-
319
- const lists = convertBetterAuthSchema(schema, { user: 'AuthUser' })
320
-
321
- expect(lists).toHaveProperty('AuthUser')
322
- expect(lists).not.toHaveProperty('User')
323
- expect(lists.AuthUser.fields).toHaveProperty('isAnonymous')
324
- })
325
-
326
- it('should prefer the baseModelKeys remap over an explicit tableSchema.modelName', () => {
327
- const schema = {
328
- user: {
329
- modelName: 'User',
330
- fields: {
331
- isAnonymous: { type: 'boolean' },
332
- },
333
- },
334
- }
335
-
336
- const lists = convertBetterAuthSchema(schema, { user: 'AuthUser' })
337
-
338
- expect(lists).toHaveProperty('AuthUser')
339
- expect(lists).not.toHaveProperty('User')
340
- })
341
-
342
- it('should resolve a rateLimit table (case-insensitively) against the configured baseModelKeys remap (issue #909)', () => {
343
- const schema = {
344
- rateLimit: {
345
- modelName: '',
346
- fields: {
347
- customField: { type: 'boolean' },
348
- },
349
- },
350
- }
351
-
352
- const lists = convertBetterAuthSchema(schema, { rateLimit: 'AuthRateLimit' })
353
-
354
- expect(lists).toHaveProperty('AuthRateLimit')
355
- expect(lists).not.toHaveProperty('RateLimit')
356
- expect(lists.AuthRateLimit.fields).toHaveProperty('customField')
357
- })
358
-
359
- it('should leave non-base tables unaffected by baseModelKeys', () => {
360
- const schema = {
361
- oauth_application: {
362
- modelName: '',
363
- fields: {
364
- data: { type: 'string' },
365
- },
366
- },
367
- }
368
-
369
- const lists = convertBetterAuthSchema(schema, { user: 'AuthUser' })
370
-
371
- expect(lists).toHaveProperty('OauthApplication')
372
- })
373
-
374
- it('should convert complex schema with multiple field types', () => {
375
- const schema = {
376
- test_table: {
377
- modelName: 'TestTable',
378
- fields: {
379
- name: { type: 'string', required: true },
380
- age: { type: 'number', defaultValue: 0 },
381
- active: { type: 'boolean', defaultValue: true },
382
- expiresAt: { type: 'date', defaultValue: 'now' },
383
- userId: {
384
- type: 'string',
385
- references: { model: 'User', field: 'id' },
386
- },
387
- },
388
- },
389
- }
390
-
391
- const lists = convertBetterAuthSchema(schema)
392
-
393
- expect(lists.TestTable.fields).toHaveProperty('name')
394
- expect(lists.TestTable.fields).toHaveProperty('age')
395
- expect(lists.TestTable.fields).toHaveProperty('active')
396
- expect(lists.TestTable.fields).toHaveProperty('expiresAt')
397
- expect(lists.TestTable.fields).toHaveProperty('userId')
398
- })
399
- })