@delmaredigital/payload-better-auth 0.3.6 → 0.3.8

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 (164) hide show
  1. package/README.md +60 -12
  2. package/dist/adapter/collections.d.ts.map +1 -1
  3. package/dist/adapter/collections.js +126 -88
  4. package/dist/adapter/collections.js.map +1 -1
  5. package/dist/adapter/index.js +197 -150
  6. package/dist/adapter/index.js.map +1 -1
  7. package/dist/components/BeforeLogin.d.ts +1 -1
  8. package/dist/components/BeforeLogin.d.ts.map +1 -1
  9. package/dist/components/BeforeLogin.js +15 -7
  10. package/dist/components/BeforeLogin.js.map +1 -1
  11. package/dist/components/LoginView.d.ts +2 -2
  12. package/dist/components/LoginView.d.ts.map +1 -1
  13. package/dist/components/LoginView.js +660 -218
  14. package/dist/components/LoginView.js.map +1 -1
  15. package/dist/components/LoginViewWrapper.d.ts +1 -1
  16. package/dist/components/LoginViewWrapper.d.ts.map +1 -1
  17. package/dist/components/LoginViewWrapper.js +14 -4
  18. package/dist/components/LoginViewWrapper.js.map +1 -1
  19. package/dist/components/LogoutButton.d.ts +1 -1
  20. package/dist/components/LogoutButton.d.ts.map +1 -1
  21. package/dist/components/LogoutButton.js +19 -11
  22. package/dist/components/LogoutButton.js.map +1 -1
  23. package/dist/components/PasskeyRegisterButton.d.ts +2 -2
  24. package/dist/components/PasskeyRegisterButton.d.ts.map +1 -1
  25. package/dist/components/PasskeyRegisterButton.js +20 -16
  26. package/dist/components/PasskeyRegisterButton.js.map +1 -1
  27. package/dist/components/PasskeySignInButton.d.ts +2 -2
  28. package/dist/components/PasskeySignInButton.d.ts.map +1 -1
  29. package/dist/components/PasskeySignInButton.js +14 -12
  30. package/dist/components/PasskeySignInButton.js.map +1 -1
  31. package/dist/components/auth/ForgotPasswordView.d.ts +1 -1
  32. package/dist/components/auth/ForgotPasswordView.d.ts.map +1 -1
  33. package/dist/components/auth/ForgotPasswordView.js +133 -43
  34. package/dist/components/auth/ForgotPasswordView.js.map +1 -1
  35. package/dist/components/auth/ResetPasswordView.d.ts +1 -1
  36. package/dist/components/auth/ResetPasswordView.d.ts.map +1 -1
  37. package/dist/components/auth/ResetPasswordView.js +154 -50
  38. package/dist/components/auth/ResetPasswordView.js.map +1 -1
  39. package/dist/components/auth/index.js +2 -2
  40. package/dist/components/auth/index.js.map +1 -1
  41. package/dist/components/management/ApiKeysManagementClient.d.ts +2 -2
  42. package/dist/components/management/ApiKeysManagementClient.d.ts.map +1 -1
  43. package/dist/components/management/ApiKeysManagementClient.js +539 -222
  44. package/dist/components/management/ApiKeysManagementClient.js.map +1 -1
  45. package/dist/components/management/PasskeysManagementClient.d.ts +2 -2
  46. package/dist/components/management/PasskeysManagementClient.d.ts.map +1 -1
  47. package/dist/components/management/PasskeysManagementClient.js +215 -92
  48. package/dist/components/management/PasskeysManagementClient.js.map +1 -1
  49. package/dist/components/management/SecurityNavLinks.d.ts +1 -1
  50. package/dist/components/management/SecurityNavLinks.d.ts.map +1 -1
  51. package/dist/components/management/SecurityNavLinks.js +51 -24
  52. package/dist/components/management/SecurityNavLinks.js.map +1 -1
  53. package/dist/components/management/TwoFactorManagementClient.d.ts +2 -2
  54. package/dist/components/management/TwoFactorManagementClient.d.ts.map +1 -1
  55. package/dist/components/management/TwoFactorManagementClient.js +270 -111
  56. package/dist/components/management/TwoFactorManagementClient.js.map +1 -1
  57. package/dist/components/management/index.js +2 -2
  58. package/dist/components/management/index.js.map +1 -1
  59. package/dist/components/management/views/ApiKeysView.d.ts +1 -1
  60. package/dist/components/management/views/ApiKeysView.d.ts.map +1 -1
  61. package/dist/components/management/views/ApiKeysView.js +19 -4
  62. package/dist/components/management/views/ApiKeysView.js.map +1 -1
  63. package/dist/components/management/views/PasskeysView.d.ts +1 -1
  64. package/dist/components/management/views/PasskeysView.d.ts.map +1 -1
  65. package/dist/components/management/views/PasskeysView.js +16 -4
  66. package/dist/components/management/views/PasskeysView.js.map +1 -1
  67. package/dist/components/management/views/TwoFactorView.d.ts +1 -1
  68. package/dist/components/management/views/TwoFactorView.d.ts.map +1 -1
  69. package/dist/components/management/views/TwoFactorView.js +16 -4
  70. package/dist/components/management/views/TwoFactorView.js.map +1 -1
  71. package/dist/components/management/views/index.js +2 -2
  72. package/dist/components/management/views/index.js.map +1 -1
  73. package/dist/components/twoFactor/TwoFactorSetupView.d.ts +1 -1
  74. package/dist/components/twoFactor/TwoFactorSetupView.d.ts.map +1 -1
  75. package/dist/components/twoFactor/TwoFactorSetupView.js +240 -87
  76. package/dist/components/twoFactor/TwoFactorSetupView.js.map +1 -1
  77. package/dist/components/twoFactor/TwoFactorVerifyView.d.ts +1 -1
  78. package/dist/components/twoFactor/TwoFactorVerifyView.d.ts.map +1 -1
  79. package/dist/components/twoFactor/TwoFactorVerifyView.js +108 -45
  80. package/dist/components/twoFactor/TwoFactorVerifyView.js.map +1 -1
  81. package/dist/components/twoFactor/index.js +2 -2
  82. package/dist/components/twoFactor/index.js.map +1 -1
  83. package/dist/exports/client.d.ts +2356 -2
  84. package/dist/exports/client.d.ts.map +1 -1
  85. package/dist/exports/client.js +48 -8
  86. package/dist/exports/client.js.map +1 -1
  87. package/dist/exports/components.js +2 -2
  88. package/dist/exports/components.js.map +1 -1
  89. package/dist/exports/management.js +3 -3
  90. package/dist/exports/management.js.map +1 -1
  91. package/dist/exports/rsc.js +2 -2
  92. package/dist/exports/rsc.js.map +1 -1
  93. package/dist/generated-types.js +4 -2
  94. package/dist/generated-types.js.map +1 -1
  95. package/dist/index.js +6 -6
  96. package/dist/index.js.map +1 -1
  97. package/dist/plugin/index.d.ts +35 -2
  98. package/dist/plugin/index.d.ts.map +1 -1
  99. package/dist/plugin/index.js +198 -162
  100. package/dist/plugin/index.js.map +1 -1
  101. package/dist/scripts/generate-types.js +66 -50
  102. package/dist/scripts/generate-types.js.map +1 -1
  103. package/dist/types/apiKey.js +7 -2
  104. package/dist/types/apiKey.js.map +1 -1
  105. package/dist/types/betterAuth.js +23 -2
  106. package/dist/types/betterAuth.js.map +1 -1
  107. package/dist/utils/access.js +78 -81
  108. package/dist/utils/access.js.map +1 -1
  109. package/dist/utils/apiKeyAccess.js +65 -72
  110. package/dist/utils/apiKeyAccess.js.map +1 -1
  111. package/dist/utils/betterAuthDefaults.js +8 -8
  112. package/dist/utils/betterAuthDefaults.js.map +1 -1
  113. package/dist/utils/detectAuthConfig.js +8 -11
  114. package/dist/utils/detectAuthConfig.js.map +1 -1
  115. package/dist/utils/detectEnabledPlugins.js +6 -7
  116. package/dist/utils/detectEnabledPlugins.js.map +1 -1
  117. package/dist/utils/firstUserAdmin.js +18 -20
  118. package/dist/utils/firstUserAdmin.js.map +1 -1
  119. package/dist/utils/generateScopes.js +40 -41
  120. package/dist/utils/generateScopes.js.map +1 -1
  121. package/dist/utils/session.js +8 -9
  122. package/dist/utils/session.js.map +1 -1
  123. package/package.json +97 -26
  124. package/src/adapter/collections.ts +621 -0
  125. package/src/adapter/index.ts +712 -0
  126. package/src/components/BeforeLogin.tsx +39 -0
  127. package/src/components/LoginView.tsx +1516 -0
  128. package/src/components/LoginViewWrapper.tsx +35 -0
  129. package/src/components/LogoutButton.tsx +58 -0
  130. package/src/components/PasskeyRegisterButton.tsx +105 -0
  131. package/src/components/PasskeySignInButton.tsx +96 -0
  132. package/src/components/auth/ForgotPasswordView.tsx +274 -0
  133. package/src/components/auth/ResetPasswordView.tsx +331 -0
  134. package/src/components/auth/index.ts +8 -0
  135. package/src/components/management/ApiKeysManagementClient.tsx +988 -0
  136. package/src/components/management/PasskeysManagementClient.tsx +409 -0
  137. package/src/components/management/SecurityNavLinks.tsx +117 -0
  138. package/src/components/management/TwoFactorManagementClient.tsx +560 -0
  139. package/src/components/management/index.ts +20 -0
  140. package/src/components/management/views/ApiKeysView.tsx +57 -0
  141. package/src/components/management/views/PasskeysView.tsx +42 -0
  142. package/src/components/management/views/TwoFactorView.tsx +42 -0
  143. package/src/components/management/views/index.ts +10 -0
  144. package/src/components/twoFactor/TwoFactorSetupView.tsx +515 -0
  145. package/src/components/twoFactor/TwoFactorVerifyView.tsx +238 -0
  146. package/src/components/twoFactor/index.ts +8 -0
  147. package/src/exports/client.ts +77 -0
  148. package/src/exports/components.ts +30 -0
  149. package/src/exports/management.ts +25 -0
  150. package/src/exports/rsc.ts +11 -0
  151. package/src/generated-types.ts +269 -0
  152. package/src/index.ts +135 -0
  153. package/src/plugin/index.ts +834 -0
  154. package/src/scripts/generate-types.ts +269 -0
  155. package/src/types/apiKey.ts +63 -0
  156. package/src/types/betterAuth.ts +253 -0
  157. package/src/utils/access.ts +410 -0
  158. package/src/utils/apiKeyAccess.ts +443 -0
  159. package/src/utils/betterAuthDefaults.ts +102 -0
  160. package/src/utils/detectAuthConfig.ts +47 -0
  161. package/src/utils/detectEnabledPlugins.ts +69 -0
  162. package/src/utils/firstUserAdmin.ts +164 -0
  163. package/src/utils/generateScopes.ts +150 -0
  164. package/src/utils/session.ts +91 -0
