@delmaredigital/payload-better-auth 0.3.11 → 0.3.13
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 +34 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/apiKeyAccess.js +69 -15
- package/dist/utils/session.d.ts +51 -14
- package/dist/utils/session.js +40 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -552,22 +552,48 @@ betterAuthStrategy({
|
|
|
552
552
|
|--------|------|-------------|
|
|
553
553
|
| `usersCollection` | `string` | The collection slug for users (default: `'users'`) |
|
|
554
554
|
|
|
555
|
-
### `getServerSession(payload, headers)`
|
|
555
|
+
### `getServerSession<TUser>(payload, headers)`
|
|
556
556
|
|
|
557
|
-
Get the current session on the server.
|
|
557
|
+
Get the current session on the server. Pass your Payload `User` type for full type safety:
|
|
558
558
|
|
|
559
559
|
```ts
|
|
560
|
-
|
|
561
|
-
|
|
560
|
+
import { getServerSession } from '@delmaredigital/payload-better-auth'
|
|
561
|
+
import type { User } from '@/payload-types'
|
|
562
|
+
|
|
563
|
+
const session = await getServerSession<User>(payload, headersList)
|
|
564
|
+
// session.user.role, session.user.firstName, etc. are fully typed
|
|
562
565
|
```
|
|
563
566
|
|
|
564
|
-
### `getServerUser(payload, headers)`
|
|
567
|
+
### `getServerUser<TUser>(payload, headers)`
|
|
565
568
|
|
|
566
|
-
Get the current user on the server (shorthand for `session.user`)
|
|
569
|
+
Get the current user on the server (shorthand for `session.user`):
|
|
567
570
|
|
|
568
571
|
```ts
|
|
569
|
-
|
|
570
|
-
|
|
572
|
+
import { getServerUser } from '@delmaredigital/payload-better-auth'
|
|
573
|
+
import type { User } from '@/payload-types'
|
|
574
|
+
|
|
575
|
+
const user = await getServerUser<User>(payload, headersList)
|
|
576
|
+
// user.role, user.firstName, etc. are fully typed
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### `createSessionHelpers<TUser>()`
|
|
580
|
+
|
|
581
|
+
Create typed session helpers bound to your User type. Define once, import everywhere — no generics needed at call sites:
|
|
582
|
+
|
|
583
|
+
```ts
|
|
584
|
+
// lib/auth.ts
|
|
585
|
+
import { createSessionHelpers } from '@delmaredigital/payload-better-auth'
|
|
586
|
+
import type { User } from '@/payload-types'
|
|
587
|
+
|
|
588
|
+
export const { getServerSession, getServerUser } = createSessionHelpers<User>()
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
```ts
|
|
592
|
+
// app/page.tsx
|
|
593
|
+
import { getServerSession } from '@/lib/auth'
|
|
594
|
+
|
|
595
|
+
const session = await getServerSession(payload, headersList)
|
|
596
|
+
// session.user is typed as User — no generic needed
|
|
571
597
|
```
|
|
572
598
|
|
|
573
599
|
### `withBetterAuthDefaults(options)`
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export { extractApiKeyFromRequest, getApiKeyInfo, hasScope, hasAnyScope as hasAn
|
|
|
22
22
|
export type { ApiKeyInfo, ApiKeyAccessConfig, } from './utils/apiKeyAccess.js';
|
|
23
23
|
export { detectAuthConfig } from './utils/detectAuthConfig.js';
|
|
24
24
|
export type { AuthDetectionResult } from './utils/detectAuthConfig.js';
|
|
25
|
-
export { getServerSession, getServerUser } from './utils/session.js';
|
|
25
|
+
export { getServerSession, getServerUser, createSessionHelpers } from './utils/session.js';
|
|
26
26
|
export type { Session } from './utils/session.js';
|
|
27
27
|
export { firstUserAdminHooks } from './utils/firstUserAdmin.js';
|
|
28
28
|
export type { FirstUserAdminOptions } from './utils/firstUserAdmin.js';
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ export { extractApiKeyFromRequest, getApiKeyInfo, hasScope, hasAnyScope as hasAn
|
|
|
20
20
|
// Auth config detection utility
|
|
21
21
|
export { detectAuthConfig } from './utils/detectAuthConfig.js';
|
|
22
22
|
// Session utilities
|
|
23
|
-
export { getServerSession, getServerUser } from './utils/session.js';
|
|
23
|
+
export { getServerSession, getServerUser, createSessionHelpers } from './utils/session.js';
|
|
24
24
|
// First user admin hook utility
|
|
25
25
|
export { firstUserAdminHooks } from './utils/firstUserAdmin.js';
|
|
26
26
|
// Better Auth defaults utility
|
|
@@ -19,9 +19,16 @@
|
|
|
19
19
|
* },
|
|
20
20
|
* }
|
|
21
21
|
* ```
|
|
22
|
-
*/
|
|
22
|
+
*/ import { createHash } from 'node:crypto';
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
24
|
// Helpers
|
|
24
25
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Hash an API key using the same algorithm as Better Auth's defaultKeyHasher.
|
|
28
|
+
* Produces a SHA-256 hash encoded as Base64URL (no padding).
|
|
29
|
+
*/ function hashApiKey(key) {
|
|
30
|
+
return createHash('sha256').update(key).digest('base64url');
|
|
31
|
+
}
|
|
25
32
|
/**
|
|
26
33
|
* Extract API key from request headers.
|
|
27
34
|
* Supports Bearer token format: Authorization: Bearer <api-key>
|
|
@@ -40,16 +47,37 @@
|
|
|
40
47
|
* Returns null if key not found or disabled.
|
|
41
48
|
*/ export async function getApiKeyInfo(req, apiKey, apiKeysCollection = 'apiKeys') {
|
|
42
49
|
try {
|
|
50
|
+
// Hash the raw API key to match Better Auth's storage format (SHA-256 + Base64URL).
|
|
51
|
+
// We query for both the hashed and raw key to support both modes:
|
|
52
|
+
// - Hashing enabled (default): only the hashed query matches
|
|
53
|
+
// - Hashing disabled (disableKeyHashing: true): only the plaintext query matches
|
|
54
|
+
const hashedKey = hashApiKey(apiKey);
|
|
43
55
|
// Try the provided collection name first
|
|
44
56
|
let results = await req.payload.find({
|
|
45
57
|
collection: apiKeysCollection,
|
|
58
|
+
overrideAccess: true,
|
|
46
59
|
where: {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
and: [
|
|
61
|
+
{
|
|
62
|
+
enabled: {
|
|
63
|
+
not_equals: false
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
or: [
|
|
68
|
+
{
|
|
69
|
+
key: {
|
|
70
|
+
equals: hashedKey
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: {
|
|
75
|
+
equals: apiKey
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
53
81
|
},
|
|
54
82
|
limit: 1,
|
|
55
83
|
depth: 0
|
|
@@ -59,13 +87,29 @@
|
|
|
59
87
|
const altSlug = apiKeysCollection === 'apiKeys' ? 'api-keys' : 'apiKeys';
|
|
60
88
|
results = await req.payload.find({
|
|
61
89
|
collection: altSlug,
|
|
90
|
+
overrideAccess: true,
|
|
62
91
|
where: {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
92
|
+
and: [
|
|
93
|
+
{
|
|
94
|
+
enabled: {
|
|
95
|
+
not_equals: false
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
or: [
|
|
100
|
+
{
|
|
101
|
+
key: {
|
|
102
|
+
equals: hashedKey
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
key: {
|
|
107
|
+
equals: apiKey
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
]
|
|
69
113
|
},
|
|
70
114
|
limit: 1,
|
|
71
115
|
depth: 0
|
|
@@ -83,8 +127,11 @@
|
|
|
83
127
|
if (Array.isArray(parsed)) {
|
|
84
128
|
scopes = parsed;
|
|
85
129
|
} else if (typeof parsed === 'object') {
|
|
86
|
-
//
|
|
87
|
-
|
|
130
|
+
// Flatten Better Auth permissions format {"resource": ["action1", "action2"]}
|
|
131
|
+
// into scope strings like ["resource:action1", "resource:action2"]
|
|
132
|
+
scopes = Object.entries(parsed).flatMap(([resource, actions])=>Array.isArray(actions) ? actions.map((action)=>`${resource}:${action}`) : [
|
|
133
|
+
resource
|
|
134
|
+
]);
|
|
88
135
|
}
|
|
89
136
|
} catch {
|
|
90
137
|
// If not JSON, treat as comma-separated
|
|
@@ -115,6 +162,13 @@
|
|
|
115
162
|
metadata = doc.metadata;
|
|
116
163
|
}
|
|
117
164
|
}
|
|
165
|
+
// Prefer scope names from metadata (stored by admin UI as original scope strings
|
|
166
|
+
// like ["pages:read", "*"]) over permissions-derived scopes, because the permissions
|
|
167
|
+
// field uses Better Auth's internal format (e.g. {"pages": {"$": ["read"]}}) and
|
|
168
|
+
// Object.keys() on that only yields collection names, not proper scope strings.
|
|
169
|
+
if (metadata?.scopes && Array.isArray(metadata.scopes)) {
|
|
170
|
+
scopes = metadata.scopes;
|
|
171
|
+
}
|
|
118
172
|
return {
|
|
119
173
|
id: String(doc.id),
|
|
120
174
|
userId,
|
package/dist/utils/session.d.ts
CHANGED
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
6
|
import type { BasePayload } from 'payload';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
type DefaultUser = {
|
|
8
|
+
id: string;
|
|
9
|
+
email: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
image?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
};
|
|
14
|
+
export type Session<TUser = DefaultUser> = {
|
|
15
|
+
user: TUser;
|
|
15
16
|
session: {
|
|
16
17
|
id: string;
|
|
17
18
|
expiresAt: Date;
|
|
@@ -21,35 +22,43 @@ export type Session = {
|
|
|
21
22
|
/**
|
|
22
23
|
* Get the current session from headers.
|
|
23
24
|
*
|
|
25
|
+
* Accepts an optional generic type parameter to narrow the user type.
|
|
26
|
+
* Pass your Payload-generated `User` type for full type safety.
|
|
27
|
+
*
|
|
24
28
|
* @example
|
|
25
29
|
* ```ts
|
|
26
30
|
* import { headers } from 'next/headers'
|
|
27
|
-
* import { getServerSession } from '@
|
|
31
|
+
* import { getServerSession } from '@delmaredigital/payload-better-auth'
|
|
32
|
+
* import type { User } from '@/payload-types'
|
|
28
33
|
*
|
|
29
34
|
* export default async function Page() {
|
|
30
35
|
* const headersList = await headers()
|
|
31
|
-
* const session = await getServerSession(payload, headersList)
|
|
36
|
+
* const session = await getServerSession<User>(payload, headersList)
|
|
32
37
|
*
|
|
33
38
|
* if (!session) {
|
|
34
39
|
* redirect('/login')
|
|
35
40
|
* }
|
|
36
41
|
*
|
|
42
|
+
* // session.user.role is fully typed
|
|
37
43
|
* return <div>Hello {session.user.name}</div>
|
|
38
44
|
* }
|
|
39
45
|
* ```
|
|
40
46
|
*/
|
|
41
|
-
export declare function getServerSession(payload: BasePayload, headers: Headers): Promise<Session | null>;
|
|
47
|
+
export declare function getServerSession<TUser = DefaultUser>(payload: BasePayload, headers: Headers): Promise<Session<TUser> | null>;
|
|
42
48
|
/**
|
|
43
49
|
* Get the current user from the session.
|
|
44
50
|
*
|
|
51
|
+
* Accepts an optional generic type parameter to narrow the user type.
|
|
52
|
+
*
|
|
45
53
|
* @example
|
|
46
54
|
* ```ts
|
|
47
55
|
* import { headers } from 'next/headers'
|
|
48
|
-
* import { getServerUser } from '@
|
|
56
|
+
* import { getServerUser } from '@delmaredigital/payload-better-auth'
|
|
57
|
+
* import type { User } from '@/payload-types'
|
|
49
58
|
*
|
|
50
59
|
* export default async function Page() {
|
|
51
60
|
* const headersList = await headers()
|
|
52
|
-
* const user = await getServerUser(payload, headersList)
|
|
61
|
+
* const user = await getServerUser<User>(payload, headersList)
|
|
53
62
|
*
|
|
54
63
|
* if (!user) {
|
|
55
64
|
* redirect('/login')
|
|
@@ -59,4 +68,32 @@ export declare function getServerSession(payload: BasePayload, headers: Headers)
|
|
|
59
68
|
* }
|
|
60
69
|
* ```
|
|
61
70
|
*/
|
|
62
|
-
export declare function getServerUser(payload: BasePayload, headers: Headers): Promise<
|
|
71
|
+
export declare function getServerUser<TUser = DefaultUser>(payload: BasePayload, headers: Headers): Promise<TUser | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Create typed session helpers bound to your User type.
|
|
74
|
+
*
|
|
75
|
+
* Define once in a shared file, then import the typed helpers
|
|
76
|
+
* everywhere — no generics needed at call sites.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* // lib/auth.ts
|
|
81
|
+
* import { createSessionHelpers } from '@delmaredigital/payload-better-auth'
|
|
82
|
+
* import type { User } from '@/payload-types'
|
|
83
|
+
*
|
|
84
|
+
* export const { getServerSession, getServerUser } = createSessionHelpers<User>()
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* ```ts
|
|
88
|
+
* // app/page.tsx
|
|
89
|
+
* import { getServerSession } from '@/lib/auth'
|
|
90
|
+
*
|
|
91
|
+
* const session = await getServerSession(payload, headersList)
|
|
92
|
+
* // session.user is typed as User — no generic needed
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function createSessionHelpers<TUser = DefaultUser>(): {
|
|
96
|
+
getServerSession: (payload: BasePayload, headers: Headers) => Promise<Session<TUser> | null>;
|
|
97
|
+
getServerUser: (payload: BasePayload, headers: Headers) => Promise<TUser | null>;
|
|
98
|
+
};
|
|
99
|
+
export {};
|
package/dist/utils/session.js
CHANGED
|
@@ -5,19 +5,24 @@
|
|
|
5
5
|
*/ /**
|
|
6
6
|
* Get the current session from headers.
|
|
7
7
|
*
|
|
8
|
+
* Accepts an optional generic type parameter to narrow the user type.
|
|
9
|
+
* Pass your Payload-generated `User` type for full type safety.
|
|
10
|
+
*
|
|
8
11
|
* @example
|
|
9
12
|
* ```ts
|
|
10
13
|
* import { headers } from 'next/headers'
|
|
11
|
-
* import { getServerSession } from '@
|
|
14
|
+
* import { getServerSession } from '@delmaredigital/payload-better-auth'
|
|
15
|
+
* import type { User } from '@/payload-types'
|
|
12
16
|
*
|
|
13
17
|
* export default async function Page() {
|
|
14
18
|
* const headersList = await headers()
|
|
15
|
-
* const session = await getServerSession(payload, headersList)
|
|
19
|
+
* const session = await getServerSession<User>(payload, headersList)
|
|
16
20
|
*
|
|
17
21
|
* if (!session) {
|
|
18
22
|
* redirect('/login')
|
|
19
23
|
* }
|
|
20
24
|
*
|
|
25
|
+
* // session.user.role is fully typed
|
|
21
26
|
* return <div>Hello {session.user.name}</div>
|
|
22
27
|
* }
|
|
23
28
|
* ```
|
|
@@ -40,14 +45,17 @@
|
|
|
40
45
|
/**
|
|
41
46
|
* Get the current user from the session.
|
|
42
47
|
*
|
|
48
|
+
* Accepts an optional generic type parameter to narrow the user type.
|
|
49
|
+
*
|
|
43
50
|
* @example
|
|
44
51
|
* ```ts
|
|
45
52
|
* import { headers } from 'next/headers'
|
|
46
|
-
* import { getServerUser } from '@
|
|
53
|
+
* import { getServerUser } from '@delmaredigital/payload-better-auth'
|
|
54
|
+
* import type { User } from '@/payload-types'
|
|
47
55
|
*
|
|
48
56
|
* export default async function Page() {
|
|
49
57
|
* const headersList = await headers()
|
|
50
|
-
* const user = await getServerUser(payload, headersList)
|
|
58
|
+
* const user = await getServerUser<User>(payload, headersList)
|
|
51
59
|
*
|
|
52
60
|
* if (!user) {
|
|
53
61
|
* redirect('/login')
|
|
@@ -60,3 +68,31 @@
|
|
|
60
68
|
const session = await getServerSession(payload, headers);
|
|
61
69
|
return session?.user ?? null;
|
|
62
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Create typed session helpers bound to your User type.
|
|
73
|
+
*
|
|
74
|
+
* Define once in a shared file, then import the typed helpers
|
|
75
|
+
* everywhere — no generics needed at call sites.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* // lib/auth.ts
|
|
80
|
+
* import { createSessionHelpers } from '@delmaredigital/payload-better-auth'
|
|
81
|
+
* import type { User } from '@/payload-types'
|
|
82
|
+
*
|
|
83
|
+
* export const { getServerSession, getServerUser } = createSessionHelpers<User>()
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* ```ts
|
|
87
|
+
* // app/page.tsx
|
|
88
|
+
* import { getServerSession } from '@/lib/auth'
|
|
89
|
+
*
|
|
90
|
+
* const session = await getServerSession(payload, headersList)
|
|
91
|
+
* // session.user is typed as User — no generic needed
|
|
92
|
+
* ```
|
|
93
|
+
*/ export function createSessionHelpers() {
|
|
94
|
+
return {
|
|
95
|
+
getServerSession: (payload, headers)=>getServerSession(payload, headers),
|
|
96
|
+
getServerUser: (payload, headers)=>getServerUser(payload, headers)
|
|
97
|
+
};
|
|
98
|
+
}
|