@delmaredigital/payload-better-auth 0.5.6 → 0.6.1
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/components/management/ApiKeysManagementClient.d.ts +16 -7
- package/dist/components/management/ApiKeysManagementClient.js +293 -286
- package/dist/components/management/views/ApiKeysView.js +55 -9
- package/dist/index.d.ts +5 -5
- package/dist/index.js +5 -5
- package/dist/plugin/index.d.ts +8 -8
- package/dist/plugin/index.js +131 -63
- package/dist/types/apiKey.d.ts +19 -47
- package/dist/types/apiKey.js +10 -5
- package/dist/utils/apiKeyAccess.d.ts +47 -95
- package/dist/utils/apiKeyAccess.js +128 -284
- package/dist/utils/generatePermissions.d.ts +11 -0
- package/dist/utils/generatePermissions.js +30 -0
- package/package.json +1 -1
- package/dist/utils/generateScopes.d.ts +0 -20
- package/dist/utils/generateScopes.js +0 -110
|
@@ -1,392 +1,236 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* API Key
|
|
2
|
+
* API Key Permission Enforcement Utilities
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* type-safe access control functions.
|
|
4
|
+
* Thin wrappers around Better Auth's verifyApiKey() for use in
|
|
5
|
+
* Payload access control. Uses BA's native permission format.
|
|
7
6
|
*
|
|
8
7
|
* @example
|
|
9
8
|
* ```ts
|
|
10
|
-
* import {
|
|
9
|
+
* import { requirePermission, allowSessionOrPermission } from '@delmaredigital/payload-better-auth'
|
|
11
10
|
*
|
|
12
11
|
* export const Posts: CollectionConfig = {
|
|
13
12
|
* slug: 'posts',
|
|
14
13
|
* access: {
|
|
15
|
-
* read:
|
|
16
|
-
* create:
|
|
17
|
-
* update:
|
|
18
|
-
* delete:
|
|
14
|
+
* read: requirePermission('posts', 'read'),
|
|
15
|
+
* create: requirePermission('posts', 'write'),
|
|
16
|
+
* update: requirePermission('posts', 'write'),
|
|
17
|
+
* delete: requirePermission('posts', 'write'),
|
|
19
18
|
* },
|
|
20
19
|
* }
|
|
21
20
|
* ```
|
|
22
|
-
*/
|
|
23
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
*/ // ─────────────────────────────────────────────────────────────────────────────
|
|
24
22
|
// Helpers
|
|
25
23
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
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
|
-
}
|
|
32
24
|
/**
|
|
33
25
|
* Extract API key from request headers.
|
|
34
26
|
* Supports Bearer token format: Authorization: Bearer <api-key>
|
|
35
27
|
*/ export function extractApiKeyFromRequest(req) {
|
|
36
28
|
const authHeader = req.headers?.get('authorization');
|
|
37
29
|
if (!authHeader) return null;
|
|
38
|
-
// Support "Bearer <key>" format
|
|
39
30
|
if (authHeader.startsWith('Bearer ')) {
|
|
40
31
|
return authHeader.slice(7).trim();
|
|
41
32
|
}
|
|
42
|
-
// Support raw key in Authorization header
|
|
43
33
|
return authHeader.trim();
|
|
44
34
|
}
|
|
45
35
|
/**
|
|
46
|
-
*
|
|
47
|
-
* Returns
|
|
48
|
-
|
|
36
|
+
* Verify an API key has the required permission using Better Auth's native verifyApiKey.
|
|
37
|
+
* Returns true if the key is valid and has the permission, false otherwise.
|
|
38
|
+
*
|
|
39
|
+
* Includes backward compatibility: if the key was created with old CRUD actions
|
|
40
|
+
* (create/update/delete), a 'write' check will fall back to checking for those.
|
|
41
|
+
*/ async function verifyKeyPermission(req, apiKey, resource, action) {
|
|
42
|
+
const auth = req.payload.betterAuth;
|
|
43
|
+
if (!auth) return false;
|
|
49
44
|
try {
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
collection: apiKeysCollection,
|
|
58
|
-
overrideAccess: true,
|
|
59
|
-
where: {
|
|
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
|
-
]
|
|
81
|
-
},
|
|
82
|
-
limit: 1,
|
|
83
|
-
depth: 0
|
|
84
|
-
}).catch(()=>null);
|
|
85
|
-
// If not found, try alternative slug
|
|
86
|
-
if (!results || results.docs.length === 0) {
|
|
87
|
-
const altSlug = apiKeysCollection === 'apiKeys' ? 'api-keys' : 'apiKeys';
|
|
88
|
-
results = await req.payload.find({
|
|
89
|
-
collection: altSlug,
|
|
90
|
-
overrideAccess: true,
|
|
91
|
-
where: {
|
|
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
|
-
}
|
|
45
|
+
// Primary check: use BA's native permission verification
|
|
46
|
+
const result = await auth.api.verifyApiKey({
|
|
47
|
+
body: {
|
|
48
|
+
key: apiKey,
|
|
49
|
+
permissions: {
|
|
50
|
+
[resource]: [
|
|
51
|
+
action
|
|
112
52
|
]
|
|
113
|
-
},
|
|
114
|
-
limit: 1,
|
|
115
|
-
depth: 0
|
|
116
|
-
}).catch(()=>null);
|
|
117
|
-
}
|
|
118
|
-
if (!results || results.docs.length === 0) {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
const doc = results.docs[0];
|
|
122
|
-
// Parse scopes from permissions field (Better Auth format) or scopes array
|
|
123
|
-
let scopes = [];
|
|
124
|
-
if (doc.permissions) {
|
|
125
|
-
try {
|
|
126
|
-
const parsed = JSON.parse(doc.permissions);
|
|
127
|
-
if (Array.isArray(parsed)) {
|
|
128
|
-
scopes = parsed;
|
|
129
|
-
} else if (typeof parsed === 'object') {
|
|
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
|
-
]);
|
|
135
53
|
}
|
|
136
|
-
} catch {
|
|
137
|
-
// If not JSON, treat as comma-separated
|
|
138
|
-
scopes = doc.permissions.split(',').map((s)=>s.trim()).filter(Boolean);
|
|
139
54
|
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (doc.referenceId) {
|
|
147
|
-
referenceId = String(doc.referenceId);
|
|
148
|
-
if (doc.referenceType === 'organization') {
|
|
149
|
-
referenceType = 'organization';
|
|
55
|
+
});
|
|
56
|
+
if (result.valid) return true;
|
|
57
|
+
// Backward compat: old keys stored CRUD actions instead of 'read'/'write'
|
|
58
|
+
const fallbackResult = await auth.api.verifyApiKey({
|
|
59
|
+
body: {
|
|
60
|
+
key: apiKey
|
|
150
61
|
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return
|
|
62
|
+
});
|
|
63
|
+
if (!fallbackResult.valid || !fallbackResult.key?.permissions) return false;
|
|
64
|
+
const perms = fallbackResult.key.permissions;
|
|
65
|
+
const actions = perms[resource];
|
|
66
|
+
if (!Array.isArray(actions)) return false;
|
|
67
|
+
if (action === 'write') {
|
|
68
|
+
// Old 'write' stored as ['read', 'create', 'update'] or ['delete']
|
|
69
|
+
return actions.some((a)=>[
|
|
70
|
+
'create',
|
|
71
|
+
'update',
|
|
72
|
+
'delete'
|
|
73
|
+
].includes(a));
|
|
74
|
+
}
|
|
75
|
+
if (action === 'read') {
|
|
76
|
+
return actions.includes('read');
|
|
159
77
|
}
|
|
160
|
-
|
|
161
|
-
let metadata;
|
|
162
|
-
if (doc.metadata) {
|
|
163
|
-
if (typeof doc.metadata === 'string') {
|
|
164
|
-
try {
|
|
165
|
-
metadata = JSON.parse(doc.metadata);
|
|
166
|
-
} catch {
|
|
167
|
-
// Ignore parse errors
|
|
168
|
-
}
|
|
169
|
-
} else {
|
|
170
|
-
metadata = doc.metadata;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// Prefer scope names from metadata (stored by admin UI as original scope strings
|
|
174
|
-
// like ["pages:read", "*"]) over permissions-derived scopes, because the permissions
|
|
175
|
-
// field uses Better Auth's internal format (e.g. {"pages": {"$": ["read"]}}) and
|
|
176
|
-
// Object.keys() on that only yields collection names, not proper scope strings.
|
|
177
|
-
if (metadata?.scopes && Array.isArray(metadata.scopes)) {
|
|
178
|
-
scopes = metadata.scopes;
|
|
179
|
-
}
|
|
180
|
-
return {
|
|
181
|
-
id: String(doc.id),
|
|
182
|
-
referenceId,
|
|
183
|
-
referenceType,
|
|
184
|
-
scopes,
|
|
185
|
-
keyPrefix: doc.start,
|
|
186
|
-
metadata
|
|
187
|
-
};
|
|
78
|
+
return false;
|
|
188
79
|
} catch {
|
|
189
|
-
return
|
|
80
|
+
return false;
|
|
190
81
|
}
|
|
191
82
|
}
|
|
192
83
|
/**
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*/
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (scope === '*') return true;
|
|
84
|
+
* Verify an API key without checking specific permissions.
|
|
85
|
+
* Returns true if the key is valid, false otherwise.
|
|
86
|
+
*/ async function verifyKeyOnly(req, apiKey) {
|
|
87
|
+
const auth = req.payload.betterAuth;
|
|
88
|
+
if (!auth) return false;
|
|
89
|
+
try {
|
|
90
|
+
const result = await auth.api.verifyApiKey({
|
|
91
|
+
body: {
|
|
92
|
+
key: apiKey
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return result.valid === true;
|
|
96
|
+
} catch {
|
|
207
97
|
return false;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Check if an API key has any of the specified scopes.
|
|
212
|
-
*/ export function hasAnyScope(keyScopes, requiredScopes) {
|
|
213
|
-
return requiredScopes.some((scope)=>hasScope(keyScopes, scope));
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Check if an API key has all of the specified scopes.
|
|
217
|
-
*/ export function hasAllScopes(keyScopes, requiredScopes) {
|
|
218
|
-
return requiredScopes.every((scope)=>hasScope(keyScopes, scope));
|
|
98
|
+
}
|
|
219
99
|
}
|
|
220
100
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
221
101
|
// Access Control Functions
|
|
222
102
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
223
103
|
/**
|
|
224
|
-
*
|
|
104
|
+
* Require a specific permission on an API key.
|
|
225
105
|
*
|
|
226
|
-
* @param
|
|
227
|
-
* @param
|
|
106
|
+
* @param resource - Collection slug (e.g., 'posts')
|
|
107
|
+
* @param action - Permission action: 'read' or 'write'
|
|
108
|
+
* @param config - Optional configuration
|
|
228
109
|
* @returns Payload access function
|
|
229
110
|
*
|
|
230
111
|
* @example
|
|
231
112
|
* ```ts
|
|
232
113
|
* access: {
|
|
233
|
-
* read:
|
|
234
|
-
* create:
|
|
114
|
+
* read: requirePermission('posts', 'read'),
|
|
115
|
+
* create: requirePermission('posts', 'write'),
|
|
235
116
|
* }
|
|
236
117
|
* ```
|
|
237
|
-
*/ export function
|
|
238
|
-
const {
|
|
118
|
+
*/ export function requirePermission(resource, action, config = {}) {
|
|
119
|
+
const { allowAuthenticatedUsers = false, extractApiKey = extractApiKeyFromRequest } = config;
|
|
239
120
|
return async ({ req })=>{
|
|
240
|
-
// If authenticated users are allowed and user is logged in without API key
|
|
241
|
-
if (allowAuthenticatedUsers && req.user) {
|
|
242
|
-
const apiKey = extractApiKey(req);
|
|
243
|
-
if (!apiKey) {
|
|
244
|
-
return true // User authenticated via session, no API key = allow
|
|
245
|
-
;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
// Extract API key from request
|
|
249
121
|
const apiKey = extractApiKey(req);
|
|
250
|
-
if (!apiKey) {
|
|
251
|
-
return
|
|
252
|
-
}
|
|
253
|
-
// Look up API key
|
|
254
|
-
const keyInfo = await getApiKeyInfo(req, apiKey, apiKeysCollection);
|
|
255
|
-
if (!keyInfo) {
|
|
256
|
-
return false;
|
|
122
|
+
if (allowAuthenticatedUsers && req.user && !apiKey) {
|
|
123
|
+
return true;
|
|
257
124
|
}
|
|
258
|
-
|
|
259
|
-
return
|
|
125
|
+
if (!apiKey) return false;
|
|
126
|
+
return verifyKeyPermission(req, apiKey, resource, action);
|
|
260
127
|
};
|
|
261
128
|
}
|
|
262
129
|
/**
|
|
263
|
-
*
|
|
130
|
+
* Require any one of the specified permissions.
|
|
264
131
|
*
|
|
265
|
-
* @param
|
|
266
|
-
* @param config -
|
|
132
|
+
* @param permissions - Array of {resource, action} pairs (at least one must match)
|
|
133
|
+
* @param config - Optional configuration
|
|
267
134
|
* @returns Payload access function
|
|
268
135
|
*
|
|
269
136
|
* @example
|
|
270
137
|
* ```ts
|
|
271
138
|
* access: {
|
|
272
|
-
* read:
|
|
139
|
+
* read: requireAnyPermission([
|
|
140
|
+
* { resource: 'posts', action: 'read' },
|
|
141
|
+
* { resource: 'pages', action: 'read' },
|
|
142
|
+
* ]),
|
|
273
143
|
* }
|
|
274
144
|
* ```
|
|
275
|
-
*/ export function
|
|
276
|
-
const {
|
|
145
|
+
*/ export function requireAnyPermission(permissions, config = {}) {
|
|
146
|
+
const { allowAuthenticatedUsers = false, extractApiKey = extractApiKeyFromRequest } = config;
|
|
277
147
|
return async ({ req })=>{
|
|
278
|
-
// If authenticated users are allowed and user is logged in without API key
|
|
279
|
-
if (allowAuthenticatedUsers && req.user) {
|
|
280
|
-
const apiKey = extractApiKey(req);
|
|
281
|
-
if (!apiKey) {
|
|
282
|
-
return true;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
148
|
const apiKey = extractApiKey(req);
|
|
286
|
-
if (!apiKey) {
|
|
287
|
-
return
|
|
149
|
+
if (allowAuthenticatedUsers && req.user && !apiKey) {
|
|
150
|
+
return true;
|
|
288
151
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
152
|
+
if (!apiKey) return false;
|
|
153
|
+
for (const perm of permissions){
|
|
154
|
+
if (await verifyKeyPermission(req, apiKey, perm.resource, perm.action)) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
292
157
|
}
|
|
293
|
-
return
|
|
158
|
+
return false;
|
|
294
159
|
};
|
|
295
160
|
}
|
|
296
161
|
/**
|
|
297
|
-
*
|
|
162
|
+
* Require all of the specified permissions.
|
|
298
163
|
*
|
|
299
|
-
* @param
|
|
300
|
-
* @param config -
|
|
164
|
+
* @param permissions - Array of {resource, action} pairs (all must match)
|
|
165
|
+
* @param config - Optional configuration
|
|
301
166
|
* @returns Payload access function
|
|
302
167
|
*
|
|
303
168
|
* @example
|
|
304
169
|
* ```ts
|
|
305
170
|
* access: {
|
|
306
|
-
* delete:
|
|
171
|
+
* delete: requireAllPermissions([
|
|
172
|
+
* { resource: 'posts', action: 'write' },
|
|
173
|
+
* { resource: 'admin', action: 'write' },
|
|
174
|
+
* ]),
|
|
307
175
|
* }
|
|
308
176
|
* ```
|
|
309
|
-
*/ export function
|
|
310
|
-
const {
|
|
177
|
+
*/ export function requireAllPermissions(permissions, config = {}) {
|
|
178
|
+
const { allowAuthenticatedUsers = false, extractApiKey = extractApiKeyFromRequest } = config;
|
|
311
179
|
return async ({ req })=>{
|
|
312
|
-
// If authenticated users are allowed and user is logged in without API key
|
|
313
|
-
if (allowAuthenticatedUsers && req.user) {
|
|
314
|
-
const apiKey = extractApiKey(req);
|
|
315
|
-
if (!apiKey) {
|
|
316
|
-
return true;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
180
|
const apiKey = extractApiKey(req);
|
|
320
|
-
if (!apiKey) {
|
|
321
|
-
return
|
|
181
|
+
if (allowAuthenticatedUsers && req.user && !apiKey) {
|
|
182
|
+
return true;
|
|
322
183
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
184
|
+
if (!apiKey) return false;
|
|
185
|
+
for (const perm of permissions){
|
|
186
|
+
if (!await verifyKeyPermission(req, apiKey, perm.resource, perm.action)) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
326
189
|
}
|
|
327
|
-
return
|
|
190
|
+
return true;
|
|
328
191
|
};
|
|
329
192
|
}
|
|
330
193
|
/**
|
|
331
|
-
*
|
|
332
|
-
* 1. Authenticated users (via session)
|
|
333
|
-
* 2. API key with required scope
|
|
334
|
-
*
|
|
335
|
-
* This is useful for endpoints that should work with both auth methods.
|
|
336
|
-
*
|
|
337
|
-
* @param scope - The required scope for API key access
|
|
338
|
-
* @param config - Configuration options
|
|
339
|
-
* @returns Payload access function
|
|
194
|
+
* Allow either authenticated session OR API key with permission.
|
|
340
195
|
*
|
|
341
196
|
* @example
|
|
342
197
|
* ```ts
|
|
343
198
|
* access: {
|
|
344
|
-
* read:
|
|
199
|
+
* read: allowSessionOrPermission('posts', 'read'),
|
|
345
200
|
* }
|
|
346
201
|
* ```
|
|
347
|
-
*/ export function
|
|
348
|
-
return
|
|
202
|
+
*/ export function allowSessionOrPermission(resource, action, config = {}) {
|
|
203
|
+
return requirePermission(resource, action, {
|
|
349
204
|
...config,
|
|
350
205
|
allowAuthenticatedUsers: true
|
|
351
206
|
});
|
|
352
207
|
}
|
|
353
208
|
/**
|
|
354
|
-
*
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
*
|
|
358
|
-
* @param scopes - Array of acceptable scopes for API key access
|
|
359
|
-
* @param config - Configuration options
|
|
360
|
-
* @returns Payload access function
|
|
361
|
-
*/ export function allowSessionOrAnyScope(scopes, config = {}) {
|
|
362
|
-
return requireAnyScope(scopes, {
|
|
209
|
+
* Allow either authenticated session OR API key with any of the permissions.
|
|
210
|
+
*/ export function allowSessionOrAnyPermission(permissions, config = {}) {
|
|
211
|
+
return requireAnyPermission(permissions, {
|
|
363
212
|
...config,
|
|
364
213
|
allowAuthenticatedUsers: true
|
|
365
214
|
});
|
|
366
215
|
}
|
|
367
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
368
|
-
// Better Auth Integration
|
|
369
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
370
216
|
/**
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
* This performs a database lookup to validate the key and retrieve
|
|
374
|
-
* its associated scopes and user.
|
|
375
|
-
*
|
|
376
|
-
* @param req - Payload request
|
|
377
|
-
* @param apiKeysCollection - The API keys collection slug
|
|
378
|
-
* @returns API key info if valid, null otherwise
|
|
217
|
+
* Require a valid API key (no specific permissions checked).
|
|
218
|
+
* Useful for apps that use role-based access and just need to verify the key exists.
|
|
379
219
|
*
|
|
380
220
|
* @example
|
|
381
221
|
* ```ts
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
* console.log('Valid API key for:', keyInfo.referenceId, keyInfo.referenceType)
|
|
385
|
-
* console.log('Scopes:', keyInfo.scopes)
|
|
222
|
+
* access: {
|
|
223
|
+
* read: requireApiKey(),
|
|
386
224
|
* }
|
|
387
225
|
* ```
|
|
388
|
-
*/ export
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
226
|
+
*/ export function requireApiKey(config = {}) {
|
|
227
|
+
const { allowAuthenticatedUsers = false, extractApiKey = extractApiKeyFromRequest } = config;
|
|
228
|
+
return async ({ req })=>{
|
|
229
|
+
const apiKey = extractApiKey(req);
|
|
230
|
+
if (allowAuthenticatedUsers && req.user && !apiKey) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
if (!apiKey) return false;
|
|
234
|
+
return verifyKeyOnly(req, apiKey);
|
|
235
|
+
};
|
|
392
236
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate permission definitions from Payload collections for the admin UI.
|
|
3
|
+
*/
|
|
4
|
+
import type { CollectionConfig } from 'payload';
|
|
5
|
+
import type { PermissionDefinition } from '../types/apiKey.js';
|
|
6
|
+
/**
|
|
7
|
+
* Generate permission definitions from Payload collections.
|
|
8
|
+
* Returns a list of collections with their available actions (read/write)
|
|
9
|
+
* for display in the API key management UI.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateCollectionPermissions(collections: CollectionConfig[], excludeCollections?: string[]): PermissionDefinition[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate permission definitions from Payload collections for the admin UI.
|
|
3
|
+
*/ /** Default collections to exclude from permissions UI */ const DEFAULT_EXCLUDED_COLLECTIONS = [
|
|
4
|
+
'sessions',
|
|
5
|
+
'verifications',
|
|
6
|
+
'accounts',
|
|
7
|
+
'twoFactors',
|
|
8
|
+
'apiKeys',
|
|
9
|
+
'api-keys'
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Convert slug to human-readable label.
|
|
13
|
+
* e.g., 'blog-posts' -> 'Blog Posts'
|
|
14
|
+
*/ function slugToLabel(slug) {
|
|
15
|
+
return slug.split('-').map((s)=>s.charAt(0).toUpperCase() + s.slice(1)).join(' ');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate permission definitions from Payload collections.
|
|
19
|
+
* Returns a list of collections with their available actions (read/write)
|
|
20
|
+
* for display in the API key management UI.
|
|
21
|
+
*/ export function generateCollectionPermissions(collections, excludeCollections = DEFAULT_EXCLUDED_COLLECTIONS) {
|
|
22
|
+
return collections.filter((c)=>!excludeCollections.includes(c.slug)).map((c)=>({
|
|
23
|
+
slug: c.slug,
|
|
24
|
+
label: (typeof c.labels?.plural === 'string' ? c.labels.plural : null) ?? slugToLabel(c.slug) + 's',
|
|
25
|
+
actions: [
|
|
26
|
+
'read',
|
|
27
|
+
'write'
|
|
28
|
+
]
|
|
29
|
+
}));
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auto-generate API key scopes from Payload collections.
|
|
3
|
-
*/
|
|
4
|
-
import type { CollectionConfig } from 'payload';
|
|
5
|
-
import type { ScopeDefinition, ApiKeyScopesConfig, AvailableScope } from '../types/apiKey.js';
|
|
6
|
-
/**
|
|
7
|
-
* Generate scopes from Payload collections.
|
|
8
|
-
* Creates {collection}:read, {collection}:write, {collection}:delete for each collection.
|
|
9
|
-
*/
|
|
10
|
-
export declare function generateScopesFromCollections(collections: CollectionConfig[], excludeCollections?: string[]): Record<string, ScopeDefinition>;
|
|
11
|
-
/**
|
|
12
|
-
* Build the final scopes configuration from plugin options and collections.
|
|
13
|
-
* Handles merging custom scopes with auto-generated collection scopes.
|
|
14
|
-
*/
|
|
15
|
-
export declare function buildAvailableScopes(collections: CollectionConfig[], config?: ApiKeyScopesConfig): AvailableScope[];
|
|
16
|
-
/**
|
|
17
|
-
* Convert selected scopes to Better Auth permission format.
|
|
18
|
-
* Used when creating an API key.
|
|
19
|
-
*/
|
|
20
|
-
export declare function scopesToPermissions(selectedScopeIds: string[], availableScopes: AvailableScope[]): Record<string, string[]>;
|