@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.
Files changed (42) hide show
  1. package/package.json +34 -91
  2. package/src/adapter/collections.ts +0 -621
  3. package/src/adapter/index.ts +0 -712
  4. package/src/components/BeforeLogin.tsx +0 -39
  5. package/src/components/LoginView.tsx +0 -1516
  6. package/src/components/LoginViewWrapper.tsx +0 -35
  7. package/src/components/LogoutButton.tsx +0 -58
  8. package/src/components/PasskeyRegisterButton.tsx +0 -105
  9. package/src/components/PasskeySignInButton.tsx +0 -96
  10. package/src/components/auth/ForgotPasswordView.tsx +0 -274
  11. package/src/components/auth/ResetPasswordView.tsx +0 -331
  12. package/src/components/auth/index.ts +0 -8
  13. package/src/components/management/ApiKeysManagementClient.tsx +0 -988
  14. package/src/components/management/PasskeysManagementClient.tsx +0 -409
  15. package/src/components/management/SecurityNavLinks.tsx +0 -117
  16. package/src/components/management/TwoFactorManagementClient.tsx +0 -560
  17. package/src/components/management/index.ts +0 -20
  18. package/src/components/management/views/ApiKeysView.tsx +0 -57
  19. package/src/components/management/views/PasskeysView.tsx +0 -42
  20. package/src/components/management/views/TwoFactorView.tsx +0 -42
  21. package/src/components/management/views/index.ts +0 -10
  22. package/src/components/twoFactor/TwoFactorSetupView.tsx +0 -515
  23. package/src/components/twoFactor/TwoFactorVerifyView.tsx +0 -238
  24. package/src/components/twoFactor/index.ts +0 -8
  25. package/src/exports/client.ts +0 -77
  26. package/src/exports/components.ts +0 -30
  27. package/src/exports/management.ts +0 -25
  28. package/src/exports/rsc.ts +0 -11
  29. package/src/generated-types.ts +0 -269
  30. package/src/index.ts +0 -135
  31. package/src/plugin/index.ts +0 -834
  32. package/src/scripts/generate-types.ts +0 -269
  33. package/src/types/apiKey.ts +0 -63
  34. package/src/types/betterAuth.ts +0 -253
  35. package/src/utils/access.ts +0 -410
  36. package/src/utils/apiKeyAccess.ts +0 -443
  37. package/src/utils/betterAuthDefaults.ts +0 -102
  38. package/src/utils/detectAuthConfig.ts +0 -47
  39. package/src/utils/detectEnabledPlugins.ts +0 -69
  40. package/src/utils/firstUserAdmin.ts +0 -164
  41. package/src/utils/generateScopes.ts +0 -150
  42. 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