@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.
- package/dist/adapter/collections.d.ts +0 -1
- package/dist/adapter/collections.js +0 -2
- package/dist/adapter/index.d.ts +0 -1
- package/dist/adapter/index.js +0 -2
- package/dist/components/BeforeLogin.d.ts +0 -1
- package/dist/components/BeforeLogin.js +0 -2
- package/dist/components/LoginView.d.ts +0 -1
- package/dist/components/LoginView.js +0 -2
- package/dist/components/LoginViewWrapper.d.ts +0 -1
- package/dist/components/LoginViewWrapper.js +0 -2
- package/dist/components/LogoutButton.d.ts +0 -1
- package/dist/components/LogoutButton.js +0 -2
- package/dist/components/PasskeyRegisterButton.d.ts +0 -1
- package/dist/components/PasskeyRegisterButton.js +0 -2
- package/dist/components/PasskeySignInButton.d.ts +0 -1
- package/dist/components/PasskeySignInButton.js +0 -2
- package/dist/components/auth/ForgotPasswordView.d.ts +0 -1
- package/dist/components/auth/ForgotPasswordView.js +0 -2
- package/dist/components/auth/ResetPasswordView.d.ts +0 -1
- package/dist/components/auth/ResetPasswordView.js +0 -2
- package/dist/components/auth/index.d.ts +0 -1
- package/dist/components/auth/index.js +0 -2
- package/dist/components/management/ApiKeysManagementClient.d.ts +0 -1
- package/dist/components/management/ApiKeysManagementClient.js +0 -2
- package/dist/components/management/PasskeysManagementClient.d.ts +0 -1
- package/dist/components/management/PasskeysManagementClient.js +0 -2
- package/dist/components/management/SecurityNavLinks.d.ts +0 -1
- package/dist/components/management/SecurityNavLinks.js +0 -2
- package/dist/components/management/TwoFactorManagementClient.d.ts +0 -1
- package/dist/components/management/TwoFactorManagementClient.js +0 -2
- package/dist/components/management/index.d.ts +0 -1
- package/dist/components/management/index.js +0 -2
- package/dist/components/management/views/ApiKeysView.d.ts +0 -1
- package/dist/components/management/views/ApiKeysView.js +0 -2
- package/dist/components/management/views/PasskeysView.d.ts +0 -1
- package/dist/components/management/views/PasskeysView.js +0 -2
- package/dist/components/management/views/TwoFactorView.d.ts +0 -1
- package/dist/components/management/views/TwoFactorView.js +0 -2
- package/dist/components/management/views/index.d.ts +0 -1
- package/dist/components/management/views/index.js +0 -2
- package/dist/components/twoFactor/TwoFactorSetupView.d.ts +0 -1
- package/dist/components/twoFactor/TwoFactorSetupView.js +0 -2
- package/dist/components/twoFactor/TwoFactorVerifyView.d.ts +0 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.js +0 -2
- package/dist/components/twoFactor/index.d.ts +0 -1
- package/dist/components/twoFactor/index.js +0 -2
- package/dist/exports/client.d.ts +0 -1
- package/dist/exports/client.js +0 -2
- package/dist/exports/components.d.ts +0 -1
- package/dist/exports/components.js +0 -2
- package/dist/exports/management.d.ts +0 -1
- package/dist/exports/management.js +0 -2
- package/dist/exports/rsc.d.ts +0 -1
- package/dist/exports/rsc.js +0 -2
- package/dist/generated-types.d.ts +0 -1
- package/dist/generated-types.js +0 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/plugin/index.d.ts +0 -1
- package/dist/plugin/index.js +0 -2
- package/dist/scripts/generate-types.d.ts +0 -1
- package/dist/scripts/generate-types.js +0 -2
- package/dist/types/apiKey.d.ts +0 -1
- package/dist/types/apiKey.js +0 -2
- package/dist/types/betterAuth.d.ts +0 -1
- package/dist/types/betterAuth.js +0 -2
- package/dist/utils/access.d.ts +0 -1
- package/dist/utils/access.js +0 -2
- package/dist/utils/apiKeyAccess.d.ts +0 -1
- package/dist/utils/apiKeyAccess.js +0 -2
- package/dist/utils/betterAuthDefaults.d.ts +0 -1
- package/dist/utils/betterAuthDefaults.js +0 -2
- package/dist/utils/detectAuthConfig.d.ts +0 -1
- package/dist/utils/detectAuthConfig.js +0 -2
- package/dist/utils/detectEnabledPlugins.d.ts +0 -1
- package/dist/utils/detectEnabledPlugins.js +0 -2
- package/dist/utils/firstUserAdmin.d.ts +0 -1
- package/dist/utils/firstUserAdmin.js +0 -2
- package/dist/utils/generateScopes.d.ts +0 -1
- package/dist/utils/generateScopes.js +0 -2
- package/dist/utils/session.d.ts +0 -1
- package/dist/utils/session.js +0 -2
- package/package.json +34 -91
- package/dist/adapter/collections.d.ts.map +0 -1
- package/dist/adapter/collections.js.map +0 -1
- package/dist/adapter/index.d.ts.map +0 -1
- package/dist/adapter/index.js.map +0 -1
- package/dist/components/BeforeLogin.d.ts.map +0 -1
- package/dist/components/BeforeLogin.js.map +0 -1
- package/dist/components/LoginView.d.ts.map +0 -1
- package/dist/components/LoginView.js.map +0 -1
- package/dist/components/LoginViewWrapper.d.ts.map +0 -1
- package/dist/components/LoginViewWrapper.js.map +0 -1
- package/dist/components/LogoutButton.d.ts.map +0 -1
- package/dist/components/LogoutButton.js.map +0 -1
- package/dist/components/PasskeyRegisterButton.d.ts.map +0 -1
- package/dist/components/PasskeyRegisterButton.js.map +0 -1
- package/dist/components/PasskeySignInButton.d.ts.map +0 -1
- package/dist/components/PasskeySignInButton.js.map +0 -1
- package/dist/components/auth/ForgotPasswordView.d.ts.map +0 -1
- package/dist/components/auth/ForgotPasswordView.js.map +0 -1
- package/dist/components/auth/ResetPasswordView.d.ts.map +0 -1
- package/dist/components/auth/ResetPasswordView.js.map +0 -1
- package/dist/components/auth/index.d.ts.map +0 -1
- package/dist/components/auth/index.js.map +0 -1
- package/dist/components/management/ApiKeysManagementClient.d.ts.map +0 -1
- package/dist/components/management/ApiKeysManagementClient.js.map +0 -1
- package/dist/components/management/PasskeysManagementClient.d.ts.map +0 -1
- package/dist/components/management/PasskeysManagementClient.js.map +0 -1
- package/dist/components/management/SecurityNavLinks.d.ts.map +0 -1
- package/dist/components/management/SecurityNavLinks.js.map +0 -1
- package/dist/components/management/TwoFactorManagementClient.d.ts.map +0 -1
- package/dist/components/management/TwoFactorManagementClient.js.map +0 -1
- package/dist/components/management/index.d.ts.map +0 -1
- package/dist/components/management/index.js.map +0 -1
- package/dist/components/management/views/ApiKeysView.d.ts.map +0 -1
- package/dist/components/management/views/ApiKeysView.js.map +0 -1
- package/dist/components/management/views/PasskeysView.d.ts.map +0 -1
- package/dist/components/management/views/PasskeysView.js.map +0 -1
- package/dist/components/management/views/TwoFactorView.d.ts.map +0 -1
- package/dist/components/management/views/TwoFactorView.js.map +0 -1
- package/dist/components/management/views/index.d.ts.map +0 -1
- package/dist/components/management/views/index.js.map +0 -1
- package/dist/components/twoFactor/TwoFactorSetupView.d.ts.map +0 -1
- package/dist/components/twoFactor/TwoFactorSetupView.js.map +0 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.d.ts.map +0 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.js.map +0 -1
- package/dist/components/twoFactor/index.d.ts.map +0 -1
- package/dist/components/twoFactor/index.js.map +0 -1
- package/dist/exports/client.d.ts.map +0 -1
- package/dist/exports/client.js.map +0 -1
- package/dist/exports/components.d.ts.map +0 -1
- package/dist/exports/components.js.map +0 -1
- package/dist/exports/management.d.ts.map +0 -1
- package/dist/exports/management.js.map +0 -1
- package/dist/exports/rsc.d.ts.map +0 -1
- package/dist/exports/rsc.js.map +0 -1
- package/dist/generated-types.d.ts.map +0 -1
- package/dist/generated-types.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plugin/index.d.ts.map +0 -1
- package/dist/plugin/index.js.map +0 -1
- package/dist/scripts/generate-types.d.ts.map +0 -1
- package/dist/scripts/generate-types.js.map +0 -1
- package/dist/types/apiKey.d.ts.map +0 -1
- package/dist/types/apiKey.js.map +0 -1
- package/dist/types/betterAuth.d.ts.map +0 -1
- package/dist/types/betterAuth.js.map +0 -1
- package/dist/utils/access.d.ts.map +0 -1
- package/dist/utils/access.js.map +0 -1
- package/dist/utils/apiKeyAccess.d.ts.map +0 -1
- package/dist/utils/apiKeyAccess.js.map +0 -1
- package/dist/utils/betterAuthDefaults.d.ts.map +0 -1
- package/dist/utils/betterAuthDefaults.js.map +0 -1
- package/dist/utils/detectAuthConfig.d.ts.map +0 -1
- package/dist/utils/detectAuthConfig.js.map +0 -1
- package/dist/utils/detectEnabledPlugins.d.ts.map +0 -1
- package/dist/utils/detectEnabledPlugins.js.map +0 -1
- package/dist/utils/firstUserAdmin.d.ts.map +0 -1
- package/dist/utils/firstUserAdmin.js.map +0 -1
- package/dist/utils/generateScopes.d.ts.map +0 -1
- package/dist/utils/generateScopes.js.map +0 -1
- package/dist/utils/session.d.ts.map +0 -1
- package/dist/utils/session.js.map +0 -1
- package/src/adapter/collections.ts +0 -621
- package/src/adapter/index.ts +0 -712
- package/src/components/BeforeLogin.tsx +0 -39
- package/src/components/LoginView.tsx +0 -1516
- package/src/components/LoginViewWrapper.tsx +0 -35
- package/src/components/LogoutButton.tsx +0 -58
- package/src/components/PasskeyRegisterButton.tsx +0 -105
- package/src/components/PasskeySignInButton.tsx +0 -96
- package/src/components/auth/ForgotPasswordView.tsx +0 -274
- package/src/components/auth/ResetPasswordView.tsx +0 -331
- package/src/components/auth/index.ts +0 -8
- package/src/components/management/ApiKeysManagementClient.tsx +0 -988
- package/src/components/management/PasskeysManagementClient.tsx +0 -409
- package/src/components/management/SecurityNavLinks.tsx +0 -117
- package/src/components/management/TwoFactorManagementClient.tsx +0 -560
- package/src/components/management/index.ts +0 -20
- package/src/components/management/views/ApiKeysView.tsx +0 -57
- package/src/components/management/views/PasskeysView.tsx +0 -42
- package/src/components/management/views/TwoFactorView.tsx +0 -42
- package/src/components/management/views/index.ts +0 -10
- package/src/components/twoFactor/TwoFactorSetupView.tsx +0 -515
- package/src/components/twoFactor/TwoFactorVerifyView.tsx +0 -238
- package/src/components/twoFactor/index.ts +0 -8
- package/src/exports/client.ts +0 -77
- package/src/exports/components.ts +0 -30
- package/src/exports/management.ts +0 -25
- package/src/exports/rsc.ts +0 -11
- package/src/generated-types.ts +0 -269
- package/src/index.ts +0 -135
- package/src/plugin/index.ts +0 -834
- package/src/scripts/generate-types.ts +0 -269
- package/src/types/apiKey.ts +0 -63
- package/src/types/betterAuth.ts +0 -253
- package/src/utils/access.ts +0 -410
- package/src/utils/apiKeyAccess.ts +0 -443
- package/src/utils/betterAuthDefaults.ts +0 -102
- package/src/utils/detectAuthConfig.ts +0 -47
- package/src/utils/detectEnabledPlugins.ts +0 -69
- package/src/utils/firstUserAdmin.ts +0 -164
- package/src/utils/generateScopes.ts +0 -150
- 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
|
-
}
|