@delmaredigital/payload-better-auth 0.3.8 → 0.3.10
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/collections.d.ts +0 -1
- package/dist/adapter/collections.js +0 -2
- package/dist/adapter/index.d.ts +0 -1
- package/dist/adapter/index.js +0 -2
- package/dist/components/BeforeLogin.d.ts +0 -1
- package/dist/components/BeforeLogin.js +0 -2
- package/dist/components/LoginView.d.ts +0 -1
- package/dist/components/LoginView.js +0 -2
- package/dist/components/LoginViewWrapper.d.ts +0 -1
- package/dist/components/LoginViewWrapper.js +0 -2
- package/dist/components/LogoutButton.d.ts +0 -1
- package/dist/components/LogoutButton.js +0 -2
- package/dist/components/PasskeyRegisterButton.d.ts +0 -1
- package/dist/components/PasskeyRegisterButton.js +0 -2
- package/dist/components/PasskeySignInButton.d.ts +0 -1
- package/dist/components/PasskeySignInButton.js +0 -2
- package/dist/components/auth/ForgotPasswordView.d.ts +0 -1
- package/dist/components/auth/ForgotPasswordView.js +0 -2
- package/dist/components/auth/ResetPasswordView.d.ts +0 -1
- package/dist/components/auth/ResetPasswordView.js +0 -2
- package/dist/components/auth/index.d.ts +0 -1
- package/dist/components/auth/index.js +0 -2
- package/dist/components/management/ApiKeysManagementClient.d.ts +0 -1
- package/dist/components/management/ApiKeysManagementClient.js +0 -2
- package/dist/components/management/PasskeysManagementClient.d.ts +0 -1
- package/dist/components/management/PasskeysManagementClient.js +0 -2
- package/dist/components/management/SecurityNavLinks.d.ts +0 -1
- package/dist/components/management/SecurityNavLinks.js +0 -2
- package/dist/components/management/TwoFactorManagementClient.d.ts +0 -1
- package/dist/components/management/TwoFactorManagementClient.js +0 -2
- package/dist/components/management/index.d.ts +0 -1
- package/dist/components/management/index.js +0 -2
- package/dist/components/management/views/ApiKeysView.d.ts +0 -1
- package/dist/components/management/views/ApiKeysView.js +0 -2
- package/dist/components/management/views/PasskeysView.d.ts +0 -1
- package/dist/components/management/views/PasskeysView.js +0 -2
- package/dist/components/management/views/TwoFactorView.d.ts +0 -1
- package/dist/components/management/views/TwoFactorView.js +0 -2
- package/dist/components/management/views/index.d.ts +0 -1
- package/dist/components/management/views/index.js +0 -2
- package/dist/components/twoFactor/TwoFactorSetupView.d.ts +0 -1
- package/dist/components/twoFactor/TwoFactorSetupView.js +0 -2
- package/dist/components/twoFactor/TwoFactorVerifyView.d.ts +0 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.js +0 -2
- package/dist/components/twoFactor/index.d.ts +0 -1
- package/dist/components/twoFactor/index.js +0 -2
- package/dist/exports/client.d.ts +0 -1
- package/dist/exports/client.js +0 -2
- package/dist/exports/components.d.ts +0 -1
- package/dist/exports/components.js +0 -2
- package/dist/exports/management.d.ts +0 -1
- package/dist/exports/management.js +0 -2
- package/dist/exports/rsc.d.ts +0 -1
- package/dist/exports/rsc.js +0 -2
- package/dist/generated-types.d.ts +0 -1
- package/dist/generated-types.js +0 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/plugin/index.d.ts +0 -1
- package/dist/plugin/index.js +0 -2
- package/dist/scripts/generate-types.d.ts +0 -1
- package/dist/scripts/generate-types.js +0 -2
- package/dist/types/apiKey.d.ts +0 -1
- package/dist/types/apiKey.js +0 -2
- package/dist/types/betterAuth.d.ts +0 -1
- package/dist/types/betterAuth.js +0 -2
- package/dist/utils/access.d.ts +0 -1
- package/dist/utils/access.js +0 -2
- package/dist/utils/apiKeyAccess.d.ts +0 -1
- package/dist/utils/apiKeyAccess.js +0 -2
- package/dist/utils/betterAuthDefaults.d.ts +0 -1
- package/dist/utils/betterAuthDefaults.js +0 -2
- package/dist/utils/detectAuthConfig.d.ts +0 -1
- package/dist/utils/detectAuthConfig.js +0 -2
- package/dist/utils/detectEnabledPlugins.d.ts +0 -1
- package/dist/utils/detectEnabledPlugins.js +0 -2
- package/dist/utils/firstUserAdmin.d.ts +0 -1
- package/dist/utils/firstUserAdmin.js +0 -2
- package/dist/utils/generateScopes.d.ts +0 -1
- package/dist/utils/generateScopes.js +0 -2
- package/dist/utils/session.d.ts +0 -1
- package/dist/utils/session.js +0 -2
- package/package.json +34 -91
- package/dist/adapter/collections.d.ts.map +0 -1
- package/dist/adapter/collections.js.map +0 -1
- package/dist/adapter/index.d.ts.map +0 -1
- package/dist/adapter/index.js.map +0 -1
- package/dist/components/BeforeLogin.d.ts.map +0 -1
- package/dist/components/BeforeLogin.js.map +0 -1
- package/dist/components/LoginView.d.ts.map +0 -1
- package/dist/components/LoginView.js.map +0 -1
- package/dist/components/LoginViewWrapper.d.ts.map +0 -1
- package/dist/components/LoginViewWrapper.js.map +0 -1
- package/dist/components/LogoutButton.d.ts.map +0 -1
- package/dist/components/LogoutButton.js.map +0 -1
- package/dist/components/PasskeyRegisterButton.d.ts.map +0 -1
- package/dist/components/PasskeyRegisterButton.js.map +0 -1
- package/dist/components/PasskeySignInButton.d.ts.map +0 -1
- package/dist/components/PasskeySignInButton.js.map +0 -1
- package/dist/components/auth/ForgotPasswordView.d.ts.map +0 -1
- package/dist/components/auth/ForgotPasswordView.js.map +0 -1
- package/dist/components/auth/ResetPasswordView.d.ts.map +0 -1
- package/dist/components/auth/ResetPasswordView.js.map +0 -1
- package/dist/components/auth/index.d.ts.map +0 -1
- package/dist/components/auth/index.js.map +0 -1
- package/dist/components/management/ApiKeysManagementClient.d.ts.map +0 -1
- package/dist/components/management/ApiKeysManagementClient.js.map +0 -1
- package/dist/components/management/PasskeysManagementClient.d.ts.map +0 -1
- package/dist/components/management/PasskeysManagementClient.js.map +0 -1
- package/dist/components/management/SecurityNavLinks.d.ts.map +0 -1
- package/dist/components/management/SecurityNavLinks.js.map +0 -1
- package/dist/components/management/TwoFactorManagementClient.d.ts.map +0 -1
- package/dist/components/management/TwoFactorManagementClient.js.map +0 -1
- package/dist/components/management/index.d.ts.map +0 -1
- package/dist/components/management/index.js.map +0 -1
- package/dist/components/management/views/ApiKeysView.d.ts.map +0 -1
- package/dist/components/management/views/ApiKeysView.js.map +0 -1
- package/dist/components/management/views/PasskeysView.d.ts.map +0 -1
- package/dist/components/management/views/PasskeysView.js.map +0 -1
- package/dist/components/management/views/TwoFactorView.d.ts.map +0 -1
- package/dist/components/management/views/TwoFactorView.js.map +0 -1
- package/dist/components/management/views/index.d.ts.map +0 -1
- package/dist/components/management/views/index.js.map +0 -1
- package/dist/components/twoFactor/TwoFactorSetupView.d.ts.map +0 -1
- package/dist/components/twoFactor/TwoFactorSetupView.js.map +0 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.d.ts.map +0 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.js.map +0 -1
- package/dist/components/twoFactor/index.d.ts.map +0 -1
- package/dist/components/twoFactor/index.js.map +0 -1
- package/dist/exports/client.d.ts.map +0 -1
- package/dist/exports/client.js.map +0 -1
- package/dist/exports/components.d.ts.map +0 -1
- package/dist/exports/components.js.map +0 -1
- package/dist/exports/management.d.ts.map +0 -1
- package/dist/exports/management.js.map +0 -1
- package/dist/exports/rsc.d.ts.map +0 -1
- package/dist/exports/rsc.js.map +0 -1
- package/dist/generated-types.d.ts.map +0 -1
- package/dist/generated-types.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plugin/index.d.ts.map +0 -1
- package/dist/plugin/index.js.map +0 -1
- package/dist/scripts/generate-types.d.ts.map +0 -1
- package/dist/scripts/generate-types.js.map +0 -1
- package/dist/types/apiKey.d.ts.map +0 -1
- package/dist/types/apiKey.js.map +0 -1
- package/dist/types/betterAuth.d.ts.map +0 -1
- package/dist/types/betterAuth.js.map +0 -1
- package/dist/utils/access.d.ts.map +0 -1
- package/dist/utils/access.js.map +0 -1
- package/dist/utils/apiKeyAccess.d.ts.map +0 -1
- package/dist/utils/apiKeyAccess.js.map +0 -1
- package/dist/utils/betterAuthDefaults.d.ts.map +0 -1
- package/dist/utils/betterAuthDefaults.js.map +0 -1
- package/dist/utils/detectAuthConfig.d.ts.map +0 -1
- package/dist/utils/detectAuthConfig.js.map +0 -1
- package/dist/utils/detectEnabledPlugins.d.ts.map +0 -1
- package/dist/utils/detectEnabledPlugins.js.map +0 -1
- package/dist/utils/firstUserAdmin.d.ts.map +0 -1
- package/dist/utils/firstUserAdmin.js.map +0 -1
- package/dist/utils/generateScopes.d.ts.map +0 -1
- package/dist/utils/generateScopes.js.map +0 -1
- package/dist/utils/session.d.ts.map +0 -1
- package/dist/utils/session.js.map +0 -1
- package/src/adapter/collections.ts +0 -621
- package/src/adapter/index.ts +0 -712
- package/src/components/BeforeLogin.tsx +0 -39
- package/src/components/LoginView.tsx +0 -1516
- package/src/components/LoginViewWrapper.tsx +0 -35
- package/src/components/LogoutButton.tsx +0 -58
- package/src/components/PasskeyRegisterButton.tsx +0 -105
- package/src/components/PasskeySignInButton.tsx +0 -96
- package/src/components/auth/ForgotPasswordView.tsx +0 -274
- package/src/components/auth/ResetPasswordView.tsx +0 -331
- package/src/components/auth/index.ts +0 -8
- package/src/components/management/ApiKeysManagementClient.tsx +0 -988
- package/src/components/management/PasskeysManagementClient.tsx +0 -409
- package/src/components/management/SecurityNavLinks.tsx +0 -117
- package/src/components/management/TwoFactorManagementClient.tsx +0 -560
- package/src/components/management/index.ts +0 -20
- package/src/components/management/views/ApiKeysView.tsx +0 -57
- package/src/components/management/views/PasskeysView.tsx +0 -42
- package/src/components/management/views/TwoFactorView.tsx +0 -42
- package/src/components/management/views/index.ts +0 -10
- package/src/components/twoFactor/TwoFactorSetupView.tsx +0 -515
- package/src/components/twoFactor/TwoFactorVerifyView.tsx +0 -238
- package/src/components/twoFactor/index.ts +0 -8
- package/src/exports/client.ts +0 -77
- package/src/exports/components.ts +0 -30
- package/src/exports/management.ts +0 -25
- package/src/exports/rsc.ts +0 -11
- package/src/generated-types.ts +0 -269
- package/src/index.ts +0 -135
- package/src/plugin/index.ts +0 -834
- package/src/scripts/generate-types.ts +0 -269
- package/src/types/apiKey.ts +0 -63
- package/src/types/betterAuth.ts +0 -253
- package/src/utils/access.ts +0 -410
- package/src/utils/apiKeyAccess.ts +0 -443
- package/src/utils/betterAuthDefaults.ts +0 -102
- package/src/utils/detectAuthConfig.ts +0 -47
- package/src/utils/detectEnabledPlugins.ts +0 -69
- package/src/utils/firstUserAdmin.ts +0 -164
- package/src/utils/generateScopes.ts +0 -150
- package/src/utils/session.ts +0 -91
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Key Scope Enforcement Utilities
|
|
3
|
-
*
|
|
4
|
-
* These utilities help enforce API key scopes in Payload access control.
|
|
5
|
-
* They extract the API key from requests, validate scopes, and provide
|
|
6
|
-
* type-safe access control functions.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* import { requireScope, requireAnyScope } from '@delmaredigital/payload-better-auth'
|
|
11
|
-
*
|
|
12
|
-
* export const Posts: CollectionConfig = {
|
|
13
|
-
* slug: 'posts',
|
|
14
|
-
* access: {
|
|
15
|
-
* read: requireAnyScope(['posts:read', 'content:read']),
|
|
16
|
-
* create: requireScope('posts:write'),
|
|
17
|
-
* update: requireScope('posts:write'),
|
|
18
|
-
* delete: requireScope('posts:delete'),
|
|
19
|
-
* },
|
|
20
|
-
* }
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import type { Access, PayloadRequest } from 'payload'
|
|
25
|
-
|
|
26
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
|
-
// Types
|
|
28
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
export type ApiKeyInfo = {
|
|
31
|
-
/** The API key ID */
|
|
32
|
-
id: string
|
|
33
|
-
/** User ID who owns this key */
|
|
34
|
-
userId: string
|
|
35
|
-
/** Array of granted scope strings */
|
|
36
|
-
scopes: string[]
|
|
37
|
-
/** The raw key (only first/last chars visible) */
|
|
38
|
-
keyPrefix?: string
|
|
39
|
-
/** Optional metadata */
|
|
40
|
-
metadata?: Record<string, unknown>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type ApiKeyAccessConfig = {
|
|
44
|
-
/**
|
|
45
|
-
* API keys collection slug.
|
|
46
|
-
* @default 'apiKeys' or 'api-keys' (auto-detected)
|
|
47
|
-
*/
|
|
48
|
-
apiKeysCollection?: string
|
|
49
|
-
/**
|
|
50
|
-
* Allow access if user is authenticated (non-API key session).
|
|
51
|
-
* Useful for allowing both API keys and regular sessions.
|
|
52
|
-
* @default false
|
|
53
|
-
*/
|
|
54
|
-
allowAuthenticatedUsers?: boolean
|
|
55
|
-
/**
|
|
56
|
-
* Custom function to extract API key from request.
|
|
57
|
-
* By default, extracts from Authorization: Bearer <key> header.
|
|
58
|
-
*/
|
|
59
|
-
extractApiKey?: (req: PayloadRequest) => string | null
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
63
|
-
// Helpers
|
|
64
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Extract API key from request headers.
|
|
68
|
-
* Supports Bearer token format: Authorization: Bearer <api-key>
|
|
69
|
-
*/
|
|
70
|
-
export function extractApiKeyFromRequest(req: PayloadRequest): string | null {
|
|
71
|
-
const authHeader = req.headers?.get('authorization')
|
|
72
|
-
if (!authHeader) return null
|
|
73
|
-
|
|
74
|
-
// Support "Bearer <key>" format
|
|
75
|
-
if (authHeader.startsWith('Bearer ')) {
|
|
76
|
-
return authHeader.slice(7).trim()
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Support raw key in Authorization header
|
|
80
|
-
return authHeader.trim()
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Look up API key info from the database.
|
|
85
|
-
* Returns null if key not found or disabled.
|
|
86
|
-
*/
|
|
87
|
-
export async function getApiKeyInfo(
|
|
88
|
-
req: PayloadRequest,
|
|
89
|
-
apiKey: string,
|
|
90
|
-
apiKeysCollection = 'apiKeys'
|
|
91
|
-
): Promise<ApiKeyInfo | null> {
|
|
92
|
-
try {
|
|
93
|
-
// Try the provided collection name first
|
|
94
|
-
let results = await req.payload.find({
|
|
95
|
-
collection: apiKeysCollection,
|
|
96
|
-
where: {
|
|
97
|
-
key: { equals: apiKey },
|
|
98
|
-
enabled: { not_equals: false },
|
|
99
|
-
},
|
|
100
|
-
limit: 1,
|
|
101
|
-
depth: 0,
|
|
102
|
-
}).catch(() => null)
|
|
103
|
-
|
|
104
|
-
// If not found, try alternative slug
|
|
105
|
-
if (!results || results.docs.length === 0) {
|
|
106
|
-
const altSlug = apiKeysCollection === 'apiKeys' ? 'api-keys' : 'apiKeys'
|
|
107
|
-
results = await req.payload.find({
|
|
108
|
-
collection: altSlug,
|
|
109
|
-
where: {
|
|
110
|
-
key: { equals: apiKey },
|
|
111
|
-
enabled: { not_equals: false },
|
|
112
|
-
},
|
|
113
|
-
limit: 1,
|
|
114
|
-
depth: 0,
|
|
115
|
-
}).catch(() => null)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (!results || results.docs.length === 0) {
|
|
119
|
-
return null
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const doc = results.docs[0] as {
|
|
123
|
-
id: string | number
|
|
124
|
-
user?: string | number | { id: string | number }
|
|
125
|
-
userId?: string | number
|
|
126
|
-
permissions?: string
|
|
127
|
-
scopes?: string[]
|
|
128
|
-
start?: string
|
|
129
|
-
metadata?: string | Record<string, unknown>
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Parse scopes from permissions field (Better Auth format) or scopes array
|
|
133
|
-
let scopes: string[] = []
|
|
134
|
-
if (doc.permissions) {
|
|
135
|
-
try {
|
|
136
|
-
const parsed = JSON.parse(doc.permissions)
|
|
137
|
-
if (Array.isArray(parsed)) {
|
|
138
|
-
scopes = parsed
|
|
139
|
-
} else if (typeof parsed === 'object') {
|
|
140
|
-
// If it's an object, extract keys or flatten
|
|
141
|
-
scopes = Object.keys(parsed)
|
|
142
|
-
}
|
|
143
|
-
} catch {
|
|
144
|
-
// If not JSON, treat as comma-separated
|
|
145
|
-
scopes = doc.permissions.split(',').map((s) => s.trim()).filter(Boolean)
|
|
146
|
-
}
|
|
147
|
-
} else if (Array.isArray(doc.scopes)) {
|
|
148
|
-
scopes = doc.scopes
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Get user ID (handle both direct field and relationship)
|
|
152
|
-
let userId: string
|
|
153
|
-
if (doc.userId) {
|
|
154
|
-
userId = String(doc.userId)
|
|
155
|
-
} else if (doc.user) {
|
|
156
|
-
userId = typeof doc.user === 'object' ? String(doc.user.id) : String(doc.user)
|
|
157
|
-
} else {
|
|
158
|
-
return null
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Parse metadata
|
|
162
|
-
let metadata: Record<string, unknown> | undefined
|
|
163
|
-
if (doc.metadata) {
|
|
164
|
-
if (typeof doc.metadata === 'string') {
|
|
165
|
-
try {
|
|
166
|
-
metadata = JSON.parse(doc.metadata)
|
|
167
|
-
} catch {
|
|
168
|
-
// Ignore parse errors
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
metadata = doc.metadata
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
id: String(doc.id),
|
|
177
|
-
userId,
|
|
178
|
-
scopes,
|
|
179
|
-
keyPrefix: doc.start,
|
|
180
|
-
metadata,
|
|
181
|
-
}
|
|
182
|
-
} catch {
|
|
183
|
-
return null
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Check if an API key has a specific scope.
|
|
189
|
-
* Supports wildcard patterns like 'posts:*' matching 'posts:read', 'posts:write', etc.
|
|
190
|
-
*/
|
|
191
|
-
export function hasScope(keyScopes: string[], requiredScope: string): boolean {
|
|
192
|
-
return keyScopes.some((scope) => {
|
|
193
|
-
// Exact match
|
|
194
|
-
if (scope === requiredScope) return true
|
|
195
|
-
|
|
196
|
-
// Wildcard match: 'posts:*' matches 'posts:read'
|
|
197
|
-
if (scope.endsWith(':*')) {
|
|
198
|
-
const prefix = scope.slice(0, -1) // Remove '*', keep ':'
|
|
199
|
-
return requiredScope.startsWith(prefix)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Global wildcard
|
|
203
|
-
if (scope === '*') return true
|
|
204
|
-
|
|
205
|
-
return false
|
|
206
|
-
})
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Check if an API key has any of the specified scopes.
|
|
211
|
-
*/
|
|
212
|
-
export function hasAnyScope(keyScopes: string[], requiredScopes: string[]): boolean {
|
|
213
|
-
return requiredScopes.some((scope) => hasScope(keyScopes, scope))
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Check if an API key has all of the specified scopes.
|
|
218
|
-
*/
|
|
219
|
-
export function hasAllScopes(keyScopes: string[], requiredScopes: string[]): boolean {
|
|
220
|
-
return requiredScopes.every((scope) => hasScope(keyScopes, scope))
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
224
|
-
// Access Control Functions
|
|
225
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Create an access control function that requires a specific scope.
|
|
229
|
-
*
|
|
230
|
-
* @param scope - The required scope string (e.g., 'posts:read')
|
|
231
|
-
* @param config - Configuration options
|
|
232
|
-
* @returns Payload access function
|
|
233
|
-
*
|
|
234
|
-
* @example
|
|
235
|
-
* ```ts
|
|
236
|
-
* access: {
|
|
237
|
-
* read: requireScope('posts:read'),
|
|
238
|
-
* create: requireScope('posts:write'),
|
|
239
|
-
* }
|
|
240
|
-
* ```
|
|
241
|
-
*/
|
|
242
|
-
export function requireScope(
|
|
243
|
-
scope: string,
|
|
244
|
-
config: ApiKeyAccessConfig = {}
|
|
245
|
-
): Access {
|
|
246
|
-
const {
|
|
247
|
-
apiKeysCollection = 'apiKeys',
|
|
248
|
-
allowAuthenticatedUsers = false,
|
|
249
|
-
extractApiKey = extractApiKeyFromRequest,
|
|
250
|
-
} = config
|
|
251
|
-
|
|
252
|
-
return async ({ req }) => {
|
|
253
|
-
// If authenticated users are allowed and user is logged in without API key
|
|
254
|
-
if (allowAuthenticatedUsers && req.user) {
|
|
255
|
-
const apiKey = extractApiKey(req)
|
|
256
|
-
if (!apiKey) {
|
|
257
|
-
return true // User authenticated via session, no API key = allow
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Extract API key from request
|
|
262
|
-
const apiKey = extractApiKey(req)
|
|
263
|
-
if (!apiKey) {
|
|
264
|
-
return false
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Look up API key
|
|
268
|
-
const keyInfo = await getApiKeyInfo(req, apiKey, apiKeysCollection)
|
|
269
|
-
if (!keyInfo) {
|
|
270
|
-
return false
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Check scope
|
|
274
|
-
return hasScope(keyInfo.scopes, scope)
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Create an access control function that requires any of the specified scopes.
|
|
280
|
-
*
|
|
281
|
-
* @param scopes - Array of acceptable scopes (at least one must match)
|
|
282
|
-
* @param config - Configuration options
|
|
283
|
-
* @returns Payload access function
|
|
284
|
-
*
|
|
285
|
-
* @example
|
|
286
|
-
* ```ts
|
|
287
|
-
* access: {
|
|
288
|
-
* read: requireAnyScope(['posts:read', 'content:read', 'admin:*']),
|
|
289
|
-
* }
|
|
290
|
-
* ```
|
|
291
|
-
*/
|
|
292
|
-
export function requireAnyScope(
|
|
293
|
-
scopes: string[],
|
|
294
|
-
config: ApiKeyAccessConfig = {}
|
|
295
|
-
): Access {
|
|
296
|
-
const {
|
|
297
|
-
apiKeysCollection = 'apiKeys',
|
|
298
|
-
allowAuthenticatedUsers = false,
|
|
299
|
-
extractApiKey = extractApiKeyFromRequest,
|
|
300
|
-
} = config
|
|
301
|
-
|
|
302
|
-
return async ({ req }) => {
|
|
303
|
-
// If authenticated users are allowed and user is logged in without API key
|
|
304
|
-
if (allowAuthenticatedUsers && req.user) {
|
|
305
|
-
const apiKey = extractApiKey(req)
|
|
306
|
-
if (!apiKey) {
|
|
307
|
-
return true
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const apiKey = extractApiKey(req)
|
|
312
|
-
if (!apiKey) {
|
|
313
|
-
return false
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const keyInfo = await getApiKeyInfo(req, apiKey, apiKeysCollection)
|
|
317
|
-
if (!keyInfo) {
|
|
318
|
-
return false
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return hasAnyScope(keyInfo.scopes, scopes)
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Create an access control function that requires all specified scopes.
|
|
327
|
-
*
|
|
328
|
-
* @param scopes - Array of required scopes (all must be present)
|
|
329
|
-
* @param config - Configuration options
|
|
330
|
-
* @returns Payload access function
|
|
331
|
-
*
|
|
332
|
-
* @example
|
|
333
|
-
* ```ts
|
|
334
|
-
* access: {
|
|
335
|
-
* delete: requireAllScopes(['posts:delete', 'admin:write']),
|
|
336
|
-
* }
|
|
337
|
-
* ```
|
|
338
|
-
*/
|
|
339
|
-
export function requireAllScopes(
|
|
340
|
-
scopes: string[],
|
|
341
|
-
config: ApiKeyAccessConfig = {}
|
|
342
|
-
): Access {
|
|
343
|
-
const {
|
|
344
|
-
apiKeysCollection = 'apiKeys',
|
|
345
|
-
allowAuthenticatedUsers = false,
|
|
346
|
-
extractApiKey = extractApiKeyFromRequest,
|
|
347
|
-
} = config
|
|
348
|
-
|
|
349
|
-
return async ({ req }) => {
|
|
350
|
-
// If authenticated users are allowed and user is logged in without API key
|
|
351
|
-
if (allowAuthenticatedUsers && req.user) {
|
|
352
|
-
const apiKey = extractApiKey(req)
|
|
353
|
-
if (!apiKey) {
|
|
354
|
-
return true
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const apiKey = extractApiKey(req)
|
|
359
|
-
if (!apiKey) {
|
|
360
|
-
return false
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const keyInfo = await getApiKeyInfo(req, apiKey, apiKeysCollection)
|
|
364
|
-
if (!keyInfo) {
|
|
365
|
-
return false
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return hasAllScopes(keyInfo.scopes, scopes)
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Create an access control function that allows either:
|
|
374
|
-
* 1. Authenticated users (via session)
|
|
375
|
-
* 2. API key with required scope
|
|
376
|
-
*
|
|
377
|
-
* This is useful for endpoints that should work with both auth methods.
|
|
378
|
-
*
|
|
379
|
-
* @param scope - The required scope for API key access
|
|
380
|
-
* @param config - Configuration options
|
|
381
|
-
* @returns Payload access function
|
|
382
|
-
*
|
|
383
|
-
* @example
|
|
384
|
-
* ```ts
|
|
385
|
-
* access: {
|
|
386
|
-
* read: allowSessionOrScope('posts:read'),
|
|
387
|
-
* }
|
|
388
|
-
* ```
|
|
389
|
-
*/
|
|
390
|
-
export function allowSessionOrScope(
|
|
391
|
-
scope: string,
|
|
392
|
-
config: Omit<ApiKeyAccessConfig, 'allowAuthenticatedUsers'> = {}
|
|
393
|
-
): Access {
|
|
394
|
-
return requireScope(scope, { ...config, allowAuthenticatedUsers: true })
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Create an access control function that allows either:
|
|
399
|
-
* 1. Authenticated users (via session)
|
|
400
|
-
* 2. API key with any of the required scopes
|
|
401
|
-
*
|
|
402
|
-
* @param scopes - Array of acceptable scopes for API key access
|
|
403
|
-
* @param config - Configuration options
|
|
404
|
-
* @returns Payload access function
|
|
405
|
-
*/
|
|
406
|
-
export function allowSessionOrAnyScope(
|
|
407
|
-
scopes: string[],
|
|
408
|
-
config: Omit<ApiKeyAccessConfig, 'allowAuthenticatedUsers'> = {}
|
|
409
|
-
): Access {
|
|
410
|
-
return requireAnyScope(scopes, { ...config, allowAuthenticatedUsers: true })
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
414
|
-
// Better Auth Integration
|
|
415
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Validate an API key and get its info.
|
|
419
|
-
*
|
|
420
|
-
* This performs a database lookup to validate the key and retrieve
|
|
421
|
-
* its associated scopes and user.
|
|
422
|
-
*
|
|
423
|
-
* @param req - Payload request
|
|
424
|
-
* @param apiKeysCollection - The API keys collection slug
|
|
425
|
-
* @returns API key info if valid, null otherwise
|
|
426
|
-
*
|
|
427
|
-
* @example
|
|
428
|
-
* ```ts
|
|
429
|
-
* const keyInfo = await validateApiKey(req)
|
|
430
|
-
* if (keyInfo) {
|
|
431
|
-
* console.log('Valid API key for user:', keyInfo.userId)
|
|
432
|
-
* console.log('Scopes:', keyInfo.scopes)
|
|
433
|
-
* }
|
|
434
|
-
* ```
|
|
435
|
-
*/
|
|
436
|
-
export async function validateApiKey(
|
|
437
|
-
req: PayloadRequest,
|
|
438
|
-
apiKeysCollection = 'apiKeys'
|
|
439
|
-
): Promise<ApiKeyInfo | null> {
|
|
440
|
-
const apiKey = extractApiKeyFromRequest(req)
|
|
441
|
-
if (!apiKey) return null
|
|
442
|
-
return getApiKeyInfo(req, apiKey, apiKeysCollection)
|
|
443
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utility to apply sensible defaults to Better Auth options.
|
|
3
|
-
*
|
|
4
|
-
* @packageDocumentation
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { BetterAuthOptions } from 'better-auth'
|
|
8
|
-
import { apiKey as betterAuthApiKey } from 'better-auth/plugins'
|
|
9
|
-
|
|
10
|
-
type ApiKeyPluginOptions = Parameters<typeof betterAuthApiKey>[0]
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* API Key plugin with sensible defaults for use with this package.
|
|
14
|
-
*
|
|
15
|
-
* Enables metadata storage by default so that scopes can be displayed
|
|
16
|
-
* in the admin UI after key creation.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```ts
|
|
20
|
-
* import { apiKeyWithDefaults } from '@delmaredigital/payload-better-auth'
|
|
21
|
-
*
|
|
22
|
-
* export const betterAuthOptions = {
|
|
23
|
-
* plugins: [
|
|
24
|
-
* apiKeyWithDefaults(), // metadata enabled by default
|
|
25
|
-
* ],
|
|
26
|
-
* }
|
|
27
|
-
* ```
|
|
28
|
-
*
|
|
29
|
-
* @example With custom options
|
|
30
|
-
* ```ts
|
|
31
|
-
* apiKeyWithDefaults({
|
|
32
|
-
* rateLimit: { max: 100, window: 60 },
|
|
33
|
-
* // enableMetadata is already true
|
|
34
|
-
* })
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
export function apiKeyWithDefaults(options?: ApiKeyPluginOptions) {
|
|
38
|
-
return betterAuthApiKey({
|
|
39
|
-
enableMetadata: true,
|
|
40
|
-
...options,
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Applies sensible defaults to Better Auth options.
|
|
46
|
-
*
|
|
47
|
-
* Currently applies the following defaults:
|
|
48
|
-
* - `trustedOrigins`: If not explicitly provided but `baseURL` is set,
|
|
49
|
-
* defaults to `[baseURL]`. This handles the common single-domain case
|
|
50
|
-
* where the app's origin should be trusted for auth requests.
|
|
51
|
-
*
|
|
52
|
-
* Multi-domain setups can still explicitly set `trustedOrigins` to include
|
|
53
|
-
* multiple origins.
|
|
54
|
-
*
|
|
55
|
-
* @example Simple case - trustedOrigins defaults to [baseURL]
|
|
56
|
-
* ```ts
|
|
57
|
-
* import { withBetterAuthDefaults } from '@delmaredigital/payload-better-auth'
|
|
58
|
-
*
|
|
59
|
-
* const auth = betterAuth(withBetterAuthDefaults({
|
|
60
|
-
* baseURL: 'https://myapp.com',
|
|
61
|
-
* // trustedOrigins automatically becomes ['https://myapp.com']
|
|
62
|
-
* }))
|
|
63
|
-
* ```
|
|
64
|
-
*
|
|
65
|
-
* @example Multi-domain case - explicit trustedOrigins respected
|
|
66
|
-
* ```ts
|
|
67
|
-
* const auth = betterAuth(withBetterAuthDefaults({
|
|
68
|
-
* baseURL: 'https://myapp.com',
|
|
69
|
-
* trustedOrigins: ['https://myapp.com', 'https://other-domain.com'],
|
|
70
|
-
* // trustedOrigins stays as explicitly provided
|
|
71
|
-
* }))
|
|
72
|
-
* ```
|
|
73
|
-
*
|
|
74
|
-
* @example With createBetterAuthPlugin
|
|
75
|
-
* ```ts
|
|
76
|
-
* createBetterAuthPlugin({
|
|
77
|
-
* createAuth: (payload) => betterAuth(withBetterAuthDefaults({
|
|
78
|
-
* database: payloadAdapter({ payloadClient: payload }),
|
|
79
|
-
* baseURL: process.env.BETTER_AUTH_URL,
|
|
80
|
-
* })),
|
|
81
|
-
* })
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
export function withBetterAuthDefaults<T extends BetterAuthOptions>(
|
|
85
|
-
options: T
|
|
86
|
-
): T {
|
|
87
|
-
// If trustedOrigins is explicitly provided, use it as-is
|
|
88
|
-
if (options.trustedOrigins !== undefined) {
|
|
89
|
-
return options
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// If baseURL is set, default trustedOrigins to [baseURL]
|
|
93
|
-
if (options.baseURL) {
|
|
94
|
-
return {
|
|
95
|
-
...options,
|
|
96
|
-
trustedOrigins: [options.baseURL],
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// No defaults to apply
|
|
101
|
-
return options
|
|
102
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utility to detect auth configuration in Payload config
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { Config, CollectionConfig } from 'payload'
|
|
6
|
-
|
|
7
|
-
export type AuthDetectionResult = {
|
|
8
|
-
/** Whether any collection has disableLocalStrategy: true */
|
|
9
|
-
hasDisableLocalStrategy: boolean
|
|
10
|
-
/** The slug of the auth collection (if found) */
|
|
11
|
-
authCollectionSlug: string | null
|
|
12
|
-
/** The auth collection config (if found) */
|
|
13
|
-
authCollectionConfig: CollectionConfig | null
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Scans Payload config to detect if any collection uses disableLocalStrategy.
|
|
18
|
-
* Used to determine whether to auto-inject admin components.
|
|
19
|
-
*/
|
|
20
|
-
export function detectAuthConfig(config: Config): AuthDetectionResult {
|
|
21
|
-
const collections = config.collections ?? []
|
|
22
|
-
|
|
23
|
-
for (const collection of collections) {
|
|
24
|
-
if (collection.auth) {
|
|
25
|
-
const auth = collection.auth
|
|
26
|
-
// disableLocalStrategy can be `true` or an object with options
|
|
27
|
-
if (
|
|
28
|
-
auth === true ||
|
|
29
|
-
(typeof auth === 'object' && auth.disableLocalStrategy)
|
|
30
|
-
) {
|
|
31
|
-
return {
|
|
32
|
-
hasDisableLocalStrategy:
|
|
33
|
-
auth === true ||
|
|
34
|
-
(typeof auth === 'object' && !!auth.disableLocalStrategy),
|
|
35
|
-
authCollectionSlug: collection.slug,
|
|
36
|
-
authCollectionConfig: collection,
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
hasDisableLocalStrategy: false,
|
|
44
|
-
authCollectionSlug: null,
|
|
45
|
-
authCollectionConfig: null,
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utility to detect which Better Auth plugins are enabled
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { BetterAuthOptions } from 'better-auth'
|
|
6
|
-
|
|
7
|
-
export type EnabledPluginsResult = {
|
|
8
|
-
hasAdmin: boolean
|
|
9
|
-
hasApiKey: boolean
|
|
10
|
-
hasTwoFactor: boolean
|
|
11
|
-
hasPasskey: boolean
|
|
12
|
-
hasMagicLink: boolean
|
|
13
|
-
hasMultiSession: boolean
|
|
14
|
-
hasOrganization: boolean
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Detects which Better Auth plugins are enabled from the options.
|
|
19
|
-
* Inspects the plugins array by checking plugin identifiers.
|
|
20
|
-
*
|
|
21
|
-
* @param options - Better Auth options containing plugins array
|
|
22
|
-
* @returns Object with boolean flags for each supported plugin
|
|
23
|
-
*/
|
|
24
|
-
export function detectEnabledPlugins(
|
|
25
|
-
options?: Partial<BetterAuthOptions>
|
|
26
|
-
): EnabledPluginsResult {
|
|
27
|
-
const plugins = options?.plugins ?? []
|
|
28
|
-
|
|
29
|
-
const result: EnabledPluginsResult = {
|
|
30
|
-
hasAdmin: false,
|
|
31
|
-
hasApiKey: false,
|
|
32
|
-
hasTwoFactor: false,
|
|
33
|
-
hasPasskey: false,
|
|
34
|
-
hasMagicLink: false,
|
|
35
|
-
hasMultiSession: false,
|
|
36
|
-
hasOrganization: false,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
for (const plugin of plugins) {
|
|
40
|
-
// Better Auth plugins have an id property
|
|
41
|
-
const id = (plugin as { id?: string }).id
|
|
42
|
-
|
|
43
|
-
switch (id) {
|
|
44
|
-
case 'admin':
|
|
45
|
-
result.hasAdmin = true
|
|
46
|
-
break
|
|
47
|
-
case 'api-key':
|
|
48
|
-
result.hasApiKey = true
|
|
49
|
-
break
|
|
50
|
-
case 'two-factor':
|
|
51
|
-
result.hasTwoFactor = true
|
|
52
|
-
break
|
|
53
|
-
case 'passkey':
|
|
54
|
-
result.hasPasskey = true
|
|
55
|
-
break
|
|
56
|
-
case 'magic-link':
|
|
57
|
-
result.hasMagicLink = true
|
|
58
|
-
break
|
|
59
|
-
case 'multi-session':
|
|
60
|
-
result.hasMultiSession = true
|
|
61
|
-
break
|
|
62
|
-
case 'organization':
|
|
63
|
-
result.hasOrganization = true
|
|
64
|
-
break
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return result
|
|
69
|
-
}
|