@delmaredigital/payload-better-auth 0.3.8 → 0.3.9
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/package.json +34 -91
- 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,560 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect, type FormEvent } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
createPayloadAuthClient,
|
|
6
|
-
type PayloadAuthClient,
|
|
7
|
-
} from '../../exports/client.js'
|
|
8
|
-
|
|
9
|
-
export type TwoFactorManagementClientProps = {
|
|
10
|
-
/** Optional pre-configured auth client */
|
|
11
|
-
authClient?: PayloadAuthClient
|
|
12
|
-
/** Page title. Default: 'Two-Factor Authentication' */
|
|
13
|
-
title?: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Client component for two-factor authentication management.
|
|
18
|
-
* Shows 2FA status and allows enabling/disabling.
|
|
19
|
-
*/
|
|
20
|
-
export function TwoFactorManagementClient({
|
|
21
|
-
authClient: providedClient,
|
|
22
|
-
title = 'Two-Factor Authentication',
|
|
23
|
-
}: TwoFactorManagementClientProps = {}) {
|
|
24
|
-
const [isEnabled, setIsEnabled] = useState(false)
|
|
25
|
-
const [loading, setLoading] = useState(true)
|
|
26
|
-
const [error, setError] = useState<string | null>(null)
|
|
27
|
-
const [step, setStep] = useState<'status' | 'password' | 'setup' | 'verify' | 'backup'>('status')
|
|
28
|
-
const [totpUri, setTotpUri] = useState<string | null>(null)
|
|
29
|
-
const [secret, setSecret] = useState<string | null>(null)
|
|
30
|
-
const [backupCodes, setBackupCodes] = useState<string[]>([])
|
|
31
|
-
const [verificationCode, setVerificationCode] = useState('')
|
|
32
|
-
const [password, setPassword] = useState('')
|
|
33
|
-
const [actionLoading, setActionLoading] = useState(false)
|
|
34
|
-
|
|
35
|
-
const getClient = () => providedClient ?? createPayloadAuthClient()
|
|
36
|
-
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
checkStatus()
|
|
39
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
40
|
-
}, [])
|
|
41
|
-
|
|
42
|
-
async function checkStatus() {
|
|
43
|
-
setLoading(true)
|
|
44
|
-
try {
|
|
45
|
-
const client = getClient()
|
|
46
|
-
const result = await client.getSession()
|
|
47
|
-
|
|
48
|
-
if (result.data?.user) {
|
|
49
|
-
setIsEnabled((result.data.user as { twoFactorEnabled?: boolean }).twoFactorEnabled ?? false)
|
|
50
|
-
} else {
|
|
51
|
-
setIsEnabled(false)
|
|
52
|
-
}
|
|
53
|
-
} catch {
|
|
54
|
-
setError('Failed to check 2FA status')
|
|
55
|
-
} finally {
|
|
56
|
-
setLoading(false)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function handleEnableClick() {
|
|
61
|
-
// Show password prompt first
|
|
62
|
-
setStep('password')
|
|
63
|
-
setPassword('')
|
|
64
|
-
setError(null)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function handleEnableWithPassword(e: FormEvent) {
|
|
68
|
-
e.preventDefault()
|
|
69
|
-
setActionLoading(true)
|
|
70
|
-
setError(null)
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const client = getClient()
|
|
74
|
-
const result = await client.twoFactor.enable({ password })
|
|
75
|
-
|
|
76
|
-
if (result.error) {
|
|
77
|
-
setError(result.error.message ?? 'Failed to enable 2FA')
|
|
78
|
-
} else if (result.data) {
|
|
79
|
-
setTotpUri(result.data.totpURI)
|
|
80
|
-
// Secret is embedded in the totpURI, extract it for manual entry option
|
|
81
|
-
const secretMatch = result.data.totpURI.match(/secret=([A-Z2-7]+)/i)
|
|
82
|
-
setSecret(secretMatch ? secretMatch[1] : null)
|
|
83
|
-
setBackupCodes(result.data.backupCodes ?? [])
|
|
84
|
-
setPassword('') // Clear password
|
|
85
|
-
setStep('setup')
|
|
86
|
-
}
|
|
87
|
-
} catch {
|
|
88
|
-
setError('Failed to enable 2FA')
|
|
89
|
-
} finally {
|
|
90
|
-
setActionLoading(false)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function handleVerify(e: FormEvent) {
|
|
95
|
-
e.preventDefault()
|
|
96
|
-
setActionLoading(true)
|
|
97
|
-
setError(null)
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const client = getClient()
|
|
101
|
-
const result = await client.twoFactor.verifyTotp({ code: verificationCode })
|
|
102
|
-
|
|
103
|
-
if (result.error) {
|
|
104
|
-
setError(result.error.message ?? 'Invalid verification code')
|
|
105
|
-
} else {
|
|
106
|
-
if (backupCodes.length > 0) {
|
|
107
|
-
setStep('backup')
|
|
108
|
-
} else {
|
|
109
|
-
setIsEnabled(true)
|
|
110
|
-
setStep('status')
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
} catch {
|
|
114
|
-
setError('Verification failed')
|
|
115
|
-
} finally {
|
|
116
|
-
setActionLoading(false)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function handleDisable() {
|
|
121
|
-
if (!confirm('Are you sure you want to disable two-factor authentication?')) {
|
|
122
|
-
return
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
setActionLoading(true)
|
|
126
|
-
setError(null)
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const client = getClient()
|
|
130
|
-
const result = await client.twoFactor.disable({ password: '' })
|
|
131
|
-
|
|
132
|
-
if (result.error) {
|
|
133
|
-
setError(result.error.message ?? 'Failed to disable 2FA')
|
|
134
|
-
} else {
|
|
135
|
-
setIsEnabled(false)
|
|
136
|
-
}
|
|
137
|
-
} catch {
|
|
138
|
-
setError('Failed to disable 2FA')
|
|
139
|
-
} finally {
|
|
140
|
-
setActionLoading(false)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function handleBackupContinue() {
|
|
145
|
-
setIsEnabled(true)
|
|
146
|
-
setStep('status')
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (loading) {
|
|
150
|
-
return (
|
|
151
|
-
<div
|
|
152
|
-
style={{
|
|
153
|
-
display: 'flex',
|
|
154
|
-
alignItems: 'center',
|
|
155
|
-
justifyContent: 'center',
|
|
156
|
-
padding: 'calc(var(--base) * 3)',
|
|
157
|
-
}}
|
|
158
|
-
>
|
|
159
|
-
<div style={{ color: 'var(--theme-text)', opacity: 0.7 }}>
|
|
160
|
-
Loading...
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<div
|
|
168
|
-
style={{
|
|
169
|
-
maxWidth: '600px',
|
|
170
|
-
margin: '0 auto',
|
|
171
|
-
padding: 'calc(var(--base) * 2)',
|
|
172
|
-
}}
|
|
173
|
-
>
|
|
174
|
-
|
|
175
|
-
<h1
|
|
176
|
-
style={{
|
|
177
|
-
color: 'var(--theme-text)',
|
|
178
|
-
fontSize: 'var(--font-size-h2)',
|
|
179
|
-
fontWeight: 600,
|
|
180
|
-
margin: '0 0 calc(var(--base) * 2) 0',
|
|
181
|
-
}}
|
|
182
|
-
>
|
|
183
|
-
{title}
|
|
184
|
-
</h1>
|
|
185
|
-
|
|
186
|
-
{error && (
|
|
187
|
-
<div
|
|
188
|
-
style={{
|
|
189
|
-
color: 'var(--theme-error-500)',
|
|
190
|
-
marginBottom: 'var(--base)',
|
|
191
|
-
fontSize: 'var(--font-size-small)',
|
|
192
|
-
padding: 'calc(var(--base) * 0.75)',
|
|
193
|
-
background: 'var(--theme-error-50)',
|
|
194
|
-
borderRadius: 'var(--style-radius-s)',
|
|
195
|
-
border: '1px solid var(--theme-error-200)',
|
|
196
|
-
}}
|
|
197
|
-
>
|
|
198
|
-
{error}
|
|
199
|
-
</div>
|
|
200
|
-
)}
|
|
201
|
-
|
|
202
|
-
{step === 'status' && (
|
|
203
|
-
<div
|
|
204
|
-
style={{
|
|
205
|
-
background: 'var(--theme-elevation-50)',
|
|
206
|
-
padding: 'calc(var(--base) * 1.5)',
|
|
207
|
-
borderRadius: 'var(--style-radius-m)',
|
|
208
|
-
border: '1px solid var(--theme-elevation-100)',
|
|
209
|
-
}}
|
|
210
|
-
>
|
|
211
|
-
<div
|
|
212
|
-
style={{
|
|
213
|
-
display: 'flex',
|
|
214
|
-
justifyContent: 'space-between',
|
|
215
|
-
alignItems: 'center',
|
|
216
|
-
}}
|
|
217
|
-
>
|
|
218
|
-
<div>
|
|
219
|
-
<div
|
|
220
|
-
style={{
|
|
221
|
-
color: 'var(--theme-text)',
|
|
222
|
-
fontWeight: 500,
|
|
223
|
-
marginBottom: 'calc(var(--base) * 0.25)',
|
|
224
|
-
}}
|
|
225
|
-
>
|
|
226
|
-
Status
|
|
227
|
-
</div>
|
|
228
|
-
<div
|
|
229
|
-
style={{
|
|
230
|
-
color: isEnabled
|
|
231
|
-
? 'var(--theme-success-500)'
|
|
232
|
-
: 'var(--theme-elevation-600)',
|
|
233
|
-
fontSize: 'var(--font-size-small)',
|
|
234
|
-
}}
|
|
235
|
-
>
|
|
236
|
-
{isEnabled ? '✓ Enabled' : 'Not enabled'}
|
|
237
|
-
</div>
|
|
238
|
-
</div>
|
|
239
|
-
|
|
240
|
-
<button
|
|
241
|
-
onClick={isEnabled ? handleDisable : handleEnableClick}
|
|
242
|
-
disabled={actionLoading}
|
|
243
|
-
style={{
|
|
244
|
-
padding: 'calc(var(--base) * 0.5) calc(var(--base) * 1)',
|
|
245
|
-
background: isEnabled
|
|
246
|
-
? 'var(--theme-error-500)'
|
|
247
|
-
: 'var(--theme-elevation-800)',
|
|
248
|
-
border: 'none',
|
|
249
|
-
borderRadius: 'var(--style-radius-s)',
|
|
250
|
-
color: 'var(--theme-elevation-50)',
|
|
251
|
-
fontSize: 'var(--font-size-small)',
|
|
252
|
-
cursor: actionLoading ? 'not-allowed' : 'pointer',
|
|
253
|
-
opacity: actionLoading ? 0.7 : 1,
|
|
254
|
-
}}
|
|
255
|
-
>
|
|
256
|
-
{actionLoading
|
|
257
|
-
? 'Loading...'
|
|
258
|
-
: isEnabled
|
|
259
|
-
? 'Disable'
|
|
260
|
-
: 'Enable'}
|
|
261
|
-
</button>
|
|
262
|
-
</div>
|
|
263
|
-
</div>
|
|
264
|
-
)}
|
|
265
|
-
|
|
266
|
-
{step === 'password' && (
|
|
267
|
-
<div
|
|
268
|
-
style={{
|
|
269
|
-
background: 'var(--theme-elevation-50)',
|
|
270
|
-
padding: 'calc(var(--base) * 2)',
|
|
271
|
-
borderRadius: 'var(--style-radius-m)',
|
|
272
|
-
border: '1px solid var(--theme-elevation-100)',
|
|
273
|
-
}}
|
|
274
|
-
>
|
|
275
|
-
<h2
|
|
276
|
-
style={{
|
|
277
|
-
color: 'var(--theme-text)',
|
|
278
|
-
fontSize: 'var(--font-size-h4)',
|
|
279
|
-
fontWeight: 500,
|
|
280
|
-
margin: '0 0 var(--base) 0',
|
|
281
|
-
}}
|
|
282
|
-
>
|
|
283
|
-
Confirm Your Password
|
|
284
|
-
</h2>
|
|
285
|
-
<p
|
|
286
|
-
style={{
|
|
287
|
-
color: 'var(--theme-text)',
|
|
288
|
-
opacity: 0.7,
|
|
289
|
-
fontSize: 'var(--font-size-small)',
|
|
290
|
-
marginBottom: 'calc(var(--base) * 1.5)',
|
|
291
|
-
}}
|
|
292
|
-
>
|
|
293
|
-
Enter your password to enable two-factor authentication.
|
|
294
|
-
</p>
|
|
295
|
-
<form onSubmit={handleEnableWithPassword}>
|
|
296
|
-
<input
|
|
297
|
-
type="password"
|
|
298
|
-
value={password}
|
|
299
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
300
|
-
placeholder="Enter your password"
|
|
301
|
-
required
|
|
302
|
-
style={{
|
|
303
|
-
width: '100%',
|
|
304
|
-
padding: 'calc(var(--base) * 0.75)',
|
|
305
|
-
background: 'var(--theme-input-bg)',
|
|
306
|
-
border: '1px solid var(--theme-elevation-150)',
|
|
307
|
-
borderRadius: 'var(--style-radius-s)',
|
|
308
|
-
color: 'var(--theme-text)',
|
|
309
|
-
fontSize: 'var(--font-size-base)',
|
|
310
|
-
marginBottom: 'var(--base)',
|
|
311
|
-
boxSizing: 'border-box',
|
|
312
|
-
}}
|
|
313
|
-
/>
|
|
314
|
-
<div style={{ display: 'flex', gap: 'calc(var(--base) * 0.5)' }}>
|
|
315
|
-
<button
|
|
316
|
-
type="submit"
|
|
317
|
-
disabled={actionLoading || !password}
|
|
318
|
-
style={{
|
|
319
|
-
padding: 'calc(var(--base) * 0.75) calc(var(--base) * 1.5)',
|
|
320
|
-
background: 'var(--theme-elevation-800)',
|
|
321
|
-
border: 'none',
|
|
322
|
-
borderRadius: 'var(--style-radius-s)',
|
|
323
|
-
color: 'var(--theme-elevation-50)',
|
|
324
|
-
fontSize: 'var(--font-size-base)',
|
|
325
|
-
cursor: actionLoading || !password ? 'not-allowed' : 'pointer',
|
|
326
|
-
opacity: actionLoading || !password ? 0.7 : 1,
|
|
327
|
-
}}
|
|
328
|
-
>
|
|
329
|
-
{actionLoading ? 'Enabling...' : 'Continue'}
|
|
330
|
-
</button>
|
|
331
|
-
<button
|
|
332
|
-
type="button"
|
|
333
|
-
onClick={() => setStep('status')}
|
|
334
|
-
style={{
|
|
335
|
-
padding: 'calc(var(--base) * 0.75) calc(var(--base) * 1.5)',
|
|
336
|
-
background: 'transparent',
|
|
337
|
-
border: '1px solid var(--theme-elevation-200)',
|
|
338
|
-
borderRadius: 'var(--style-radius-s)',
|
|
339
|
-
color: 'var(--theme-text)',
|
|
340
|
-
fontSize: 'var(--font-size-base)',
|
|
341
|
-
cursor: 'pointer',
|
|
342
|
-
}}
|
|
343
|
-
>
|
|
344
|
-
Cancel
|
|
345
|
-
</button>
|
|
346
|
-
</div>
|
|
347
|
-
</form>
|
|
348
|
-
</div>
|
|
349
|
-
)}
|
|
350
|
-
|
|
351
|
-
{step === 'setup' && totpUri && (
|
|
352
|
-
<div
|
|
353
|
-
style={{
|
|
354
|
-
background: 'var(--theme-elevation-50)',
|
|
355
|
-
padding: 'calc(var(--base) * 2)',
|
|
356
|
-
borderRadius: 'var(--style-radius-m)',
|
|
357
|
-
textAlign: 'center',
|
|
358
|
-
}}
|
|
359
|
-
>
|
|
360
|
-
<p
|
|
361
|
-
style={{
|
|
362
|
-
color: 'var(--theme-text)',
|
|
363
|
-
opacity: 0.7,
|
|
364
|
-
marginBottom: 'calc(var(--base) * 1.5)',
|
|
365
|
-
}}
|
|
366
|
-
>
|
|
367
|
-
Scan this QR code with your authenticator app:
|
|
368
|
-
</p>
|
|
369
|
-
|
|
370
|
-
<img
|
|
371
|
-
src={`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(totpUri)}`}
|
|
372
|
-
alt="QR Code"
|
|
373
|
-
style={{
|
|
374
|
-
width: '200px',
|
|
375
|
-
height: '200px',
|
|
376
|
-
border: '1px solid var(--theme-elevation-150)',
|
|
377
|
-
borderRadius: 'var(--style-radius-s)',
|
|
378
|
-
marginBottom: 'var(--base)',
|
|
379
|
-
}}
|
|
380
|
-
/>
|
|
381
|
-
|
|
382
|
-
{secret && (
|
|
383
|
-
<div style={{ marginBottom: 'calc(var(--base) * 1.5)' }}>
|
|
384
|
-
<p
|
|
385
|
-
style={{
|
|
386
|
-
color: 'var(--theme-text)',
|
|
387
|
-
opacity: 0.7,
|
|
388
|
-
fontSize: 'var(--font-size-small)',
|
|
389
|
-
marginBottom: 'calc(var(--base) * 0.5)',
|
|
390
|
-
}}
|
|
391
|
-
>
|
|
392
|
-
Or enter manually:
|
|
393
|
-
</p>
|
|
394
|
-
<code
|
|
395
|
-
style={{
|
|
396
|
-
display: 'inline-block',
|
|
397
|
-
padding: 'calc(var(--base) * 0.5)',
|
|
398
|
-
background: 'var(--theme-elevation-100)',
|
|
399
|
-
borderRadius: 'var(--style-radius-s)',
|
|
400
|
-
fontFamily: 'monospace',
|
|
401
|
-
fontSize: 'var(--font-size-small)',
|
|
402
|
-
color: 'var(--theme-text)',
|
|
403
|
-
}}
|
|
404
|
-
>
|
|
405
|
-
{secret}
|
|
406
|
-
</code>
|
|
407
|
-
</div>
|
|
408
|
-
)}
|
|
409
|
-
|
|
410
|
-
<form onSubmit={handleVerify}>
|
|
411
|
-
<input
|
|
412
|
-
type="text"
|
|
413
|
-
inputMode="numeric"
|
|
414
|
-
pattern="[0-9]*"
|
|
415
|
-
value={verificationCode}
|
|
416
|
-
onChange={(e) =>
|
|
417
|
-
setVerificationCode(e.target.value.replace(/\D/g, '').slice(0, 6))
|
|
418
|
-
}
|
|
419
|
-
placeholder="Enter 6-digit code"
|
|
420
|
-
style={{
|
|
421
|
-
width: '100%',
|
|
422
|
-
maxWidth: '200px',
|
|
423
|
-
padding: 'calc(var(--base) * 0.75)',
|
|
424
|
-
background: 'var(--theme-input-bg)',
|
|
425
|
-
border: '1px solid var(--theme-elevation-150)',
|
|
426
|
-
borderRadius: 'var(--style-radius-s)',
|
|
427
|
-
color: 'var(--theme-text)',
|
|
428
|
-
fontSize: 'var(--font-size-h4)',
|
|
429
|
-
fontFamily: 'monospace',
|
|
430
|
-
textAlign: 'center',
|
|
431
|
-
letterSpacing: '0.5em',
|
|
432
|
-
marginBottom: 'var(--base)',
|
|
433
|
-
boxSizing: 'border-box',
|
|
434
|
-
}}
|
|
435
|
-
/>
|
|
436
|
-
<br />
|
|
437
|
-
<button
|
|
438
|
-
type="submit"
|
|
439
|
-
disabled={actionLoading || verificationCode.length !== 6}
|
|
440
|
-
style={{
|
|
441
|
-
padding: 'calc(var(--base) * 0.75) calc(var(--base) * 2)',
|
|
442
|
-
background: 'var(--theme-elevation-800)',
|
|
443
|
-
border: 'none',
|
|
444
|
-
borderRadius: 'var(--style-radius-s)',
|
|
445
|
-
color: 'var(--theme-elevation-50)',
|
|
446
|
-
fontSize: 'var(--font-size-base)',
|
|
447
|
-
cursor:
|
|
448
|
-
actionLoading || verificationCode.length !== 6
|
|
449
|
-
? 'not-allowed'
|
|
450
|
-
: 'pointer',
|
|
451
|
-
opacity:
|
|
452
|
-
actionLoading || verificationCode.length !== 6 ? 0.7 : 1,
|
|
453
|
-
}}
|
|
454
|
-
>
|
|
455
|
-
{actionLoading ? 'Verifying...' : 'Verify'}
|
|
456
|
-
</button>
|
|
457
|
-
</form>
|
|
458
|
-
</div>
|
|
459
|
-
)}
|
|
460
|
-
|
|
461
|
-
{step === 'backup' && (
|
|
462
|
-
<div
|
|
463
|
-
style={{
|
|
464
|
-
background: 'var(--theme-elevation-50)',
|
|
465
|
-
padding: 'calc(var(--base) * 2)',
|
|
466
|
-
borderRadius: 'var(--style-radius-m)',
|
|
467
|
-
}}
|
|
468
|
-
>
|
|
469
|
-
<h2
|
|
470
|
-
style={{
|
|
471
|
-
color: 'var(--theme-text)',
|
|
472
|
-
fontSize: 'var(--font-size-h3)',
|
|
473
|
-
fontWeight: 600,
|
|
474
|
-
margin: '0 0 var(--base) 0',
|
|
475
|
-
textAlign: 'center',
|
|
476
|
-
}}
|
|
477
|
-
>
|
|
478
|
-
Save Your Backup Codes
|
|
479
|
-
</h2>
|
|
480
|
-
<p
|
|
481
|
-
style={{
|
|
482
|
-
color: 'var(--theme-text)',
|
|
483
|
-
opacity: 0.7,
|
|
484
|
-
fontSize: 'var(--font-size-small)',
|
|
485
|
-
textAlign: 'center',
|
|
486
|
-
marginBottom: 'calc(var(--base) * 1.5)',
|
|
487
|
-
}}
|
|
488
|
-
>
|
|
489
|
-
Store these codes safely. Use them if you lose your authenticator.
|
|
490
|
-
</p>
|
|
491
|
-
|
|
492
|
-
<div
|
|
493
|
-
style={{
|
|
494
|
-
background: 'var(--theme-elevation-100)',
|
|
495
|
-
padding: 'var(--base)',
|
|
496
|
-
borderRadius: 'var(--style-radius-s)',
|
|
497
|
-
marginBottom: 'var(--base)',
|
|
498
|
-
fontFamily: 'monospace',
|
|
499
|
-
}}
|
|
500
|
-
>
|
|
501
|
-
<div
|
|
502
|
-
style={{
|
|
503
|
-
display: 'grid',
|
|
504
|
-
gridTemplateColumns: 'repeat(2, 1fr)',
|
|
505
|
-
gap: 'calc(var(--base) * 0.5)',
|
|
506
|
-
}}
|
|
507
|
-
>
|
|
508
|
-
{backupCodes.map((code, index) => (
|
|
509
|
-
<div
|
|
510
|
-
key={index}
|
|
511
|
-
style={{
|
|
512
|
-
color: 'var(--theme-text)',
|
|
513
|
-
padding: 'calc(var(--base) * 0.25)',
|
|
514
|
-
}}
|
|
515
|
-
>
|
|
516
|
-
{code}
|
|
517
|
-
</div>
|
|
518
|
-
))}
|
|
519
|
-
</div>
|
|
520
|
-
</div>
|
|
521
|
-
|
|
522
|
-
<button
|
|
523
|
-
onClick={() => navigator.clipboard.writeText(backupCodes.join('\n'))}
|
|
524
|
-
style={{
|
|
525
|
-
width: '100%',
|
|
526
|
-
padding: 'calc(var(--base) * 0.5)',
|
|
527
|
-
background: 'var(--theme-elevation-150)',
|
|
528
|
-
border: 'none',
|
|
529
|
-
borderRadius: 'var(--style-radius-s)',
|
|
530
|
-
color: 'var(--theme-text)',
|
|
531
|
-
fontSize: 'var(--font-size-small)',
|
|
532
|
-
cursor: 'pointer',
|
|
533
|
-
marginBottom: 'var(--base)',
|
|
534
|
-
}}
|
|
535
|
-
>
|
|
536
|
-
Copy to Clipboard
|
|
537
|
-
</button>
|
|
538
|
-
|
|
539
|
-
<button
|
|
540
|
-
onClick={handleBackupContinue}
|
|
541
|
-
style={{
|
|
542
|
-
width: '100%',
|
|
543
|
-
padding: 'calc(var(--base) * 0.75)',
|
|
544
|
-
background: 'var(--theme-elevation-800)',
|
|
545
|
-
border: 'none',
|
|
546
|
-
borderRadius: 'var(--style-radius-s)',
|
|
547
|
-
color: 'var(--theme-elevation-50)',
|
|
548
|
-
fontSize: 'var(--font-size-base)',
|
|
549
|
-
cursor: 'pointer',
|
|
550
|
-
}}
|
|
551
|
-
>
|
|
552
|
-
I've Saved My Codes
|
|
553
|
-
</button>
|
|
554
|
-
</div>
|
|
555
|
-
)}
|
|
556
|
-
</div>
|
|
557
|
-
)
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
export default TwoFactorManagementClient
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Management UI Components for Better Auth
|
|
3
|
-
*
|
|
4
|
-
* Client components for security feature management.
|
|
5
|
-
* These are used within the server component views.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Client components
|
|
9
|
-
export { TwoFactorManagementClient } from './TwoFactorManagementClient.js'
|
|
10
|
-
export type { TwoFactorManagementClientProps } from './TwoFactorManagementClient.js'
|
|
11
|
-
|
|
12
|
-
export { ApiKeysManagementClient } from './ApiKeysManagementClient.js'
|
|
13
|
-
export type { ApiKeysManagementClientProps } from './ApiKeysManagementClient.js'
|
|
14
|
-
|
|
15
|
-
export { PasskeysManagementClient } from './PasskeysManagementClient.js'
|
|
16
|
-
export type { PasskeysManagementClientProps } from './PasskeysManagementClient.js'
|
|
17
|
-
|
|
18
|
-
// Nav links (client component)
|
|
19
|
-
export { SecurityNavLinks } from './SecurityNavLinks.js'
|
|
20
|
-
export type { SecurityNavLinksProps } from './SecurityNavLinks.js'
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { AdminViewProps, Locale } from 'payload'
|
|
2
|
-
import { DefaultTemplate } from '@payloadcms/next/templates'
|
|
3
|
-
import { getVisibleEntities } from '@payloadcms/ui/shared'
|
|
4
|
-
import { ApiKeysManagementClient } from '../ApiKeysManagementClient.js'
|
|
5
|
-
import { getApiKeyScopesConfig } from '../../../plugin/index.js'
|
|
6
|
-
import { buildAvailableScopes } from '../../../utils/generateScopes.js'
|
|
7
|
-
|
|
8
|
-
type ApiKeysViewProps = AdminViewProps
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* API Keys management view for Payload admin panel.
|
|
12
|
-
* Server component that provides the admin layout.
|
|
13
|
-
*/
|
|
14
|
-
export async function ApiKeysView({
|
|
15
|
-
initPageResult,
|
|
16
|
-
params,
|
|
17
|
-
searchParams,
|
|
18
|
-
}: ApiKeysViewProps) {
|
|
19
|
-
const { req } = initPageResult
|
|
20
|
-
const { payload } = req
|
|
21
|
-
|
|
22
|
-
// Await params/searchParams for Next.js 15+ compatibility
|
|
23
|
-
const resolvedParams = params ? await params : undefined
|
|
24
|
-
const resolvedSearchParams = searchParams ? await searchParams : undefined
|
|
25
|
-
|
|
26
|
-
const visibleEntities = getVisibleEntities({ req })
|
|
27
|
-
|
|
28
|
-
// Build available scopes from plugin config and collections
|
|
29
|
-
const scopesConfig = getApiKeyScopesConfig()
|
|
30
|
-
const availableScopes = buildAvailableScopes(
|
|
31
|
-
payload.config.collections,
|
|
32
|
-
scopesConfig
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
// Get default scopes from config
|
|
36
|
-
const defaultScopes = scopesConfig?.defaultScopes ?? []
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<DefaultTemplate
|
|
40
|
-
i18n={req.i18n}
|
|
41
|
-
locale={req.locale as Locale | undefined}
|
|
42
|
-
params={resolvedParams}
|
|
43
|
-
payload={payload}
|
|
44
|
-
permissions={initPageResult.permissions}
|
|
45
|
-
searchParams={resolvedSearchParams}
|
|
46
|
-
user={req.user ?? undefined}
|
|
47
|
-
visibleEntities={visibleEntities}
|
|
48
|
-
>
|
|
49
|
-
<ApiKeysManagementClient
|
|
50
|
-
availableScopes={availableScopes}
|
|
51
|
-
defaultScopes={defaultScopes}
|
|
52
|
-
/>
|
|
53
|
-
</DefaultTemplate>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export default ApiKeysView
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { AdminViewProps, Locale } from 'payload'
|
|
2
|
-
import { DefaultTemplate } from '@payloadcms/next/templates'
|
|
3
|
-
import { getVisibleEntities } from '@payloadcms/ui/shared'
|
|
4
|
-
import { PasskeysManagementClient } from '../PasskeysManagementClient.js'
|
|
5
|
-
|
|
6
|
-
type PasskeysViewProps = AdminViewProps
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Passkeys management view for Payload admin panel.
|
|
10
|
-
* Server component that provides the admin layout.
|
|
11
|
-
*/
|
|
12
|
-
export async function PasskeysView({
|
|
13
|
-
initPageResult,
|
|
14
|
-
params,
|
|
15
|
-
searchParams,
|
|
16
|
-
}: PasskeysViewProps) {
|
|
17
|
-
const { req } = initPageResult
|
|
18
|
-
const { payload } = req
|
|
19
|
-
|
|
20
|
-
// Await params/searchParams for Next.js 15+ compatibility
|
|
21
|
-
const resolvedParams = params ? await params : undefined
|
|
22
|
-
const resolvedSearchParams = searchParams ? await searchParams : undefined
|
|
23
|
-
|
|
24
|
-
const visibleEntities = getVisibleEntities({ req })
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<DefaultTemplate
|
|
28
|
-
i18n={req.i18n}
|
|
29
|
-
locale={req.locale as Locale | undefined}
|
|
30
|
-
params={resolvedParams}
|
|
31
|
-
payload={payload}
|
|
32
|
-
permissions={initPageResult.permissions}
|
|
33
|
-
searchParams={resolvedSearchParams}
|
|
34
|
-
user={req.user ?? undefined}
|
|
35
|
-
visibleEntities={visibleEntities}
|
|
36
|
-
>
|
|
37
|
-
<PasskeysManagementClient />
|
|
38
|
-
</DefaultTemplate>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export default PasskeysView
|