@delmaredigital/payload-better-auth 0.4.1 → 0.4.3
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 +116 -5
- package/dist/adapter/collections.js +9 -1
- package/dist/adapter/index.d.ts +19 -0
- package/dist/adapter/index.js +96 -32
- package/dist/components/LogoutButton.d.ts +3 -0
- package/dist/components/LogoutButton.js +20 -8
- package/dist/components/management/PasskeysManagementClient.js +100 -197
- package/dist/components/management/SecurityNavLinks.d.ts +3 -6
- package/dist/components/management/SecurityNavLinks.js +12 -30
- package/dist/components/management/TwoFactorManagementClient.d.ts +3 -1
- package/dist/components/management/TwoFactorManagementClient.js +115 -248
- package/dist/components/management/fields/PasskeysField.d.ts +13 -0
- package/dist/components/management/fields/PasskeysField.js +32 -0
- package/dist/components/management/fields/TwoFactorField.d.ts +19 -0
- package/dist/components/management/fields/TwoFactorField.js +48 -0
- package/dist/exports/components.d.ts +2 -0
- package/dist/exports/components.js +3 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/plugin/index.js +47 -27
- package/package.json +6 -6
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
|
+
import { Button, Banner } from '@payloadcms/ui';
|
|
5
|
+
import { CopyIcon } from '@payloadcms/ui/icons/Copy';
|
|
4
6
|
import { createPayloadAuthClient } from '../../exports/client.js';
|
|
5
7
|
/**
|
|
6
8
|
* Client component for two-factor authentication management.
|
|
7
9
|
* Shows 2FA status and allows enabling/disabling.
|
|
8
|
-
*/ export function TwoFactorManagementClient({ authClient: providedClient, title = 'Two-Factor Authentication' } = {}) {
|
|
10
|
+
*/ export function TwoFactorManagementClient({ authClient: providedClient, title = 'Two-Factor Authentication', onComplete } = {}) {
|
|
9
11
|
const [isEnabled, setIsEnabled] = useState(false);
|
|
10
12
|
const [loading, setLoading] = useState(true);
|
|
11
13
|
const [error, setError] = useState(null);
|
|
@@ -43,8 +45,7 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
43
45
|
setPassword('');
|
|
44
46
|
setError(null);
|
|
45
47
|
}
|
|
46
|
-
async function handleEnableWithPassword(
|
|
47
|
-
e.preventDefault();
|
|
48
|
+
async function handleEnableWithPassword() {
|
|
48
49
|
setActionLoading(true);
|
|
49
50
|
setError(null);
|
|
50
51
|
try {
|
|
@@ -69,8 +70,7 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
69
70
|
setActionLoading(false);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
|
-
async function handleVerify(
|
|
73
|
-
e.preventDefault();
|
|
73
|
+
async function handleVerify() {
|
|
74
74
|
setActionLoading(true);
|
|
75
75
|
setError(null);
|
|
76
76
|
try {
|
|
@@ -86,6 +86,7 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
86
86
|
} else {
|
|
87
87
|
setIsEnabled(true);
|
|
88
88
|
setStep('status');
|
|
89
|
+
onComplete?.();
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
} catch {
|
|
@@ -109,6 +110,7 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
109
110
|
setError(result.error.message ?? 'Failed to disable 2FA');
|
|
110
111
|
} else {
|
|
111
112
|
setIsEnabled(false);
|
|
113
|
+
onComplete?.();
|
|
112
114
|
}
|
|
113
115
|
} catch {
|
|
114
116
|
setError('Failed to disable 2FA');
|
|
@@ -119,186 +121,92 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
119
121
|
function handleBackupContinue() {
|
|
120
122
|
setIsEnabled(true);
|
|
121
123
|
setStep('status');
|
|
124
|
+
onComplete?.();
|
|
122
125
|
}
|
|
123
126
|
if (loading) {
|
|
124
|
-
return /*#__PURE__*/ _jsx("
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
alignItems: 'center',
|
|
128
|
-
justifyContent: 'center',
|
|
129
|
-
padding: 'calc(var(--base) * 3)'
|
|
130
|
-
},
|
|
131
|
-
children: /*#__PURE__*/ _jsx("div", {
|
|
132
|
-
style: {
|
|
133
|
-
color: 'var(--theme-text)',
|
|
134
|
-
opacity: 0.7
|
|
135
|
-
},
|
|
136
|
-
children: "Loading..."
|
|
137
|
-
})
|
|
127
|
+
return /*#__PURE__*/ _jsx("p", {
|
|
128
|
+
className: "field-description",
|
|
129
|
+
children: "Loading..."
|
|
138
130
|
});
|
|
139
131
|
}
|
|
140
132
|
return /*#__PURE__*/ _jsxs("div", {
|
|
141
|
-
|
|
142
|
-
maxWidth: '600px',
|
|
143
|
-
margin: '0 auto',
|
|
144
|
-
padding: 'calc(var(--base) * 2)'
|
|
145
|
-
},
|
|
133
|
+
className: "field-type two-factor-management",
|
|
146
134
|
children: [
|
|
147
|
-
/*#__PURE__*/ _jsx(
|
|
148
|
-
|
|
149
|
-
color: 'var(--theme-text)',
|
|
150
|
-
fontSize: 'var(--font-size-h2)',
|
|
151
|
-
fontWeight: 600,
|
|
152
|
-
margin: '0 0 calc(var(--base) * 2) 0'
|
|
153
|
-
},
|
|
154
|
-
children: title
|
|
155
|
-
}),
|
|
156
|
-
error && /*#__PURE__*/ _jsx("div", {
|
|
157
|
-
style: {
|
|
158
|
-
color: 'var(--theme-error-500)',
|
|
159
|
-
marginBottom: 'var(--base)',
|
|
160
|
-
fontSize: 'var(--font-size-small)',
|
|
161
|
-
padding: 'calc(var(--base) * 0.75)',
|
|
162
|
-
background: 'var(--theme-error-50)',
|
|
163
|
-
borderRadius: 'var(--style-radius-s)',
|
|
164
|
-
border: '1px solid var(--theme-error-200)'
|
|
165
|
-
},
|
|
135
|
+
error && /*#__PURE__*/ _jsx(Banner, {
|
|
136
|
+
type: "error",
|
|
166
137
|
children: error
|
|
167
138
|
}),
|
|
168
|
-
step === 'status' && /*#__PURE__*/
|
|
139
|
+
step === 'status' && /*#__PURE__*/ _jsxs("div", {
|
|
169
140
|
style: {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
border: '1px solid var(--theme-elevation-100)'
|
|
174
|
-
},
|
|
175
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
176
|
-
style: {
|
|
177
|
-
display: 'flex',
|
|
178
|
-
justifyContent: 'space-between',
|
|
179
|
-
alignItems: 'center'
|
|
180
|
-
},
|
|
181
|
-
children: [
|
|
182
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
183
|
-
children: [
|
|
184
|
-
/*#__PURE__*/ _jsx("div", {
|
|
185
|
-
style: {
|
|
186
|
-
color: 'var(--theme-text)',
|
|
187
|
-
fontWeight: 500,
|
|
188
|
-
marginBottom: 'calc(var(--base) * 0.25)'
|
|
189
|
-
},
|
|
190
|
-
children: "Status"
|
|
191
|
-
}),
|
|
192
|
-
/*#__PURE__*/ _jsx("div", {
|
|
193
|
-
style: {
|
|
194
|
-
color: isEnabled ? 'var(--theme-success-500)' : 'var(--theme-elevation-600)',
|
|
195
|
-
fontSize: 'var(--font-size-small)'
|
|
196
|
-
},
|
|
197
|
-
children: isEnabled ? '✓ Enabled' : 'Not enabled'
|
|
198
|
-
})
|
|
199
|
-
]
|
|
200
|
-
}),
|
|
201
|
-
/*#__PURE__*/ _jsx("button", {
|
|
202
|
-
onClick: isEnabled ? handleDisable : handleEnableClick,
|
|
203
|
-
disabled: actionLoading,
|
|
204
|
-
style: {
|
|
205
|
-
padding: 'calc(var(--base) * 0.5) calc(var(--base) * 1)',
|
|
206
|
-
background: isEnabled ? 'var(--theme-error-500)' : 'var(--theme-elevation-800)',
|
|
207
|
-
border: 'none',
|
|
208
|
-
borderRadius: 'var(--style-radius-s)',
|
|
209
|
-
color: 'var(--theme-elevation-50)',
|
|
210
|
-
fontSize: 'var(--font-size-small)',
|
|
211
|
-
cursor: actionLoading ? 'not-allowed' : 'pointer',
|
|
212
|
-
opacity: actionLoading ? 0.7 : 1
|
|
213
|
-
},
|
|
214
|
-
children: actionLoading ? 'Loading...' : isEnabled ? 'Disable' : 'Enable'
|
|
215
|
-
})
|
|
216
|
-
]
|
|
217
|
-
})
|
|
218
|
-
}),
|
|
219
|
-
step === 'password' && /*#__PURE__*/ _jsxs("div", {
|
|
220
|
-
style: {
|
|
221
|
-
background: 'var(--theme-elevation-50)',
|
|
222
|
-
padding: 'calc(var(--base) * 2)',
|
|
223
|
-
borderRadius: 'var(--style-radius-m)',
|
|
224
|
-
border: '1px solid var(--theme-elevation-100)'
|
|
141
|
+
display: 'flex',
|
|
142
|
+
justifyContent: 'space-between',
|
|
143
|
+
alignItems: 'center'
|
|
225
144
|
},
|
|
226
145
|
children: [
|
|
227
|
-
/*#__PURE__*/ _jsx("
|
|
146
|
+
/*#__PURE__*/ _jsx("p", {
|
|
147
|
+
className: "field-description",
|
|
228
148
|
style: {
|
|
229
|
-
|
|
230
|
-
fontSize: 'var(--font-size-h4)',
|
|
231
|
-
fontWeight: 500,
|
|
232
|
-
margin: '0 0 var(--base) 0'
|
|
149
|
+
margin: 0
|
|
233
150
|
},
|
|
234
|
-
children:
|
|
151
|
+
children: isEnabled ? 'Two-factor authentication is enabled.' : 'Two-factor authentication is not enabled.'
|
|
235
152
|
}),
|
|
153
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
154
|
+
buttonStyle: isEnabled ? 'error' : 'secondary',
|
|
155
|
+
size: "small",
|
|
156
|
+
onClick: isEnabled ? handleDisable : handleEnableClick,
|
|
157
|
+
disabled: actionLoading,
|
|
158
|
+
children: actionLoading ? 'Loading...' : isEnabled ? 'Disable' : 'Enable'
|
|
159
|
+
})
|
|
160
|
+
]
|
|
161
|
+
}),
|
|
162
|
+
step === 'password' && /*#__PURE__*/ _jsxs("div", {
|
|
163
|
+
children: [
|
|
236
164
|
/*#__PURE__*/ _jsx("p", {
|
|
165
|
+
className: "field-description",
|
|
166
|
+
children: "Enter your password to enable two-factor authentication."
|
|
167
|
+
}),
|
|
168
|
+
/*#__PURE__*/ _jsx("input", {
|
|
169
|
+
type: "password",
|
|
170
|
+
value: password,
|
|
171
|
+
onChange: (e)=>setPassword(e.target.value),
|
|
172
|
+
onKeyDown: (e)=>{
|
|
173
|
+
if (e.key === 'Enter' && password) {
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
handleEnableWithPassword();
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
placeholder: "Enter your password",
|
|
179
|
+
className: "field-type__wrap",
|
|
237
180
|
style: {
|
|
181
|
+
width: '100%',
|
|
182
|
+
padding: 'var(--base)',
|
|
183
|
+
background: 'var(--theme-input-bg)',
|
|
184
|
+
border: '1px solid var(--theme-border-color)',
|
|
185
|
+
borderRadius: 'var(--style-radius-s)',
|
|
238
186
|
color: 'var(--theme-text)',
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
children: "Enter your password to enable two-factor authentication."
|
|
187
|
+
fontSize: 'var(--base-body-size)',
|
|
188
|
+
marginBottom: 'var(--base)',
|
|
189
|
+
boxSizing: 'border-box'
|
|
190
|
+
}
|
|
244
191
|
}),
|
|
245
|
-
/*#__PURE__*/ _jsxs("
|
|
246
|
-
|
|
192
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
193
|
+
style: {
|
|
194
|
+
display: 'flex',
|
|
195
|
+
gap: 'calc(var(--base) * 0.5)'
|
|
196
|
+
},
|
|
247
197
|
children: [
|
|
248
|
-
/*#__PURE__*/ _jsx(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
style: {
|
|
255
|
-
width: '100%',
|
|
256
|
-
padding: 'calc(var(--base) * 0.75)',
|
|
257
|
-
background: 'var(--theme-input-bg)',
|
|
258
|
-
border: '1px solid var(--theme-elevation-150)',
|
|
259
|
-
borderRadius: 'var(--style-radius-s)',
|
|
260
|
-
color: 'var(--theme-text)',
|
|
261
|
-
fontSize: 'var(--font-size-base)',
|
|
262
|
-
marginBottom: 'var(--base)',
|
|
263
|
-
boxSizing: 'border-box'
|
|
264
|
-
}
|
|
198
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
199
|
+
buttonStyle: "primary",
|
|
200
|
+
size: "small",
|
|
201
|
+
onClick: handleEnableWithPassword,
|
|
202
|
+
disabled: actionLoading || !password,
|
|
203
|
+
children: actionLoading ? 'Enabling...' : 'Continue'
|
|
265
204
|
}),
|
|
266
|
-
/*#__PURE__*/
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
children: [
|
|
272
|
-
/*#__PURE__*/ _jsx("button", {
|
|
273
|
-
type: "submit",
|
|
274
|
-
disabled: actionLoading || !password,
|
|
275
|
-
style: {
|
|
276
|
-
padding: 'calc(var(--base) * 0.75) calc(var(--base) * 1.5)',
|
|
277
|
-
background: 'var(--theme-elevation-800)',
|
|
278
|
-
border: 'none',
|
|
279
|
-
borderRadius: 'var(--style-radius-s)',
|
|
280
|
-
color: 'var(--theme-elevation-50)',
|
|
281
|
-
fontSize: 'var(--font-size-base)',
|
|
282
|
-
cursor: actionLoading || !password ? 'not-allowed' : 'pointer',
|
|
283
|
-
opacity: actionLoading || !password ? 0.7 : 1
|
|
284
|
-
},
|
|
285
|
-
children: actionLoading ? 'Enabling...' : 'Continue'
|
|
286
|
-
}),
|
|
287
|
-
/*#__PURE__*/ _jsx("button", {
|
|
288
|
-
type: "button",
|
|
289
|
-
onClick: ()=>setStep('status'),
|
|
290
|
-
style: {
|
|
291
|
-
padding: 'calc(var(--base) * 0.75) calc(var(--base) * 1.5)',
|
|
292
|
-
background: 'transparent',
|
|
293
|
-
border: '1px solid var(--theme-elevation-200)',
|
|
294
|
-
borderRadius: 'var(--style-radius-s)',
|
|
295
|
-
color: 'var(--theme-text)',
|
|
296
|
-
fontSize: 'var(--font-size-base)',
|
|
297
|
-
cursor: 'pointer'
|
|
298
|
-
},
|
|
299
|
-
children: "Cancel"
|
|
300
|
-
})
|
|
301
|
-
]
|
|
205
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
206
|
+
buttonStyle: "secondary",
|
|
207
|
+
size: "small",
|
|
208
|
+
onClick: ()=>setStep('status'),
|
|
209
|
+
children: "Cancel"
|
|
302
210
|
})
|
|
303
211
|
]
|
|
304
212
|
})
|
|
@@ -306,18 +214,11 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
306
214
|
}),
|
|
307
215
|
step === 'setup' && totpUri && /*#__PURE__*/ _jsxs("div", {
|
|
308
216
|
style: {
|
|
309
|
-
background: 'var(--theme-elevation-50)',
|
|
310
|
-
padding: 'calc(var(--base) * 2)',
|
|
311
|
-
borderRadius: 'var(--style-radius-m)',
|
|
312
217
|
textAlign: 'center'
|
|
313
218
|
},
|
|
314
219
|
children: [
|
|
315
220
|
/*#__PURE__*/ _jsx("p", {
|
|
316
|
-
|
|
317
|
-
color: 'var(--theme-text)',
|
|
318
|
-
opacity: 0.7,
|
|
319
|
-
marginBottom: 'calc(var(--base) * 1.5)'
|
|
320
|
-
},
|
|
221
|
+
className: "field-description",
|
|
321
222
|
children: "Scan this QR code with your authenticator app:"
|
|
322
223
|
}),
|
|
323
224
|
/*#__PURE__*/ _jsx("img", {
|
|
@@ -326,7 +227,7 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
326
227
|
style: {
|
|
327
228
|
width: '200px',
|
|
328
229
|
height: '200px',
|
|
329
|
-
border: '1px solid var(--theme-
|
|
230
|
+
border: '1px solid var(--theme-border-color)',
|
|
330
231
|
borderRadius: 'var(--style-radius-s)',
|
|
331
232
|
marginBottom: 'var(--base)'
|
|
332
233
|
}
|
|
@@ -337,10 +238,8 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
337
238
|
},
|
|
338
239
|
children: [
|
|
339
240
|
/*#__PURE__*/ _jsx("p", {
|
|
241
|
+
className: "field-description",
|
|
340
242
|
style: {
|
|
341
|
-
color: 'var(--theme-text)',
|
|
342
|
-
opacity: 0.7,
|
|
343
|
-
fontSize: 'var(--font-size-small)',
|
|
344
243
|
marginBottom: 'calc(var(--base) * 0.5)'
|
|
345
244
|
},
|
|
346
245
|
children: "Or enter manually:"
|
|
@@ -352,15 +251,14 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
352
251
|
background: 'var(--theme-elevation-100)',
|
|
353
252
|
borderRadius: 'var(--style-radius-s)',
|
|
354
253
|
fontFamily: 'monospace',
|
|
355
|
-
fontSize: 'var(--
|
|
254
|
+
fontSize: 'var(--base-body-size)',
|
|
356
255
|
color: 'var(--theme-text)'
|
|
357
256
|
},
|
|
358
257
|
children: secret
|
|
359
258
|
})
|
|
360
259
|
]
|
|
361
260
|
}),
|
|
362
|
-
/*#__PURE__*/ _jsxs("
|
|
363
|
-
onSubmit: handleVerify,
|
|
261
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
364
262
|
children: [
|
|
365
263
|
/*#__PURE__*/ _jsx("input", {
|
|
366
264
|
type: "text",
|
|
@@ -368,16 +266,21 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
368
266
|
pattern: "[0-9]*",
|
|
369
267
|
value: verificationCode,
|
|
370
268
|
onChange: (e)=>setVerificationCode(e.target.value.replace(/\D/g, '').slice(0, 6)),
|
|
371
|
-
|
|
269
|
+
onKeyDown: (e)=>{
|
|
270
|
+
if (e.key === 'Enter' && verificationCode.length === 6) {
|
|
271
|
+
e.preventDefault();
|
|
272
|
+
handleVerify();
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
placeholder: "000000",
|
|
372
276
|
style: {
|
|
373
|
-
width: '
|
|
374
|
-
|
|
375
|
-
padding: 'calc(var(--base) * 0.75)',
|
|
277
|
+
width: '200px',
|
|
278
|
+
padding: 'var(--base)',
|
|
376
279
|
background: 'var(--theme-input-bg)',
|
|
377
|
-
border: '1px solid var(--theme-
|
|
280
|
+
border: '1px solid var(--theme-border-color)',
|
|
378
281
|
borderRadius: 'var(--style-radius-s)',
|
|
379
282
|
color: 'var(--theme-text)',
|
|
380
|
-
fontSize: '
|
|
283
|
+
fontSize: '1.5rem',
|
|
381
284
|
fontFamily: 'monospace',
|
|
382
285
|
textAlign: 'center',
|
|
383
286
|
letterSpacing: '0.5em',
|
|
@@ -386,19 +289,11 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
386
289
|
}
|
|
387
290
|
}),
|
|
388
291
|
/*#__PURE__*/ _jsx("br", {}),
|
|
389
|
-
/*#__PURE__*/ _jsx(
|
|
390
|
-
|
|
292
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
293
|
+
buttonStyle: "primary",
|
|
294
|
+
size: "small",
|
|
295
|
+
onClick: handleVerify,
|
|
391
296
|
disabled: actionLoading || verificationCode.length !== 6,
|
|
392
|
-
style: {
|
|
393
|
-
padding: 'calc(var(--base) * 0.75) calc(var(--base) * 2)',
|
|
394
|
-
background: 'var(--theme-elevation-800)',
|
|
395
|
-
border: 'none',
|
|
396
|
-
borderRadius: 'var(--style-radius-s)',
|
|
397
|
-
color: 'var(--theme-elevation-50)',
|
|
398
|
-
fontSize: 'var(--font-size-base)',
|
|
399
|
-
cursor: actionLoading || verificationCode.length !== 6 ? 'not-allowed' : 'pointer',
|
|
400
|
-
opacity: actionLoading || verificationCode.length !== 6 ? 0.7 : 1
|
|
401
|
-
},
|
|
402
297
|
children: actionLoading ? 'Verifying...' : 'Verify'
|
|
403
298
|
})
|
|
404
299
|
]
|
|
@@ -406,37 +301,17 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
406
301
|
]
|
|
407
302
|
}),
|
|
408
303
|
step === 'backup' && /*#__PURE__*/ _jsxs("div", {
|
|
409
|
-
style: {
|
|
410
|
-
background: 'var(--theme-elevation-50)',
|
|
411
|
-
padding: 'calc(var(--base) * 2)',
|
|
412
|
-
borderRadius: 'var(--style-radius-m)'
|
|
413
|
-
},
|
|
414
304
|
children: [
|
|
415
|
-
/*#__PURE__*/ _jsx(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
fontSize: 'var(--font-size-h3)',
|
|
419
|
-
fontWeight: 600,
|
|
420
|
-
margin: '0 0 var(--base) 0',
|
|
421
|
-
textAlign: 'center'
|
|
422
|
-
},
|
|
423
|
-
children: "Save Your Backup Codes"
|
|
424
|
-
}),
|
|
425
|
-
/*#__PURE__*/ _jsx("p", {
|
|
426
|
-
style: {
|
|
427
|
-
color: 'var(--theme-text)',
|
|
428
|
-
opacity: 0.7,
|
|
429
|
-
fontSize: 'var(--font-size-small)',
|
|
430
|
-
textAlign: 'center',
|
|
431
|
-
marginBottom: 'calc(var(--base) * 1.5)'
|
|
432
|
-
},
|
|
433
|
-
children: "Store these codes safely. Use them if you lose your authenticator."
|
|
305
|
+
/*#__PURE__*/ _jsx(Banner, {
|
|
306
|
+
type: "info",
|
|
307
|
+
children: "Save these backup codes in a safe place. You can use them to sign in if you lose access to your authenticator app."
|
|
434
308
|
}),
|
|
435
309
|
/*#__PURE__*/ _jsx("div", {
|
|
436
310
|
style: {
|
|
437
311
|
background: 'var(--theme-elevation-100)',
|
|
438
312
|
padding: 'var(--base)',
|
|
439
313
|
borderRadius: 'var(--style-radius-s)',
|
|
314
|
+
marginTop: 'var(--base)',
|
|
440
315
|
marginBottom: 'var(--base)',
|
|
441
316
|
fontFamily: 'monospace'
|
|
442
317
|
},
|
|
@@ -455,34 +330,26 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
455
330
|
}, index))
|
|
456
331
|
})
|
|
457
332
|
}),
|
|
458
|
-
/*#__PURE__*/
|
|
459
|
-
onClick: ()=>navigator.clipboard.writeText(backupCodes.join('\n')),
|
|
460
|
-
style: {
|
|
461
|
-
width: '100%',
|
|
462
|
-
padding: 'calc(var(--base) * 0.5)',
|
|
463
|
-
background: 'var(--theme-elevation-150)',
|
|
464
|
-
border: 'none',
|
|
465
|
-
borderRadius: 'var(--style-radius-s)',
|
|
466
|
-
color: 'var(--theme-text)',
|
|
467
|
-
fontSize: 'var(--font-size-small)',
|
|
468
|
-
cursor: 'pointer',
|
|
469
|
-
marginBottom: 'var(--base)'
|
|
470
|
-
},
|
|
471
|
-
children: "Copy to Clipboard"
|
|
472
|
-
}),
|
|
473
|
-
/*#__PURE__*/ _jsx("button", {
|
|
474
|
-
onClick: handleBackupContinue,
|
|
333
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
475
334
|
style: {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
background: 'var(--theme-elevation-800)',
|
|
479
|
-
border: 'none',
|
|
480
|
-
borderRadius: 'var(--style-radius-s)',
|
|
481
|
-
color: 'var(--theme-elevation-50)',
|
|
482
|
-
fontSize: 'var(--font-size-base)',
|
|
483
|
-
cursor: 'pointer'
|
|
335
|
+
display: 'flex',
|
|
336
|
+
gap: 'calc(var(--base) * 0.5)'
|
|
484
337
|
},
|
|
485
|
-
children:
|
|
338
|
+
children: [
|
|
339
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
340
|
+
buttonStyle: "secondary",
|
|
341
|
+
size: "small",
|
|
342
|
+
icon: /*#__PURE__*/ _jsx(CopyIcon, {}),
|
|
343
|
+
onClick: ()=>navigator.clipboard.writeText(backupCodes.join('\n')),
|
|
344
|
+
children: "Copy to Clipboard"
|
|
345
|
+
}),
|
|
346
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
347
|
+
buttonStyle: "primary",
|
|
348
|
+
size: "small",
|
|
349
|
+
onClick: handleBackupContinue,
|
|
350
|
+
children: "I've Saved My Codes"
|
|
351
|
+
})
|
|
352
|
+
]
|
|
486
353
|
})
|
|
487
354
|
]
|
|
488
355
|
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around PasskeysManagementClient for use as a Payload `ui` field.
|
|
3
|
+
*
|
|
4
|
+
* Better Auth's passkey APIs are session-based (always operate on the logged-in user).
|
|
5
|
+
* When viewing another user's document, we show an info message instead of the
|
|
6
|
+
* management UI to avoid displaying the admin's own passkeys on someone else's page.
|
|
7
|
+
*
|
|
8
|
+
* While auth context is hydrating (user is null), we show the management UI since
|
|
9
|
+
* the API is session-based and will only ever return the logged-in user's passkeys —
|
|
10
|
+
* no other user's data can be leaked.
|
|
11
|
+
*/
|
|
12
|
+
export declare function PasskeysField(): import("react").JSX.Element;
|
|
13
|
+
export default PasskeysField;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useAuth, useDocumentInfo } from '@payloadcms/ui';
|
|
4
|
+
import { PasskeysManagementClient } from '../PasskeysManagementClient.js';
|
|
5
|
+
/**
|
|
6
|
+
* Thin wrapper around PasskeysManagementClient for use as a Payload `ui` field.
|
|
7
|
+
*
|
|
8
|
+
* Better Auth's passkey APIs are session-based (always operate on the logged-in user).
|
|
9
|
+
* When viewing another user's document, we show an info message instead of the
|
|
10
|
+
* management UI to avoid displaying the admin's own passkeys on someone else's page.
|
|
11
|
+
*
|
|
12
|
+
* While auth context is hydrating (user is null), we show the management UI since
|
|
13
|
+
* the API is session-based and will only ever return the logged-in user's passkeys —
|
|
14
|
+
* no other user's data can be leaked.
|
|
15
|
+
*/ export function PasskeysField() {
|
|
16
|
+
const { id: documentId } = useDocumentInfo();
|
|
17
|
+
const { user } = useAuth();
|
|
18
|
+
// Only block when we KNOW it's a different user.
|
|
19
|
+
// While auth is loading (user is null), show the UI — it's session-based
|
|
20
|
+
// and only returns the logged-in user's own passkeys regardless.
|
|
21
|
+
if (user && String(documentId) !== String(user.id)) {
|
|
22
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
23
|
+
className: "field-type",
|
|
24
|
+
children: /*#__PURE__*/ _jsx("p", {
|
|
25
|
+
className: "field-description",
|
|
26
|
+
children: "Passkeys can only be managed by the account owner."
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return /*#__PURE__*/ _jsx(PasskeysManagementClient, {});
|
|
31
|
+
}
|
|
32
|
+
export default PasskeysField;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper around TwoFactorManagementClient for use as a Payload `ui` field.
|
|
3
|
+
*
|
|
4
|
+
* Better Auth's 2FA APIs are session-based (always operate on the logged-in user).
|
|
5
|
+
* When viewing another user's document, we show an info message instead of the
|
|
6
|
+
* management UI to avoid the admin accidentally modifying their own 2FA settings
|
|
7
|
+
* while on someone else's page.
|
|
8
|
+
*
|
|
9
|
+
* While auth context is hydrating (user is null), we show the management UI since
|
|
10
|
+
* the API is session-based and will only ever operate on the logged-in user's own
|
|
11
|
+
* 2FA settings — no other user's data can be leaked or modified.
|
|
12
|
+
*
|
|
13
|
+
* After 2FA is enabled or disabled, triggers a Next.js router refresh so that
|
|
14
|
+
* the document form re-fetches from the DB and picks up the `twoFactorEnabled`
|
|
15
|
+
* value that Better Auth wrote. Without this, navigating away without clicking
|
|
16
|
+
* Save would overwrite the change.
|
|
17
|
+
*/
|
|
18
|
+
export declare function TwoFactorField(): import("react").JSX.Element;
|
|
19
|
+
export default TwoFactorField;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useRouter } from 'next/navigation.js';
|
|
4
|
+
import { useCallback } from 'react';
|
|
5
|
+
import { useAuth, useDocumentInfo } from '@payloadcms/ui';
|
|
6
|
+
import { TwoFactorManagementClient } from '../TwoFactorManagementClient.js';
|
|
7
|
+
/**
|
|
8
|
+
* Wrapper around TwoFactorManagementClient for use as a Payload `ui` field.
|
|
9
|
+
*
|
|
10
|
+
* Better Auth's 2FA APIs are session-based (always operate on the logged-in user).
|
|
11
|
+
* When viewing another user's document, we show an info message instead of the
|
|
12
|
+
* management UI to avoid the admin accidentally modifying their own 2FA settings
|
|
13
|
+
* while on someone else's page.
|
|
14
|
+
*
|
|
15
|
+
* While auth context is hydrating (user is null), we show the management UI since
|
|
16
|
+
* the API is session-based and will only ever operate on the logged-in user's own
|
|
17
|
+
* 2FA settings — no other user's data can be leaked or modified.
|
|
18
|
+
*
|
|
19
|
+
* After 2FA is enabled or disabled, triggers a Next.js router refresh so that
|
|
20
|
+
* the document form re-fetches from the DB and picks up the `twoFactorEnabled`
|
|
21
|
+
* value that Better Auth wrote. Without this, navigating away without clicking
|
|
22
|
+
* Save would overwrite the change.
|
|
23
|
+
*/ export function TwoFactorField() {
|
|
24
|
+
const router = useRouter();
|
|
25
|
+
const { id: documentId } = useDocumentInfo();
|
|
26
|
+
const { user } = useAuth();
|
|
27
|
+
const handleComplete = useCallback(()=>{
|
|
28
|
+
router.refresh();
|
|
29
|
+
}, [
|
|
30
|
+
router
|
|
31
|
+
]);
|
|
32
|
+
// Only block when we KNOW it's a different user.
|
|
33
|
+
// While auth is loading (user is null), show the UI — it's session-based
|
|
34
|
+
// and only operates on the logged-in user's own 2FA regardless.
|
|
35
|
+
if (user && String(documentId) !== String(user.id)) {
|
|
36
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
37
|
+
className: "field-type",
|
|
38
|
+
children: /*#__PURE__*/ _jsx("p", {
|
|
39
|
+
className: "field-description",
|
|
40
|
+
children: "Two-factor authentication can only be managed by the account owner."
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return /*#__PURE__*/ _jsx(TwoFactorManagementClient, {
|
|
45
|
+
onComplete: handleComplete
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export default TwoFactorField;
|
|
@@ -21,3 +21,5 @@ export type { PasskeySignInButtonProps } from '../components/PasskeySignInButton
|
|
|
21
21
|
export { PasskeySignInButton } from '../components/PasskeySignInButton.js';
|
|
22
22
|
export type { PasskeyRegisterButtonProps } from '../components/PasskeyRegisterButton.js';
|
|
23
23
|
export { PasskeyRegisterButton } from '../components/PasskeyRegisterButton.js';
|
|
24
|
+
export { TwoFactorField } from '../components/management/fields/TwoFactorField.js';
|
|
25
|
+
export { PasskeysField } from '../components/management/fields/PasskeysField.js';
|
|
@@ -12,3 +12,6 @@ export { TwoFactorSetupView } from '../components/twoFactor/TwoFactorSetupView.j
|
|
|
12
12
|
export { TwoFactorVerifyView } from '../components/twoFactor/TwoFactorVerifyView.js';
|
|
13
13
|
export { PasskeySignInButton } from '../components/PasskeySignInButton.js';
|
|
14
14
|
export { PasskeyRegisterButton } from '../components/PasskeyRegisterButton.js';
|
|
15
|
+
// Management UI field wrappers (for Payload ui fields)
|
|
16
|
+
export { TwoFactorField } from '../components/management/fields/TwoFactorField.js';
|
|
17
|
+
export { PasskeysField } from '../components/management/fields/PasskeysField.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/
|
|
9
|
-
export { payloadAdapter } from './adapter/index.js';
|
|
10
|
-
export type { PayloadAdapterConfig } from './adapter/index.js';
|
|
9
|
+
export { payloadAdapter, detectDbType, resolveIdType } from './adapter/index.js';
|
|
10
|
+
export type { PayloadAdapterConfig, DbType } from './adapter/index.js';
|
|
11
11
|
export { betterAuthCollections } from './adapter/collections.js';
|
|
12
12
|
export type { BetterAuthCollectionsOptions } from './adapter/collections.js';
|
|
13
13
|
export { createBetterAuthPlugin, betterAuthStrategy, resetAuthInstance, getApiKeyScopesConfig, } from './plugin/index.js';
|