@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.
- package/README.md +60 -12
- package/dist/adapter/collections.d.ts.map +1 -1
- package/dist/adapter/collections.js +126 -88
- package/dist/adapter/collections.js.map +1 -1
- package/dist/adapter/index.js +197 -150
- package/dist/adapter/index.js.map +1 -1
- package/dist/components/BeforeLogin.d.ts +1 -1
- package/dist/components/BeforeLogin.d.ts.map +1 -1
- package/dist/components/BeforeLogin.js +15 -7
- package/dist/components/BeforeLogin.js.map +1 -1
- package/dist/components/LoginView.d.ts +2 -2
- package/dist/components/LoginView.d.ts.map +1 -1
- package/dist/components/LoginView.js +660 -218
- package/dist/components/LoginView.js.map +1 -1
- package/dist/components/LoginViewWrapper.d.ts +1 -1
- package/dist/components/LoginViewWrapper.d.ts.map +1 -1
- package/dist/components/LoginViewWrapper.js +14 -4
- package/dist/components/LoginViewWrapper.js.map +1 -1
- package/dist/components/LogoutButton.d.ts +1 -1
- package/dist/components/LogoutButton.d.ts.map +1 -1
- package/dist/components/LogoutButton.js +19 -11
- package/dist/components/LogoutButton.js.map +1 -1
- package/dist/components/PasskeyRegisterButton.d.ts +2 -2
- package/dist/components/PasskeyRegisterButton.d.ts.map +1 -1
- package/dist/components/PasskeyRegisterButton.js +20 -16
- package/dist/components/PasskeyRegisterButton.js.map +1 -1
- package/dist/components/PasskeySignInButton.d.ts +2 -2
- package/dist/components/PasskeySignInButton.d.ts.map +1 -1
- package/dist/components/PasskeySignInButton.js +14 -12
- package/dist/components/PasskeySignInButton.js.map +1 -1
- package/dist/components/auth/ForgotPasswordView.d.ts +1 -1
- package/dist/components/auth/ForgotPasswordView.d.ts.map +1 -1
- package/dist/components/auth/ForgotPasswordView.js +133 -43
- package/dist/components/auth/ForgotPasswordView.js.map +1 -1
- package/dist/components/auth/ResetPasswordView.d.ts +1 -1
- package/dist/components/auth/ResetPasswordView.d.ts.map +1 -1
- package/dist/components/auth/ResetPasswordView.js +154 -50
- package/dist/components/auth/ResetPasswordView.js.map +1 -1
- package/dist/components/auth/index.js +2 -2
- package/dist/components/auth/index.js.map +1 -1
- package/dist/components/management/ApiKeysManagementClient.d.ts +2 -2
- package/dist/components/management/ApiKeysManagementClient.d.ts.map +1 -1
- package/dist/components/management/ApiKeysManagementClient.js +539 -222
- package/dist/components/management/ApiKeysManagementClient.js.map +1 -1
- package/dist/components/management/PasskeysManagementClient.d.ts +2 -2
- package/dist/components/management/PasskeysManagementClient.d.ts.map +1 -1
- package/dist/components/management/PasskeysManagementClient.js +215 -92
- package/dist/components/management/PasskeysManagementClient.js.map +1 -1
- package/dist/components/management/SecurityNavLinks.d.ts +1 -1
- package/dist/components/management/SecurityNavLinks.d.ts.map +1 -1
- package/dist/components/management/SecurityNavLinks.js +51 -24
- package/dist/components/management/SecurityNavLinks.js.map +1 -1
- package/dist/components/management/TwoFactorManagementClient.d.ts +2 -2
- package/dist/components/management/TwoFactorManagementClient.d.ts.map +1 -1
- package/dist/components/management/TwoFactorManagementClient.js +270 -111
- package/dist/components/management/TwoFactorManagementClient.js.map +1 -1
- package/dist/components/management/index.js +2 -2
- package/dist/components/management/index.js.map +1 -1
- package/dist/components/management/views/ApiKeysView.d.ts +1 -1
- package/dist/components/management/views/ApiKeysView.d.ts.map +1 -1
- package/dist/components/management/views/ApiKeysView.js +19 -4
- package/dist/components/management/views/ApiKeysView.js.map +1 -1
- package/dist/components/management/views/PasskeysView.d.ts +1 -1
- package/dist/components/management/views/PasskeysView.d.ts.map +1 -1
- package/dist/components/management/views/PasskeysView.js +16 -4
- package/dist/components/management/views/PasskeysView.js.map +1 -1
- package/dist/components/management/views/TwoFactorView.d.ts +1 -1
- package/dist/components/management/views/TwoFactorView.d.ts.map +1 -1
- package/dist/components/management/views/TwoFactorView.js +16 -4
- package/dist/components/management/views/TwoFactorView.js.map +1 -1
- package/dist/components/management/views/index.js +2 -2
- package/dist/components/management/views/index.js.map +1 -1
- package/dist/components/twoFactor/TwoFactorSetupView.d.ts +1 -1
- package/dist/components/twoFactor/TwoFactorSetupView.d.ts.map +1 -1
- package/dist/components/twoFactor/TwoFactorSetupView.js +240 -87
- package/dist/components/twoFactor/TwoFactorSetupView.js.map +1 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.d.ts +1 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.d.ts.map +1 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.js +108 -45
- package/dist/components/twoFactor/TwoFactorVerifyView.js.map +1 -1
- package/dist/components/twoFactor/index.js +2 -2
- package/dist/components/twoFactor/index.js.map +1 -1
- package/dist/exports/client.d.ts +2356 -2
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +48 -8
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/components.js +2 -2
- package/dist/exports/components.js.map +1 -1
- package/dist/exports/management.js +3 -3
- package/dist/exports/management.js.map +1 -1
- package/dist/exports/rsc.js +2 -2
- package/dist/exports/rsc.js.map +1 -1
- package/dist/generated-types.js +4 -2
- package/dist/generated-types.js.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/plugin/index.d.ts +35 -2
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +198 -162
- package/dist/plugin/index.js.map +1 -1
- package/dist/scripts/generate-types.js +66 -50
- package/dist/scripts/generate-types.js.map +1 -1
- package/dist/types/apiKey.js +7 -2
- package/dist/types/apiKey.js.map +1 -1
- package/dist/types/betterAuth.js +23 -2
- package/dist/types/betterAuth.js.map +1 -1
- package/dist/utils/access.js +78 -81
- package/dist/utils/access.js.map +1 -1
- package/dist/utils/apiKeyAccess.js +65 -72
- package/dist/utils/apiKeyAccess.js.map +1 -1
- package/dist/utils/betterAuthDefaults.js +8 -8
- package/dist/utils/betterAuthDefaults.js.map +1 -1
- package/dist/utils/detectAuthConfig.js +8 -11
- package/dist/utils/detectAuthConfig.js.map +1 -1
- package/dist/utils/detectEnabledPlugins.js +6 -7
- package/dist/utils/detectEnabledPlugins.js.map +1 -1
- package/dist/utils/firstUserAdmin.js +18 -20
- package/dist/utils/firstUserAdmin.js.map +1 -1
- package/dist/utils/generateScopes.js +40 -41
- package/dist/utils/generateScopes.js.map +1 -1
- package/dist/utils/session.js +8 -9
- package/dist/utils/session.js.map +1 -1
- package/package.json +97 -26
- package/src/adapter/collections.ts +621 -0
- package/src/adapter/index.ts +712 -0
- package/src/components/BeforeLogin.tsx +39 -0
- package/src/components/LoginView.tsx +1516 -0
- package/src/components/LoginViewWrapper.tsx +35 -0
- package/src/components/LogoutButton.tsx +58 -0
- package/src/components/PasskeyRegisterButton.tsx +105 -0
- package/src/components/PasskeySignInButton.tsx +96 -0
- package/src/components/auth/ForgotPasswordView.tsx +274 -0
- package/src/components/auth/ResetPasswordView.tsx +331 -0
- package/src/components/auth/index.ts +8 -0
- package/src/components/management/ApiKeysManagementClient.tsx +988 -0
- package/src/components/management/PasskeysManagementClient.tsx +409 -0
- package/src/components/management/SecurityNavLinks.tsx +117 -0
- package/src/components/management/TwoFactorManagementClient.tsx +560 -0
- package/src/components/management/index.ts +20 -0
- package/src/components/management/views/ApiKeysView.tsx +57 -0
- package/src/components/management/views/PasskeysView.tsx +42 -0
- package/src/components/management/views/TwoFactorView.tsx +42 -0
- package/src/components/management/views/index.ts +10 -0
- package/src/components/twoFactor/TwoFactorSetupView.tsx +515 -0
- package/src/components/twoFactor/TwoFactorVerifyView.tsx +238 -0
- package/src/components/twoFactor/index.ts +8 -0
- package/src/exports/client.ts +77 -0
- package/src/exports/components.ts +30 -0
- package/src/exports/management.ts +25 -0
- package/src/exports/rsc.ts +11 -0
- package/src/generated-types.ts +269 -0
- package/src/index.ts +135 -0
- package/src/plugin/index.ts +834 -0
- package/src/scripts/generate-types.ts +269 -0
- package/src/types/apiKey.ts +63 -0
- package/src/types/betterAuth.ts +253 -0
- package/src/utils/access.ts +410 -0
- package/src/utils/apiKeyAccess.ts +443 -0
- package/src/utils/betterAuthDefaults.ts +102 -0
- package/src/utils/detectAuthConfig.ts +47 -0
- package/src/utils/detectEnabledPlugins.ts +69 -0
- package/src/utils/firstUserAdmin.ts +164 -0
- package/src/utils/generateScopes.ts +150 -0
- package/src/utils/session.ts +91 -0
package/dist/utils/access.js
CHANGED
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
* },
|
|
19
19
|
* }
|
|
20
20
|
* ```
|
|
21
|
-
*/
|
|
22
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
*/ // ─────────────────────────────────────────────────────────────────────────────
|
|
23
22
|
// Role Checking Utilities
|
|
24
23
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
24
|
/**
|
|
@@ -32,19 +31,17 @@
|
|
|
32
31
|
*
|
|
33
32
|
* @param role - The role value from the user object
|
|
34
33
|
* @returns Array of role strings
|
|
35
|
-
*/
|
|
36
|
-
export function normalizeRoles(role) {
|
|
34
|
+
*/ export function normalizeRoles(role) {
|
|
37
35
|
if (Array.isArray(role)) {
|
|
38
|
-
return role.filter((r)
|
|
36
|
+
return role.filter((r)=>typeof r === 'string');
|
|
39
37
|
}
|
|
40
38
|
if (typeof role === 'string') {
|
|
41
39
|
if (role.includes(',')) {
|
|
42
|
-
return role
|
|
43
|
-
.split(',')
|
|
44
|
-
.map((r) => r.trim())
|
|
45
|
-
.filter(Boolean);
|
|
40
|
+
return role.split(',').map((r)=>r.trim()).filter(Boolean);
|
|
46
41
|
}
|
|
47
|
-
return role ? [
|
|
42
|
+
return role ? [
|
|
43
|
+
role
|
|
44
|
+
] : [];
|
|
48
45
|
}
|
|
49
46
|
return [];
|
|
50
47
|
}
|
|
@@ -61,12 +58,10 @@ export function normalizeRoles(role) {
|
|
|
61
58
|
* hasAnyRole(user, ['admin']) // true
|
|
62
59
|
* hasAnyRole(user, ['superadmin']) // false
|
|
63
60
|
* ```
|
|
64
|
-
*/
|
|
65
|
-
|
|
66
|
-
if (!user?.role)
|
|
67
|
-
return false;
|
|
61
|
+
*/ export function hasAnyRole(user, roles) {
|
|
62
|
+
if (!user?.role) return false;
|
|
68
63
|
const userRoles = normalizeRoles(user.role);
|
|
69
|
-
return userRoles.some((role)
|
|
64
|
+
return userRoles.some((role)=>roles.includes(role));
|
|
70
65
|
}
|
|
71
66
|
/**
|
|
72
67
|
* Check if a user has all of the specified roles.
|
|
@@ -81,12 +76,10 @@ export function hasAnyRole(user, roles) {
|
|
|
81
76
|
* hasAllRoles(user, ['admin', 'editor']) // true
|
|
82
77
|
* hasAllRoles(user, ['admin', 'superadmin']) // false
|
|
83
78
|
* ```
|
|
84
|
-
*/
|
|
85
|
-
|
|
86
|
-
if (!user?.role)
|
|
87
|
-
return false;
|
|
79
|
+
*/ export function hasAllRoles(user, roles) {
|
|
80
|
+
if (!user?.role) return false;
|
|
88
81
|
const userRoles = normalizeRoles(user.role);
|
|
89
|
-
return roles.every((role)
|
|
82
|
+
return roles.every((role)=>userRoles.includes(role));
|
|
90
83
|
}
|
|
91
84
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
92
85
|
// Access Control Functions
|
|
@@ -98,10 +91,11 @@ export function hasAllRoles(user, roles) {
|
|
|
98
91
|
*
|
|
99
92
|
* @param config - Configuration with admin roles
|
|
100
93
|
* @returns Access check function
|
|
101
|
-
*/
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
94
|
+
*/ export function hasAdminRoles(config = {}) {
|
|
95
|
+
const { adminRoles = [
|
|
96
|
+
'admin'
|
|
97
|
+
] } = config;
|
|
98
|
+
return ({ req })=>{
|
|
105
99
|
return hasAnyRole(req.user, adminRoles);
|
|
106
100
|
};
|
|
107
101
|
}
|
|
@@ -117,11 +111,12 @@ export function hasAdminRoles(config = {}) {
|
|
|
117
111
|
* delete: isAdmin({ adminRoles: ['admin', 'superadmin'] }),
|
|
118
112
|
* }
|
|
119
113
|
* ```
|
|
120
|
-
*/
|
|
121
|
-
export function isAdmin(config = {}) {
|
|
114
|
+
*/ export function isAdmin(config = {}) {
|
|
122
115
|
const checkAdmin = hasAdminRoles(config);
|
|
123
|
-
return ({ req })
|
|
124
|
-
return checkAdmin({
|
|
116
|
+
return ({ req })=>{
|
|
117
|
+
return checkAdmin({
|
|
118
|
+
req
|
|
119
|
+
});
|
|
125
120
|
};
|
|
126
121
|
}
|
|
127
122
|
/**
|
|
@@ -142,11 +137,12 @@ export function isAdmin(config = {}) {
|
|
|
142
137
|
* },
|
|
143
138
|
* ]
|
|
144
139
|
* ```
|
|
145
|
-
*/
|
|
146
|
-
export function isAdminField(config = {}) {
|
|
140
|
+
*/ export function isAdminField(config = {}) {
|
|
147
141
|
const checkAdmin = hasAdminRoles(config);
|
|
148
|
-
return ({ req })
|
|
149
|
-
return checkAdmin({
|
|
142
|
+
return ({ req })=>{
|
|
143
|
+
return checkAdmin({
|
|
144
|
+
req
|
|
145
|
+
});
|
|
150
146
|
};
|
|
151
147
|
}
|
|
152
148
|
/**
|
|
@@ -165,22 +161,25 @@ export function isAdminField(config = {}) {
|
|
|
165
161
|
* update: isAdminOrSelf({ adminRoles: ['admin'] }),
|
|
166
162
|
* }
|
|
167
163
|
* ```
|
|
168
|
-
*/
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
164
|
+
*/ export function isAdminOrSelf(config = {}) {
|
|
165
|
+
const { adminRoles = [
|
|
166
|
+
'admin'
|
|
167
|
+
], idField = 'id' } = config;
|
|
168
|
+
const checkAdmin = hasAdminRoles({
|
|
169
|
+
adminRoles
|
|
170
|
+
});
|
|
171
|
+
return ({ req })=>{
|
|
173
172
|
// Admins can access everything
|
|
174
|
-
if (checkAdmin({
|
|
175
|
-
|
|
173
|
+
if (checkAdmin({
|
|
174
|
+
req
|
|
175
|
+
})) return true;
|
|
176
176
|
// Non-authenticated users have no access
|
|
177
|
-
if (!req.user)
|
|
178
|
-
return false;
|
|
177
|
+
if (!req.user) return false;
|
|
179
178
|
// Restrict to own record
|
|
180
179
|
return {
|
|
181
180
|
[idField]: {
|
|
182
|
-
equals: req.user.id
|
|
183
|
-
}
|
|
181
|
+
equals: req.user.id
|
|
182
|
+
}
|
|
184
183
|
};
|
|
185
184
|
};
|
|
186
185
|
}
|
|
@@ -206,51 +205,53 @@ export function isAdminOrSelf(config = {}) {
|
|
|
206
205
|
* }),
|
|
207
206
|
* }
|
|
208
207
|
* ```
|
|
209
|
-
*/
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
208
|
+
*/ export function canUpdateOwnFields(config = {}) {
|
|
209
|
+
const { adminRoles = [
|
|
210
|
+
'admin'
|
|
211
|
+
], allowedFields = [
|
|
212
|
+
'name'
|
|
213
|
+
], idField = 'id', userSlug = 'users' } = config;
|
|
214
|
+
const checkAdmin = hasAdminRoles({
|
|
215
|
+
adminRoles
|
|
216
|
+
});
|
|
217
|
+
return async ({ req, id, data })=>{
|
|
214
218
|
// Admins can update everything
|
|
215
|
-
if (checkAdmin({
|
|
216
|
-
|
|
219
|
+
if (checkAdmin({
|
|
220
|
+
req
|
|
221
|
+
})) return true;
|
|
217
222
|
// Must be authenticated
|
|
218
|
-
if (!req.user)
|
|
219
|
-
return false;
|
|
223
|
+
if (!req.user) return false;
|
|
220
224
|
// Must be updating own record
|
|
221
225
|
const userId = req.user[idField];
|
|
222
|
-
if (userId !== id || !data)
|
|
223
|
-
return false;
|
|
226
|
+
if (userId !== id || !data) return false;
|
|
224
227
|
const dataKeys = Object.keys(data);
|
|
225
|
-
const effectiveAllowed = [
|
|
228
|
+
const effectiveAllowed = [
|
|
229
|
+
...allowedFields
|
|
230
|
+
];
|
|
226
231
|
// Handle password changes specially
|
|
227
232
|
const hasCurrentPassword = dataKeys.includes('currentPassword');
|
|
228
233
|
const hasPassword = dataKeys.includes('password');
|
|
229
234
|
if (hasPassword || hasCurrentPassword) {
|
|
230
235
|
// Both must be provided for password change
|
|
231
|
-
if (!(hasCurrentPassword && hasPassword))
|
|
232
|
-
return false;
|
|
236
|
+
if (!(hasCurrentPassword && hasPassword)) return false;
|
|
233
237
|
try {
|
|
234
238
|
// Verify current password
|
|
235
|
-
if (!req.user.email)
|
|
236
|
-
return false;
|
|
239
|
+
if (!req.user.email) return false;
|
|
237
240
|
const result = await req.payload.login({
|
|
238
241
|
collection: userSlug,
|
|
239
242
|
data: {
|
|
240
243
|
email: req.user.email,
|
|
241
|
-
password: data.currentPassword
|
|
242
|
-
}
|
|
244
|
+
password: data.currentPassword
|
|
245
|
+
}
|
|
243
246
|
});
|
|
244
|
-
if (!result)
|
|
245
|
-
return false;
|
|
247
|
+
if (!result) return false;
|
|
246
248
|
effectiveAllowed.push('password', 'currentPassword');
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
+
} catch {
|
|
249
250
|
return false;
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
253
|
// Check all fields are allowed
|
|
253
|
-
const hasDisallowed = dataKeys.some((key)
|
|
254
|
+
const hasDisallowed = dataKeys.some((key)=>!effectiveAllowed.includes(key));
|
|
254
255
|
return !hasDisallowed;
|
|
255
256
|
};
|
|
256
257
|
}
|
|
@@ -268,9 +269,8 @@ export function canUpdateOwnFields(config = {}) {
|
|
|
268
269
|
* read: isAuthenticated(),
|
|
269
270
|
* }
|
|
270
271
|
* ```
|
|
271
|
-
*/
|
|
272
|
-
|
|
273
|
-
return ({ req }) => {
|
|
272
|
+
*/ export function isAuthenticated() {
|
|
273
|
+
return ({ req })=>{
|
|
274
274
|
return !!req.user;
|
|
275
275
|
};
|
|
276
276
|
}
|
|
@@ -278,9 +278,8 @@ export function isAuthenticated() {
|
|
|
278
278
|
* Field access control: Allow any authenticated user.
|
|
279
279
|
*
|
|
280
280
|
* @returns Payload field access function
|
|
281
|
-
*/
|
|
282
|
-
|
|
283
|
-
return ({ req }) => {
|
|
281
|
+
*/ export function isAuthenticatedField() {
|
|
282
|
+
return ({ req })=>{
|
|
284
283
|
return !!req.user;
|
|
285
284
|
};
|
|
286
285
|
}
|
|
@@ -300,9 +299,8 @@ export function isAuthenticatedField() {
|
|
|
300
299
|
* update: hasRole(['admin', 'editor']),
|
|
301
300
|
* }
|
|
302
301
|
* ```
|
|
303
|
-
*/
|
|
304
|
-
|
|
305
|
-
return ({ req }) => {
|
|
302
|
+
*/ export function hasRole(roles) {
|
|
303
|
+
return ({ req })=>{
|
|
306
304
|
return hasAnyRole(req.user, roles);
|
|
307
305
|
};
|
|
308
306
|
}
|
|
@@ -311,9 +309,8 @@ export function hasRole(roles) {
|
|
|
311
309
|
*
|
|
312
310
|
* @param roles - Roles that have access
|
|
313
311
|
* @returns Payload field access function
|
|
314
|
-
*/
|
|
315
|
-
|
|
316
|
-
return ({ req }) => {
|
|
312
|
+
*/ export function hasRoleField(roles) {
|
|
313
|
+
return ({ req })=>{
|
|
317
314
|
return hasAnyRole(req.user, roles);
|
|
318
315
|
};
|
|
319
316
|
}
|
|
@@ -329,10 +326,10 @@ export function hasRoleField(roles) {
|
|
|
329
326
|
* delete: requireAllRoles(['admin', 'verified']),
|
|
330
327
|
* }
|
|
331
328
|
* ```
|
|
332
|
-
*/
|
|
333
|
-
|
|
334
|
-
return ({ req }) => {
|
|
329
|
+
*/ export function requireAllRoles(roles) {
|
|
330
|
+
return ({ req })=>{
|
|
335
331
|
return hasAllRoles(req.user, roles);
|
|
336
332
|
};
|
|
337
333
|
}
|
|
334
|
+
|
|
338
335
|
//# sourceMappingURL=access.js.map
|
package/dist/utils/access.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/utils/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAqCH,gFAAgF;AAChF,0BAA0B;AAC1B,gFAAgF;AAEhF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,IAAa;IAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAA;IAC/D,CAAC;IAED,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,IAAI;iBACR,KAAK,CAAC,GAAG,CAAC;iBACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBACpB,MAAM,CAAC,OAAO,CAAC,CAAA;QACpB,CAAC;QACD,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC3B,CAAC;IAED,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,UAAU,CACxB,IAA2C,EAC3C,KAAe;IAEf,IAAI,CAAC,IAAI,EAAE,IAAI;QAAE,OAAO,KAAK,CAAA;IAC7B,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3C,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;AACvD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CACzB,IAA2C,EAC3C,KAAe;IAEf,IAAI,CAAC,IAAI,EAAE,IAAI;QAAE,OAAO,KAAK,CAAA;IAC7B,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3C,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;AACxD,CAAC;AAED,gFAAgF;AAChF,2BAA2B;AAC3B,gFAAgF;AAEhF;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAC3B,SAA0B,EAAE;IAE5B,MAAM,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,EAAE,GAAG,MAAM,CAAA;IAEzC,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACjB,OAAO,UAAU,CAAC,GAAG,CAAC,IAAiC,EAAE,UAAU,CAAC,CAAA;IACtE,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,OAAO,CAAC,SAA0B,EAAE;IAClD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IAExC,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACjB,OAAO,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;IAC5B,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,YAAY,CAAC,SAA0B,EAAE;IACvD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IAExC,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACjB,OAAO,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;IAC5B,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa,CAAC,SAA2B,EAAE;IACzD,MAAM,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,MAAM,CAAA;IACzD,MAAM,UAAU,GAAG,aAAa,CAAC,EAAE,UAAU,EAAE,CAAC,CAAA;IAEhD,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACjB,+BAA+B;QAC/B,IAAI,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC;YAAE,OAAO,IAAI,CAAA;QAEpC,yCAAyC;QACzC,IAAI,CAAC,GAAG,CAAC,IAAI;YAAE,OAAO,KAAK,CAAA;QAE3B,yBAAyB;QACzB,OAAO;YACL,CAAC,OAAO,CAAC,EAAE;gBACT,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE;aACpB;SACF,CAAA;IACH,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAA4B,EAAE;IAC/D,MAAM,EACJ,UAAU,GAAG,CAAC,OAAO,CAAC,EACtB,aAAa,GAAG,CAAC,MAAM,CAAC,EACxB,OAAO,GAAG,IAAI,EACd,QAAQ,GAAG,OAAO,GACnB,GAAG,MAAM,CAAA;IACV,MAAM,UAAU,GAAG,aAAa,CAAC,EAAE,UAAU,EAAE,CAAC,CAAA;IAEhD,OAAO,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACjC,+BAA+B;QAC/B,IAAI,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC;YAAE,OAAO,IAAI,CAAA;QAEpC,wBAAwB;QACxB,IAAI,CAAC,GAAG,CAAC,IAAI;YAAE,OAAO,KAAK,CAAA;QAE3B,8BAA8B;QAC9B,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAChC,IAAI,MAAM,KAAK,EAAE,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAA;QAExC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClC,MAAM,gBAAgB,GAAG,CAAC,GAAG,aAAa,CAAC,CAAA;QAE3C,oCAAoC;QACpC,MAAM,kBAAkB,GAAG,QAAQ,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAA;QAC/D,MAAM,WAAW,GAAG,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;QAEjD,IAAI,WAAW,IAAI,kBAAkB,EAAE,CAAC;YACtC,4CAA4C;YAC5C,IAAI,CAAC,CAAC,kBAAkB,IAAI,WAAW,CAAC;gBAAE,OAAO,KAAK,CAAA;YAEtD,IAAI,CAAC;gBACH,0BAA0B;gBAC1B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK;oBAAE,OAAO,KAAK,CAAA;gBAEjC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC;oBACrC,UAAU,EAAE,QAAQ;oBACpB,IAAI,EAAE;wBACJ,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,KAAe;wBAC/B,QAAQ,EAAE,IAAI,CAAC,eAAyB;qBACzC;iBACF,CAAC,CAAA;gBAEF,IAAI,CAAC,MAAM;oBAAE,OAAO,KAAK,CAAA;gBAEzB,gBAAgB,CAAC,IAAI,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAA;YACtD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;QAED,+BAA+B;QAC/B,MAAM,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAA;QAC7E,OAAO,CAAC,aAAa,CAAA;IACvB,CAAC,CAAA;AACH,CAAC;AAED,gFAAgF;AAChF,uBAAuB;AACvB,gFAAgF;AAEhF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACjB,OAAO,CAAC,CAAC,GAAG,CAAC,IAAI,CAAA;IACnB,CAAC,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACjB,OAAO,CAAC,CAAC,GAAG,CAAC,IAAI,CAAA;IACnB,CAAC,CAAA;AACH,CAAC;AAED,gFAAgF;AAChF,oBAAoB;AACpB,gFAAgF;AAEhF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,OAAO,CAAC,KAAe;IACrC,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACjB,OAAO,UAAU,CAAC,GAAG,CAAC,IAAiC,EAAE,KAAK,CAAC,CAAA;IACjE,CAAC,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAe;IAC1C,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACjB,OAAO,UAAU,CAAC,GAAG,CAAC,IAAiC,EAAE,KAAK,CAAC,CAAA;IACjE,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,eAAe,CAAC,KAAe;IAC7C,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACjB,OAAO,WAAW,CAAC,GAAG,CAAC,IAAiC,EAAE,KAAK,CAAC,CAAA;IAClE,CAAC,CAAA;AACH,CAAC"}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/access.ts"],"sourcesContent":["/**\n * Access control utilities for Payload collections.\n *\n * These helpers simplify common access control patterns when using\n * Better Auth with Payload CMS. They handle role checking, self-access\n * patterns, and field-level permissions.\n *\n * @example\n * ```ts\n * import { isAdmin, isAdminOrSelf } from '@delmaredigital/payload-better-auth'\n *\n * export const Users: CollectionConfig = {\n * slug: 'users',\n * access: {\n * read: isAdminOrSelf({ adminRoles: ['admin', 'editor'] }),\n * update: isAdminOrSelf({ adminRoles: ['admin'] }),\n * delete: isAdmin({ adminRoles: ['admin'] }),\n * },\n * }\n * ```\n */\n\nimport type { Access, FieldAccess, PayloadRequest } from 'payload'\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type RoleCheckConfig = {\n /**\n * Roles considered admin roles.\n * @default ['admin']\n */\n adminRoles?: string[]\n}\n\nexport type SelfAccessConfig = RoleCheckConfig & {\n /**\n * The field to use for user ID comparison.\n * @default 'id'\n */\n idField?: string\n}\n\nexport type FieldUpdateConfig = SelfAccessConfig & {\n /**\n * Fields the user is allowed to update on their own record.\n * Password is handled specially and requires currentPassword.\n * @default ['name']\n */\n allowedFields?: string[]\n /**\n * The user collection slug for password verification.\n */\n userSlug?: string\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Role Checking Utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Normalize a user's role to an array.\n *\n * Handles various role formats:\n * - Array of roles\n * - Comma-separated string\n * - Single role string\n *\n * @param role - The role value from the user object\n * @returns Array of role strings\n */\nexport function normalizeRoles(role: unknown): string[] {\n if (Array.isArray(role)) {\n return role.filter((r): r is string => typeof r === 'string')\n }\n\n if (typeof role === 'string') {\n if (role.includes(',')) {\n return role\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean)\n }\n return role ? [role] : []\n }\n\n return []\n}\n\n/**\n * Check if a user has any of the specified roles.\n *\n * @param user - The user object\n * @param roles - Roles to check for\n * @returns True if user has at least one matching role\n *\n * @example\n * ```ts\n * const user = { role: ['admin', 'editor'] }\n * hasAnyRole(user, ['admin']) // true\n * hasAnyRole(user, ['superadmin']) // false\n * ```\n */\nexport function hasAnyRole(\n user: { role?: unknown } | null | undefined,\n roles: string[]\n): boolean {\n if (!user?.role) return false\n const userRoles = normalizeRoles(user.role)\n return userRoles.some((role) => roles.includes(role))\n}\n\n/**\n * Check if a user has all of the specified roles.\n *\n * @param user - The user object\n * @param roles - Roles to check for\n * @returns True if user has all matching roles\n *\n * @example\n * ```ts\n * const user = { role: ['admin', 'editor'] }\n * hasAllRoles(user, ['admin', 'editor']) // true\n * hasAllRoles(user, ['admin', 'superadmin']) // false\n * ```\n */\nexport function hasAllRoles(\n user: { role?: unknown } | null | undefined,\n roles: string[]\n): boolean {\n if (!user?.role) return false\n const userRoles = normalizeRoles(user.role)\n return roles.every((role) => userRoles.includes(role))\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Access Control Functions\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Check if the current request user has admin roles.\n *\n * Use this as a reusable check within access functions.\n *\n * @param config - Configuration with admin roles\n * @returns Access check function\n */\nexport function hasAdminRoles(\n config: RoleCheckConfig = {}\n): (args: { req: PayloadRequest }) => boolean {\n const { adminRoles = ['admin'] } = config\n\n return ({ req }) => {\n return hasAnyRole(req.user as { role?: unknown } | null, adminRoles)\n }\n}\n\n/**\n * Access control: Only allow users with admin roles.\n *\n * @param config - Configuration with admin roles\n * @returns Payload access function\n *\n * @example\n * ```ts\n * access: {\n * delete: isAdmin({ adminRoles: ['admin', 'superadmin'] }),\n * }\n * ```\n */\nexport function isAdmin(config: RoleCheckConfig = {}): Access {\n const checkAdmin = hasAdminRoles(config)\n\n return ({ req }) => {\n return checkAdmin({ req })\n }\n}\n\n/**\n * Field access control: Only allow users with admin roles.\n *\n * @param config - Configuration with admin roles\n * @returns Payload field access function\n *\n * @example\n * ```ts\n * fields: [\n * {\n * name: 'role',\n * type: 'select',\n * access: {\n * update: isAdminField({ adminRoles: ['admin'] }),\n * },\n * },\n * ]\n * ```\n */\nexport function isAdminField(config: RoleCheckConfig = {}): FieldAccess {\n const checkAdmin = hasAdminRoles(config)\n\n return ({ req }) => {\n return checkAdmin({ req })\n }\n}\n\n/**\n * Access control: Allow admin OR the user accessing their own record.\n *\n * Returns a query constraint for non-admin users to limit access\n * to their own records only.\n *\n * @param config - Configuration with admin roles and ID field\n * @returns Payload access function\n *\n * @example\n * ```ts\n * access: {\n * read: isAdminOrSelf({ adminRoles: ['admin'] }),\n * update: isAdminOrSelf({ adminRoles: ['admin'] }),\n * }\n * ```\n */\nexport function isAdminOrSelf(config: SelfAccessConfig = {}): Access {\n const { adminRoles = ['admin'], idField = 'id' } = config\n const checkAdmin = hasAdminRoles({ adminRoles })\n\n return ({ req }) => {\n // Admins can access everything\n if (checkAdmin({ req })) return true\n\n // Non-authenticated users have no access\n if (!req.user) return false\n\n // Restrict to own record\n return {\n [idField]: {\n equals: req.user.id,\n },\n }\n }\n}\n\n/**\n * Access control: Allow admin OR user updating allowed fields on own record.\n *\n * This is useful for allowing users to update specific fields (like name)\n * on their own profile while preventing them from changing sensitive fields\n * like role.\n *\n * Password changes require `currentPassword` to be provided and validated.\n *\n * @param config - Configuration with admin roles, allowed fields, and user slug\n * @returns Payload access function\n *\n * @example\n * ```ts\n * access: {\n * update: canUpdateOwnFields({\n * adminRoles: ['admin'],\n * allowedFields: ['name', 'image'],\n * userSlug: 'users',\n * }),\n * }\n * ```\n */\nexport function canUpdateOwnFields(config: FieldUpdateConfig = {}): Access {\n const {\n adminRoles = ['admin'],\n allowedFields = ['name'],\n idField = 'id',\n userSlug = 'users',\n } = config\n const checkAdmin = hasAdminRoles({ adminRoles })\n\n return async ({ req, id, data }) => {\n // Admins can update everything\n if (checkAdmin({ req })) return true\n\n // Must be authenticated\n if (!req.user) return false\n\n // Must be updating own record\n const userId = req.user[idField]\n if (userId !== id || !data) return false\n\n const dataKeys = Object.keys(data)\n const effectiveAllowed = [...allowedFields]\n\n // Handle password changes specially\n const hasCurrentPassword = dataKeys.includes('currentPassword')\n const hasPassword = dataKeys.includes('password')\n\n if (hasPassword || hasCurrentPassword) {\n // Both must be provided for password change\n if (!(hasCurrentPassword && hasPassword)) return false\n\n try {\n // Verify current password\n if (!req.user.email) return false\n\n const result = await req.payload.login({\n collection: userSlug,\n data: {\n email: req.user.email as string,\n password: data.currentPassword as string,\n },\n })\n\n if (!result) return false\n\n effectiveAllowed.push('password', 'currentPassword')\n } catch {\n return false\n }\n }\n\n // Check all fields are allowed\n const hasDisallowed = dataKeys.some((key) => !effectiveAllowed.includes(key))\n return !hasDisallowed\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Authenticated Access\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Access control: Allow any authenticated user.\n *\n * @returns Payload access function\n *\n * @example\n * ```ts\n * access: {\n * read: isAuthenticated(),\n * }\n * ```\n */\nexport function isAuthenticated(): Access {\n return ({ req }) => {\n return !!req.user\n }\n}\n\n/**\n * Field access control: Allow any authenticated user.\n *\n * @returns Payload field access function\n */\nexport function isAuthenticatedField(): FieldAccess {\n return ({ req }) => {\n return !!req.user\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Role-Based Access\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Access control: Allow users with any of the specified roles.\n *\n * @param roles - Roles that have access\n * @returns Payload access function\n *\n * @example\n * ```ts\n * access: {\n * read: hasRole(['admin', 'editor', 'viewer']),\n * update: hasRole(['admin', 'editor']),\n * }\n * ```\n */\nexport function hasRole(roles: string[]): Access {\n return ({ req }) => {\n return hasAnyRole(req.user as { role?: unknown } | null, roles)\n }\n}\n\n/**\n * Field access control: Allow users with any of the specified roles.\n *\n * @param roles - Roles that have access\n * @returns Payload field access function\n */\nexport function hasRoleField(roles: string[]): FieldAccess {\n return ({ req }) => {\n return hasAnyRole(req.user as { role?: unknown } | null, roles)\n }\n}\n\n/**\n * Access control: Allow users with all of the specified roles.\n *\n * @param roles - All roles required for access\n * @returns Payload access function\n *\n * @example\n * ```ts\n * access: {\n * delete: requireAllRoles(['admin', 'verified']),\n * }\n * ```\n */\nexport function requireAllRoles(roles: string[]): Access {\n return ({ req }) => {\n return hasAllRoles(req.user as { role?: unknown } | null, roles)\n }\n}\n"],"names":["normalizeRoles","role","Array","isArray","filter","r","includes","split","map","trim","Boolean","hasAnyRole","user","roles","userRoles","some","hasAllRoles","every","hasAdminRoles","config","adminRoles","req","isAdmin","checkAdmin","isAdminField","isAdminOrSelf","idField","equals","id","canUpdateOwnFields","allowedFields","userSlug","data","userId","dataKeys","Object","keys","effectiveAllowed","hasCurrentPassword","hasPassword","email","result","payload","login","collection","password","currentPassword","push","hasDisallowed","key","isAuthenticated","isAuthenticatedField","hasRole","hasRoleField","requireAllRoles"],"mappings":"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"}
|
|
@@ -19,18 +19,15 @@
|
|
|
19
19
|
* },
|
|
20
20
|
* }
|
|
21
21
|
* ```
|
|
22
|
-
*/
|
|
23
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
*/ // ─────────────────────────────────────────────────────────────────────────────
|
|
24
23
|
// Helpers
|
|
25
24
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
25
|
/**
|
|
27
26
|
* Extract API key from request headers.
|
|
28
27
|
* Supports Bearer token format: Authorization: Bearer <api-key>
|
|
29
|
-
*/
|
|
30
|
-
export function extractApiKeyFromRequest(req) {
|
|
28
|
+
*/ export function extractApiKeyFromRequest(req) {
|
|
31
29
|
const authHeader = req.headers?.get('authorization');
|
|
32
|
-
if (!authHeader)
|
|
33
|
-
return null;
|
|
30
|
+
if (!authHeader) return null;
|
|
34
31
|
// Support "Bearer <key>" format
|
|
35
32
|
if (authHeader.startsWith('Bearer ')) {
|
|
36
33
|
return authHeader.slice(7).trim();
|
|
@@ -41,31 +38,38 @@ export function extractApiKeyFromRequest(req) {
|
|
|
41
38
|
/**
|
|
42
39
|
* Look up API key info from the database.
|
|
43
40
|
* Returns null if key not found or disabled.
|
|
44
|
-
*/
|
|
45
|
-
export async function getApiKeyInfo(req, apiKey, apiKeysCollection = 'apiKeys') {
|
|
41
|
+
*/ export async function getApiKeyInfo(req, apiKey, apiKeysCollection = 'apiKeys') {
|
|
46
42
|
try {
|
|
47
43
|
// Try the provided collection name first
|
|
48
44
|
let results = await req.payload.find({
|
|
49
45
|
collection: apiKeysCollection,
|
|
50
46
|
where: {
|
|
51
|
-
key: {
|
|
52
|
-
|
|
47
|
+
key: {
|
|
48
|
+
equals: apiKey
|
|
49
|
+
},
|
|
50
|
+
enabled: {
|
|
51
|
+
not_equals: false
|
|
52
|
+
}
|
|
53
53
|
},
|
|
54
54
|
limit: 1,
|
|
55
|
-
depth: 0
|
|
56
|
-
}).catch(()
|
|
55
|
+
depth: 0
|
|
56
|
+
}).catch(()=>null);
|
|
57
57
|
// If not found, try alternative slug
|
|
58
58
|
if (!results || results.docs.length === 0) {
|
|
59
59
|
const altSlug = apiKeysCollection === 'apiKeys' ? 'api-keys' : 'apiKeys';
|
|
60
60
|
results = await req.payload.find({
|
|
61
61
|
collection: altSlug,
|
|
62
62
|
where: {
|
|
63
|
-
key: {
|
|
64
|
-
|
|
63
|
+
key: {
|
|
64
|
+
equals: apiKey
|
|
65
|
+
},
|
|
66
|
+
enabled: {
|
|
67
|
+
not_equals: false
|
|
68
|
+
}
|
|
65
69
|
},
|
|
66
70
|
limit: 1,
|
|
67
|
-
depth: 0
|
|
68
|
-
}).catch(()
|
|
71
|
+
depth: 0
|
|
72
|
+
}).catch(()=>null);
|
|
69
73
|
}
|
|
70
74
|
if (!results || results.docs.length === 0) {
|
|
71
75
|
return null;
|
|
@@ -78,29 +82,24 @@ export async function getApiKeyInfo(req, apiKey, apiKeysCollection = 'apiKeys')
|
|
|
78
82
|
const parsed = JSON.parse(doc.permissions);
|
|
79
83
|
if (Array.isArray(parsed)) {
|
|
80
84
|
scopes = parsed;
|
|
81
|
-
}
|
|
82
|
-
else if (typeof parsed === 'object') {
|
|
85
|
+
} else if (typeof parsed === 'object') {
|
|
83
86
|
// If it's an object, extract keys or flatten
|
|
84
87
|
scopes = Object.keys(parsed);
|
|
85
88
|
}
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
89
|
+
} catch {
|
|
88
90
|
// If not JSON, treat as comma-separated
|
|
89
|
-
scopes = doc.permissions.split(',').map((s)
|
|
91
|
+
scopes = doc.permissions.split(',').map((s)=>s.trim()).filter(Boolean);
|
|
90
92
|
}
|
|
91
|
-
}
|
|
92
|
-
else if (Array.isArray(doc.scopes)) {
|
|
93
|
+
} else if (Array.isArray(doc.scopes)) {
|
|
93
94
|
scopes = doc.scopes;
|
|
94
95
|
}
|
|
95
96
|
// Get user ID (handle both direct field and relationship)
|
|
96
97
|
let userId;
|
|
97
98
|
if (doc.userId) {
|
|
98
99
|
userId = String(doc.userId);
|
|
99
|
-
}
|
|
100
|
-
else if (doc.user) {
|
|
100
|
+
} else if (doc.user) {
|
|
101
101
|
userId = typeof doc.user === 'object' ? String(doc.user.id) : String(doc.user);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
102
|
+
} else {
|
|
104
103
|
return null;
|
|
105
104
|
}
|
|
106
105
|
// Parse metadata
|
|
@@ -109,12 +108,10 @@ export async function getApiKeyInfo(req, apiKey, apiKeysCollection = 'apiKeys')
|
|
|
109
108
|
if (typeof doc.metadata === 'string') {
|
|
110
109
|
try {
|
|
111
110
|
metadata = JSON.parse(doc.metadata);
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore parse errors
|
|
112
113
|
}
|
|
113
|
-
|
|
114
|
-
// Ignore parse errors
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
114
|
+
} else {
|
|
118
115
|
metadata = doc.metadata;
|
|
119
116
|
}
|
|
120
117
|
}
|
|
@@ -123,44 +120,39 @@ export async function getApiKeyInfo(req, apiKey, apiKeysCollection = 'apiKeys')
|
|
|
123
120
|
userId,
|
|
124
121
|
scopes,
|
|
125
122
|
keyPrefix: doc.start,
|
|
126
|
-
metadata
|
|
123
|
+
metadata
|
|
127
124
|
};
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
125
|
+
} catch {
|
|
130
126
|
return null;
|
|
131
127
|
}
|
|
132
128
|
}
|
|
133
129
|
/**
|
|
134
130
|
* Check if an API key has a specific scope.
|
|
135
131
|
* Supports wildcard patterns like 'posts:*' matching 'posts:read', 'posts:write', etc.
|
|
136
|
-
*/
|
|
137
|
-
|
|
138
|
-
return keyScopes.some((scope) => {
|
|
132
|
+
*/ export function hasScope(keyScopes, requiredScope) {
|
|
133
|
+
return keyScopes.some((scope)=>{
|
|
139
134
|
// Exact match
|
|
140
|
-
if (scope === requiredScope)
|
|
141
|
-
return true;
|
|
135
|
+
if (scope === requiredScope) return true;
|
|
142
136
|
// Wildcard match: 'posts:*' matches 'posts:read'
|
|
143
137
|
if (scope.endsWith(':*')) {
|
|
144
|
-
const prefix = scope.slice(0, -1)
|
|
138
|
+
const prefix = scope.slice(0, -1) // Remove '*', keep ':'
|
|
139
|
+
;
|
|
145
140
|
return requiredScope.startsWith(prefix);
|
|
146
141
|
}
|
|
147
142
|
// Global wildcard
|
|
148
|
-
if (scope === '*')
|
|
149
|
-
return true;
|
|
143
|
+
if (scope === '*') return true;
|
|
150
144
|
return false;
|
|
151
145
|
});
|
|
152
146
|
}
|
|
153
147
|
/**
|
|
154
148
|
* Check if an API key has any of the specified scopes.
|
|
155
|
-
*/
|
|
156
|
-
|
|
157
|
-
return requiredScopes.some((scope) => hasScope(keyScopes, scope));
|
|
149
|
+
*/ export function hasAnyScope(keyScopes, requiredScopes) {
|
|
150
|
+
return requiredScopes.some((scope)=>hasScope(keyScopes, scope));
|
|
158
151
|
}
|
|
159
152
|
/**
|
|
160
153
|
* Check if an API key has all of the specified scopes.
|
|
161
|
-
*/
|
|
162
|
-
|
|
163
|
-
return requiredScopes.every((scope) => hasScope(keyScopes, scope));
|
|
154
|
+
*/ export function hasAllScopes(keyScopes, requiredScopes) {
|
|
155
|
+
return requiredScopes.every((scope)=>hasScope(keyScopes, scope));
|
|
164
156
|
}
|
|
165
157
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
166
158
|
// Access Control Functions
|
|
@@ -179,15 +171,15 @@ export function hasAllScopes(keyScopes, requiredScopes) {
|
|
|
179
171
|
* create: requireScope('posts:write'),
|
|
180
172
|
* }
|
|
181
173
|
* ```
|
|
182
|
-
*/
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return async ({ req }) => {
|
|
174
|
+
*/ export function requireScope(scope, config = {}) {
|
|
175
|
+
const { apiKeysCollection = 'apiKeys', allowAuthenticatedUsers = false, extractApiKey = extractApiKeyFromRequest } = config;
|
|
176
|
+
return async ({ req })=>{
|
|
186
177
|
// If authenticated users are allowed and user is logged in without API key
|
|
187
178
|
if (allowAuthenticatedUsers && req.user) {
|
|
188
179
|
const apiKey = extractApiKey(req);
|
|
189
180
|
if (!apiKey) {
|
|
190
|
-
return true
|
|
181
|
+
return true // User authenticated via session, no API key = allow
|
|
182
|
+
;
|
|
191
183
|
}
|
|
192
184
|
}
|
|
193
185
|
// Extract API key from request
|
|
@@ -217,10 +209,9 @@ export function requireScope(scope, config = {}) {
|
|
|
217
209
|
* read: requireAnyScope(['posts:read', 'content:read', 'admin:*']),
|
|
218
210
|
* }
|
|
219
211
|
* ```
|
|
220
|
-
*/
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return async ({ req }) => {
|
|
212
|
+
*/ export function requireAnyScope(scopes, config = {}) {
|
|
213
|
+
const { apiKeysCollection = 'apiKeys', allowAuthenticatedUsers = false, extractApiKey = extractApiKeyFromRequest } = config;
|
|
214
|
+
return async ({ req })=>{
|
|
224
215
|
// If authenticated users are allowed and user is logged in without API key
|
|
225
216
|
if (allowAuthenticatedUsers && req.user) {
|
|
226
217
|
const apiKey = extractApiKey(req);
|
|
@@ -252,10 +243,9 @@ export function requireAnyScope(scopes, config = {}) {
|
|
|
252
243
|
* delete: requireAllScopes(['posts:delete', 'admin:write']),
|
|
253
244
|
* }
|
|
254
245
|
* ```
|
|
255
|
-
*/
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
return async ({ req }) => {
|
|
246
|
+
*/ export function requireAllScopes(scopes, config = {}) {
|
|
247
|
+
const { apiKeysCollection = 'apiKeys', allowAuthenticatedUsers = false, extractApiKey = extractApiKeyFromRequest } = config;
|
|
248
|
+
return async ({ req })=>{
|
|
259
249
|
// If authenticated users are allowed and user is logged in without API key
|
|
260
250
|
if (allowAuthenticatedUsers && req.user) {
|
|
261
251
|
const apiKey = extractApiKey(req);
|
|
@@ -291,9 +281,11 @@ export function requireAllScopes(scopes, config = {}) {
|
|
|
291
281
|
* read: allowSessionOrScope('posts:read'),
|
|
292
282
|
* }
|
|
293
283
|
* ```
|
|
294
|
-
*/
|
|
295
|
-
|
|
296
|
-
|
|
284
|
+
*/ export function allowSessionOrScope(scope, config = {}) {
|
|
285
|
+
return requireScope(scope, {
|
|
286
|
+
...config,
|
|
287
|
+
allowAuthenticatedUsers: true
|
|
288
|
+
});
|
|
297
289
|
}
|
|
298
290
|
/**
|
|
299
291
|
* Create an access control function that allows either:
|
|
@@ -303,9 +295,11 @@ export function allowSessionOrScope(scope, config = {}) {
|
|
|
303
295
|
* @param scopes - Array of acceptable scopes for API key access
|
|
304
296
|
* @param config - Configuration options
|
|
305
297
|
* @returns Payload access function
|
|
306
|
-
*/
|
|
307
|
-
|
|
308
|
-
|
|
298
|
+
*/ export function allowSessionOrAnyScope(scopes, config = {}) {
|
|
299
|
+
return requireAnyScope(scopes, {
|
|
300
|
+
...config,
|
|
301
|
+
allowAuthenticatedUsers: true
|
|
302
|
+
});
|
|
309
303
|
}
|
|
310
304
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
311
305
|
// Better Auth Integration
|
|
@@ -328,11 +322,10 @@ export function allowSessionOrAnyScope(scopes, config = {}) {
|
|
|
328
322
|
* console.log('Scopes:', keyInfo.scopes)
|
|
329
323
|
* }
|
|
330
324
|
* ```
|
|
331
|
-
*/
|
|
332
|
-
export async function validateApiKey(req, apiKeysCollection = 'apiKeys') {
|
|
325
|
+
*/ export async function validateApiKey(req, apiKeysCollection = 'apiKeys') {
|
|
333
326
|
const apiKey = extractApiKeyFromRequest(req);
|
|
334
|
-
if (!apiKey)
|
|
335
|
-
return null;
|
|
327
|
+
if (!apiKey) return null;
|
|
336
328
|
return getApiKeyInfo(req, apiKey, apiKeysCollection);
|
|
337
329
|
}
|
|
330
|
+
|
|
338
331
|
//# sourceMappingURL=apiKeyAccess.js.map
|