@delmaredigital/payload-better-auth 0.3.14 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/components/management/SecurityNavLinks.d.ts +1 -1
- package/dist/components/management/SecurityNavLinks.js +16 -63
- package/dist/exports/client.d.ts +4 -2
- package/dist/plugin/index.d.ts +11 -0
- package/dist/plugin/index.js +14 -1
- package/dist/utils/betterAuthDefaults.d.ts +4 -2
- package/package.json +16 -12
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ For additional documentation and references, visit: [https://deepwiki.com/delmar
|
|
|
41
41
|
| `@payloadcms/next` | >= 3.69.0 |
|
|
42
42
|
| `@payloadcms/ui` | >= 3.69.0 |
|
|
43
43
|
| `better-auth` | >= 1.4.0 |
|
|
44
|
+
| `@better-auth/passkey` | >= 1.4.18 (if using passkeys) |
|
|
44
45
|
| `next` | >= 15.4.8 |
|
|
45
46
|
| `react` | >= 19.2.1 |
|
|
46
47
|
|
|
@@ -48,6 +49,9 @@ For additional documentation and references, visit: [https://deepwiki.com/delmar
|
|
|
48
49
|
|
|
49
50
|
```bash
|
|
50
51
|
pnpm add @delmaredigital/payload-better-auth better-auth
|
|
52
|
+
|
|
53
|
+
# If using passkeys:
|
|
54
|
+
pnpm add @better-auth/passkey
|
|
51
55
|
```
|
|
52
56
|
|
|
53
57
|
### Environment Variables
|
|
@@ -545,12 +549,14 @@ Payload auth strategy for Better Auth session validation.
|
|
|
545
549
|
```ts
|
|
546
550
|
betterAuthStrategy({
|
|
547
551
|
usersCollection: 'users',
|
|
552
|
+
idType: 'number', // default — coerces session field IDs for serial IDs
|
|
548
553
|
})
|
|
549
554
|
```
|
|
550
555
|
|
|
551
556
|
| Option | Type | Description |
|
|
552
557
|
|--------|------|-------------|
|
|
553
558
|
| `usersCollection` | `string` | The collection slug for users (default: `'users'`) |
|
|
559
|
+
| `idType` | `'number' \| 'text'` | Coerces string IDs in session fields (`activeOrganizationId`, etc.) to numbers. Defaults to `'number'` matching the adapter default. Set to `'text'` for UUID IDs. |
|
|
554
560
|
|
|
555
561
|
### `getServerSession<TUser>(payload, headers)`
|
|
556
562
|
|
|
@@ -992,9 +998,7 @@ The adapter uses Better Auth's `createAdapterFactory` which is **schema-aware**
|
|
|
992
998
|
| API Keys | `better-auth` (core) | Auto-generates apikeys collection |
|
|
993
999
|
| Organizations | `better-auth` (core) | Auto-generates organizations, members, invitations |
|
|
994
1000
|
| Admin | `better-auth` (core) | Adds admin fields to users |
|
|
995
|
-
| Passkey |
|
|
996
|
-
|
|
997
|
-
**Note:** The `@better-auth/passkey` package is bundled with this package - no separate installation required.
|
|
1001
|
+
| Passkey | `@better-auth/passkey` (peer dep) | Auto-generates passkeys collection |
|
|
998
1002
|
|
|
999
1003
|
### Example: Core Plugins
|
|
1000
1004
|
|
|
@@ -11,7 +11,7 @@ export type SecurityNavLinksProps = {
|
|
|
11
11
|
/**
|
|
12
12
|
* Navigation links for security management features.
|
|
13
13
|
* Rendered in admin sidebar via afterNavLinks injection.
|
|
14
|
-
* Uses Payload's nav CSS classes for native styling.
|
|
14
|
+
* Uses Payload's NavGroup and nav CSS classes for native styling.
|
|
15
15
|
*
|
|
16
16
|
* Links are conditionally shown based on which Better Auth plugins are enabled.
|
|
17
17
|
*/
|
|
@@ -1,92 +1,45 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { NavGroup } from '@payloadcms/ui';
|
|
3
4
|
/**
|
|
4
5
|
* Navigation links for security management features.
|
|
5
6
|
* Rendered in admin sidebar via afterNavLinks injection.
|
|
6
|
-
* Uses Payload's nav CSS classes for native styling.
|
|
7
|
+
* Uses Payload's NavGroup and nav CSS classes for native styling.
|
|
7
8
|
*
|
|
8
9
|
* Links are conditionally shown based on which Better Auth plugins are enabled.
|
|
9
10
|
*/ export function SecurityNavLinks({ basePath = '/admin/security', showTwoFactor = true, showApiKeys = true, showPasskeys = true } = {}) {
|
|
10
|
-
// Build links based on enabled plugins
|
|
11
11
|
const links = [];
|
|
12
12
|
if (showTwoFactor) {
|
|
13
13
|
links.push({
|
|
14
14
|
href: `${basePath}/two-factor`,
|
|
15
|
-
label: 'Two-Factor Auth'
|
|
16
|
-
icon: '📱'
|
|
15
|
+
label: 'Two-Factor Auth'
|
|
17
16
|
});
|
|
18
17
|
}
|
|
19
18
|
if (showApiKeys) {
|
|
20
19
|
links.push({
|
|
21
20
|
href: `${basePath}/api-keys`,
|
|
22
|
-
label: 'API Keys'
|
|
23
|
-
icon: '🔑'
|
|
21
|
+
label: 'API Keys'
|
|
24
22
|
});
|
|
25
23
|
}
|
|
26
24
|
if (showPasskeys) {
|
|
27
25
|
links.push({
|
|
28
26
|
href: `${basePath}/passkeys`,
|
|
29
|
-
label: 'Passkeys'
|
|
30
|
-
icon: '🔐'
|
|
27
|
+
label: 'Passkeys'
|
|
31
28
|
});
|
|
32
29
|
}
|
|
33
|
-
// Don't render anything if no plugins are enabled
|
|
34
30
|
if (links.length === 0) {
|
|
35
31
|
return null;
|
|
36
32
|
}
|
|
37
|
-
return /*#__PURE__*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
fontWeight: 600,
|
|
48
|
-
color: 'var(--theme-elevation-500)',
|
|
49
|
-
padding: '0 calc(var(--base) * 0.75)',
|
|
50
|
-
marginBottom: 'calc(var(--base) * 0.5)',
|
|
51
|
-
textTransform: 'uppercase',
|
|
52
|
-
letterSpacing: '0.5px'
|
|
53
|
-
},
|
|
54
|
-
children: "Security"
|
|
55
|
-
}),
|
|
56
|
-
links.map((link)=>/*#__PURE__*/ _jsxs("a", {
|
|
57
|
-
href: link.href,
|
|
58
|
-
className: "nav__link",
|
|
59
|
-
style: {
|
|
60
|
-
display: 'flex',
|
|
61
|
-
alignItems: 'center',
|
|
62
|
-
gap: 'calc(var(--base) * 0.5)',
|
|
63
|
-
padding: 'calc(var(--base) * 0.5) calc(var(--base) * 0.75)',
|
|
64
|
-
color: 'var(--theme-elevation-800)',
|
|
65
|
-
textDecoration: 'none',
|
|
66
|
-
fontSize: 'var(--font-size-small)',
|
|
67
|
-
borderRadius: 'var(--style-radius-s)',
|
|
68
|
-
transition: 'background-color 150ms ease'
|
|
69
|
-
},
|
|
70
|
-
onMouseEnter: (e)=>{
|
|
71
|
-
e.currentTarget.style.backgroundColor = 'var(--theme-elevation-50)';
|
|
72
|
-
},
|
|
73
|
-
onMouseLeave: (e)=>{
|
|
74
|
-
e.currentTarget.style.backgroundColor = 'transparent';
|
|
75
|
-
},
|
|
76
|
-
children: [
|
|
77
|
-
/*#__PURE__*/ _jsx("span", {
|
|
78
|
-
style: {
|
|
79
|
-
fontSize: '14px'
|
|
80
|
-
},
|
|
81
|
-
children: link.icon
|
|
82
|
-
}),
|
|
83
|
-
/*#__PURE__*/ _jsx("span", {
|
|
84
|
-
className: "nav__link-label",
|
|
85
|
-
children: link.label
|
|
86
|
-
})
|
|
87
|
-
]
|
|
88
|
-
}, link.href))
|
|
89
|
-
]
|
|
33
|
+
return /*#__PURE__*/ _jsx(NavGroup, {
|
|
34
|
+
label: "Security",
|
|
35
|
+
children: links.map((link)=>/*#__PURE__*/ _jsx("a", {
|
|
36
|
+
href: link.href,
|
|
37
|
+
className: "nav__link",
|
|
38
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
39
|
+
className: "nav__link-label",
|
|
40
|
+
children: link.label
|
|
41
|
+
})
|
|
42
|
+
}, link.href))
|
|
90
43
|
});
|
|
91
44
|
}
|
|
92
45
|
export default SecurityNavLinks;
|
package/dist/exports/client.d.ts
CHANGED
|
@@ -722,6 +722,8 @@ export declare const payloadAuthPlugins: readonly [{
|
|
|
722
722
|
responseHeaders?: Headers | undefined;
|
|
723
723
|
} & import("better-auth").PluginContext & import("better-auth").InfoContext & {
|
|
724
724
|
options: import("better-auth").BetterAuthOptions;
|
|
725
|
+
appName: string;
|
|
726
|
+
baseURL: string;
|
|
725
727
|
trustedOrigins: string[];
|
|
726
728
|
isTrustedOrigin: (url: string, settings?: {
|
|
727
729
|
allowRelativePaths: boolean;
|
|
@@ -790,8 +792,8 @@ export declare const payloadAuthPlugins: readonly [{
|
|
|
790
792
|
}) => Promise<void>;
|
|
791
793
|
skipOriginCheck: boolean | string[];
|
|
792
794
|
skipCSRFCheck: boolean;
|
|
793
|
-
runInBackground: (promise: Promise<
|
|
794
|
-
runInBackgroundOrAwait: (promise: Promise<unknown> |
|
|
795
|
+
runInBackground: (promise: Promise<unknown>) => void;
|
|
796
|
+
runInBackgroundOrAwait: (promise: Promise<unknown> | void) => import("better-auth").Awaitable<unknown>;
|
|
795
797
|
}>;
|
|
796
798
|
}>;
|
|
797
799
|
}[];
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -178,6 +178,17 @@ export type BetterAuthStrategyOptions = {
|
|
|
178
178
|
* @default 'members'
|
|
179
179
|
*/
|
|
180
180
|
membersCollection?: string;
|
|
181
|
+
/**
|
|
182
|
+
* ID type strategy matching your adapter's `adapterConfig.idType`.
|
|
183
|
+
*
|
|
184
|
+
* When `'number'` (default), coerces string IDs in session fields
|
|
185
|
+
* (e.g., `activeOrganizationId`) to numbers before merging onto `req.user`.
|
|
186
|
+
* Better Auth always returns string IDs from `api.getSession()`, but Payload
|
|
187
|
+
* relationship fields expect numbers when using serial IDs.
|
|
188
|
+
*
|
|
189
|
+
* @default 'number'
|
|
190
|
+
*/
|
|
191
|
+
idType?: 'number' | 'text';
|
|
181
192
|
};
|
|
182
193
|
/**
|
|
183
194
|
* Payload auth strategy that uses Better Auth for authentication.
|
package/dist/plugin/index.js
CHANGED
|
@@ -459,7 +459,7 @@ let apiKeyScopesConfig = undefined;
|
|
|
459
459
|
* }
|
|
460
460
|
* ```
|
|
461
461
|
*/ export function betterAuthStrategy(options = {}) {
|
|
462
|
-
const { usersCollection = 'users', membersCollection = 'members' } = options;
|
|
462
|
+
const { usersCollection = 'users', membersCollection = 'members', idType = 'number' } = options;
|
|
463
463
|
return {
|
|
464
464
|
name: 'better-auth',
|
|
465
465
|
authenticate: async ({ payload, headers })=>{
|
|
@@ -498,6 +498,19 @@ let apiKeyScopesConfig = undefined;
|
|
|
498
498
|
// Extract session fields to merge onto user (e.g., activeOrganizationId from org plugin)
|
|
499
499
|
// Exclude fields that might conflict with user fields
|
|
500
500
|
const { id: _sessionId, userId: _userId, expiresAt: _expiresAt, token: _token, ...sessionFields } = sessionData.session || {};
|
|
501
|
+
// Coerce string IDs in session fields to numbers when using serial IDs.
|
|
502
|
+
// BA's api.getSession() always returns strings, but Payload relationship
|
|
503
|
+
// fields expect numbers for serial IDs.
|
|
504
|
+
if (idType === 'number') {
|
|
505
|
+
for (const [key, value] of Object.entries(sessionFields)){
|
|
506
|
+
if (typeof value !== 'string') continue;
|
|
507
|
+
if (key === 'id' || /(?:Id|_id)$/.test(key)) {
|
|
508
|
+
if (/^\d+$/.test(value)) {
|
|
509
|
+
sessionFields[key] = parseInt(value, 10);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
501
514
|
// If there's an active organization, fetch the user's role in that org
|
|
502
515
|
let organizationRole;
|
|
503
516
|
if (sessionFields.activeOrganizationId) {
|
|
@@ -89,6 +89,8 @@ export declare function apiKeyWithDefaults(options?: ApiKeyPluginOptions): {
|
|
|
89
89
|
responseHeaders?: Headers | undefined;
|
|
90
90
|
} & import("better-auth").PluginContext & import("better-auth").InfoContext & {
|
|
91
91
|
options: BetterAuthOptions;
|
|
92
|
+
appName: string;
|
|
93
|
+
baseURL: string;
|
|
92
94
|
trustedOrigins: string[];
|
|
93
95
|
isTrustedOrigin: (url: string, settings?: {
|
|
94
96
|
allowRelativePaths: boolean;
|
|
@@ -157,8 +159,8 @@ export declare function apiKeyWithDefaults(options?: ApiKeyPluginOptions): {
|
|
|
157
159
|
}) => Promise<void>;
|
|
158
160
|
skipOriginCheck: boolean | string[];
|
|
159
161
|
skipCSRFCheck: boolean;
|
|
160
|
-
runInBackground: (promise: Promise<
|
|
161
|
-
runInBackgroundOrAwait: (promise: Promise<unknown> |
|
|
162
|
+
runInBackground: (promise: Promise<unknown>) => void;
|
|
163
|
+
runInBackgroundOrAwait: (promise: Promise<unknown> | void) => import("better-auth").Awaitable<unknown>;
|
|
162
164
|
}>;
|
|
163
165
|
}>;
|
|
164
166
|
}[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delmaredigital/payload-better-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Better Auth adapter and plugins for Payload CMS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -74,10 +74,8 @@
|
|
|
74
74
|
"test:coverage": "vitest run --coverage",
|
|
75
75
|
"prepublishOnly": "pnpm build"
|
|
76
76
|
},
|
|
77
|
-
"dependencies": {
|
|
78
|
-
"@better-auth/passkey": "^1.4.17"
|
|
79
|
-
},
|
|
80
77
|
"peerDependencies": {
|
|
78
|
+
"@better-auth/passkey": ">=1.4.18",
|
|
81
79
|
"@payloadcms/next": ">=3.69.0",
|
|
82
80
|
"@payloadcms/ui": ">=3.69.0",
|
|
83
81
|
"better-auth": ">=1.4.0",
|
|
@@ -85,17 +83,23 @@
|
|
|
85
83
|
"payload": ">=3.69.0",
|
|
86
84
|
"react": ">=19.2.1"
|
|
87
85
|
},
|
|
86
|
+
"peerDependenciesMeta": {
|
|
87
|
+
"@better-auth/passkey": {
|
|
88
|
+
"optional": true
|
|
89
|
+
}
|
|
90
|
+
},
|
|
88
91
|
"devDependencies": {
|
|
89
|
-
"@payloadcms/next": "^3.
|
|
90
|
-
"@payloadcms/ui": "^3.
|
|
92
|
+
"@payloadcms/next": "^3.76.1",
|
|
93
|
+
"@payloadcms/ui": "^3.76.1",
|
|
91
94
|
"@swc/cli": "^0.6.0",
|
|
92
|
-
"@swc/core": "^1.
|
|
93
|
-
"@types/node": "^25.
|
|
94
|
-
"@types/react": "^19.2.
|
|
95
|
+
"@swc/core": "^1.15.11",
|
|
96
|
+
"@types/node": "^25.2.3",
|
|
97
|
+
"@types/react": "^19.2.14",
|
|
95
98
|
"@vitest/coverage-v8": "^2.1.9",
|
|
96
|
-
"better-auth": "^1.4.
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
+
"@better-auth/passkey": "^1.4.18",
|
|
100
|
+
"better-auth": "^1.4.18",
|
|
101
|
+
"next": "^16.1.6",
|
|
102
|
+
"payload": "^3.76.1",
|
|
99
103
|
"react": "^19.2.4",
|
|
100
104
|
"tsx": "^4.21.0",
|
|
101
105
|
"typescript": "^5.9.3",
|