@delmaredigital/payload-better-auth 0.3.8 → 0.3.10

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 (206) hide show
  1. package/dist/adapter/collections.d.ts +0 -1
  2. package/dist/adapter/collections.js +0 -2
  3. package/dist/adapter/index.d.ts +0 -1
  4. package/dist/adapter/index.js +0 -2
  5. package/dist/components/BeforeLogin.d.ts +0 -1
  6. package/dist/components/BeforeLogin.js +0 -2
  7. package/dist/components/LoginView.d.ts +0 -1
  8. package/dist/components/LoginView.js +0 -2
  9. package/dist/components/LoginViewWrapper.d.ts +0 -1
  10. package/dist/components/LoginViewWrapper.js +0 -2
  11. package/dist/components/LogoutButton.d.ts +0 -1
  12. package/dist/components/LogoutButton.js +0 -2
  13. package/dist/components/PasskeyRegisterButton.d.ts +0 -1
  14. package/dist/components/PasskeyRegisterButton.js +0 -2
  15. package/dist/components/PasskeySignInButton.d.ts +0 -1
  16. package/dist/components/PasskeySignInButton.js +0 -2
  17. package/dist/components/auth/ForgotPasswordView.d.ts +0 -1
  18. package/dist/components/auth/ForgotPasswordView.js +0 -2
  19. package/dist/components/auth/ResetPasswordView.d.ts +0 -1
  20. package/dist/components/auth/ResetPasswordView.js +0 -2
  21. package/dist/components/auth/index.d.ts +0 -1
  22. package/dist/components/auth/index.js +0 -2
  23. package/dist/components/management/ApiKeysManagementClient.d.ts +0 -1
  24. package/dist/components/management/ApiKeysManagementClient.js +0 -2
  25. package/dist/components/management/PasskeysManagementClient.d.ts +0 -1
  26. package/dist/components/management/PasskeysManagementClient.js +0 -2
  27. package/dist/components/management/SecurityNavLinks.d.ts +0 -1
  28. package/dist/components/management/SecurityNavLinks.js +0 -2
  29. package/dist/components/management/TwoFactorManagementClient.d.ts +0 -1
  30. package/dist/components/management/TwoFactorManagementClient.js +0 -2
  31. package/dist/components/management/index.d.ts +0 -1
  32. package/dist/components/management/index.js +0 -2
  33. package/dist/components/management/views/ApiKeysView.d.ts +0 -1
  34. package/dist/components/management/views/ApiKeysView.js +0 -2
  35. package/dist/components/management/views/PasskeysView.d.ts +0 -1
  36. package/dist/components/management/views/PasskeysView.js +0 -2
  37. package/dist/components/management/views/TwoFactorView.d.ts +0 -1
  38. package/dist/components/management/views/TwoFactorView.js +0 -2
  39. package/dist/components/management/views/index.d.ts +0 -1
  40. package/dist/components/management/views/index.js +0 -2
  41. package/dist/components/twoFactor/TwoFactorSetupView.d.ts +0 -1
  42. package/dist/components/twoFactor/TwoFactorSetupView.js +0 -2
  43. package/dist/components/twoFactor/TwoFactorVerifyView.d.ts +0 -1
  44. package/dist/components/twoFactor/TwoFactorVerifyView.js +0 -2
  45. package/dist/components/twoFactor/index.d.ts +0 -1
  46. package/dist/components/twoFactor/index.js +0 -2
  47. package/dist/exports/client.d.ts +0 -1
  48. package/dist/exports/client.js +0 -2
  49. package/dist/exports/components.d.ts +0 -1
  50. package/dist/exports/components.js +0 -2
  51. package/dist/exports/management.d.ts +0 -1
  52. package/dist/exports/management.js +0 -2
  53. package/dist/exports/rsc.d.ts +0 -1
  54. package/dist/exports/rsc.js +0 -2
  55. package/dist/generated-types.d.ts +0 -1
  56. package/dist/generated-types.js +0 -2
  57. package/dist/index.d.ts +0 -1
  58. package/dist/index.js +0 -2
  59. package/dist/plugin/index.d.ts +0 -1
  60. package/dist/plugin/index.js +0 -2
  61. package/dist/scripts/generate-types.d.ts +0 -1
  62. package/dist/scripts/generate-types.js +0 -2
  63. package/dist/types/apiKey.d.ts +0 -1
  64. package/dist/types/apiKey.js +0 -2
  65. package/dist/types/betterAuth.d.ts +0 -1
  66. package/dist/types/betterAuth.js +0 -2
  67. package/dist/utils/access.d.ts +0 -1
  68. package/dist/utils/access.js +0 -2
  69. package/dist/utils/apiKeyAccess.d.ts +0 -1
  70. package/dist/utils/apiKeyAccess.js +0 -2
  71. package/dist/utils/betterAuthDefaults.d.ts +0 -1
  72. package/dist/utils/betterAuthDefaults.js +0 -2
  73. package/dist/utils/detectAuthConfig.d.ts +0 -1
  74. package/dist/utils/detectAuthConfig.js +0 -2
  75. package/dist/utils/detectEnabledPlugins.d.ts +0 -1
  76. package/dist/utils/detectEnabledPlugins.js +0 -2
  77. package/dist/utils/firstUserAdmin.d.ts +0 -1
  78. package/dist/utils/firstUserAdmin.js +0 -2
  79. package/dist/utils/generateScopes.d.ts +0 -1
  80. package/dist/utils/generateScopes.js +0 -2
  81. package/dist/utils/session.d.ts +0 -1
  82. package/dist/utils/session.js +0 -2
  83. package/package.json +34 -91
  84. package/dist/adapter/collections.d.ts.map +0 -1
  85. package/dist/adapter/collections.js.map +0 -1
  86. package/dist/adapter/index.d.ts.map +0 -1
  87. package/dist/adapter/index.js.map +0 -1
  88. package/dist/components/BeforeLogin.d.ts.map +0 -1
  89. package/dist/components/BeforeLogin.js.map +0 -1
  90. package/dist/components/LoginView.d.ts.map +0 -1
  91. package/dist/components/LoginView.js.map +0 -1
  92. package/dist/components/LoginViewWrapper.d.ts.map +0 -1
  93. package/dist/components/LoginViewWrapper.js.map +0 -1
  94. package/dist/components/LogoutButton.d.ts.map +0 -1
  95. package/dist/components/LogoutButton.js.map +0 -1
  96. package/dist/components/PasskeyRegisterButton.d.ts.map +0 -1
  97. package/dist/components/PasskeyRegisterButton.js.map +0 -1
  98. package/dist/components/PasskeySignInButton.d.ts.map +0 -1
  99. package/dist/components/PasskeySignInButton.js.map +0 -1
  100. package/dist/components/auth/ForgotPasswordView.d.ts.map +0 -1
  101. package/dist/components/auth/ForgotPasswordView.js.map +0 -1
  102. package/dist/components/auth/ResetPasswordView.d.ts.map +0 -1
  103. package/dist/components/auth/ResetPasswordView.js.map +0 -1
  104. package/dist/components/auth/index.d.ts.map +0 -1
  105. package/dist/components/auth/index.js.map +0 -1
  106. package/dist/components/management/ApiKeysManagementClient.d.ts.map +0 -1
  107. package/dist/components/management/ApiKeysManagementClient.js.map +0 -1
  108. package/dist/components/management/PasskeysManagementClient.d.ts.map +0 -1
  109. package/dist/components/management/PasskeysManagementClient.js.map +0 -1
  110. package/dist/components/management/SecurityNavLinks.d.ts.map +0 -1
  111. package/dist/components/management/SecurityNavLinks.js.map +0 -1
  112. package/dist/components/management/TwoFactorManagementClient.d.ts.map +0 -1
  113. package/dist/components/management/TwoFactorManagementClient.js.map +0 -1
  114. package/dist/components/management/index.d.ts.map +0 -1
  115. package/dist/components/management/index.js.map +0 -1
  116. package/dist/components/management/views/ApiKeysView.d.ts.map +0 -1
  117. package/dist/components/management/views/ApiKeysView.js.map +0 -1
  118. package/dist/components/management/views/PasskeysView.d.ts.map +0 -1
  119. package/dist/components/management/views/PasskeysView.js.map +0 -1
  120. package/dist/components/management/views/TwoFactorView.d.ts.map +0 -1
  121. package/dist/components/management/views/TwoFactorView.js.map +0 -1
  122. package/dist/components/management/views/index.d.ts.map +0 -1
  123. package/dist/components/management/views/index.js.map +0 -1
  124. package/dist/components/twoFactor/TwoFactorSetupView.d.ts.map +0 -1
  125. package/dist/components/twoFactor/TwoFactorSetupView.js.map +0 -1
  126. package/dist/components/twoFactor/TwoFactorVerifyView.d.ts.map +0 -1
  127. package/dist/components/twoFactor/TwoFactorVerifyView.js.map +0 -1
  128. package/dist/components/twoFactor/index.d.ts.map +0 -1
  129. package/dist/components/twoFactor/index.js.map +0 -1
  130. package/dist/exports/client.d.ts.map +0 -1
  131. package/dist/exports/client.js.map +0 -1
  132. package/dist/exports/components.d.ts.map +0 -1
  133. package/dist/exports/components.js.map +0 -1
  134. package/dist/exports/management.d.ts.map +0 -1
  135. package/dist/exports/management.js.map +0 -1
  136. package/dist/exports/rsc.d.ts.map +0 -1
  137. package/dist/exports/rsc.js.map +0 -1
  138. package/dist/generated-types.d.ts.map +0 -1
  139. package/dist/generated-types.js.map +0 -1
  140. package/dist/index.d.ts.map +0 -1
  141. package/dist/index.js.map +0 -1
  142. package/dist/plugin/index.d.ts.map +0 -1
  143. package/dist/plugin/index.js.map +0 -1
  144. package/dist/scripts/generate-types.d.ts.map +0 -1
  145. package/dist/scripts/generate-types.js.map +0 -1
  146. package/dist/types/apiKey.d.ts.map +0 -1
  147. package/dist/types/apiKey.js.map +0 -1
  148. package/dist/types/betterAuth.d.ts.map +0 -1
  149. package/dist/types/betterAuth.js.map +0 -1
  150. package/dist/utils/access.d.ts.map +0 -1
  151. package/dist/utils/access.js.map +0 -1
  152. package/dist/utils/apiKeyAccess.d.ts.map +0 -1
  153. package/dist/utils/apiKeyAccess.js.map +0 -1
  154. package/dist/utils/betterAuthDefaults.d.ts.map +0 -1
  155. package/dist/utils/betterAuthDefaults.js.map +0 -1
  156. package/dist/utils/detectAuthConfig.d.ts.map +0 -1
  157. package/dist/utils/detectAuthConfig.js.map +0 -1
  158. package/dist/utils/detectEnabledPlugins.d.ts.map +0 -1
  159. package/dist/utils/detectEnabledPlugins.js.map +0 -1
  160. package/dist/utils/firstUserAdmin.d.ts.map +0 -1
  161. package/dist/utils/firstUserAdmin.js.map +0 -1
  162. package/dist/utils/generateScopes.d.ts.map +0 -1
  163. package/dist/utils/generateScopes.js.map +0 -1
  164. package/dist/utils/session.d.ts.map +0 -1
  165. package/dist/utils/session.js.map +0 -1
  166. package/src/adapter/collections.ts +0 -621
  167. package/src/adapter/index.ts +0 -712
  168. package/src/components/BeforeLogin.tsx +0 -39
  169. package/src/components/LoginView.tsx +0 -1516
  170. package/src/components/LoginViewWrapper.tsx +0 -35
  171. package/src/components/LogoutButton.tsx +0 -58
  172. package/src/components/PasskeyRegisterButton.tsx +0 -105
  173. package/src/components/PasskeySignInButton.tsx +0 -96
  174. package/src/components/auth/ForgotPasswordView.tsx +0 -274
  175. package/src/components/auth/ResetPasswordView.tsx +0 -331
  176. package/src/components/auth/index.ts +0 -8
  177. package/src/components/management/ApiKeysManagementClient.tsx +0 -988
  178. package/src/components/management/PasskeysManagementClient.tsx +0 -409
  179. package/src/components/management/SecurityNavLinks.tsx +0 -117
  180. package/src/components/management/TwoFactorManagementClient.tsx +0 -560
  181. package/src/components/management/index.ts +0 -20
  182. package/src/components/management/views/ApiKeysView.tsx +0 -57
  183. package/src/components/management/views/PasskeysView.tsx +0 -42
  184. package/src/components/management/views/TwoFactorView.tsx +0 -42
  185. package/src/components/management/views/index.ts +0 -10
  186. package/src/components/twoFactor/TwoFactorSetupView.tsx +0 -515
  187. package/src/components/twoFactor/TwoFactorVerifyView.tsx +0 -238
  188. package/src/components/twoFactor/index.ts +0 -8
  189. package/src/exports/client.ts +0 -77
  190. package/src/exports/components.ts +0 -30
  191. package/src/exports/management.ts +0 -25
  192. package/src/exports/rsc.ts +0 -11
  193. package/src/generated-types.ts +0 -269
  194. package/src/index.ts +0 -135
  195. package/src/plugin/index.ts +0 -834
  196. package/src/scripts/generate-types.ts +0 -269
  197. package/src/types/apiKey.ts +0 -63
  198. package/src/types/betterAuth.ts +0 -253
  199. package/src/utils/access.ts +0 -410
  200. package/src/utils/apiKeyAccess.ts +0 -443
  201. package/src/utils/betterAuthDefaults.ts +0 -102
  202. package/src/utils/detectAuthConfig.ts +0 -47
  203. package/src/utils/detectEnabledPlugins.ts +0 -69
  204. package/src/utils/firstUserAdmin.ts +0 -164
  205. package/src/utils/generateScopes.ts +0 -150
  206. package/src/utils/session.ts +0 -91