package/README.md CHANGED
@@ -209,7 +209,7 @@ export default buildConfig({
209
209
  })
210
210
  ```
211
211
 
212
- > **⚠️ Important:** Do NOT add a custom `beforeLogin` component to your admin config. The Better Auth plugin automatically injects its own login page, logout button, and redirect handling.
212
+ > **⚠️ Note:** The plugin automatically injects its own login page, logout button, and redirect handling. Don't add a custom `beforeLogin` in Payload's `admin.components` directly - use the plugin's options instead (see [Customization](#customization) for `disableLoginView`, `loginViewComponent`, etc.).
213
213
 
214
214
  ### Step 4: Client-Side Auth
215
215
 
@@ -228,12 +228,29 @@ export const { useSession, signIn, signUp, signOut, twoFactor, passkey } = authC
228
228
 
229
229
  **Note:** `createPayloadAuthClient()` automatically uses `window.location.origin` as the base URL, so it works seamlessly across local dev, Vercel previews, and production without any configuration.
230
230
 
231
- For full control, you can also use the raw `createAuthClient` from Better Auth:
231
+ **Adding custom plugins (e.g., Stripe):**
232
+
233
+ For custom plugins with full TypeScript support, use `createAuthClient` with `payloadAuthPlugins`:
232
234
 
233
235
  ```ts
