@delmaredigital/payload-better-auth 0.5.5 → 0.6.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/dist/adapter/index.js +5 -5
- package/dist/components/LoginView.js +1 -1
- package/dist/components/PasskeyRegisterButton.js +1 -1
- package/dist/components/PasskeySignInButton.js +1 -1
- package/dist/components/management/ApiKeysManagementClient.d.ts +5 -7
- package/dist/components/management/ApiKeysManagementClient.js +217 -287
- package/dist/components/management/PasskeysManagementClient.js +1 -1
- package/dist/components/management/views/ApiKeysView.js +6 -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 +27 -64
- 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
|
@@ -2,8 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { DefaultTemplate } from '@payloadcms/next/templates';
|
|
3
3
|
import { getVisibleEntities } from '@payloadcms/ui/shared';
|
|
4
4
|
import { ApiKeysManagementClient } from '../ApiKeysManagementClient.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { getApiKeyPermissionsConfig } from '../../../plugin/index.js';
|
|
6
|
+
import { generateCollectionPermissions } from '../../../utils/generatePermissions.js';
|
|
7
7
|
/**
|
|
8
8
|
* API Keys management view for Payload admin panel.
|
|
9
9
|
* Server component that provides the admin layout.
|
|
@@ -16,11 +16,9 @@ import { buildAvailableScopes } from '../../../utils/generateScopes.js';
|
|
|
16
16
|
const visibleEntities = getVisibleEntities({
|
|
17
17
|
req
|
|
18
18
|
});
|
|
19
|
-
// Build
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
// Get default scopes from config
|
|
23
|
-
const defaultScopes = scopesConfig?.defaultScopes ?? [];
|
|
19
|
+
// Build permission definitions from collections
|
|
20
|
+
const permissionsConfig = getApiKeyPermissionsConfig();
|
|
21
|
+
const permissions = generateCollectionPermissions(payload.config.collections, permissionsConfig?.excludeCollections);
|
|
24
22
|
return /*#__PURE__*/ _jsx(DefaultTemplate, {
|
|
25
23
|
i18n: req.i18n,
|
|
26
24
|
locale: req.locale,
|
|
@@ -31,8 +29,7 @@ import { buildAvailableScopes } from '../../../utils/generateScopes.js';
|
|
|
31
29
|
user: req.user ?? undefined,
|
|
32
30
|
visibleEntities: visibleEntities,
|
|
33
31
|
children: /*#__PURE__*/ _jsx(ApiKeysManagementClient, {
|
|
34
|
-
|
|
35
|
-
defaultScopes: defaultScopes
|
|
32
|
+
permissions: permissions
|
|
36
33
|
})
|
|
37
34
|
});
|
|
38
35
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,16 +10,16 @@ export { payloadAdapter, detectDbType, resolveIdType } from './adapter/index.js'
|
|
|
10
10
|
export type { PayloadAdapterConfig, DbType } from './adapter/index.js';
|
|
11
11
|
export { betterAuthCollections } from './adapter/collections.js';
|
|
12
12
|
export type { BetterAuthCollectionsOptions } from './adapter/collections.js';
|
|
13
|
-
export { createBetterAuthPlugin, betterAuthStrategy, resetAuthInstance,
|
|
13
|
+
export { createBetterAuthPlugin, betterAuthStrategy, resetAuthInstance, getApiKeyPermissionsConfig, } from './plugin/index.js';
|
|
14
14
|
export type { Auth, CreateAuthFunction, BetterAuthPluginOptions, BetterAuthPluginAdminOptions, BetterAuthStrategyOptions, } from './plugin/index.js';
|
|
15
15
|
export type { BetterAuthReturn, PayloadWithAuth, PayloadRequestWithBetterAuth, CollectionHookWithBetterAuth, EndpointWithBetterAuth, RoleArray, } from './types/betterAuth.js';
|
|
16
16
|
export type { User, Session as BetterAuthSession, Account, Verification, Apikey, Passkey, Organization, Member, Invitation, Team, TeamMember, TwoFactor, BaseUserFields, BaseSessionFields, BaseAccountFields, UserPluginFields, SessionPluginFields, BetterAuthFullSchema, ModelKey, PluginId, } from './generated-types.js';
|
|
17
|
-
export type {
|
|
18
|
-
export {
|
|
17
|
+
export type { PermissionDefinition, ApiKeyPermissionsConfig, } from './types/apiKey.js';
|
|
18
|
+
export { generateCollectionPermissions, } from './utils/generatePermissions.js';
|
|
19
19
|
export { normalizeRoles, hasAnyRole, hasAllRoles, hasAdminRoles, isAdmin, isAdminField, isAdminOrSelf, canUpdateOwnFields, isAuthenticated, isAuthenticatedField, hasRole, hasRoleField, requireAllRoles, } from './utils/access.js';
|
|
20
20
|
export type { RoleCheckConfig, SelfAccessConfig, FieldUpdateConfig, } from './utils/access.js';
|
|
21
|
-
export { extractApiKeyFromRequest,
|
|
22
|
-
export type {
|
|
21
|
+
export { extractApiKeyFromRequest, requirePermission, requireAnyPermission, requireAllPermissions, allowSessionOrPermission, allowSessionOrAnyPermission, requireApiKey, } from './utils/apiKeyAccess.js';
|
|
22
|
+
export type { ApiKeyPermissionConfig, PermissionCheck, } from './utils/apiKeyAccess.js';
|
|
23
23
|
export { detectAuthConfig } from './utils/detectAuthConfig.js';
|
|
24
24
|
export type { AuthDetectionResult } from './utils/detectAuthConfig.js';
|
|
25
25
|
export { getServerSession, getServerUser, createSessionHelpers } from './utils/session.js';
|
package/dist/index.js
CHANGED
|
@@ -10,13 +10,13 @@ export { payloadAdapter, detectDbType, resolveIdType } from './adapter/index.js'
|
|
|
10
10
|
// Collection generator plugin
|
|
11
11
|
export { betterAuthCollections } from './adapter/collections.js';
|
|
12
12
|
// Payload plugin and strategy
|
|
13
|
-
export { createBetterAuthPlugin, betterAuthStrategy, resetAuthInstance,
|
|
14
|
-
//
|
|
15
|
-
export {
|
|
13
|
+
export { createBetterAuthPlugin, betterAuthStrategy, resetAuthInstance, getApiKeyPermissionsConfig } from './plugin/index.js';
|
|
14
|
+
// Permission utilities
|
|
15
|
+
export { generateCollectionPermissions } from './utils/generatePermissions.js';
|
|
16
16
|
// Access control utilities
|
|
17
17
|
export { normalizeRoles, hasAnyRole, hasAllRoles, hasAdminRoles, isAdmin, isAdminField, isAdminOrSelf, canUpdateOwnFields, isAuthenticated, isAuthenticatedField, hasRole, hasRoleField, requireAllRoles } from './utils/access.js';
|
|
18
|
-
// API key
|
|
19
|
-
export { extractApiKeyFromRequest,
|
|
18
|
+
// API key permission enforcement utilities
|
|
19
|
+
export { extractApiKeyFromRequest, requirePermission, requireAnyPermission, requireAllPermissions, allowSessionOrPermission, allowSessionOrAnyPermission, requireApiKey } from './utils/apiKeyAccess.js';
|
|
20
20
|
// Auth config detection utility
|
|
21
21
|
export { detectAuthConfig } from './utils/detectAuthConfig.js';
|
|
22
22
|
// Session utilities
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { Plugin, AuthStrategy, BasePayload } from 'payload';
|
|
7
7
|
import type { betterAuth, BetterAuthOptions } from 'better-auth';
|
|
8
|
-
import type {
|
|
8
|
+
import type { ApiKeyPermissionsConfig } from '../types/apiKey.js';
|
|
9
9
|
export type Auth = ReturnType<typeof betterAuth>;
|
|
10
10
|
export type { PayloadWithAuth } from '../types/betterAuth.js';
|
|
11
11
|
export type CreateAuthFunction = (payload: BasePayload) => any;
|
|
@@ -97,11 +97,11 @@ export type BetterAuthPluginAdminOptions = {
|
|
|
97
97
|
passkeys?: string;
|
|
98
98
|
};
|
|
99
99
|
/**
|
|
100
|
-
* API key
|
|
101
|
-
* Controls which
|
|
102
|
-
* When not provided,
|
|
100
|
+
* API key permissions configuration.
|
|
101
|
+
* Controls which permissions are available when creating API keys.
|
|
102
|
+
* When not provided, permissions are auto-generated from Payload collections.
|
|
103
103
|
*/
|
|
104
|
-
apiKey?:
|
|
104
|
+
apiKey?: ApiKeyPermissionsConfig;
|
|
105
105
|
};
|
|
106
106
|
export type BetterAuthPluginOptions = {
|
|
107
107
|
/**
|
|
@@ -133,10 +133,10 @@ export type BetterAuthPluginOptions = {
|
|
|
133
133
|
admin?: BetterAuthPluginAdminOptions;
|
|
134
134
|
};
|
|
135
135
|
/**
|
|
136
|
-
* Get the
|
|
137
|
-
* Used by the ApiKeysView to
|
|
136
|
+
* Get the stored API key permissions config.
|
|
137
|
+
* Used by the ApiKeysView server component to generate permission definitions.
|
|
138
138
|
*/
|
|
139
|
-
export declare function
|
|
139
|
+
export declare function getApiKeyPermissionsConfig(): ApiKeyPermissionsConfig | undefined;
|
|
140
140
|
/**
|
|
141
141
|
* Payload plugin that initializes Better Auth.
|
|
142
142
|
*
|
package/dist/plugin/index.js
CHANGED
|
@@ -4,22 +4,21 @@
|
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/ import { detectAuthConfig } from '../utils/detectAuthConfig.js';
|
|
6
6
|
import { detectEnabledPlugins } from '../utils/detectEnabledPlugins.js';
|
|
7
|
-
import { buildAvailableScopes, scopesToPermissions } from '../utils/generateScopes.js';
|
|
8
7
|
import { hasAnyRole, normalizeRoles } from '../utils/access.js';
|
|
9
8
|
// Track auth instance for HMR
|
|
10
9
|
let authInstance = null;
|
|
11
|
-
// Store API key
|
|
12
|
-
let
|
|
10
|
+
// Store API key permissions config for access by management views
|
|
11
|
+
let apiKeyPermissionsConfig = undefined;
|
|
13
12
|
/**
|
|
14
|
-
* Get the
|
|
15
|
-
* Used by the ApiKeysView to
|
|
16
|
-
*/ export function
|
|
17
|
-
return
|
|
13
|
+
* Get the stored API key permissions config.
|
|
14
|
+
* Used by the ApiKeysView server component to generate permission definitions.
|
|
15
|
+
*/ export function getApiKeyPermissionsConfig() {
|
|
16
|
+
return apiKeyPermissionsConfig;
|
|
18
17
|
}
|
|
19
18
|
/**
|
|
20
|
-
* Handle API key creation
|
|
21
|
-
*
|
|
22
|
-
*/ async function
|
|
19
|
+
* Handle API key creation server-side.
|
|
20
|
+
* Passes permissions directly from the client to Better Auth's server API.
|
|
21
|
+
*/ async function handleApiKeyCreate(authApi, headers, body) {
|
|
23
22
|
try {
|
|
24
23
|
// Get the current session to find the user
|
|
25
24
|
const session = await authApi.getSession({
|
|
@@ -35,16 +34,8 @@ let apiKeyScopesConfig = undefined;
|
|
|
35
34
|
}
|
|
36
35
|
});
|
|
37
36
|
}
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
// Build permissions from scopes if any are provided
|
|
41
|
-
let permissions;
|
|
42
|
-
if (scopes.length > 0) {
|
|
43
|
-
const scopesConfig = getApiKeyScopesConfig();
|
|
44
|
-
const availableScopes = buildAvailableScopes(payload.config.collections, scopesConfig);
|
|
45
|
-
permissions = scopesToPermissions(scopes, availableScopes);
|
|
46
|
-
}
|
|
47
|
-
// Build the API key creation options
|
|
37
|
+
// Permissions come directly from the client in BA's native format
|
|
38
|
+
const permissions = body.permissions;
|
|
48
39
|
const createOptions = {
|
|
49
40
|
body: {
|
|
50
41
|
name: body.name,
|
|
@@ -52,10 +43,7 @@ let apiKeyScopesConfig = undefined;
|
|
|
52
43
|
expiresIn: body.expiresIn,
|
|
53
44
|
prefix: body.prefix,
|
|
54
45
|
permissions: permissions && Object.keys(permissions).length > 0 ? permissions : undefined,
|
|
55
|
-
metadata:
|
|
56
|
-
...body.metadata,
|
|
57
|
-
scopes
|
|
58
|
-
} : body.metadata
|
|
46
|
+
metadata: body.metadata
|
|
59
47
|
}
|
|
60
48
|
};
|
|
61
49
|
// Call Better Auth's server-side API
|
|
@@ -69,38 +57,13 @@ let apiKeyScopesConfig = undefined;
|
|
|
69
57
|
}
|
|
70
58
|
});
|
|
71
59
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
'Content-Type': 'application/json'
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
} catch (createError) {
|
|
81
|
-
// Check if error is due to metadata being disabled
|
|
82
|
-
const errorMessage = createError instanceof Error ? createError.message : String(createError);
|
|
83
|
-
const isMetadataDisabled = errorMessage.toLowerCase().includes('metadata') && errorMessage.toLowerCase().includes('disabled');
|
|
84
|
-
if (isMetadataDisabled && createOptions.body.metadata) {
|
|
85
|
-
// Retry without metadata - key will still work, just won't show scopes in UI
|
|
86
|
-
console.warn('[better-auth] Metadata disabled, creating API key without scope metadata. Enable metadata with apiKey({ enableMetadata: true }) for better UX.');
|
|
87
|
-
const optionsWithoutMetadata = {
|
|
88
|
-
body: {
|
|
89
|
-
...createOptions.body,
|
|
90
|
-
metadata: undefined
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
const result = await authApi.createApiKey(optionsWithoutMetadata);
|
|
94
|
-
return new Response(JSON.stringify(result), {
|
|
95
|
-
status: 200,
|
|
96
|
-
headers: {
|
|
97
|
-
'Content-Type': 'application/json'
|
|
98
|
-
}
|
|
99
|
-
});
|
|
60
|
+
const result = await authApi.createApiKey(createOptions);
|
|
61
|
+
return new Response(JSON.stringify(result), {
|
|
62
|
+
status: 200,
|
|
63
|
+
headers: {
|
|
64
|
+
'Content-Type': 'application/json'
|
|
100
65
|
}
|
|
101
|
-
|
|
102
|
-
throw createError;
|
|
103
|
-
}
|
|
66
|
+
});
|
|
104
67
|
} catch (error) {
|
|
105
68
|
console.error('[better-auth] API key creation error:', error);
|
|
106
69
|
const message = error instanceof Error ? error.message : 'Failed to create API key';
|
|
@@ -171,9 +134,10 @@ let apiKeyScopesConfig = undefined;
|
|
|
171
134
|
}
|
|
172
135
|
}
|
|
173
136
|
}
|
|
174
|
-
// Guard API key mutation endpoints — require admin role
|
|
137
|
+
// Guard API key mutation and list endpoints — require admin role
|
|
175
138
|
const isApiKeyMutation = req.method === 'POST' && (pathname.endsWith('/api-key/create') || pathname.endsWith('/api-key/update') || pathname.endsWith('/api-key/delete'));
|
|
176
|
-
|
|
139
|
+
const isApiKeyList = req.method === 'GET' && pathname.endsWith('/api-key/list');
|
|
140
|
+
if (isApiKeyMutation || isApiKeyList) {
|
|
177
141
|
const session = await auth.api.getSession({
|
|
178
142
|
headers: req.headers
|
|
179
143
|
});
|
|
@@ -188,7 +152,7 @@ let apiKeyScopesConfig = undefined;
|
|
|
188
152
|
});
|
|
189
153
|
}
|
|
190
154
|
// Resolve required role: apiKey config > login config > default 'admin'
|
|
191
|
-
const requiredRole =
|
|
155
|
+
const requiredRole = apiKeyPermissionsConfig?.requiredRole ?? adminOptions?.login?.requiredRole ?? 'admin';
|
|
192
156
|
if (requiredRole !== null) {
|
|
193
157
|
// Find the auth collection slug from Payload's config
|
|
194
158
|
const authSlug = req.payload.config.collections.find((c)=>typeof c.auth === 'object' || c.auth === true)?.slug ?? 'users';
|
|
@@ -210,11 +174,10 @@ let apiKeyScopesConfig = undefined;
|
|
|
210
174
|
}
|
|
211
175
|
}
|
|
212
176
|
}
|
|
213
|
-
// Intercept API key creation requests
|
|
214
|
-
|
|
215
|
-
const isApiKeyCreate = req.method === 'POST' && pathname.endsWith('/api-key/create') && parsedBody?.scopes && Array.isArray(parsedBody.scopes);
|
|
177
|
+
// Intercept API key creation requests to inject userId from session
|
|
178
|
+
const isApiKeyCreate = req.method === 'POST' && pathname.endsWith('/api-key/create');
|
|
216
179
|
if (isApiKeyCreate && parsedBody) {
|
|
217
|
-
return
|
|
180
|
+
return handleApiKeyCreate(auth.api, req.headers, parsedBody);
|
|
218
181
|
}
|
|
219
182
|
// Create a new Request for Better Auth
|
|
220
183
|
const request = new Request(url.toString(), {
|
|
@@ -436,8 +399,8 @@ let apiKeyScopesConfig = undefined;
|
|
|
436
399
|
* ```
|
|
437
400
|
*/ export function createBetterAuthPlugin(options) {
|
|
438
401
|
const { createAuth, authBasePath = '/auth', autoRegisterEndpoints = true, autoInjectAdminComponents = true } = options;
|
|
439
|
-
// Store API key
|
|
440
|
-
|
|
402
|
+
// Store API key permissions config for access by management views
|
|
403
|
+
apiKeyPermissionsConfig = options.admin?.apiKey;
|
|
441
404
|
return (incomingConfig)=>{
|
|
442
405
|
// Inject admin components if enabled
|
|
443
406
|
let config = autoInjectAdminComponents ? injectAdminComponents(incomingConfig, options) : incomingConfig;
|
package/dist/types/apiKey.d.ts
CHANGED
|
@@ -1,55 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* API Key
|
|
2
|
+
* API Key Permission Types
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Uses Better Auth's native permission format: Record<string, string[]>
|
|
5
|
+
* where keys are resource names (collection slugs) and values are action arrays.
|
|
6
|
+
*
|
|
7
|
+
* Convention: two actions per collection — 'read' and 'write'.
|
|
8
|
+
* - read: view records
|
|
9
|
+
* - write: full access (create, update, delete) — implies read
|
|
5
10
|
*/
|
|
6
11
|
/**
|
|
7
|
-
* A
|
|
8
|
-
*
|
|
12
|
+
* A permission definition for the admin UI.
|
|
13
|
+
* Describes a collection's available permission levels.
|
|
9
14
|
*/
|
|
10
|
-
export type
|
|
11
|
-
/**
|
|
15
|
+
export type PermissionDefinition = {
|
|
16
|
+
/** Collection slug (e.g., 'posts') */
|
|
17
|
+
slug: string;
|
|
18
|
+
/** Human-readable label (e.g., 'Posts') */
|
|
12
19
|
label: string;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Permission mapping: { resourceType: ['action1', 'action2'] }
|
|
17
|
-
* Maps to Better Auth's permission format.
|
|
18
|
-
* Use '*' for resource to match all resources.
|
|
19
|
-
* Use '*' in actions array to grant all actions on a resource.
|
|
20
|
-
*/
|
|
21
|
-
permissions: Record<string, string[]>;
|
|
22
|
-
/** If true, only admin users can create keys with this scope */
|
|
23
|
-
adminOnly?: boolean;
|
|
20
|
+
/** Available actions — always ['read', 'write'] for auto-generated */
|
|
21
|
+
actions: string[];
|
|
24
22
|
};
|
|
25
23
|
/**
|
|
26
|
-
* Configuration options for API key
|
|
27
|
-
* Can be used in plugin options to customize available scopes.
|
|
24
|
+
* Configuration options for API key permissions.
|
|
28
25
|
*/
|
|
29
|
-
export type
|
|
30
|
-
/**
|
|
31
|
-
* Custom scope definitions.
|
|
32
|
-
* Key is the scope ID (e.g., 'content:read'), value is the scope definition.
|
|
33
|
-
*/
|
|
34
|
-
scopes?: Record<string, ScopeDefinition>;
|
|
35
|
-
/**
|
|
36
|
-
* Include auto-generated collection scopes.
|
|
37
|
-
* When true (default), generates {collection}:read, {collection}:write, {collection}:delete
|
|
38
|
-
* for each Payload collection.
|
|
39
|
-
* @default true when no custom scopes provided, false when custom scopes provided
|
|
40
|
-
*/
|
|
41
|
-
includeCollectionScopes?: boolean;
|
|
26
|
+
export type ApiKeyPermissionsConfig = {
|
|
42
27
|
/**
|
|
43
|
-
* Collections to exclude from
|
|
44
|
-
*
|
|
45
|
-
* @default ['sessions', 'verifications', 'accounts', 'twoFactors']
|
|
28
|
+
* Collections to exclude from the permissions UI.
|
|
29
|
+
* @default ['sessions', 'verifications', 'accounts', 'twoFactors', 'apiKeys']
|
|
46
30
|
*/
|
|
47
31
|
excludeCollections?: string[];
|
|
48
|
-
/**
|
|
49
|
-
* Default scopes assigned to new API keys when user doesn't select any.
|
|
50
|
-
* If not provided, keys without scopes will have no permissions.
|
|
51
|
-
*/
|
|
52
|
-
defaultScopes?: string[];
|
|
53
32
|
/**
|
|
54
33
|
* Role(s) required to create, update, and delete API keys.
|
|
55
34
|
* - string: Single role required (e.g., 'admin')
|
|
@@ -59,10 +38,3 @@ export type ApiKeyScopesConfig = {
|
|
|
59
38
|
*/
|
|
60
39
|
requiredRole?: string | string[] | null;
|
|
61
40
|
};
|
|
62
|
-
/**
|
|
63
|
-
* Scope data passed to the API keys management client component.
|
|
64
|
-
*/
|
|
65
|
-
export type AvailableScope = ScopeDefinition & {
|
|
66
|
-
/** The scope ID (e.g., 'content:read') */
|
|
67
|
-
id: string;
|
|
68
|
-
};
|
package/dist/types/apiKey.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* API Key
|
|
2
|
+
* API Key Permission Types
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Uses Better Auth's native permission format: Record<string, string[]>
|
|
5
|
+
* where keys are resource names (collection slugs) and values are action arrays.
|
|
6
|
+
*
|
|
7
|
+
* Convention: two actions per collection — 'read' and 'write'.
|
|
8
|
+
* - read: view records
|
|
9
|
+
* - write: full access (create, update, delete) — implies read
|
|
5
10
|
*/ /**
|
|
6
|
-
* A
|
|
7
|
-
*
|
|
11
|
+
* A permission definition for the admin UI.
|
|
12
|
+
* Describes a collection's available permission levels.
|
|
8
13
|
*/ /**
|
|
9
|
-
*
|
|
14
|
+
* Configuration options for API key permissions.
|
|
10
15
|
*/ export { };
|
|
@@ -1,46 +1,26 @@
|
|
|
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
21
|
*/
|
|
23
22
|
import type { Access, PayloadRequest } from 'payload';
|
|
24
|
-
export type
|
|
25
|
-
/** The API key ID */
|
|
26
|
-
id: string;
|
|
27
|
-
/** Reference ID (user or organization) who owns this key */
|
|
28
|
-
referenceId: string;
|
|
29
|
-
/** Reference type - whether key is owned by a user or organization */
|
|
30
|
-
referenceType: 'user' | 'organization';
|
|
31
|
-
/** Array of granted scope strings */
|
|
32
|
-
scopes: string[];
|
|
33
|
-
/** The raw key (only first/last chars visible) */
|
|
34
|
-
keyPrefix?: string;
|
|
35
|
-
/** Optional metadata */
|
|
36
|
-
metadata?: Record<string, unknown>;
|
|
37
|
-
};
|
|
38
|
-
export type ApiKeyAccessConfig = {
|
|
39
|
-
/**
|
|
40
|
-
* API keys collection slug.
|
|
41
|
-
* @default 'apiKeys' or 'api-keys' (auto-detected)
|
|
42
|
-
*/
|
|
43
|
-
apiKeysCollection?: string;
|
|
23
|
+
export type ApiKeyPermissionConfig = {
|
|
44
24
|
/**
|
|
45
25
|
* Allow access if user is authenticated (non-API key session).
|
|
46
26
|
* Useful for allowing both API keys and regular sessions.
|
|
@@ -53,121 +33,93 @@ export type ApiKeyAccessConfig = {
|
|
|
53
33
|
*/
|
|
54
34
|
extractApiKey?: (req: PayloadRequest) => string | null;
|
|
55
35
|
};
|
|
36
|
+
/** A single permission check: resource + action */
|
|
37
|
+
export type PermissionCheck = {
|
|
38
|
+
resource: string;
|
|
39
|
+
action: string;
|
|
40
|
+
};
|
|
56
41
|
/**
|
|
57
42
|
* Extract API key from request headers.
|
|
58
43
|
* Supports Bearer token format: Authorization: Bearer <api-key>
|
|
59
44
|
*/
|
|
60
45
|
export declare function extractApiKeyFromRequest(req: PayloadRequest): string | null;
|
|
61
46
|
/**
|
|
62
|
-
*
|
|
63
|
-
* Returns null if key not found or disabled.
|
|
64
|
-
*/
|
|
65
|
-
export declare function getApiKeyInfo(req: PayloadRequest, apiKey: string, apiKeysCollection?: string): Promise<ApiKeyInfo | null>;
|
|
66
|
-
/**
|
|
67
|
-
* Check if an API key has a specific scope.
|
|
68
|
-
* Supports wildcard patterns like 'posts:*' matching 'posts:read', 'posts:write', etc.
|
|
69
|
-
*/
|
|
70
|
-
export declare function hasScope(keyScopes: string[], requiredScope: string): boolean;
|
|
71
|
-
/**
|
|
72
|
-
* Check if an API key has any of the specified scopes.
|
|
73
|
-
*/
|
|
74
|
-
export declare function hasAnyScope(keyScopes: string[], requiredScopes: string[]): boolean;
|
|
75
|
-
/**
|
|
76
|
-
* Check if an API key has all of the specified scopes.
|
|
77
|
-
*/
|
|
78
|
-
export declare function hasAllScopes(keyScopes: string[], requiredScopes: string[]): boolean;
|
|
79
|
-
/**
|
|
80
|
-
* Create an access control function that requires a specific scope.
|
|
47
|
+
* Require a specific permission on an API key.
|
|
81
48
|
*
|
|
82
|
-
* @param
|
|
83
|
-
* @param
|
|
49
|
+
* @param resource - Collection slug (e.g., 'posts')
|
|
50
|
+
* @param action - Permission action: 'read' or 'write'
|
|
51
|
+
* @param config - Optional configuration
|
|
84
52
|
* @returns Payload access function
|
|
85
53
|
*
|
|
86
54
|
* @example
|
|
87
55
|
* ```ts
|
|
88
56
|
* access: {
|
|
89
|
-
* read:
|
|
90
|
-
* create:
|
|
57
|
+
* read: requirePermission('posts', 'read'),
|
|
58
|
+
* create: requirePermission('posts', 'write'),
|
|
91
59
|
* }
|
|
92
60
|
* ```
|
|
93
61
|
*/
|
|
94
|
-
export declare function
|
|
62
|
+
export declare function requirePermission(resource: string, action: string, config?: ApiKeyPermissionConfig): Access;
|
|
95
63
|
/**
|
|
96
|
-
*
|
|
64
|
+
* Require any one of the specified permissions.
|
|
97
65
|
*
|
|
98
|
-
* @param
|
|
99
|
-
* @param config -
|
|
66
|
+
* @param permissions - Array of {resource, action} pairs (at least one must match)
|
|
67
|
+
* @param config - Optional configuration
|
|
100
68
|
* @returns Payload access function
|
|
101
69
|
*
|
|
102
70
|
* @example
|
|
103
71
|
* ```ts
|
|
104
72
|
* access: {
|
|
105
|
-
* read:
|
|
73
|
+
* read: requireAnyPermission([
|
|
74
|
+
* { resource: 'posts', action: 'read' },
|
|
75
|
+
* { resource: 'pages', action: 'read' },
|
|
76
|
+
* ]),
|
|
106
77
|
* }
|
|
107
78
|
* ```
|
|
108
79
|
*/
|
|
109
|
-
export declare function
|
|
80
|
+
export declare function requireAnyPermission(permissions: PermissionCheck[], config?: ApiKeyPermissionConfig): Access;
|
|
110
81
|
/**
|
|
111
|
-
*
|
|
82
|
+
* Require all of the specified permissions.
|
|
112
83
|
*
|
|
113
|
-
* @param
|
|
114
|
-
* @param config -
|
|
84
|
+
* @param permissions - Array of {resource, action} pairs (all must match)
|
|
85
|
+
* @param config - Optional configuration
|
|
115
86
|
* @returns Payload access function
|
|
116
87
|
*
|
|
117
88
|
* @example
|
|
118
89
|
* ```ts
|
|
119
90
|
* access: {
|
|
120
|
-
* delete:
|
|
91
|
+
* delete: requireAllPermissions([
|
|
92
|
+
* { resource: 'posts', action: 'write' },
|
|
93
|
+
* { resource: 'admin', action: 'write' },
|
|
94
|
+
* ]),
|
|
121
95
|
* }
|
|
122
96
|
* ```
|
|
123
97
|
*/
|
|
124
|
-
export declare function
|
|
98
|
+
export declare function requireAllPermissions(permissions: PermissionCheck[], config?: ApiKeyPermissionConfig): Access;
|
|
125
99
|
/**
|
|
126
|
-
*
|
|
127
|
-
* 1. Authenticated users (via session)
|
|
128
|
-
* 2. API key with required scope
|
|
129
|
-
*
|
|
130
|
-
* This is useful for endpoints that should work with both auth methods.
|
|
131
|
-
*
|
|
132
|
-
* @param scope - The required scope for API key access
|
|
133
|
-
* @param config - Configuration options
|
|
134
|
-
* @returns Payload access function
|
|
100
|
+
* Allow either authenticated session OR API key with permission.
|
|
135
101
|
*
|
|
136
102
|
* @example
|
|
137
103
|
* ```ts
|
|
138
104
|
* access: {
|
|
139
|
-
* read:
|
|
105
|
+
* read: allowSessionOrPermission('posts', 'read'),
|
|
140
106
|
* }
|
|
141
107
|
* ```
|
|
142
108
|
*/
|
|
143
|
-
export declare function
|
|
109
|
+
export declare function allowSessionOrPermission(resource: string, action: string, config?: Omit<ApiKeyPermissionConfig, 'allowAuthenticatedUsers'>): Access;
|
|
144
110
|
/**
|
|
145
|
-
*
|
|
146
|
-
* 1. Authenticated users (via session)
|
|
147
|
-
* 2. API key with any of the required scopes
|
|
148
|
-
*
|
|
149
|
-
* @param scopes - Array of acceptable scopes for API key access
|
|
150
|
-
* @param config - Configuration options
|
|
151
|
-
* @returns Payload access function
|
|
111
|
+
* Allow either authenticated session OR API key with any of the permissions.
|
|
152
112
|
*/
|
|
153
|
-
export declare function
|
|
113
|
+
export declare function allowSessionOrAnyPermission(permissions: PermissionCheck[], config?: Omit<ApiKeyPermissionConfig, 'allowAuthenticatedUsers'>): Access;
|
|
154
114
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* This performs a database lookup to validate the key and retrieve
|
|
158
|
-
* its associated scopes and user.
|
|
159
|
-
*
|
|
160
|
-
* @param req - Payload request
|
|
161
|
-
* @param apiKeysCollection - The API keys collection slug
|
|
162
|
-
* @returns API key info if valid, null otherwise
|
|
115
|
+
* Require a valid API key (no specific permissions checked).
|
|
116
|
+
* Useful for apps that use role-based access and just need to verify the key exists.
|
|
163
117
|
*
|
|
164
118
|
* @example
|
|
165
119
|
* ```ts
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
* console.log('Valid API key for:', keyInfo.referenceId, keyInfo.referenceType)
|
|
169
|
-
* console.log('Scopes:', keyInfo.scopes)
|
|
120
|
+
* access: {
|
|
121
|
+
* read: requireApiKey(),
|
|
170
122
|
* }
|
|
171
123
|
* ```
|
|
172
124
|
*/
|
|
173
|
-
export declare function
|
|
125
|
+
export declare function requireApiKey(config?: ApiKeyPermissionConfig): Access;
|