@stackframe/stack 2.6.11 → 2.6.15
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/CHANGELOG.md +38 -0
- package/dist/components/passkey-button.d.mts +7 -0
- package/dist/components/passkey-button.d.ts +7 -0
- package/dist/components/passkey-button.js +58 -0
- package/dist/components/passkey-button.js.map +1 -0
- package/dist/components-page/account-settings.js +343 -102
- package/dist/components-page/account-settings.js.map +1 -1
- package/dist/components-page/auth-page.d.mts +1 -0
- package/dist/components-page/auth-page.d.ts +1 -0
- package/dist/components-page/auth-page.js +5 -1
- package/dist/components-page/auth-page.js.map +1 -1
- package/dist/esm/components/passkey-button.js +34 -0
- package/dist/esm/components/passkey-button.js.map +1 -0
- package/dist/esm/components-page/account-settings.js +344 -103
- package/dist/esm/components-page/account-settings.js.map +1 -1
- package/dist/esm/components-page/auth-page.js +5 -1
- package/dist/esm/components-page/auth-page.js.map +1 -1
- package/dist/esm/generated/global-css.js +1 -1
- package/dist/esm/generated/global-css.js.map +1 -1
- package/dist/esm/generated/quetzal-translations.js +2268 -1824
- package/dist/esm/generated/quetzal-translations.js.map +1 -1
- package/dist/esm/lib/stack-app.js +197 -13
- package/dist/esm/lib/stack-app.js.map +1 -1
- package/dist/generated/global-css.d.mts +1 -1
- package/dist/generated/global-css.d.ts +1 -1
- package/dist/generated/global-css.js +1 -1
- package/dist/generated/global-css.js.map +1 -1
- package/dist/generated/quetzal-translations.d.mts +2 -2
- package/dist/generated/quetzal-translations.d.ts +2 -2
- package/dist/generated/quetzal-translations.js +2268 -1824
- package/dist/generated/quetzal-translations.js.map +1 -1
- package/dist/lib/stack-app.d.mts +60 -19
- package/dist/lib/stack-app.d.ts +60 -19
- package/dist/lib/stack-app.js +197 -13
- package/dist/lib/stack-app.js.map +1 -1
- package/package.json +5 -4
|
@@ -9,7 +9,7 @@ import { yupObject, yupString } from "@stackframe/stack-shared/dist/schema-field
|
|
|
9
9
|
import { generateRandomValues } from "@stackframe/stack-shared/dist/utils/crypto";
|
|
10
10
|
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
|
|
11
11
|
import { runAsynchronously, runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
|
12
|
-
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button, Input, Label, PasswordInput, Separator, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
|
|
12
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActionCell, Badge, Button, Input, Label, PasswordInput, Separator, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
|
|
13
13
|
import { CirclePlus, Contact, Edit, Settings, ShieldCheck } from "lucide-react";
|
|
14
14
|
import { useRouter } from "next/navigation";
|
|
15
15
|
import { TOTPController, createTOTPKeyURI } from "oslo/otp";
|
|
@@ -44,11 +44,11 @@ function AccountSettings(props) {
|
|
|
44
44
|
content: /* @__PURE__ */ jsx(ProfilePage, {})
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
|
-
title: t("
|
|
47
|
+
title: t("Emails & Auth"),
|
|
48
48
|
type: "item",
|
|
49
|
-
id: "
|
|
49
|
+
id: "auth",
|
|
50
50
|
icon: ShieldCheck,
|
|
51
|
-
content: /* @__PURE__ */ jsx(
|
|
51
|
+
content: /* @__PURE__ */ jsx(EmailsAndAuthPage, {})
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
54
|
title: t("Settings"),
|
|
@@ -145,54 +145,284 @@ function ProfilePage() {
|
|
|
145
145
|
)
|
|
146
146
|
] });
|
|
147
147
|
}
|
|
148
|
-
function
|
|
149
|
-
const
|
|
148
|
+
function EmailsSection() {
|
|
149
|
+
const { t } = useTranslation();
|
|
150
|
+
const user = useUser({ or: "redirect" });
|
|
151
|
+
const contactChannels = user.useContactChannels();
|
|
152
|
+
const [addingEmail, setAddingEmail] = useState(contactChannels.length === 0);
|
|
153
|
+
const [addingEmailLoading, setAddingEmailLoading] = useState(false);
|
|
154
|
+
const [addedEmail, setAddedEmail] = useState(null);
|
|
155
|
+
const isLastEmail = contactChannels.filter((x) => x.usedForAuth && x.type === "email").length === 1;
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (addedEmail) {
|
|
158
|
+
runAsynchronously(async () => {
|
|
159
|
+
const cc = contactChannels.find((x) => x.value === addedEmail);
|
|
160
|
+
if (cc && !cc.isVerified) {
|
|
161
|
+
await cc.sendVerificationEmail();
|
|
162
|
+
}
|
|
163
|
+
setAddedEmail(null);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}, [contactChannels, addedEmail]);
|
|
167
|
+
const emailSchema = yupObject({
|
|
168
|
+
email: yupString().email(t("Please enter a valid email address")).notOneOf(contactChannels.map((x) => x.value), t("Email already exists")).required(t("Email is required"))
|
|
169
|
+
});
|
|
170
|
+
const { register, handleSubmit, formState: { errors }, reset } = useForm({
|
|
171
|
+
resolver: yupResolver(emailSchema)
|
|
172
|
+
});
|
|
173
|
+
const onSubmit = async (data) => {
|
|
174
|
+
setAddingEmailLoading(true);
|
|
175
|
+
try {
|
|
176
|
+
await user.createContactChannel({ type: "email", value: data.email, usedForAuth: false });
|
|
177
|
+
setAddedEmail(data.email);
|
|
178
|
+
} finally {
|
|
179
|
+
setAddingEmailLoading(false);
|
|
180
|
+
}
|
|
181
|
+
setAddingEmail(false);
|
|
182
|
+
reset();
|
|
183
|
+
};
|
|
184
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
185
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row justify-between mb-4 gap-4", children: [
|
|
186
|
+
/* @__PURE__ */ jsx(Typography, { className: "font-medium", children: t("Emails") }),
|
|
187
|
+
addingEmail ? /* @__PURE__ */ jsxs(
|
|
188
|
+
"form",
|
|
189
|
+
{
|
|
190
|
+
onSubmit: (e) => {
|
|
191
|
+
e.preventDefault();
|
|
192
|
+
runAsynchronously(handleSubmit(onSubmit));
|
|
193
|
+
},
|
|
194
|
+
className: "flex flex-col",
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
197
|
+
/* @__PURE__ */ jsx(
|
|
198
|
+
Input,
|
|
199
|
+
{
|
|
200
|
+
...register("email"),
|
|
201
|
+
placeholder: t("Enter email")
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", loading: addingEmailLoading, children: t("Add") }),
|
|
205
|
+
/* @__PURE__ */ jsx(
|
|
206
|
+
Button,
|
|
207
|
+
{
|
|
208
|
+
variant: "secondary",
|
|
209
|
+
onClick: () => {
|
|
210
|
+
setAddingEmail(false);
|
|
211
|
+
reset();
|
|
212
|
+
},
|
|
213
|
+
children: t("Cancel")
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
] }),
|
|
217
|
+
errors.email && /* @__PURE__ */ jsx(FormWarningText, { text: errors.email.message })
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
) : /* @__PURE__ */ jsx("div", { className: "flex md:justify-end", children: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setAddingEmail(true), children: t("Add an email") }) })
|
|
221
|
+
] }),
|
|
222
|
+
contactChannels.length > 0 ? /* @__PURE__ */ jsx("div", { className: "border rounded-md", children: /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsx(TableBody, { children: contactChannels.filter((x) => x.type === "email").sort((a, b) => {
|
|
223
|
+
if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1;
|
|
224
|
+
if (a.isVerified !== b.isVerified) return a.isVerified ? -1 : 1;
|
|
225
|
+
return 0;
|
|
226
|
+
}).map((x) => /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
227
|
+
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row gap-2 md:gap-4", children: [
|
|
228
|
+
x.value,
|
|
229
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
230
|
+
x.isPrimary ? /* @__PURE__ */ jsx(Badge, { children: t("Primary") }) : null,
|
|
231
|
+
!x.isVerified ? /* @__PURE__ */ jsx(Badge, { variant: "destructive", children: t("Unverified") }) : null,
|
|
232
|
+
x.usedForAuth ? /* @__PURE__ */ jsx(Badge, { variant: "outline", children: t("Used for sign-in") }) : null
|
|
233
|
+
] })
|
|
234
|
+
] }) }),
|
|
235
|
+
/* @__PURE__ */ jsx(TableCell, { className: "flex justify-end", children: /* @__PURE__ */ jsx(ActionCell, { items: [
|
|
236
|
+
...!x.isVerified ? [{
|
|
237
|
+
item: t("Send verification email"),
|
|
238
|
+
onClick: async () => {
|
|
239
|
+
await x.sendVerificationEmail();
|
|
240
|
+
}
|
|
241
|
+
}] : [],
|
|
242
|
+
...!x.isPrimary && x.isVerified ? [{
|
|
243
|
+
item: t("Set as primary"),
|
|
244
|
+
onClick: async () => {
|
|
245
|
+
await x.update({ isPrimary: true });
|
|
246
|
+
}
|
|
247
|
+
}] : !x.isPrimary ? [{
|
|
248
|
+
item: t("Set as primary"),
|
|
249
|
+
onClick: async () => {
|
|
250
|
+
},
|
|
251
|
+
disabled: true,
|
|
252
|
+
disabledTooltip: t("Please verify your email first")
|
|
253
|
+
}] : [],
|
|
254
|
+
...!x.usedForAuth && x.isVerified ? [{
|
|
255
|
+
item: t("Use for sign-in"),
|
|
256
|
+
onClick: async () => {
|
|
257
|
+
await x.update({ usedForAuth: true });
|
|
258
|
+
}
|
|
259
|
+
}] : [],
|
|
260
|
+
...x.usedForAuth && !isLastEmail ? [{
|
|
261
|
+
item: t("Stop using for sign-in"),
|
|
262
|
+
onClick: async () => {
|
|
263
|
+
await x.update({ usedForAuth: false });
|
|
264
|
+
}
|
|
265
|
+
}] : x.usedForAuth ? [{
|
|
266
|
+
item: t("Stop using for sign-in"),
|
|
267
|
+
onClick: async () => {
|
|
268
|
+
},
|
|
269
|
+
disabled: true,
|
|
270
|
+
disabledTooltip: t("You can not remove your last sign-in email")
|
|
271
|
+
}] : [],
|
|
272
|
+
...!isLastEmail || !x.usedForAuth ? [{
|
|
273
|
+
item: t("Remove"),
|
|
274
|
+
onClick: async () => {
|
|
275
|
+
await x.delete();
|
|
276
|
+
},
|
|
277
|
+
danger: true
|
|
278
|
+
}] : [{
|
|
279
|
+
item: t("Remove"),
|
|
280
|
+
onClick: async () => {
|
|
281
|
+
},
|
|
282
|
+
disabled: true,
|
|
283
|
+
disabledTooltip: t("You can not remove your last sign-in email")
|
|
284
|
+
}]
|
|
285
|
+
] }) })
|
|
286
|
+
] }, x.id)) }) }) }) : null
|
|
287
|
+
] });
|
|
288
|
+
}
|
|
289
|
+
function EmailsAndAuthPage() {
|
|
150
290
|
const passwordSection = usePasswordSection();
|
|
151
291
|
const mfaSection = useMfaSection();
|
|
292
|
+
const otpSection = useOtpSection();
|
|
293
|
+
const passkeySection = usePasskeySection();
|
|
152
294
|
return /* @__PURE__ */ jsxs(PageLayout, { children: [
|
|
153
|
-
|
|
295
|
+
/* @__PURE__ */ jsx(EmailsSection, {}),
|
|
154
296
|
passwordSection,
|
|
297
|
+
passkeySection,
|
|
298
|
+
otpSection,
|
|
155
299
|
mfaSection
|
|
156
300
|
] });
|
|
157
301
|
}
|
|
158
|
-
function
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
302
|
+
function usePasskeySection() {
|
|
303
|
+
const { t } = useTranslation();
|
|
304
|
+
const user = useUser({ or: "throw" });
|
|
305
|
+
const stackApp = useStackApp();
|
|
306
|
+
const project = stackApp.useProject();
|
|
307
|
+
const contactChannels = user.useContactChannels();
|
|
308
|
+
const has_passkey = user.passkeyAuthEnabled;
|
|
309
|
+
const isLastAuth = user.passkeyAuthEnabled && !user.hasPassword && user.oauthProviders.length === 0 && !user.otpAuthEnabled;
|
|
310
|
+
const [showConfirmationModal, setShowConfirmationModal] = useState(false);
|
|
311
|
+
const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
|
|
312
|
+
if (!project.config.passkeyEnabled) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
const handleDeletePasskey = async () => {
|
|
316
|
+
await user.update({ passkeyAuthEnabled: false });
|
|
317
|
+
setShowConfirmationModal(false);
|
|
318
|
+
};
|
|
319
|
+
const handleAddNewPasskey = async () => {
|
|
320
|
+
await user.registerPasskey();
|
|
321
|
+
};
|
|
322
|
+
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Section, { title: t("Passkey"), description: has_passkey ? t("Passkey registered") : t("Register a passkey"), children: /* @__PURE__ */ jsxs("div", { className: "flex md:justify-end", children: [
|
|
323
|
+
!hasValidEmail && /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("To enable Passkey sign-in, please add a verified email and set it as your sign-in email.") }),
|
|
324
|
+
hasValidEmail && has_passkey && isLastAuth && /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("Passkey sign-in is enabled and cannot be disabled as it is currently the only sign-in method") }),
|
|
325
|
+
!has_passkey && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { onClick: handleAddNewPasskey, variant: "secondary", children: t("Add new passkey") }) }),
|
|
326
|
+
hasValidEmail && has_passkey && !isLastAuth && !showConfirmationModal && /* @__PURE__ */ jsx(
|
|
327
|
+
Button,
|
|
328
|
+
{
|
|
329
|
+
variant: "secondary",
|
|
330
|
+
onClick: () => setShowConfirmationModal(true),
|
|
331
|
+
children: t("Delete Passkey")
|
|
332
|
+
}
|
|
333
|
+
),
|
|
334
|
+
hasValidEmail && has_passkey && !isLastAuth && showConfirmationModal && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
335
|
+
/* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to disable Passkey sign-in? You will not be able to sign in with your passkey anymore.") }),
|
|
336
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
337
|
+
/* @__PURE__ */ jsx(
|
|
338
|
+
Button,
|
|
339
|
+
{
|
|
340
|
+
variant: "destructive",
|
|
341
|
+
onClick: handleDeletePasskey,
|
|
342
|
+
children: t("Disable")
|
|
343
|
+
}
|
|
344
|
+
),
|
|
345
|
+
/* @__PURE__ */ jsx(
|
|
346
|
+
Button,
|
|
347
|
+
{
|
|
348
|
+
variant: "secondary",
|
|
349
|
+
onClick: () => setShowConfirmationModal(false),
|
|
350
|
+
children: t("Cancel")
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
] })
|
|
354
|
+
] })
|
|
355
|
+
] }) }) });
|
|
165
356
|
}
|
|
166
|
-
function
|
|
357
|
+
function useOtpSection() {
|
|
167
358
|
const { t } = useTranslation();
|
|
168
|
-
const user = useUser({ or: "
|
|
169
|
-
const
|
|
170
|
-
|
|
359
|
+
const user = useUser({ or: "throw" });
|
|
360
|
+
const project = useStackApp().useProject();
|
|
361
|
+
const contactChannels = user.useContactChannels();
|
|
362
|
+
const isLastAuth = user.otpAuthEnabled && !user.hasPassword && user.oauthProviders.length === 0 && !user.passkeyAuthEnabled;
|
|
363
|
+
const [disabling, setDisabling] = useState(false);
|
|
364
|
+
const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
|
|
365
|
+
if (!project.config.magicLinkEnabled) {
|
|
171
366
|
return null;
|
|
172
367
|
}
|
|
173
|
-
|
|
174
|
-
|
|
368
|
+
const handleDisableOTP = async () => {
|
|
369
|
+
await user.update({ otpAuthEnabled: false });
|
|
370
|
+
setDisabling(false);
|
|
371
|
+
};
|
|
372
|
+
return /* @__PURE__ */ jsx(Section, { title: t("OTP sign-in"), description: user.otpAuthEnabled ? t("OTP/magic link sign-in is currently enabled.") : t("Enable sign-in via magic link or OTP sent to your sign-in emails."), children: /* @__PURE__ */ jsx("div", { className: "flex md:justify-end", children: hasValidEmail ? user.otpAuthEnabled ? !isLastAuth ? !disabling ? /* @__PURE__ */ jsx(
|
|
373
|
+
Button,
|
|
175
374
|
{
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
children:
|
|
375
|
+
variant: "secondary",
|
|
376
|
+
onClick: () => setDisabling(true),
|
|
377
|
+
children: t("Disable OTP")
|
|
378
|
+
}
|
|
379
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
380
|
+
/* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to disable OTP sign-in? You will not be able to sign in with only emails anymore.") }),
|
|
381
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
382
|
+
/* @__PURE__ */ jsx(
|
|
179
383
|
Button,
|
|
180
384
|
{
|
|
181
|
-
|
|
182
|
-
onClick:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
385
|
+
variant: "destructive",
|
|
386
|
+
onClick: handleDisableOTP,
|
|
387
|
+
children: t("Disable")
|
|
388
|
+
}
|
|
389
|
+
),
|
|
390
|
+
/* @__PURE__ */ jsx(
|
|
391
|
+
Button,
|
|
392
|
+
{
|
|
393
|
+
variant: "secondary",
|
|
394
|
+
onClick: () => setDisabling(false),
|
|
395
|
+
children: t("Cancel")
|
|
187
396
|
}
|
|
188
|
-
)
|
|
397
|
+
)
|
|
398
|
+
] })
|
|
399
|
+
] }) : /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("OTP sign-in is enabled and cannot be disabled as it is currently the only sign-in method") }) : /* @__PURE__ */ jsx(
|
|
400
|
+
Button,
|
|
401
|
+
{
|
|
402
|
+
variant: "secondary",
|
|
403
|
+
onClick: async () => {
|
|
404
|
+
await user.update({ otpAuthEnabled: true });
|
|
405
|
+
},
|
|
406
|
+
children: t("Enable OTP")
|
|
189
407
|
}
|
|
190
|
-
);
|
|
408
|
+
) : /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("To enable OTP sign-in, please add a verified email and set it as your sign-in email.") }) }) });
|
|
409
|
+
}
|
|
410
|
+
function SettingsPage() {
|
|
411
|
+
const deleteAccountSection = useDeleteAccountSection();
|
|
412
|
+
const signOutSection = useSignOutSection();
|
|
413
|
+
return /* @__PURE__ */ jsxs(PageLayout, { children: [
|
|
414
|
+
deleteAccountSection,
|
|
415
|
+
signOutSection
|
|
416
|
+
] });
|
|
191
417
|
}
|
|
192
418
|
function usePasswordSection() {
|
|
193
419
|
const { t } = useTranslation();
|
|
420
|
+
const user = useUser({ or: "throw" });
|
|
421
|
+
const contactChannels = user.useContactChannels();
|
|
422
|
+
const [changingPassword, setChangingPassword] = useState(false);
|
|
423
|
+
const [loading, setLoading] = useState(false);
|
|
194
424
|
const passwordSchema = yupObject({
|
|
195
|
-
oldPassword: yupString().required(t("Please enter your old password")),
|
|
425
|
+
oldPassword: user.hasPassword ? yupString().required(t("Please enter your old password")) : yupString(),
|
|
196
426
|
newPassword: yupString().required(t("Please enter your password")).test({
|
|
197
427
|
name: "is-valid-password",
|
|
198
428
|
test: (value, ctx) => {
|
|
@@ -206,23 +436,20 @@ function usePasswordSection() {
|
|
|
206
436
|
}),
|
|
207
437
|
newPasswordRepeat: yupString().nullable().oneOf([yup.ref("newPassword"), "", null], t("Passwords do not match")).required(t("Please repeat your password"))
|
|
208
438
|
});
|
|
209
|
-
const user = useUser({ or: "throw" });
|
|
210
|
-
const [changingPassword, setChangingPassword] = useState(false);
|
|
211
439
|
const { register, handleSubmit, setError, formState: { errors }, clearErrors, reset } = useForm({
|
|
212
440
|
resolver: yupResolver(passwordSchema)
|
|
213
441
|
});
|
|
214
|
-
const
|
|
215
|
-
const [loading, setLoading] = useState(false);
|
|
442
|
+
const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
|
|
216
443
|
const onSubmit = async (data) => {
|
|
217
444
|
setLoading(true);
|
|
218
445
|
try {
|
|
219
446
|
const { oldPassword, newPassword } = data;
|
|
220
|
-
const error = await user.updatePassword({ oldPassword, newPassword });
|
|
447
|
+
const error = user.hasPassword ? await user.updatePassword({ oldPassword, newPassword }) : await user.setPassword({ password: newPassword });
|
|
221
448
|
if (error) {
|
|
222
449
|
setError("oldPassword", { type: "manual", message: t("Incorrect password") });
|
|
223
450
|
} else {
|
|
224
451
|
reset();
|
|
225
|
-
|
|
452
|
+
setChangingPassword(false);
|
|
226
453
|
}
|
|
227
454
|
} finally {
|
|
228
455
|
setLoading(false);
|
|
@@ -230,69 +457,83 @@ function usePasswordSection() {
|
|
|
230
457
|
};
|
|
231
458
|
const registerPassword = register("newPassword");
|
|
232
459
|
const registerPasswordRepeat = register("newPasswordRepeat");
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
460
|
+
return /* @__PURE__ */ jsx(
|
|
461
|
+
Section,
|
|
462
|
+
{
|
|
463
|
+
title: t("Password"),
|
|
464
|
+
description: user.hasPassword ? t("Update your password") : t("Set a password for your account"),
|
|
465
|
+
children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4", children: !changingPassword ? hasValidEmail ? /* @__PURE__ */ jsx(
|
|
466
|
+
Button,
|
|
467
|
+
{
|
|
468
|
+
variant: "secondary",
|
|
469
|
+
onClick: () => setChangingPassword(true),
|
|
470
|
+
children: user.hasPassword ? t("Update password") : t("Set password")
|
|
471
|
+
}
|
|
472
|
+
) : /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("To set a password, please add a verified email and set it as your sign-in email.") }) : /* @__PURE__ */ jsxs(
|
|
473
|
+
"form",
|
|
474
|
+
{
|
|
475
|
+
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
|
|
476
|
+
noValidate: true,
|
|
477
|
+
children: [
|
|
478
|
+
user.hasPassword && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
479
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "old-password", className: "mb-1", children: t("Old password") }),
|
|
480
|
+
/* @__PURE__ */ jsx(
|
|
481
|
+
Input,
|
|
482
|
+
{
|
|
483
|
+
id: "old-password",
|
|
484
|
+
type: "password",
|
|
485
|
+
...register("oldPassword")
|
|
486
|
+
}
|
|
487
|
+
),
|
|
488
|
+
/* @__PURE__ */ jsx(FormWarningText, { text: errors.oldPassword?.message?.toString() })
|
|
489
|
+
] }),
|
|
490
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "new-password", className: "mt-4 mb-1", children: t("New password") }),
|
|
491
|
+
/* @__PURE__ */ jsx(
|
|
492
|
+
PasswordInput,
|
|
493
|
+
{
|
|
494
|
+
id: "new-password",
|
|
495
|
+
...registerPassword,
|
|
496
|
+
onChange: (e) => {
|
|
497
|
+
clearErrors("newPassword");
|
|
498
|
+
clearErrors("newPasswordRepeat");
|
|
499
|
+
runAsynchronously(registerPassword.onChange(e));
|
|
500
|
+
}
|
|
273
501
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
502
|
+
),
|
|
503
|
+
/* @__PURE__ */ jsx(FormWarningText, { text: errors.newPassword?.message?.toString() }),
|
|
504
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "repeat-password", className: "mt-4 mb-1", children: t("Repeat new password") }),
|
|
505
|
+
/* @__PURE__ */ jsx(
|
|
506
|
+
PasswordInput,
|
|
507
|
+
{
|
|
508
|
+
id: "repeat-password",
|
|
509
|
+
...registerPasswordRepeat,
|
|
510
|
+
onChange: (e) => {
|
|
511
|
+
clearErrors("newPassword");
|
|
512
|
+
clearErrors("newPasswordRepeat");
|
|
513
|
+
runAsynchronously(registerPasswordRepeat.onChange(e));
|
|
514
|
+
}
|
|
287
515
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
516
|
+
),
|
|
517
|
+
/* @__PURE__ */ jsx(FormWarningText, { text: errors.newPasswordRepeat?.message?.toString() }),
|
|
518
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex gap-4", children: [
|
|
519
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", loading, children: user.hasPassword ? t("Update Password") : t("Set Password") }),
|
|
520
|
+
/* @__PURE__ */ jsx(
|
|
521
|
+
Button,
|
|
522
|
+
{
|
|
523
|
+
variant: "secondary",
|
|
524
|
+
onClick: () => {
|
|
525
|
+
setChangingPassword(false);
|
|
526
|
+
reset();
|
|
527
|
+
},
|
|
528
|
+
children: t("Cancel")
|
|
529
|
+
}
|
|
530
|
+
)
|
|
531
|
+
] })
|
|
532
|
+
]
|
|
533
|
+
}
|
|
534
|
+
) })
|
|
535
|
+
}
|
|
536
|
+
);
|
|
296
537
|
}
|
|
297
538
|
function useMfaSection() {
|
|
298
539
|
const { t } = useTranslation();
|
|
@@ -323,7 +564,7 @@ function useMfaSection() {
|
|
|
323
564
|
return /* @__PURE__ */ jsx(
|
|
324
565
|
Section,
|
|
325
566
|
{
|
|
326
|
-
title: t("Multi-factor
|
|
567
|
+
title: t("Multi-factor authentication"),
|
|
327
568
|
description: isEnabled ? t("Multi-factor authentication is currently enabled.") : t("Multi-factor authentication is currently disabled."),
|
|
328
569
|
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
329
570
|
!isEnabled && generatedSecret && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -366,18 +607,18 @@ function useMfaSection() {
|
|
|
366
607
|
totpMultiFactorSecret: null
|
|
367
608
|
});
|
|
368
609
|
},
|
|
369
|
-
children: t("Disable")
|
|
610
|
+
children: t("Disable MFA")
|
|
370
611
|
}
|
|
371
612
|
) : !generatedSecret && /* @__PURE__ */ jsx(
|
|
372
613
|
Button,
|
|
373
614
|
{
|
|
374
|
-
variant: "
|
|
615
|
+
variant: "secondary",
|
|
375
616
|
onClick: async () => {
|
|
376
617
|
const secret = generateRandomValues(new Uint8Array(20));
|
|
377
618
|
setQrCodeUrl(await generateTotpQrCode(project, user, secret));
|
|
378
619
|
setGeneratedSecret(secret);
|
|
379
620
|
},
|
|
380
|
-
children: t("Enable")
|
|
621
|
+
children: t("Enable MFA")
|
|
381
622
|
}
|
|
382
623
|
) })
|
|
383
624
|
] })
|