@@ -1,621 +0,0 @@
1
- /**
2
- * Auto-generate Payload collections from Better Auth schema
3
- *
4
- * @packageDocumentation
5
- */
6
-
7
- import type { Config, CollectionConfig, Field, Plugin, CollectionBeforeChangeHook } from 'payload'
8
- import type { BetterAuthOptions } from 'better-auth'
9
- import { getAuthTables } from 'better-auth/db'
10
- import type { FirstUserAdminOptions } from '../utils/firstUserAdmin.js'
11
- import { isAdmin } from '../utils/access.js'
12
-
13
- export type { FirstUserAdminOptions }
14
-
15
- export type BetterAuthCollectionsOptions = {
16
- /**
17
- * Better Auth options. Pass the same options you use for betterAuth().
18
- * The plugin reads the schema to generate collections.
19
- */
20
- betterAuthOptions?: BetterAuthOptions
21
-
22
- /**
23
- * Collections to skip (they already exist in your config)
24
- * Default: ['user'] - assumes you have a Users collection
25
- */
26
- skipCollections?: string[]
27
-
28
- /**
29
- * Admin group name for generated collections
30
- * Default: 'Auth'
31
- */
32
- adminGroup?: string
33
-
34
- /**
35
- * Custom access control for generated collections.
36
- * By default, only admins can read/delete, and create/update are disabled.
37
- */
38
- access?: CollectionConfig['access']
39
-
40
- /**
41
- * Whether to pluralize collection slugs (add 's' suffix).
42
- * Should match your adapter's usePlural setting.
43
- * Default: true (matches Payload conventions)
44
- */
45
- usePlural?: boolean
46
-
47
- /**
48
- * Configure saveToJWT for session-related fields.
49
- * This controls which fields are included in JWT tokens.
50
- * Default: true
51
- */
52
- configureSaveToJWT?: boolean
53
-
54
- /**
55
- * Automatically make the first registered user an admin.
56
- * Enabled by default. Set to `false` to disable, or provide options to customize.
57
- *
58
- * @default true
59
- *
60
- * @example Disable
61
- * ```ts
62
- * betterAuthCollections({
63
- * betterAuthOptions: authOptions,
64
- * firstUserAdmin: false,
65
- * })
66
- * ```
67
- *
68
- * @example Custom roles
69
- * ```ts
70
- * betterAuthCollections({
71
- * betterAuthOptions: authOptions,
72
- * firstUserAdmin: {
73
- * adminRole: 'super-admin',
74
- * defaultRole: 'member',
75
- * },
76
- * })
77
- * ```
78
- */
79
- firstUserAdmin?: boolean | FirstUserAdminOptions
80
-
81
- /**
82
- * Customize a generated collection before it's added to config.
83
- * Use this to add hooks, modify fields, or adjust any collection setting.
84
- *
85
- * @example
86
- * ```ts
87
- * customizeCollection: (modelKey, collection) => {
88
- * if (modelKey === 'session') {
89
- * return {
90
- * ...collection,
91
- * hooks: {
92
- * afterDelete: [myCleanupHook],
93
- * },
94
- * }
95
- * }
96
- * return collection
97
- * }
98
- * ```
99
- */
100
- customizeCollection?: (
101
- modelKey: string,
102
- collection: CollectionConfig
103
- ) => CollectionConfig
104
- }
105
-
106
- /**
107
- * Creates a beforeChange hook that makes the first user an admin.
108
- */
109
- function createFirstUserAdminHook(
110
- options: FirstUserAdminOptions,
111
- usersSlug: string
112
- ): CollectionBeforeChangeHook {
113
- const {
114
- adminRole = 'admin',
115
- defaultRole = 'user',
116
- roleField = 'role',
117
- } = options
118
-
119
- return async ({ data, operation, req }) => {
120
- if (operation !== 'create') {
121
- return data
122
- }
123
-
124
- try {
125
- const { totalDocs } = await req.payload.count({
126
- collection: usersSlug,
127
- overrideAccess: true,
128
- })
129
-
130
- if (totalDocs === 0) {
131
- // First user becomes admin
132
- return {
133
- ...data,
134
- [roleField]: adminRole,
135
- }
136
- }
137
-
138
- // Subsequent users get default role if not already set
139
- return {
140
- ...data,
141
- [roleField]: data[roleField] ?? defaultRole,
142
- }
143
- } catch (error) {
144
- // On error, don't block user creation - just use provided or default role
145
- console.warn('[betterAuthCollections] Failed to check user count:', error)
146
- return {
147
- ...data,
148
- [roleField]: data[roleField] ?? defaultRole,
149
- }
150
- }
151
- }
152
- }
153
-
154
- /**
155
- * Inject the first-user-admin hook into a collection's hooks.
156
- */
157
- function injectFirstUserAdminHook(
158
- collection: CollectionConfig,
159
- options: FirstUserAdminOptions,
160
- usersSlug: string
161
- ): CollectionConfig {
162
- const hook = createFirstUserAdminHook(options, usersSlug)
163
- const existingHooks = collection.hooks?.beforeChange ?? []
164
-
165
- return {
166
- ...collection,
167
- hooks: {
168
- ...collection.hooks,
169
- beforeChange: [hook, ...(Array.isArray(existingHooks) ? existingHooks : [existingHooks])],
170
- },
171
- }
172
- }
173
-
174
- /**
175
- * Determine if a field should be saved to JWT.
176
- * Session-critical fields are included, large data fields are excluded.
177
- */
178
- function getSaveToJWT(modelKey: string, fieldName: string): boolean | undefined {
179
- // Session fields - include core session data
180
- if (modelKey === 'session') {
181
- const includeFields = ['token', 'expiresAt', 'user', 'userId', 'ipAddress', 'userAgent', 'activeOrganizationId', 'activeTeamId']
182
- const excludeFields = ['createdAt', 'updatedAt']
183
-
184
- if (includeFields.some(f => fieldName === f || fieldName.endsWith(f.charAt(0).toUpperCase() + f.slice(1)))) {
185
- return true
186
- }
187
- if (excludeFields.includes(fieldName)) {
188
- return false
189
- }
190
- }
191
-
192
- // User fields - include essential auth data
193
- if (modelKey === 'user') {
194
- const includeFields = ['role', 'email', 'emailVerified', 'name', 'twoFactorEnabled', 'banned']
195
- const excludeFields = ['image', 'password', 'banReason']
196
-
197
- if (includeFields.includes(fieldName)) {
198
- return true
199
- }
200
- if (excludeFields.includes(fieldName)) {
201
- return false
202
- }
203
- }
204
-
205
- // Account fields - generally not in JWT
206
- if (modelKey === 'account') {
207
- return false
208
- }
209
-
210
- // Verification fields - not in JWT
211
- if (modelKey === 'verification') {
212
- return false
213
- }
214
-
215
- // Default: don't set (let Payload decide)
216
- return undefined
217
- }
218
-
219
- /**
220
- * Simple pluralization (add 's' suffix)
221
- */
222
- function pluralize(name: string): string {
223
- if (name.endsWith('s')) return name
224
- return `${name}s`
225
- }
226
-
227
- function mapFieldType(
228
- type: string,
229
- fieldName: string,
230
- hasReferences: boolean
231
- ): Field['type'] {
232
- if (hasReferences) {
233
- return 'relationship'
234
- }
235
-
236
- switch (type) {
237
- case 'boolean':
238
- return 'checkbox'
239
- case 'number':
240
- return 'number'
241
- case 'date':
242
- return 'date'
243
- case 'string':
244
- if (fieldName === 'email') return 'email'
245
- return 'text'
246
- case 'json':
247
- case 'object':
248
- return 'json'
249
- case 'string[]':
250
- case 'array':
251
- return 'json' // Payload doesn't have native string array, use JSON
252
- default:
253
- return 'text'
254
- }
255
- }
256
-
257
- function extractRelationTarget(
258
- fieldName: string,
259
- usePlural: boolean
260
- ): string {
261
- const base = fieldName.replace(/(_id|Id)$/, '')
262
- return usePlural ? pluralize(base) : base
263
- }
264
-
265
- function generateCollection(
266
- modelKey: string,
267
- table: ReturnType<typeof getAuthTables>[string],
268
- usePlural: boolean,
269
- adminGroup: string,
270
- customAccess?: BetterAuthCollectionsOptions['access'],
271
- configureSaveToJWT = true
272
- ): CollectionConfig {
273
- // Use modelName from schema if set, otherwise apply pluralization to modelKey
274
- const baseName = table.modelName ?? modelKey
275
- const slug = usePlural ? pluralize(baseName) : baseName
276
- const fields: Field[] = []
277
-
278
- for (const [fieldKey, fieldDef] of Object.entries(table.fields)) {
279
- if (['id', 'createdAt', 'updatedAt'].includes(fieldKey)) {
280
- continue
281
- }
282
-
283
- const fieldName = fieldDef.fieldName ?? fieldKey
284
- const hasReferences = fieldDef.references !== undefined
285
- const fieldType = mapFieldType(fieldDef.type as string, fieldKey, hasReferences)
286
-
287
- if (fieldType === 'relationship') {
288
- // Use schema reference if available, otherwise infer from field name
289
- let relationTo: string
290
- if (fieldDef.references?.model) {
291
- relationTo = usePlural ? pluralize(fieldDef.references.model) : fieldDef.references.model
292
- } else {
293
- relationTo = extractRelationTarget(fieldKey, usePlural)
294
- }
295
-
296
- const relFieldName = fieldName.replace(/(_id|Id)$/, '')
297
- const saveToJWT = configureSaveToJWT ? getSaveToJWT(modelKey, relFieldName) : undefined
298
-
299
- fields.push({
300
- name: relFieldName,
301
- type: 'relationship',
302
- relationTo,
303
- required: fieldDef.required ?? false,
304
- index: true,
305
- ...(saveToJWT !== undefined && { saveToJWT }),
306
- } as Field)
307
- continue
308
- }
309
-
310
- const saveToJWT = configureSaveToJWT ? getSaveToJWT(modelKey, fieldName) : undefined
311
- const field: Record<string, unknown> = {
312
- name: fieldName,
313
- type: fieldType,
314
- ...(saveToJWT !== undefined && { saveToJWT }),
315
- }
316
-
317
- if (fieldDef.required) field.required = true
318
- if (fieldDef.unique) {
319
- field.unique = true
320
- field.index = true
321
- }
322
-
323
- if (fieldDef.defaultValue !== undefined) {
324
- let defaultValue: unknown = fieldDef.defaultValue
325
- if (typeof defaultValue === 'function') {
326
- try {
327
- defaultValue = (defaultValue as () => unknown)()
328
- } catch {
329
- defaultValue = undefined
330
- }
331
- }
332
- if (defaultValue !== undefined && defaultValue !== null) {
333
- field.defaultValue = defaultValue
334
- }
335
- }
336
-
337
- fields.push(field as Field)
338
- }
339
-
340
- const titleField = ['name', 'email', 'title', 'identifier'].find((f) =>
341
- fields.some((field) => 'name' in field && field.name === f)
342
- )
343
-
344
- // Default access: admin-only read/delete, disabled manual create/update via admin UI
345
- // The adapter uses overrideAccess: true for programmatic operations from Better Auth
346
- const defaultAccess: CollectionConfig['access'] = {
347
- read: isAdmin(),
348
- create: () => false, // Manual creation disabled - Better Auth manages these
349
- update: () => false, // Manual update disabled - Better Auth manages these
350
- delete: isAdmin(),
351
- }
352
-
353
- return {
354
- slug,
355
- admin: {
356
- useAsTitle: titleField ?? 'id',
357
- group: adminGroup,
358
- description: `Auto-generated from Better Auth schema (${modelKey})`,
359
- },
360
- access: customAccess ?? defaultAccess,
361
- fields,
362
- timestamps: true,
363
- }
364
- }
365
-
366
- /**
367
- * Get existing field names from a collection, handling nested field structures.
368
- */
369
- function getExistingFieldNames(fields: Field[]): Set<string> {
370
- const names = new Set<string>()
371
- for (const field of fields) {
372
- if ('name' in field && field.name) {
373
- names.add(field.name)
374
- }
375
- }
376
- return names
377
- }
378
-
379
- /**
380
- * Augment an existing collection with missing fields from Better Auth schema.
381
- * This ensures user-defined collections (like 'users') get plugin fields automatically.
382
- */
383
- function augmentCollectionWithMissingFields(
384
- collection: CollectionConfig,
385
- table: ReturnType<typeof getAuthTables>[string],
386
- usePlural: boolean,
387
- modelKey: string,
388
- configureSaveToJWT = true
389
- ): CollectionConfig {
390
- const existingFieldNames = getExistingFieldNames(collection.fields)
391
- const missingFields: Field[] = []
392
-
393
- for (const [fieldKey, fieldDef] of Object.entries(table.fields)) {
394
- // Skip standard fields that Payload handles
395
- if (['id', 'createdAt', 'updatedAt'].includes(fieldKey)) {
396
- continue
397
- }
398
-
399
- const fieldName = fieldDef.fieldName ?? fieldKey
400
- const hasReferences = fieldDef.references !== undefined
401
-
402
- // For reference fields, check the name without Id suffix
403
- const payloadFieldName = hasReferences
404
- ? fieldName.replace(/(_id|Id)$/, '')
405
- : fieldName
406
-
407
- // Skip if field already exists
408
- if (existingFieldNames.has(payloadFieldName)) {
409
- continue
410
- }
411
-
412
- // Generate the missing field
413
- const fieldType = mapFieldType(fieldDef.type as string, fieldKey, hasReferences)
414
-
415
- if (fieldType === 'relationship') {
416
- let relationTo: string
417
- if (fieldDef.references?.model) {
418
- relationTo = usePlural ? pluralize(fieldDef.references.model) : fieldDef.references.model
419
- } else {
420
- relationTo = extractRelationTarget(fieldKey, usePlural)
421
- }
422
-
423
- const saveToJWT = configureSaveToJWT ? getSaveToJWT(modelKey, payloadFieldName) : undefined
424
-
425
- missingFields.push({
426
- name: payloadFieldName,
427
- type: 'relationship',
428
- relationTo,
429
- required: fieldDef.required ?? false,
430
- index: true,
431
- admin: {
432
- description: `Auto-added by Better Auth (${fieldKey})`,
433
- },
434
- ...(saveToJWT !== undefined && { saveToJWT }),
435
- } as Field)
436
- } else {
437
- const saveToJWT = configureSaveToJWT ? getSaveToJWT(modelKey, payloadFieldName) : undefined
438
- const field: Record<string, unknown> = {
439
- name: payloadFieldName,
440
- type: fieldType,
441
- admin: {
442
- description: `Auto-added by Better Auth (${fieldKey})`,
443
- },
444
- ...(saveToJWT !== undefined && { saveToJWT }),
445
- }
446
-
447
- if (fieldDef.required) field.required = true
448
- if (fieldDef.unique) {
449
- field.unique = true
450
- field.index = true
451
- }
452
-
453
- if (fieldDef.defaultValue !== undefined) {
454
- let defaultValue: unknown = fieldDef.defaultValue
455
- if (typeof defaultValue === 'function') {
456
- try {
457
- defaultValue = (defaultValue as () => unknown)()
458
- } catch {
459
- defaultValue = undefined
460
- }
461
- }
462
- if (defaultValue !== undefined && defaultValue !== null) {
463
- field.defaultValue = defaultValue
464
- }
465
- }
466
-
467
- missingFields.push(field as Field)
468
- }
469
- }
470
-
471
- // Return original if no fields to add
472
- if (missingFields.length === 0) {
473
- return collection
474
- }
475
-
476
- // Return augmented collection
477
- return {
478
- ...collection,
479
- fields: [...collection.fields, ...missingFields],
480
- }
481
- }
482
-
483
- /**
484
- * Payload plugin that auto-generates collections from Better Auth schema.
485
- *
486
- * @example Basic usage
487
- * ```ts
488
- * import { betterAuthCollections } from '@delmaredigital/payload-better-auth'
489
- *
490
- * export default buildConfig({
491
- * plugins: [
492
- * betterAuthCollections({
493
- * betterAuthOptions: { ... },
494
- * skipCollections: ['user'], // Define Users yourself
495
- * }),
496
- * ],
497
- * })
498
- * ```
499
- *
500
- * @example With customization callback
501
- * ```ts
502
- * betterAuthCollections({
503
- * betterAuthOptions: authOptions,
504
- * customizeCollection: (modelKey, collection) => {
505
- * if (modelKey === 'session') {
506
- * return {
507
- * ...collection,
508
- * hooks: { afterDelete: [cleanupHook] },
509
- * }
510
- * }
511
- * return collection
512
- * },
513
- * })
514
- * ```
515
- */
516
- export function betterAuthCollections(
517
- options: BetterAuthCollectionsOptions = {}
518
- ): Plugin {
519
- const {
520
- betterAuthOptions = {},
521
- skipCollections = ['user'],
522
- adminGroup = 'Auth',
523
- access,
524
- usePlural = true,
525
- configureSaveToJWT = true,
526
- firstUserAdmin,
527
- customizeCollection,
528
- } = options
529
-
530
- // Parse firstUserAdmin option (defaults to true)
531
- const firstUserAdminOptions: FirstUserAdminOptions | null =
532
- firstUserAdmin === false
533
- ? null
534
- : typeof firstUserAdmin === 'object'
535
- ? firstUserAdmin
536
- : {} // true or undefined = enabled with defaults
537
-
538
- return (incomingConfig: Config): Config => {
539
- const existingCollections = new Map(
540
- (incomingConfig.collections ?? []).map((c) => [c.slug, c])
541
- )
542
-
543
- const tables = getAuthTables(betterAuthOptions)
544
- const generatedCollections: CollectionConfig[] = []
545
- const augmentedCollections: CollectionConfig[] = []
546
-
547
- // Calculate users collection slug for firstUserAdmin hook
548
- const userTable = tables['user']
549
- const usersSlug = usePlural
550
- ? pluralize(userTable?.modelName ?? 'user')
551
- : (userTable?.modelName ?? 'user')
552
-
553
- for (const [modelKey, table] of Object.entries(tables)) {
554
- // Calculate slug
555
- const baseName = table.modelName ?? modelKey
556
- const slug = usePlural ? pluralize(baseName) : baseName
557
-
558
- // Check if this collection already exists
559
- const existingCollection = existingCollections.get(slug)
560
-
561
- if (existingCollection) {
562
- // Augment existing collection with missing fields from Better Auth schema
563
- let augmented = augmentCollectionWithMissingFields(
564
- existingCollection,
565
- table,
566
- usePlural,
567
- modelKey,
568
- configureSaveToJWT
569
- )
570
-
571
- // Inject first-user-admin hook for user collection
572
- if (modelKey === 'user' && firstUserAdminOptions) {
573
- augmented = injectFirstUserAdminHook(augmented, firstUserAdminOptions, usersSlug)
574
- }
575
-
576
- if (augmented !== existingCollection) {
577
- augmentedCollections.push(augmented)
578
- existingCollections.set(slug, augmented)
579
- }
580
- continue
581
- }
582
-
583
- // Skip if explicitly told to (but still augment if exists above)
584
- if (skipCollections.includes(modelKey)) {
585
- continue
586
- }
587
-
588
- let collection = generateCollection(
589
- modelKey,
590
- table,
591
- usePlural,
592
- adminGroup,
593
- access,
594
- configureSaveToJWT
595
- )
596
-
597
- // Inject first-user-admin hook for user collection
598
- if (modelKey === 'user' && firstUserAdminOptions) {
599
- collection = injectFirstUserAdminHook(collection, firstUserAdminOptions, usersSlug)
600
- }
601
-
602
- // Apply customization callback if provided
603
- if (customizeCollection) {
604
- collection = customizeCollection(modelKey, collection)
605
- }
606
-
607
- generatedCollections.push(collection)
608
- }
609
-
610
- // Merge: replace augmented collections, add new ones
611
- const finalCollections = (incomingConfig.collections ?? []).map((c) => {
612
- const augmented = augmentedCollections.find((a) => a.slug === c.slug)
613
- return augmented ?? c
614
- })
615
-
616
- return {
617
- ...incomingConfig,
618
- collections: [...finalCollections, ...generatedCollections],
619
- }
620
- }
621
- }