@opensaas/stack-auth 0.1.0 → 0.1.1
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 +11 -0
- package/CLAUDE.md +196 -0
- package/LICENSE +21 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +24 -2
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +16 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +7 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/schema-converter.d.ts +40 -0
- package/dist/server/schema-converter.d.ts.map +1 -0
- package/dist/server/schema-converter.js +250 -0
- package/dist/server/schema-converter.js.map +1 -0
- package/package.json +14 -4
- package/src/config/index.ts +28 -2
- package/src/config/types.ts +19 -1
- package/src/plugins/index.ts +7 -0
- package/src/server/index.ts +3 -0
- package/src/server/schema-converter.ts +299 -0
- package/tests/config.test.ts +270 -0
- package/tests/lists.test.ts +356 -0
- package/tests/schema-converter.test.ts +304 -0
- package/tsconfig.test.json +7 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert Better Auth database schema to OpenSaaS list configs
|
|
3
|
+
* Allows dynamic list generation from Better Auth plugins
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { list } from '@opensaas/stack-core'
|
|
7
|
+
import { text, timestamp, checkbox, integer } from '@opensaas/stack-core/fields'
|
|
8
|
+
import type { ListConfig, FieldConfig } from '@opensaas/stack-core'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Better Auth field attribute structure
|
|
12
|
+
* Inferred from better-auth internal types
|
|
13
|
+
*/
|
|
14
|
+
type BetterAuthFieldAttribute = {
|
|
15
|
+
type: string // 'string' | 'number' | 'boolean' | 'date' | etc.
|
|
16
|
+
required?: boolean
|
|
17
|
+
unique?: boolean
|
|
18
|
+
references?: {
|
|
19
|
+
model: string
|
|
20
|
+
field: string
|
|
21
|
+
onDelete?: 'cascade' | 'set null' | 'restrict'
|
|
22
|
+
}
|
|
23
|
+
defaultValue?: unknown
|
|
24
|
+
returned?: boolean
|
|
25
|
+
input?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Better Auth table schema structure
|
|
30
|
+
*/
|
|
31
|
+
type BetterAuthTableSchema = {
|
|
32
|
+
modelName: string
|
|
33
|
+
fields: Record<string, BetterAuthFieldAttribute>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Convert Better Auth field type to OpenSaaS field config
|
|
38
|
+
*/
|
|
39
|
+
function convertField(
|
|
40
|
+
fieldName: string,
|
|
41
|
+
betterAuthField: BetterAuthFieldAttribute,
|
|
42
|
+
): FieldConfig | null {
|
|
43
|
+
const { type, required, unique, defaultValue, references } = betterAuthField
|
|
44
|
+
|
|
45
|
+
// System fields are auto-generated by OpenSaaS
|
|
46
|
+
if (fieldName === 'id' || fieldName === 'createdAt' || fieldName === 'updatedAt') {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle references (relationships)
|
|
51
|
+
if (references) {
|
|
52
|
+
// Relationships are handled separately
|
|
53
|
+
// Return null here and handle in relationship pass
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Map Better Auth types to OpenSaaS field types
|
|
58
|
+
switch (type) {
|
|
59
|
+
case 'string':
|
|
60
|
+
return text({
|
|
61
|
+
validation: { isRequired: required },
|
|
62
|
+
isIndexed: unique ? 'unique' : undefined,
|
|
63
|
+
defaultValue: typeof defaultValue === 'string' ? defaultValue : undefined,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
case 'number':
|
|
67
|
+
return integer({
|
|
68
|
+
validation: { isRequired: required },
|
|
69
|
+
defaultValue: typeof defaultValue === 'number' ? defaultValue : undefined,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
case 'boolean':
|
|
73
|
+
return checkbox({
|
|
74
|
+
defaultValue: typeof defaultValue === 'boolean' ? defaultValue : false,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
case 'date':
|
|
78
|
+
return timestamp({
|
|
79
|
+
defaultValue: defaultValue === 'now' ? { kind: 'now' } : undefined,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
default:
|
|
83
|
+
// Unknown type - default to text
|
|
84
|
+
console.warn(
|
|
85
|
+
`[stack-auth] Unknown Better Auth field type "${type}" for field "${fieldName}", defaulting to text field`,
|
|
86
|
+
)
|
|
87
|
+
return text({
|
|
88
|
+
validation: { isRequired: required },
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get default access control for auth tables
|
|
95
|
+
* Most auth tables should only be accessible to their owners
|
|
96
|
+
*/
|
|
97
|
+
function getDefaultAccess(tableName: string): ListConfig['access'] {
|
|
98
|
+
const lowerTableName = tableName.toLowerCase()
|
|
99
|
+
|
|
100
|
+
// User table - special access control
|
|
101
|
+
if (lowerTableName === 'user') {
|
|
102
|
+
return {
|
|
103
|
+
operation: {
|
|
104
|
+
query: () => true, // Anyone can query users
|
|
105
|
+
create: () => true, // Anyone can create (sign up)
|
|
106
|
+
update: ({ session, item }) => {
|
|
107
|
+
if (!session) return false
|
|
108
|
+
const userId = (session as { userId?: string }).userId
|
|
109
|
+
const itemId = (item as { id?: string })?.id
|
|
110
|
+
return userId === itemId
|
|
111
|
+
},
|
|
112
|
+
delete: ({ session, item }) => {
|
|
113
|
+
if (!session) return false
|
|
114
|
+
const userId = (session as { userId?: string }).userId
|
|
115
|
+
const itemId = (item as { id?: string })?.id
|
|
116
|
+
return userId === itemId
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Session table
|
|
123
|
+
if (lowerTableName === 'session') {
|
|
124
|
+
return {
|
|
125
|
+
operation: {
|
|
126
|
+
query: ({ session }) => {
|
|
127
|
+
if (!session) return false
|
|
128
|
+
const userId = (session as { userId?: string }).userId
|
|
129
|
+
if (!userId) return false
|
|
130
|
+
return {
|
|
131
|
+
user: { id: { equals: userId } },
|
|
132
|
+
} as Record<string, unknown>
|
|
133
|
+
},
|
|
134
|
+
create: () => true, // Better-auth handles session creation
|
|
135
|
+
update: () => false, // No manual updates
|
|
136
|
+
delete: ({ session, item }) => {
|
|
137
|
+
if (!session) return false
|
|
138
|
+
const userId = (session as { userId?: string }).userId
|
|
139
|
+
const itemUserId = (item as { user?: { id?: string } })?.user?.id
|
|
140
|
+
return userId === itemUserId
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Account table
|
|
147
|
+
if (lowerTableName === 'account') {
|
|
148
|
+
return {
|
|
149
|
+
operation: {
|
|
150
|
+
query: ({ session }) => {
|
|
151
|
+
if (!session) return false
|
|
152
|
+
const userId = (session as { userId?: string }).userId
|
|
153
|
+
if (!userId) return false
|
|
154
|
+
return {
|
|
155
|
+
user: { id: { equals: userId } },
|
|
156
|
+
} as Record<string, unknown>
|
|
157
|
+
},
|
|
158
|
+
create: () => true, // Better-auth handles account creation
|
|
159
|
+
update: ({ session, item }) => {
|
|
160
|
+
if (!session) return false
|
|
161
|
+
const userId = (session as { userId?: string }).userId
|
|
162
|
+
const itemUserId = (item as { user?: { id?: string } })?.user?.id
|
|
163
|
+
return userId === itemUserId
|
|
164
|
+
},
|
|
165
|
+
delete: ({ session, item }) => {
|
|
166
|
+
if (!session) return false
|
|
167
|
+
const userId = (session as { userId?: string }).userId
|
|
168
|
+
const itemUserId = (item as { user?: { id?: string } })?.user?.id
|
|
169
|
+
return userId === itemUserId
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Verification table
|
|
176
|
+
if (lowerTableName === 'verification') {
|
|
177
|
+
return {
|
|
178
|
+
operation: {
|
|
179
|
+
query: () => false, // No public querying
|
|
180
|
+
create: () => true, // Better-auth creates verification tokens
|
|
181
|
+
update: () => false, // No updates
|
|
182
|
+
delete: () => true, // Better-auth deletes used/expired tokens
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// OAuth tables (from MCP/OIDC plugins)
|
|
188
|
+
if (
|
|
189
|
+
lowerTableName === 'oauthapplication' ||
|
|
190
|
+
lowerTableName === 'oauthaccesstoken' ||
|
|
191
|
+
lowerTableName === 'oauthconsent'
|
|
192
|
+
) {
|
|
193
|
+
return {
|
|
194
|
+
operation: {
|
|
195
|
+
query: ({ session }) => {
|
|
196
|
+
if (!session) return false
|
|
197
|
+
const userId = (session as { userId?: string }).userId
|
|
198
|
+
if (!userId) return false
|
|
199
|
+
// Filter by userId if field exists
|
|
200
|
+
return {
|
|
201
|
+
userId: { equals: userId },
|
|
202
|
+
} as Record<string, unknown>
|
|
203
|
+
},
|
|
204
|
+
create: () => true, // Better-auth/plugins handle creation
|
|
205
|
+
update: ({ session, item }) => {
|
|
206
|
+
if (!session) return false
|
|
207
|
+
const userId = (session as { userId?: string }).userId
|
|
208
|
+
const itemUserId = (item as { userId?: string })?.userId
|
|
209
|
+
return userId === itemUserId
|
|
210
|
+
},
|
|
211
|
+
delete: ({ session, item }) => {
|
|
212
|
+
if (!session) return false
|
|
213
|
+
const userId = (session as { userId?: string }).userId
|
|
214
|
+
const itemUserId = (item as { userId?: string })?.userId
|
|
215
|
+
return userId === itemUserId
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Default: restrict all access (safest default)
|
|
222
|
+
return {
|
|
223
|
+
operation: {
|
|
224
|
+
query: () => false,
|
|
225
|
+
create: () => false,
|
|
226
|
+
update: () => false,
|
|
227
|
+
delete: () => false,
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Convert Better Auth table schema to OpenSaaS ListConfig
|
|
234
|
+
*/
|
|
235
|
+
export function convertTableToList(
|
|
236
|
+
tableName: string,
|
|
237
|
+
tableSchema: BetterAuthTableSchema,
|
|
238
|
+
): ListConfig {
|
|
239
|
+
const fields: Record<string, FieldConfig> = {}
|
|
240
|
+
|
|
241
|
+
// First pass: convert regular fields
|
|
242
|
+
for (const [fieldName, fieldAttr] of Object.entries(tableSchema.fields)) {
|
|
243
|
+
const fieldConfig = convertField(fieldName, fieldAttr)
|
|
244
|
+
if (fieldConfig) {
|
|
245
|
+
fields[fieldName] = fieldConfig
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Second pass: add relationships
|
|
250
|
+
// NOTE: Better Auth uses one-way references, but OpenSaaS requires bidirectional relationships
|
|
251
|
+
// We skip relationship conversion for now and rely on the foreign key fields
|
|
252
|
+
// Users can manually add bidirectional relationships if needed by extending the User list
|
|
253
|
+
for (const [fieldName, fieldAttr] of Object.entries(tableSchema.fields)) {
|
|
254
|
+
if (fieldAttr.references) {
|
|
255
|
+
// For now, treat reference fields as regular text fields (foreign keys)
|
|
256
|
+
// This preserves the userId, clientId, etc. fields that Better Auth expects
|
|
257
|
+
if (!fields[fieldName]) {
|
|
258
|
+
fields[fieldName] = text({
|
|
259
|
+
validation: { isRequired: fieldAttr.required },
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Create list config with default access control
|
|
266
|
+
return list({
|
|
267
|
+
fields,
|
|
268
|
+
access: getDefaultAccess(tableName),
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Convert all Better Auth tables to OpenSaaS list configs
|
|
274
|
+
* This is called by withAuth() to generate lists from Better Auth + plugins
|
|
275
|
+
*/
|
|
276
|
+
export function convertBetterAuthSchema(
|
|
277
|
+
tables: Record<string, BetterAuthTableSchema>,
|
|
278
|
+
): Record<string, ListConfig> {
|
|
279
|
+
const lists: Record<string, ListConfig> = {}
|
|
280
|
+
|
|
281
|
+
for (const [tableName, tableSchema] of Object.entries(tables)) {
|
|
282
|
+
// Convert table name to PascalCase for OpenSaaS list key
|
|
283
|
+
const listKey = tableSchema.modelName || toPascalCase(tableName)
|
|
284
|
+
lists[listKey] = convertTableToList(tableName, tableSchema)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return lists
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Convert table name to PascalCase
|
|
292
|
+
* e.g., "oauth_application" -> "OauthApplication"
|
|
293
|
+
*/
|
|
294
|
+
function toPascalCase(str: string): string {
|
|
295
|
+
return str
|
|
296
|
+
.split(/[_-]/)
|
|
297
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
298
|
+
.join('')
|
|
299
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { normalizeAuthConfig, authConfig, withAuth } from '../src/config/index.js'
|
|
3
|
+
import { config, list } from '@opensaas/stack-core'
|
|
4
|
+
import { text } from '@opensaas/stack-core/fields'
|
|
5
|
+
import type { AuthConfig } from '../src/config/types.js'
|
|
6
|
+
|
|
7
|
+
describe('normalizeAuthConfig', () => {
|
|
8
|
+
it('should apply default values for disabled features', () => {
|
|
9
|
+
const result = normalizeAuthConfig({})
|
|
10
|
+
|
|
11
|
+
expect(result.emailAndPassword.enabled).toBe(false)
|
|
12
|
+
expect(result.emailAndPassword.minPasswordLength).toBe(8)
|
|
13
|
+
expect(result.emailVerification.enabled).toBe(false)
|
|
14
|
+
expect(result.passwordReset.enabled).toBe(false)
|
|
15
|
+
expect(result.session.expiresIn).toBe(604800) // 7 days
|
|
16
|
+
expect(result.sessionFields).toEqual(['userId', 'email', 'name'])
|
|
17
|
+
expect(result.socialProviders).toEqual({})
|
|
18
|
+
expect(result.betterAuthPlugins).toEqual([])
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should normalize email and password config', () => {
|
|
22
|
+
const result = normalizeAuthConfig({
|
|
23
|
+
emailAndPassword: {
|
|
24
|
+
enabled: true,
|
|
25
|
+
minPasswordLength: 12,
|
|
26
|
+
requireConfirmation: false,
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
expect(result.emailAndPassword.enabled).toBe(true)
|
|
31
|
+
expect(result.emailAndPassword.minPasswordLength).toBe(12)
|
|
32
|
+
expect(result.emailAndPassword.requireConfirmation).toBe(false)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should apply defaults for partial email and password config', () => {
|
|
36
|
+
const result = normalizeAuthConfig({
|
|
37
|
+
emailAndPassword: { enabled: true },
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
expect(result.emailAndPassword.enabled).toBe(true)
|
|
41
|
+
expect(result.emailAndPassword.minPasswordLength).toBe(8)
|
|
42
|
+
expect(result.emailAndPassword.requireConfirmation).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should normalize email verification config', () => {
|
|
46
|
+
const result = normalizeAuthConfig({
|
|
47
|
+
emailVerification: {
|
|
48
|
+
enabled: true,
|
|
49
|
+
sendOnSignUp: false,
|
|
50
|
+
tokenExpiration: 3600,
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(result.emailVerification.enabled).toBe(true)
|
|
55
|
+
expect(result.emailVerification.sendOnSignUp).toBe(false)
|
|
56
|
+
expect(result.emailVerification.tokenExpiration).toBe(3600)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should normalize password reset config', () => {
|
|
60
|
+
const result = normalizeAuthConfig({
|
|
61
|
+
passwordReset: {
|
|
62
|
+
enabled: true,
|
|
63
|
+
tokenExpiration: 7200,
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
expect(result.passwordReset.enabled).toBe(true)
|
|
68
|
+
expect(result.passwordReset.tokenExpiration).toBe(7200)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should normalize session config', () => {
|
|
72
|
+
const result = normalizeAuthConfig({
|
|
73
|
+
session: {
|
|
74
|
+
expiresIn: 86400, // 1 day
|
|
75
|
+
updateAge: false,
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
expect(result.session.expiresIn).toBe(86400)
|
|
80
|
+
expect(result.session.updateAge).toBe(false)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should normalize custom session fields', () => {
|
|
84
|
+
const result = normalizeAuthConfig({
|
|
85
|
+
sessionFields: ['userId', 'email', 'role'],
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
expect(result.sessionFields).toEqual(['userId', 'email', 'role'])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should include social providers', () => {
|
|
92
|
+
const result = normalizeAuthConfig({
|
|
93
|
+
socialProviders: {
|
|
94
|
+
github: {
|
|
95
|
+
clientId: 'github-id',
|
|
96
|
+
clientSecret: 'github-secret',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
expect(result.socialProviders).toEqual({
|
|
102
|
+
github: {
|
|
103
|
+
clientId: 'github-id',
|
|
104
|
+
clientSecret: 'github-secret',
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should include extendUserList config', () => {
|
|
110
|
+
const result = normalizeAuthConfig({
|
|
111
|
+
extendUserList: {
|
|
112
|
+
fields: {
|
|
113
|
+
role: text(),
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
expect(result.extendUserList).toHaveProperty('fields')
|
|
119
|
+
expect(result.extendUserList.fields).toHaveProperty('role')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('should include custom sendEmail function', () => {
|
|
123
|
+
const mockSendEmail = async () => {}
|
|
124
|
+
|
|
125
|
+
const result = normalizeAuthConfig({
|
|
126
|
+
sendEmail: mockSendEmail,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
expect(result.sendEmail).toBe(mockSendEmail)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should provide default sendEmail that logs to console', () => {
|
|
133
|
+
const result = normalizeAuthConfig({})
|
|
134
|
+
|
|
135
|
+
expect(typeof result.sendEmail).toBe('function')
|
|
136
|
+
// Default sendEmail should be a function that accepts email params
|
|
137
|
+
expect(result.sendEmail.length).toBe(1)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should include betterAuthPlugins', () => {
|
|
141
|
+
const mockPlugin = { id: 'test-plugin' }
|
|
142
|
+
|
|
143
|
+
const result = normalizeAuthConfig({
|
|
144
|
+
betterAuthPlugins: [mockPlugin],
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
expect(result.betterAuthPlugins).toEqual([mockPlugin])
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
describe('authConfig', () => {
|
|
152
|
+
it('should return the input config unchanged', () => {
|
|
153
|
+
const input: AuthConfig = {
|
|
154
|
+
emailAndPassword: { enabled: true },
|
|
155
|
+
sessionFields: ['userId', 'email'],
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const result = authConfig(input)
|
|
159
|
+
|
|
160
|
+
expect(result).toEqual(input)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('should work with empty config', () => {
|
|
164
|
+
const result = authConfig({})
|
|
165
|
+
|
|
166
|
+
expect(result).toEqual({})
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
describe('withAuth', () => {
|
|
171
|
+
it('should merge auth lists into opensaas config', () => {
|
|
172
|
+
const baseConfig = config({
|
|
173
|
+
db: {
|
|
174
|
+
provider: 'sqlite',
|
|
175
|
+
url: 'file:./test.db',
|
|
176
|
+
},
|
|
177
|
+
lists: {
|
|
178
|
+
Post: list({
|
|
179
|
+
fields: {
|
|
180
|
+
title: text(),
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
const result = withAuth(baseConfig, authConfig({}))
|
|
187
|
+
|
|
188
|
+
// Should have both user lists and auth lists
|
|
189
|
+
expect(result.lists).toHaveProperty('Post')
|
|
190
|
+
expect(result.lists).toHaveProperty('User')
|
|
191
|
+
expect(result.lists).toHaveProperty('Session')
|
|
192
|
+
expect(result.lists).toHaveProperty('Account')
|
|
193
|
+
expect(result.lists).toHaveProperty('Verification')
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('should preserve database config', () => {
|
|
197
|
+
const baseConfig = config({
|
|
198
|
+
db: {
|
|
199
|
+
provider: 'postgresql',
|
|
200
|
+
url: 'postgresql://test',
|
|
201
|
+
},
|
|
202
|
+
lists: {},
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const result = withAuth(baseConfig, authConfig({}))
|
|
206
|
+
|
|
207
|
+
expect(result.db.provider).toBe('postgresql')
|
|
208
|
+
expect(result.db.url).toBe('postgresql://test')
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('should attach normalized auth config internally', () => {
|
|
212
|
+
const baseConfig = config({
|
|
213
|
+
db: { provider: 'sqlite', url: 'file:./test.db' },
|
|
214
|
+
lists: {},
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
const result = withAuth(
|
|
218
|
+
baseConfig,
|
|
219
|
+
authConfig({
|
|
220
|
+
emailAndPassword: { enabled: true },
|
|
221
|
+
}),
|
|
222
|
+
) as typeof baseConfig & { __authConfig?: unknown }
|
|
223
|
+
|
|
224
|
+
expect(result.__authConfig).toBeDefined()
|
|
225
|
+
expect(
|
|
226
|
+
(result.__authConfig as { emailAndPassword: { enabled: boolean } }).emailAndPassword.enabled,
|
|
227
|
+
).toBe(true)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should handle empty lists in base config', () => {
|
|
231
|
+
const baseConfig = config({
|
|
232
|
+
db: { provider: 'sqlite', url: 'file:./test.db' },
|
|
233
|
+
lists: {},
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
const result = withAuth(baseConfig, authConfig({}))
|
|
237
|
+
|
|
238
|
+
expect(result.lists).toHaveProperty('User')
|
|
239
|
+
expect(result.lists).toHaveProperty('Session')
|
|
240
|
+
expect(result.lists).toHaveProperty('Account')
|
|
241
|
+
expect(result.lists).toHaveProperty('Verification')
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('should extend User list with custom fields', () => {
|
|
245
|
+
const baseConfig = config({
|
|
246
|
+
db: { provider: 'sqlite', url: 'file:./test.db' },
|
|
247
|
+
lists: {},
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
const result = withAuth(
|
|
251
|
+
baseConfig,
|
|
252
|
+
authConfig({
|
|
253
|
+
extendUserList: {
|
|
254
|
+
fields: {
|
|
255
|
+
role: text(),
|
|
256
|
+
company: text(),
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
}),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
const userList = result.lists.User
|
|
263
|
+
expect(userList).toBeDefined()
|
|
264
|
+
expect(userList.fields).toHaveProperty('role')
|
|
265
|
+
expect(userList.fields).toHaveProperty('company')
|
|
266
|
+
// Should also have base auth fields
|
|
267
|
+
expect(userList.fields).toHaveProperty('email')
|
|
268
|
+
expect(userList.fields).toHaveProperty('name')
|
|
269
|
+
})
|
|
270
|
+
})
|