234
- import { createAuthClient } from '@delmaredigital/payload-better-auth/client'
236
+ // src/lib/auth/client.ts
237
+ 'use client'
238
+
239
+ import { createAuthClient, payloadAuthPlugins } from '@delmaredigital/payload-better-auth/client'
240
+ import { stripeClient } from '@better-auth/stripe/client'
241
+
242
+ // Spread payloadAuthPlugins to include defaults (twoFactor, apiKey, passkey)
243
+ // Then add your custom plugins - full type safety!
244
+ export const authClient = createAuthClient({
245
+ plugins: [...payloadAuthPlugins, stripeClient({ subscription: true })],
246
+ })
247
+
248
+ // authClient.subscription is fully typed
249
+ export const { useSession, signIn, signUp, signOut, twoFactor, passkey, subscription } = authClient
235
250
  ```
236
251
 
252
+ This approach uses Better Auth's native `createAuthClient` with our default plugins, giving you full type inference for any custom plugins you add.
253
+
237
254
  ### Step 5: Server-Side Session Access
238
255
 
239
256
  ```ts
@@ -350,12 +367,23 @@ betterAuthCollections({
350
367
  | `betterAuthOptions` | `BetterAuthOptions` | Your Better Auth options |
351
368
  | `skipCollections` | `string[]` | Collections to skip generating (default: `['user']`) |
352
369
  | `adminGroup` | `string` | Admin panel group name (default: `'Auth'`) |
353
- | `access` | `CollectionConfig['access']` | Custom access control for generated collections |
370
+ | `access` | `CollectionConfig['access']` | Custom access control for generated collections. **Note**: Replaces default access entirely (see caution below). |
354
371
  | `usePlural` | `boolean` | Pluralize collection slugs (default: `true`) |
355
372
  | `configureSaveToJWT` | `boolean` | Auto-configure `saveToJWT` for session-critical fields (default: `true`) |
356
373
  | `firstUserAdmin` | `boolean \| FirstUserAdminOptions` | Make first registered user an admin (default: `true`) |
357
374
  | `customizeCollection` | `(modelKey, collection) => CollectionConfig` | Customize generated collections |
358
375
 
376
+ > **⚠️ Caution on Custom Access:**
377
+ > When providing the `access` option, it **completely replaces** the default access object for all auto-generated collections. It does not merge with or override individual properties.
378
+ >
379
+ > By default, the plugin sets:
380
+ > - `read`: `isAdmin()`
381
+ > - `delete`: `isAdmin()`
382
+ > - `create`: `() => false` (Manual creation disabled - Better Auth manages these)
383
+ > - `update`: `() => false` (Manual update disabled - Better Auth manages these)
384
+ >
385
+ > You must explicitly handle all access types to ensure your collections remain secure and functional.
386
+
359
387
  **First User Admin:**
360
388
 
361
389
  By default, the first user to register is automatically assigned the admin role. This provides a better out-of-the-box experience - no need to manually update the database to create your first admin.
@@ -428,7 +456,7 @@ createBetterAuthPlugin({
428
456
  | `admin.login.afterLoginPath` | `string` | `'/admin'` | Redirect path after successful login |
429
457
  | `admin.login.requiredRole` | `string \| string[] \| null` | `'admin'` | Required role(s) for admin access. Array = any role matches (unless `requireAllRoles`). Set to `null` to disable. |
430
458
  | `admin.login.requireAllRoles` | `boolean` | `false` | When `requiredRole` is an array, require ALL roles (true) or ANY role (false). |
431
- | `admin.login.enablePasskey` | `boolean` | `false` | Enable passkey (WebAuthn) sign-in option |
459
+ | `admin.login.enablePasskey` | `boolean \| 'auto'` | `false` | Enable passkey (WebAuthn) sign-in option. `'auto'` detects if passkey plugin is available. |
432
460
  | `admin.login.enableSignUp` | `boolean \| 'auto'` | `'auto'` | Enable user registration. `'auto'` detects if sign-up endpoint is available. |
433
461
  | `admin.login.defaultSignUpRole` | `string` | `'user'` | Default role assigned to new users during registration |
434
462
  | `admin.login.enableForgotPassword` | `boolean \| 'auto'` | `'auto'` | Enable forgot password link. `'auto'` detects if endpoint is available. |
@@ -679,6 +707,17 @@ This is useful when you need:
679
707
  - Integration with external identity providers
680
708
  - Custom branding or UI requirements
681
709
 
710
+ **Frontend Login (outside admin panel):**
711
+
712
+ For user-facing login pages (not the Payload admin), you don't need to configure anything in the plugin. Just use the auth client directly in your own React components:
713
+
714
+ ```tsx
715
+ import { authClient } from '@/lib/auth/client'
716
+
717
+ // Use authClient.signIn.email(), authClient.signUp.email(), etc.
718
+ // See "Handling 2FA in Custom Login Forms" section below for a complete example
719
+ ```
720
+
682
721
  ### Custom Admin Components
683
722
 
684
723
  Override specific admin components while keeping others auto-injected:
@@ -1152,15 +1191,18 @@ export const auth = betterAuth({
1152
1191
 
1153
1192
  ### User Registration
1154
1193
 
1155
- The `LoginView` includes an optional "Create account" link that automatically detects if user registration is available. When enabled, users can register directly from the login page.
1194
+ The `LoginView` **automatically detects** if user registration is available by checking Better Auth's sign-up endpoint. If your Better Auth config has `emailAndPassword.enabled: true` (and not `disableSignUp: true`), the "Create account" link appears automatically.
1156
1195
 
1157
- **Configuration:**
1196
+ **No configuration needed** for most cases - it just works based on your Better Auth settings.
1197
+
1198
+ **Optional overrides** (only if you need to force show/hide):
1158
1199
  ```typescript
1159
1200
  createBetterAuthPlugin({
1160
1201
  createAuth,
1161
1202
  admin: {
1162
1203
  login: {
1163
- enableSignUp: true, // or 'auto' (default) to detect availability
1204
+ // These options override auto-detection (usually not needed)
1205
+ enableSignUp: 'auto', // 'auto' (default) | true | false
1164
1206
  defaultSignUpRole: 'user', // Role assigned to new users (default: 'user')
1165
1207
  },
1166
1208
  },
@@ -1168,22 +1210,28 @@ createBetterAuthPlugin({
1168
1210
  ```
1169
1211
 
1170
1212
  **Notes:**
1213
+ - `'auto'` (default): Detects availability from Better Auth's sign-up endpoint
1214
+ - `true`: Always show registration (even if Better Auth returns 404)
1215
+ - `false`: Never show registration
1171
1216
  - New users are assigned `defaultSignUpRole` (default: `'user'`)
1172
1217
  - If email verification is required, users see a success message to check their email
1173
1218
  - Role-based access control still applies - users without `requiredRole` see "Access Denied"
1174
1219
 
1175
1220
  ### Password Reset Flow
1176
1221
 
1177
- The `LoginView` includes an inline "Forgot password?" link that automatically detects if password reset is available. When clicked, users can request a reset link without leaving the login page.
1222
+ The `LoginView` **automatically detects** if password reset is available by checking Better Auth's reset endpoint. The "Forgot password?" link appears automatically when available.
1178
1223
 
1179
- **Configuration:**
1224
+ **No configuration needed** for most cases - it just works based on your Better Auth settings.
1225
+
1226
+ **Optional overrides** (only if you need to force show/hide or use a custom URL):
1180
1227
  ```typescript
1181
1228
  createBetterAuthPlugin({
1182
1229
  createAuth,
1183
1230
  admin: {
1184
1231
  login: {
1185
- enableForgotPassword: true, // or 'auto' (default) to detect availability
1186
- resetPasswordUrl: '/custom-reset', // Optional: redirect to custom page instead
1232
+ // These options override auto-detection (usually not needed)
1233
+ enableForgotPassword: 'auto', // 'auto' (default) | true | false
1234
+ resetPasswordUrl: '/custom-reset', // Optional: redirect to custom page instead of inline form
1187
1235
  },
1188
1236
  },
1189
1237
  })
@@ -1 +1 @@
1
- {"version":3,"file":"collections.d.ts","sourceRoot":"","sources":["../../src/adapter/collections.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAU,gBAAgB,EAAS,MAAM,EAA8B,MAAM,SAAS,CAAA;AAClG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAEvE,YAAY,EAAE,qBAAqB,EAAE,CAAA;AAErC,MAAM,MAAM,4BAA4B,GAAG;IACzC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IAErC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAE1B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;OAGG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAEnC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IAEnB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAE5B;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,qBAAqB,CAAA;IAEhD;;;;;;;;;;;;;;;;;;OAkBG;IACH,mBAAmB,CAAC,EAAE,CACpB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,gBAAgB,KACzB,gBAAgB,CAAA;CACtB,CAAA;AA2XD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,4BAAiC,GACzC,MAAM,CAuGR"}
1
+ {"version":3,"file":"collections.d.ts","sourceRoot":"","sources":["../../src/adapter/collections.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAU,gBAAgB,EAAS,MAAM,EAA8B,MAAM,SAAS,CAAA;AAClG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAGvE,YAAY,EAAE,qBAAqB,EAAE,CAAA;AAErC,MAAM,MAAM,4BAA4B,GAAG;IACzC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IAErC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAE1B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;OAGG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAEnC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IAEnB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAE5B;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,qBAAqB,CAAA;IAEhD;;;;;;;;;;;;;;;;;;OAkBG;IACH,mBAAmB,CAAC,EAAE,CACpB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,gBAAgB,KACzB,gBAAgB,CAAA;CACtB,CAAA;AA2XD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,4BAAiC,GACzC,MAAM,CAuGR"}
@@ -2,69 +2,82 @@
2
2
  * Auto-generate Payload collections from Better Auth schema
3
3
  *
4
4
  * @packageDocumentation
5
- */
6
- import { getAuthTables } from 'better-auth/db';
5
+ */ import { getAuthTables } from 'better-auth/db';
6
+ import { isAdmin } from '../utils/access.js';
7
7
  /**
8
8
  * Creates a beforeChange hook that makes the first user an admin.
9
- */
10
- function createFirstUserAdminHook(options, usersSlug) {
11
- const { adminRole = 'admin', defaultRole = 'user', roleField = 'role', } = options;
12
- return async ({ data, operation, req }) => {
9
+ */ function createFirstUserAdminHook(options, usersSlug) {
10
+ const { adminRole = 'admin', defaultRole = 'user', roleField = 'role' } = options;
11
+ return async ({ data, operation, req })=>{
13
12
  if (operation !== 'create') {
14
13
  return data;
15
14
  }
16
15
  try {
17
16
  const { totalDocs } = await req.payload.count({
18
17
  collection: usersSlug,
19
- overrideAccess: true,
18
+ overrideAccess: true
20
19
  });
21
20
  if (totalDocs === 0) {
22
21
  // First user becomes admin
23
22
  return {
24
23
  ...data,
25
- [roleField]: adminRole,
24
+ [roleField]: adminRole
26
25
  };
27
26
  }
28
27
  // Subsequent users get default role if not already set
29
28
  return {
30
29
  ...data,
31
- [roleField]: data[roleField] ?? defaultRole,
30
+ [roleField]: data[roleField] ?? defaultRole
32
31
  };
33
- }
34
- catch (error) {
32
+ } catch (error) {
35
33
  // On error, don't block user creation - just use provided or default role
36
34
  console.warn('[betterAuthCollections] Failed to check user count:', error);
37
35
  return {
38
36
  ...data,
39
- [roleField]: data[roleField] ?? defaultRole,
37
+ [roleField]: data[roleField] ?? defaultRole
40
38
  };
41
39
  }
42
40
  };
43
41
  }
44
42
  /**
45
43
  * Inject the first-user-admin hook into a collection's hooks.
46
- */
47
- function injectFirstUserAdminHook(collection, options, usersSlug) {
44
+ */ function injectFirstUserAdminHook(collection, options, usersSlug) {
48
45
  const hook = createFirstUserAdminHook(options, usersSlug);
49
46
  const existingHooks = collection.hooks?.beforeChange ?? [];
50
47
  return {
51
48
  ...collection,
52
49
  hooks: {
53
50
  ...collection.hooks,
54
- beforeChange: [hook, ...(Array.isArray(existingHooks) ? existingHooks : [existingHooks])],
55
- },
51
+ beforeChange: [
52
+ hook,
53
+ ...Array.isArray(existingHooks) ? existingHooks : [
54
+ existingHooks
55
+ ]
56
+ ]
57
+ }
56
58
  };
57
59
  }
58
60
  /**
59
61
  * Determine if a field should be saved to JWT.
60
62
  * Session-critical fields are included, large data fields are excluded.
61
- */
62
- function getSaveToJWT(modelKey, fieldName) {
63
+ */ function getSaveToJWT(modelKey, fieldName) {
63
64
  // Session fields - include core session data
64
65
  if (modelKey === 'session') {
65
- const includeFields = ['token', 'expiresAt', 'user', 'userId', 'ipAddress', 'userAgent', 'activeOrganizationId', 'activeTeamId'];
66
- const excludeFields = ['createdAt', 'updatedAt'];
67
- if (includeFields.some(f => fieldName === f || fieldName.endsWith(f.charAt(0).toUpperCase() + f.slice(1)))) {
66
+ const includeFields = [
67
+ 'token',
68
+ 'expiresAt',
69
+ 'user',
70
+ 'userId',
71
+ 'ipAddress',
72
+ 'userAgent',
73
+ 'activeOrganizationId',
74
+ 'activeTeamId'
75
+ ];
76
+ const excludeFields = [
77
+ 'createdAt',
78
+ 'updatedAt'
79
+ ];
80
+ if (includeFields.some((f)=>fieldName === f || fieldName.endsWith(f.charAt(0).toUpperCase() + f.slice(1)))) {
68
81
  return true;
69
82
  }
70
83
  if (excludeFields.includes(fieldName)) {
@@ -73,8 +86,19 @@ function getSaveToJWT(modelKey, fieldName) {
73
86
  }
74
87
  // User fields - include essential auth data
75
88
  if (modelKey === 'user') {
76
- const includeFields = ['role', 'email', 'emailVerified', 'name', 'twoFactorEnabled', 'banned'];
77
- const excludeFields = ['image', 'password', 'banReason'];
89
+ const includeFields = [
90
+ 'role',
91
+ 'email',
92
+ 'emailVerified',
93
+ 'name',
94
+ 'twoFactorEnabled',
95
+ 'banned'
96
+ ];
97
+ const excludeFields = [
98
+ 'image',
99
+ 'password',
100
+ 'banReason'
101
+ ];
78
102
  if (includeFields.includes(fieldName)) {
79
103
  return true;
80
104
  }
@@ -95,17 +119,15 @@ function getSaveToJWT(modelKey, fieldName) {
95
119
  }
96
120
  /**
97
121
  * Simple pluralization (add 's' suffix)
98
- */
99
- function pluralize(name) {
100
- if (name.endsWith('s'))
101
- return name;
122
+ */ function pluralize(name) {
123
+ if (name.endsWith('s')) return name;
102
124
  return `${name}s`;
103
125
  }
104
126
  function mapFieldType(type, fieldName, hasReferences) {
105
127
  if (hasReferences) {
106
128
  return 'relationship';
107
129
  }
108
- switch (type) {
130
+ switch(type){
109
131
  case 'boolean':
110
132
  return 'checkbox';
111
133
  case 'number':
@@ -113,15 +135,15 @@ function mapFieldType(type, fieldName, hasReferences) {
113
135
  case 'date':
114
136
  return 'date';
115
137
  case 'string':
116
- if (fieldName === 'email')
117
- return 'email';
138
+ if (fieldName === 'email') return 'email';
118
139
  return 'text';
119
140
  case 'json':
120
141
  case 'object':
121
142
  return 'json';
122
143
  case 'string[]':
123
144
  case 'array':
124
- return 'json'; // Payload doesn't have native string array, use JSON
145
+ return 'json' // Payload doesn't have native string array, use JSON
146
+ ;
125
147
  default:
126
148
  return 'text';
127
149
  }
@@ -135,8 +157,12 @@ function generateCollection(modelKey, table, usePlural, adminGroup, customAccess
135
157
  const baseName = table.modelName ?? modelKey;
136
158
  const slug = usePlural ? pluralize(baseName) : baseName;
137
159
  const fields = [];
138
- for (const [fieldKey, fieldDef] of Object.entries(table.fields)) {
139
- if (['id', 'createdAt', 'updatedAt'].includes(fieldKey)) {
160
+ for (const [fieldKey, fieldDef] of Object.entries(table.fields)){
161
+ if ([
162
+ 'id',
163
+ 'createdAt',
164
+ 'updatedAt'
165
+ ].includes(fieldKey)) {
140
166
  continue;
141
167
  }
142
168
  const fieldName = fieldDef.fieldName ?? fieldKey;
@@ -147,8 +173,7 @@ function generateCollection(modelKey, table, usePlural, adminGroup, customAccess
147
173
  let relationTo;
148
174
  if (fieldDef.references?.model) {
149
175
  relationTo = usePlural ? pluralize(fieldDef.references.model) : fieldDef.references.model;
150
- }
151
- else {
176
+ } else {
152
177
  relationTo = extractRelationTarget(fieldKey, usePlural);
153
178
  }
154
179
  const relFieldName = fieldName.replace(/(_id|Id)$/, '');
@@ -159,7 +184,9 @@ function generateCollection(modelKey, table, usePlural, adminGroup, customAccess
159
184
  relationTo,
160
185
  required: fieldDef.required ?? false,
161
186
  index: true,
162
- ...(saveToJWT !== undefined && { saveToJWT }),
187
+ ...saveToJWT !== undefined && {
188
+ saveToJWT
189
+ }
163
190
  });
164
191
  continue;
165
192
  }
@@ -167,10 +194,11 @@ function generateCollection(modelKey, table, usePlural, adminGroup, customAccess
167
194
  const field = {
168
195
  name: fieldName,
169
196
  type: fieldType,
170
- ...(saveToJWT !== undefined && { saveToJWT }),
197
+ ...saveToJWT !== undefined && {
198
+ saveToJWT
199
+ }
171
200
  };
172
- if (fieldDef.required)
173
- field.required = true;
201
+ if (fieldDef.required) field.required = true;
174
202
  if (fieldDef.unique) {
175
203
  field.unique = true;
176
204
  field.index = true;
@@ -180,8 +208,7 @@ function generateCollection(modelKey, table, usePlural, adminGroup, customAccess
180
208
  if (typeof defaultValue === 'function') {
181
209
  try {
182
210
  defaultValue = defaultValue();
183
- }
184
- catch {
211
+ } catch {
185
212
  defaultValue = undefined;
186
213
  }
187
214
  }
@@ -191,33 +218,37 @@ function generateCollection(modelKey, table, usePlural, adminGroup, customAccess
191
218
  }
192
219
  fields.push(field);
193
220
  }
194
- const titleField = ['name', 'email', 'title', 'identifier'].find((f) => fields.some((field) => 'name' in field && field.name === f));
221
+ const titleField = [
222
+ 'name',
223
+ 'email',
224
+ 'title',
225
+ 'identifier'
226
+ ].find((f)=>fields.some((field)=>'name' in field && field.name === f));
195
227
  // Default access: admin-only read/delete, disabled manual create/update via admin UI
196
228
  // The adapter uses overrideAccess: true for programmatic operations from Better Auth
197
229
  const defaultAccess = {
198
- read: ({ req }) => req.user?.role === 'admin',
199
- create: () => false, // Manual creation disabled - Better Auth manages these
200
- update: () => false, // Manual update disabled - Better Auth manages these
201
- delete: ({ req }) => req.user?.role === 'admin',
230
+ read: isAdmin(),
231
+ create: ()=>false,
232
+ update: ()=>false,
233
+ delete: isAdmin()
202
234
  };
203
235
  return {
204
236
  slug,
205
237
  admin: {
206
238
  useAsTitle: titleField ?? 'id',
207
239
  group: adminGroup,
208
- description: `Auto-generated from Better Auth schema (${modelKey})`,
240
+ description: `Auto-generated from Better Auth schema (${modelKey})`
209
241
  },
210
242
  access: customAccess ?? defaultAccess,
211
243
  fields,
212
- timestamps: true,
244
+ timestamps: true
213
245
  };
214
246
  }
215
247
  /**
216
248
  * Get existing field names from a collection, handling nested field structures.
217
- */
218
- function getExistingFieldNames(fields) {
249
+ */ function getExistingFieldNames(fields) {
219
250
  const names = new Set();
220
- for (const field of fields) {
251
+ for (const field of fields){
221
252
  if ('name' in field && field.name) {
222
253
  names.add(field.name);
223
254
  }
@@ -227,21 +258,22 @@ function getExistingFieldNames(fields) {
227
258
  /**
228
259
  * Augment an existing collection with missing fields from Better Auth schema.
229
260
  * This ensures user-defined collections (like 'users') get plugin fields automatically.
230
- */
231
- function augmentCollectionWithMissingFields(collection, table, usePlural, modelKey, configureSaveToJWT = true) {
261
+ */ function augmentCollectionWithMissingFields(collection, table, usePlural, modelKey, configureSaveToJWT = true) {
232
262
  const existingFieldNames = getExistingFieldNames(collection.fields);
233
263
  const missingFields = [];
234
- for (const [fieldKey, fieldDef] of Object.entries(table.fields)) {
264
+ for (const [fieldKey, fieldDef] of Object.entries(table.fields)){
235
265
  // Skip standard fields that Payload handles
236
- if (['id', 'createdAt', 'updatedAt'].includes(fieldKey)) {
266
+ if ([
267
+ 'id',
268
+ 'createdAt',
269
+ 'updatedAt'
270
+ ].includes(fieldKey)) {
237
271
  continue;
238
272
  }
239
273
  const fieldName = fieldDef.fieldName ?? fieldKey;
240
274
  const hasReferences = fieldDef.references !== undefined;
241
275
  // For reference fields, check the name without Id suffix
242
- const payloadFieldName = hasReferences
243
- ? fieldName.replace(/(_id|Id)$/, '')
244
- : fieldName;
276
+ const payloadFieldName = hasReferences ? fieldName.replace(/(_id|Id)$/, '') : fieldName;
245
277
  // Skip if field already exists
246
278
  if (existingFieldNames.has(payloadFieldName)) {
247
279
  continue;
@@ -252,8 +284,7 @@ function augmentCollectionWithMissingFields(collection, table, usePlural, modelK
252
284
  let relationTo;
253
285
  if (fieldDef.references?.model) {
254
286
  relationTo = usePlural ? pluralize(fieldDef.references.model) : fieldDef.references.model;
255
- }
256
- else {
287
+ } else {
257
288
  relationTo = extractRelationTarget(fieldKey, usePlural);
258
289
  }
259
290
  const saveToJWT = configureSaveToJWT ? getSaveToJWT(modelKey, payloadFieldName) : undefined;
@@ -264,23 +295,25 @@ function augmentCollectionWithMissingFields(collection, table, usePlural, modelK
264
295
  required: fieldDef.required ?? false,
265
296
  index: true,
266
297
  admin: {
267
- description: `Auto-added by Better Auth (${fieldKey})`,
298
+ description: `Auto-added by Better Auth (${fieldKey})`
268
299
  },
269
- ...(saveToJWT !== undefined && { saveToJWT }),
300
+ ...saveToJWT !== undefined && {
301
+ saveToJWT
302
+ }
270
303
  });
271
- }
272
- else {
304
+ } else {
273
305
  const saveToJWT = configureSaveToJWT ? getSaveToJWT(modelKey, payloadFieldName) : undefined;
274
306
  const field = {
275
307
  name: payloadFieldName,
276
308
  type: fieldType,
277
309
  admin: {
278
- description: `Auto-added by Better Auth (${fieldKey})`,
310
+ description: `Auto-added by Better Auth (${fieldKey})`
279
311
  },
280
- ...(saveToJWT !== undefined && { saveToJWT }),
312
+ ...saveToJWT !== undefined && {
313
+ saveToJWT
314
+ }
281
315
  };
282
- if (fieldDef.required)
283
- field.required = true;
316
+ if (fieldDef.required) field.required = true;
284
317
  if (fieldDef.unique) {
285
318
  field.unique = true;
286
319
  field.index = true;
@@ -290,8 +323,7 @@ function augmentCollectionWithMissingFields(collection, table, usePlural, modelK
290
323
  if (typeof defaultValue === 'function') {
291
324
  try {
292
325
  defaultValue = defaultValue();
293
- }
294
- catch {
326
+ } catch {
295
327
  defaultValue = undefined;
296
328
  }
297
329
  }
@@ -309,7 +341,10 @@ function augmentCollectionWithMissingFields(collection, table, usePlural, modelK
309
341
  // Return augmented collection
310
342
  return {
311
343
  ...collection,
312
- fields: [...collection.fields, ...missingFields],
344
+ fields: [
345
+ ...collection.fields,
346
+ ...missingFields
347
+ ]
313
348
  };
314
349
  }
315
350
  /**
@@ -344,26 +379,25 @@ function augmentCollectionWithMissingFields(collection, table, usePlural, modelK
344
379
  * },
345
380
  * })
346
381
  * ```
347
- */
348
- export function betterAuthCollections(options = {}) {
349
- const { betterAuthOptions = {}, skipCollections = ['user'], adminGroup = 'Auth', access, usePlural = true, configureSaveToJWT = true, firstUserAdmin, customizeCollection, } = options;
382
+ */ export function betterAuthCollections(options = {}) {
383
+ const { betterAuthOptions = {}, skipCollections = [
384
+ 'user'
385
+ ], adminGroup = 'Auth', access, usePlural = true, configureSaveToJWT = true, firstUserAdmin, customizeCollection } = options;
350
386
  // Parse firstUserAdmin option (defaults to true)
351
- const firstUserAdminOptions = firstUserAdmin === false
352
- ? null
353
- : typeof firstUserAdmin === 'object'
354
- ? firstUserAdmin
355
- : {}; // true or undefined = enabled with defaults
356
- return (incomingConfig) => {
357
- const existingCollections = new Map((incomingConfig.collections ?? []).map((c) => [c.slug, c]));
387
+ const firstUserAdminOptions = firstUserAdmin === false ? null : typeof firstUserAdmin === 'object' ? firstUserAdmin : {} // true or undefined = enabled with defaults
388
+ ;
389
+ return (incomingConfig)=>{
390
+ const existingCollections = new Map((incomingConfig.collections ?? []).map((c)=>[
391
+ c.slug,
392
+ c
393
+ ]));
358
394
  const tables = getAuthTables(betterAuthOptions);
359
395
  const generatedCollections = [];
360
396
  const augmentedCollections = [];
361
397
  // Calculate users collection slug for firstUserAdmin hook
362
398
  const userTable = tables['user'];
363
- const usersSlug = usePlural
364
- ? pluralize(userTable?.modelName ?? 'user')
365
- : (userTable?.modelName ?? 'user');
366
- for (const [modelKey, table] of Object.entries(tables)) {
399
+ const usersSlug = usePlural ? pluralize(userTable?.modelName ?? 'user') : userTable?.modelName ?? 'user';
400
+ for (const [modelKey, table] of Object.entries(tables)){
367
401
  // Calculate slug
368
402
  const baseName = table.modelName ?? modelKey;
369
403
  const slug = usePlural ? pluralize(baseName) : baseName;
@@ -398,14 +432,18 @@ export function betterAuthCollections(options = {}) {
398
432
  generatedCollections.push(collection);
399
433
  }
400
434
  // Merge: replace augmented collections, add new ones
401
- const finalCollections = (incomingConfig.collections ?? []).map((c) => {
402
- const augmented = augmentedCollections.find((a) => a.slug === c.slug);
435
+ const finalCollections = (incomingConfig.collections ?? []).map((c)=>{
436
+ const augmented = augmentedCollections.find((a)=>a.slug === c.slug);
403
437
  return augmented ?? c;
404
438
  });
405
439
  return {
406
440
  ...incomingConfig,
407
- collections: [...finalCollections, ...generatedCollections],
441
+ collections: [
442
+ ...finalCollections,
443
+ ...generatedCollections
444
+ ]
408
445
  };
409
446
  };
410
447
  }
448
+
411
449
  //# sourceMappingURL=collections.js.map