@puzzle-ui/fullstack 0.0.0-stage → 0.5.1

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 (80) hide show
  1. package/README.md +65 -2
  2. package/dist/LICENSE +21 -0
  3. package/dist/auth/client/auth.css +206 -0
  4. package/dist/auth/client/experience.cjs +340 -0
  5. package/dist/auth/client/experience.cjs.map +1 -0
  6. package/dist/auth/client/experience.d.cts +67 -0
  7. package/dist/auth/client/experience.d.ts +67 -0
  8. package/dist/auth/client/experience.js +340 -0
  9. package/dist/auth/client/experience.js.map +1 -0
  10. package/dist/auth/client/index.cjs +234 -0
  11. package/dist/auth/client/index.cjs.map +1 -0
  12. package/dist/auth/client/index.d.cts +48 -0
  13. package/dist/auth/client/index.d.ts +48 -0
  14. package/dist/auth/client/index.js +216 -0
  15. package/dist/auth/client/index.js.map +1 -0
  16. package/dist/auth/client/messages.cjs +80 -0
  17. package/dist/auth/client/messages.cjs.map +1 -0
  18. package/dist/auth/client/messages.d.cts +92 -0
  19. package/dist/auth/client/messages.d.ts +92 -0
  20. package/dist/auth/client/messages.js +79 -0
  21. package/dist/auth/client/messages.js.map +1 -0
  22. package/dist/auth/client/passkeys.cjs +43 -0
  23. package/dist/auth/client/passkeys.cjs.map +1 -0
  24. package/dist/auth/client/passkeys.d.cts +23 -0
  25. package/dist/auth/client/passkeys.d.ts +23 -0
  26. package/dist/auth/client/passkeys.js +40 -0
  27. package/dist/auth/client/passkeys.js.map +1 -0
  28. package/dist/auth/client/react.cjs +859 -0
  29. package/dist/auth/client/react.cjs.map +1 -0
  30. package/dist/auth/client/react.d.cts +72 -0
  31. package/dist/auth/client/react.d.ts +72 -0
  32. package/dist/auth/client/react.js +850 -0
  33. package/dist/auth/client/react.js.map +1 -0
  34. package/dist/auth/next/index.cjs +40 -0
  35. package/dist/auth/next/index.cjs.map +1 -0
  36. package/dist/auth/next/index.d.cts +12 -0
  37. package/dist/auth/next/index.d.ts +12 -0
  38. package/dist/auth/next/index.js +38 -0
  39. package/dist/auth/next/index.js.map +1 -0
  40. package/dist/auth/server/health.cjs +51 -0
  41. package/dist/auth/server/health.cjs.map +1 -0
  42. package/dist/auth/server/health.d.cts +15 -0
  43. package/dist/auth/server/health.d.ts +15 -0
  44. package/dist/auth/server/health.js +51 -0
  45. package/dist/auth/server/health.js.map +1 -0
  46. package/dist/auth/server/index.cjs +108 -0
  47. package/dist/auth/server/index.cjs.map +1 -0
  48. package/dist/auth/server/index.d.cts +22 -0
  49. package/dist/auth/server/index.d.ts +22 -0
  50. package/dist/auth/server/index.js +88 -0
  51. package/dist/auth/server/index.js.map +1 -0
  52. package/dist/auth/server/lifecycle.cjs +126 -0
  53. package/dist/auth/server/lifecycle.cjs.map +1 -0
  54. package/dist/auth/server/lifecycle.d.cts +20 -0
  55. package/dist/auth/server/lifecycle.d.ts +20 -0
  56. package/dist/auth/server/lifecycle.js +126 -0
  57. package/dist/auth/server/lifecycle.js.map +1 -0
  58. package/dist/auth/server/mfa.cjs +682 -0
  59. package/dist/auth/server/mfa.cjs.map +1 -0
  60. package/dist/auth/server/mfa.d.cts +90 -0
  61. package/dist/auth/server/mfa.d.ts +90 -0
  62. package/dist/auth/server/mfa.js +673 -0
  63. package/dist/auth/server/mfa.js.map +1 -0
  64. package/dist/auth/server/session.cjs +58 -0
  65. package/dist/auth/server/session.cjs.map +1 -0
  66. package/dist/auth/server/session.d.cts +14 -0
  67. package/dist/auth/server/session.d.ts +14 -0
  68. package/dist/auth/server/session.js +53 -0
  69. package/dist/auth/server/session.js.map +1 -0
  70. package/dist/auth/shared/index.cjs +0 -0
  71. package/dist/auth/shared/index.d.cts +64 -0
  72. package/dist/auth/shared/index.d.ts +64 -0
  73. package/dist/auth/shared/index.js +1 -0
  74. package/dist/auth/tanstack/index.cjs +34 -0
  75. package/dist/auth/tanstack/index.cjs.map +1 -0
  76. package/dist/auth/tanstack/index.d.cts +7 -0
  77. package/dist/auth/tanstack/index.d.ts +7 -0
  78. package/dist/auth/tanstack/index.js +32 -0
  79. package/dist/auth/tanstack/index.js.map +1 -0
  80. package/package.json +113 -4
@@ -0,0 +1,850 @@
1
+ "use client";
2
+ import { createAuthClient } from "./index.js";
3
+ import { getAuthLocale, resolveAuthMessages } from "./messages.js";
4
+ import "./auth.css";
5
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
6
+ import { Alert, Button, FormField, TextInput } from "@puzzle-ui/ui";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ // src/auth/client/react.tsx
9
+ const RequiredMfaEnrollment = ({ className }) => {
10
+ const { client, snapshot, messages, refresh } = useAuth();
11
+ const [enrollment, setEnrollment] = useState(null);
12
+ const [code, setCode] = useState("");
13
+ const [recoveryCodes, setRecoveryCodes] = useState([]);
14
+ const [loading, setLoading] = useState(false);
15
+ const [error, setError] = useState(null);
16
+ if (snapshot.status !== "mfa-enrollment-required" && snapshot.status !== "mfa-enrollment-pending") return null;
17
+ const handleTotpStart = async () => {
18
+ setError(null);
19
+ setLoading(true);
20
+ try {
21
+ setEnrollment(await client.beginRequiredTotpEnrollment());
22
+ } catch (cause) {
23
+ setError(getErrorMessage(cause, messages));
24
+ } finally {
25
+ setLoading(false);
26
+ }
27
+ };
28
+ const handleTotpFinish = async (event) => {
29
+ event.preventDefault();
30
+ if (!enrollment) return;
31
+ setError(null);
32
+ setLoading(true);
33
+ try {
34
+ const result = await client.verifyRequiredTotpEnrollment(enrollment.factorId, code);
35
+ setRecoveryCodes(result.recoveryCodes);
36
+ setEnrollment(null);
37
+ await refresh();
38
+ } catch (cause) {
39
+ setError(getErrorMessage(cause, messages));
40
+ } finally {
41
+ setLoading(false);
42
+ }
43
+ };
44
+ const handlePasskey = async () => {
45
+ setError(null);
46
+ setLoading(true);
47
+ try {
48
+ const result = await client.registerRequiredPasskey();
49
+ setRecoveryCodes(result.recoveryCodes);
50
+ await refresh();
51
+ } catch (cause) {
52
+ setError(getErrorMessage(cause, messages));
53
+ } finally {
54
+ setLoading(false);
55
+ }
56
+ };
57
+ return /* @__PURE__ */ jsxs("section", {
58
+ className: `puzzle-auth ${className ?? ""}`,
59
+ children: [
60
+ /* @__PURE__ */ jsxs("header", {
61
+ className: "puzzle-auth__header",
62
+ children: [/* @__PURE__ */ jsx("h1", { children: messages.mfaTitle }), /* @__PURE__ */ jsx("p", { children: "Additional authentication setup is required before you can continue." })]
63
+ }),
64
+ error && /* @__PURE__ */ jsx(Alert, {
65
+ severity: "error",
66
+ children: error
67
+ }),
68
+ recoveryCodes.length > 0 ? /* @__PURE__ */ jsxs("div", {
69
+ className: "puzzle-auth__recovery-codes",
70
+ children: [/* @__PURE__ */ jsx(Alert, {
71
+ severity: "success",
72
+ children: messages.saveRecoveryCodes
73
+ }), /* @__PURE__ */ jsx("ul", { children: recoveryCodes.map((value) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: value }) }, value)) })]
74
+ }) : enrollment ? /* @__PURE__ */ jsxs("form", {
75
+ className: "puzzle-auth__form",
76
+ onSubmit: handleTotpFinish,
77
+ children: [
78
+ /* @__PURE__ */ jsx(FormField, {
79
+ label: messages.totpSecret,
80
+ children: /* @__PURE__ */ jsx(TextInput, {
81
+ value: enrollment.secret,
82
+ readOnly: true,
83
+ "aria-label": messages.totpSecret
84
+ })
85
+ }),
86
+ /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("a", {
87
+ href: enrollment.otpauthUrl,
88
+ children: "Open authenticator application"
89
+ }) }),
90
+ /* @__PURE__ */ jsx(FormField, {
91
+ label: messages.verificationCode,
92
+ required: true,
93
+ controlId: "puzzle-auth-required-totp",
94
+ children: /* @__PURE__ */ jsx(TextInput, {
95
+ value: code,
96
+ autoComplete: "one-time-code",
97
+ onChange: (event) => setCode(event.currentTarget.value),
98
+ required: true
99
+ })
100
+ }),
101
+ /* @__PURE__ */ jsx(Button, {
102
+ type: "submit",
103
+ loading,
104
+ disabled: !code.trim(),
105
+ children: messages.verifyTotp
106
+ })
107
+ ]
108
+ }) : /* @__PURE__ */ jsxs("div", {
109
+ className: "puzzle-auth__actions",
110
+ children: [/* @__PURE__ */ jsx(Button, {
111
+ disabled: loading,
112
+ loading,
113
+ onClick: () => void handleTotpStart(),
114
+ children: messages.addTotp
115
+ }), client.isPasskeySupported() && /* @__PURE__ */ jsx(Button, {
116
+ variant: "outline",
117
+ disabled: loading,
118
+ onClick: () => void handlePasskey(),
119
+ children: messages.addPasskey
120
+ })]
121
+ })
122
+ ]
123
+ });
124
+ };
125
+ const loadingSnapshot = {
126
+ status: "loading",
127
+ user: null
128
+ };
129
+ const AuthContext = createContext(null);
130
+ const AuthProvider = ({ children, client: suppliedClient, options, initialSnapshot = loadingSnapshot, locale: localeInput, messages: messageOverrides, onAuthStateChange }) => {
131
+ const [createdClient] = useState(() => createAuthClient(options));
132
+ const client = suppliedClient ?? createdClient;
133
+ const locale = getAuthLocale(localeInput);
134
+ const resolvedMessages = useMemo(() => resolveAuthMessages(locale, void 0, messageOverrides), [locale, messageOverrides]);
135
+ const [snapshot, setSnapshot] = useState(initialSnapshot);
136
+ const [error, setError] = useState(null);
137
+ const publish = useCallback((next) => {
138
+ setSnapshot(next);
139
+ setError(null);
140
+ onAuthStateChange?.(next);
141
+ }, [onAuthStateChange]);
142
+ const refresh = useCallback(async () => {
143
+ try {
144
+ const next = await client.getSnapshot();
145
+ publish(next);
146
+ return next;
147
+ } catch (cause) {
148
+ const nextError = cause instanceof Error ? cause : new Error(resolvedMessages.unexpectedError);
149
+ setError(nextError);
150
+ throw nextError;
151
+ }
152
+ }, [
153
+ client,
154
+ publish,
155
+ resolvedMessages.unexpectedError
156
+ ]);
157
+ useEffect(() => {
158
+ let active = true;
159
+ client.getSnapshot().then((next) => {
160
+ if (active) publish(next);
161
+ }, (cause) => {
162
+ if (active) setError(cause instanceof Error ? cause : new Error(resolvedMessages.unexpectedError));
163
+ });
164
+ return () => {
165
+ active = false;
166
+ };
167
+ }, [
168
+ client,
169
+ publish,
170
+ resolvedMessages.unexpectedError
171
+ ]);
172
+ const signIn = useCallback(async (input) => {
173
+ const next = await client.signIn(input);
174
+ publish(next);
175
+ return next;
176
+ }, [client, publish]);
177
+ const signUp = useCallback(async (input) => {
178
+ const next = await client.register(input);
179
+ publish(next);
180
+ return next;
181
+ }, [client, publish]);
182
+ const signOut = useCallback(async () => {
183
+ await client.signOut();
184
+ publish({
185
+ status: "unauthenticated",
186
+ user: null
187
+ });
188
+ }, [client, publish]);
189
+ const verifyMfaTotp = useCallback(async (code) => {
190
+ const next = await client.verifyMfaTotp(code);
191
+ publish(next);
192
+ return next;
193
+ }, [client, publish]);
194
+ const verifyMfaRecoveryCode = useCallback(async (code) => {
195
+ const next = await client.verifyMfaRecoveryCode(code);
196
+ publish(next);
197
+ return next;
198
+ }, [client, publish]);
199
+ const signInWithPasskey = useCallback(async () => {
200
+ const next = await client.signInWithPasskey();
201
+ publish(next);
202
+ return next;
203
+ }, [client, publish]);
204
+ const verifyMfaWithPasskey = useCallback(async () => {
205
+ const next = await client.verifyMfaWithPasskey();
206
+ publish(next);
207
+ return next;
208
+ }, [client, publish]);
209
+ const requestEmailVerification = useCallback(() => client.requestEmailVerification(), [client]);
210
+ const verifyEmail = useCallback(async (token) => {
211
+ const result = await client.verifyEmail(token);
212
+ publish(await client.getSnapshot());
213
+ return result;
214
+ }, [client, publish]);
215
+ const requestPasswordReset = useCallback((email) => client.requestPasswordReset(email), [client]);
216
+ const resetPassword = useCallback((token, newPassword) => client.resetPassword(token, newPassword), [client]);
217
+ const changePassword = useCallback(async (currentPassword, newPassword) => {
218
+ const result = await client.changePassword(currentPassword, newPassword);
219
+ publish(await client.getSnapshot());
220
+ return result;
221
+ }, [client, publish]);
222
+ const value = useMemo(() => ({
223
+ client,
224
+ snapshot,
225
+ messages: resolvedMessages,
226
+ locale,
227
+ error,
228
+ isLoading: snapshot.status === "loading",
229
+ isAuthenticated: snapshot.status === "authenticated",
230
+ isPendingMfa: snapshot.status === "mfa-pending" || snapshot.status === "step-up-pending",
231
+ refresh,
232
+ signIn,
233
+ signUp,
234
+ signOut,
235
+ verifyMfaTotp,
236
+ verifyMfaRecoveryCode,
237
+ signInWithPasskey,
238
+ verifyMfaWithPasskey,
239
+ requestEmailVerification,
240
+ verifyEmail,
241
+ requestPasswordReset,
242
+ resetPassword,
243
+ changePassword
244
+ }), [
245
+ client,
246
+ snapshot,
247
+ resolvedMessages,
248
+ locale,
249
+ error,
250
+ refresh,
251
+ signIn,
252
+ signUp,
253
+ signOut,
254
+ verifyMfaTotp,
255
+ verifyMfaRecoveryCode,
256
+ signInWithPasskey,
257
+ verifyMfaWithPasskey
258
+ ]);
259
+ return /* @__PURE__ */ jsx(AuthContext.Provider, {
260
+ value,
261
+ children
262
+ });
263
+ };
264
+ const useAuth = () => {
265
+ const context = useContext(AuthContext);
266
+ if (!context) throw new Error("useAuth must be used inside an AuthProvider.");
267
+ return context;
268
+ };
269
+ const useSession = () => {
270
+ const { snapshot, isLoading, isAuthenticated } = useAuth();
271
+ return {
272
+ status: snapshot.status,
273
+ mfa: snapshot.mfa,
274
+ isLoading,
275
+ isAuthenticated
276
+ };
277
+ };
278
+ const useUser = () => useAuth().snapshot.user;
279
+ const getErrorMessage = (error, messages) => {
280
+ if (typeof error === "object" && error !== null && "code" in error) {
281
+ if (error.code === "INVALID_CREDENTIALS") return messages.invalidCredentials;
282
+ }
283
+ return error instanceof Error ? error.message : messages.unexpectedError;
284
+ };
285
+ const redirectAfterSuccess = (redirectTo) => {
286
+ if (!redirectTo || typeof window === "undefined" || !redirectTo.startsWith("/") || redirectTo.startsWith("//")) return;
287
+ window.location.assign(redirectTo);
288
+ };
289
+ const SignInForm = ({ className, redirectTo, onSuccess, onForgotPassword, onSignUp }) => {
290
+ const { signIn, signInWithPasskey, messages, client } = useAuth();
291
+ const [email, setEmail] = useState("");
292
+ const [password, setPassword] = useState("");
293
+ const [error, setError] = useState(null);
294
+ const [loading, setLoading] = useState(false);
295
+ const handleSubmit = async (event) => {
296
+ event.preventDefault();
297
+ setError(null);
298
+ setLoading(true);
299
+ try {
300
+ if ((await signIn({
301
+ email,
302
+ password
303
+ })).status === "authenticated") {
304
+ onSuccess?.();
305
+ redirectAfterSuccess(redirectTo);
306
+ }
307
+ } catch (cause) {
308
+ setError(getErrorMessage(cause, messages));
309
+ } finally {
310
+ setLoading(false);
311
+ }
312
+ };
313
+ const handlePasskey = async () => {
314
+ setError(null);
315
+ setLoading(true);
316
+ try {
317
+ if ((await signInWithPasskey()).status === "authenticated") {
318
+ onSuccess?.();
319
+ redirectAfterSuccess(redirectTo);
320
+ }
321
+ } catch (cause) {
322
+ setError(getErrorMessage(cause, messages));
323
+ } finally {
324
+ setLoading(false);
325
+ }
326
+ };
327
+ return /* @__PURE__ */ jsxs("section", {
328
+ className: `puzzle-auth ${className ?? ""}`,
329
+ children: [
330
+ /* @__PURE__ */ jsx("header", {
331
+ className: "puzzle-auth__header",
332
+ children: /* @__PURE__ */ jsx("h1", { children: messages.signIn })
333
+ }),
334
+ error && /* @__PURE__ */ jsx(Alert, {
335
+ severity: "error",
336
+ children: error
337
+ }),
338
+ /* @__PURE__ */ jsxs("form", {
339
+ className: "puzzle-auth__form",
340
+ onSubmit: handleSubmit,
341
+ noValidate: true,
342
+ children: [
343
+ /* @__PURE__ */ jsx(FormField, {
344
+ label: messages.email,
345
+ required: true,
346
+ controlId: "puzzle-auth-email",
347
+ children: /* @__PURE__ */ jsx(TextInput, {
348
+ type: "email",
349
+ name: "email",
350
+ autoComplete: "email",
351
+ value: email,
352
+ onChange: (event) => setEmail(event.currentTarget.value),
353
+ required: true,
354
+ disabled: loading
355
+ })
356
+ }),
357
+ /* @__PURE__ */ jsx(FormField, {
358
+ label: messages.password,
359
+ required: true,
360
+ controlId: "puzzle-auth-password",
361
+ children: /* @__PURE__ */ jsx(TextInput, {
362
+ type: "password",
363
+ name: "password",
364
+ autoComplete: "current-password",
365
+ value: password,
366
+ onChange: (event) => setPassword(event.currentTarget.value),
367
+ required: true,
368
+ disabled: loading
369
+ })
370
+ }),
371
+ onForgotPassword && /* @__PURE__ */ jsx("button", {
372
+ className: "puzzle-auth__text-button",
373
+ type: "button",
374
+ onClick: onForgotPassword,
375
+ children: messages.forgotPassword
376
+ }),
377
+ /* @__PURE__ */ jsx(Button, {
378
+ type: "submit",
379
+ fullWidth: true,
380
+ loading,
381
+ disabled: !email.trim() || !password,
382
+ children: loading ? messages.signingIn : messages.submit
383
+ })
384
+ ]
385
+ }),
386
+ client.isPasskeySupported() && /* @__PURE__ */ jsx(Button, {
387
+ variant: "outline",
388
+ fullWidth: true,
389
+ disabled: loading,
390
+ onClick: handlePasskey,
391
+ children: messages.usePasskey
392
+ }),
393
+ onSignUp && /* @__PURE__ */ jsxs("footer", {
394
+ className: "puzzle-auth__footer",
395
+ children: [/* @__PURE__ */ jsx("span", { children: messages.noAccount }), /* @__PURE__ */ jsx("button", {
396
+ type: "button",
397
+ className: "puzzle-auth__text-button",
398
+ onClick: onSignUp,
399
+ children: messages.createAccount
400
+ })]
401
+ })
402
+ ]
403
+ });
404
+ };
405
+ const SignUpForm = ({ className, redirectTo, onSuccess, onSignIn }) => {
406
+ const { signUp, messages } = useAuth();
407
+ const [name, setName] = useState("");
408
+ const [email, setEmail] = useState("");
409
+ const [password, setPassword] = useState("");
410
+ const [confirmation, setConfirmation] = useState("");
411
+ const [error, setError] = useState(null);
412
+ const [loading, setLoading] = useState(false);
413
+ const handleSubmit = async (event) => {
414
+ event.preventDefault();
415
+ setError(null);
416
+ if (password !== confirmation) {
417
+ setError(messages.passwordMismatch);
418
+ return;
419
+ }
420
+ if (!password) {
421
+ setError(messages.passwordRequired);
422
+ return;
423
+ }
424
+ setLoading(true);
425
+ try {
426
+ if ((await signUp({
427
+ name: name.trim() || null,
428
+ email,
429
+ password
430
+ })).status === "authenticated") {
431
+ onSuccess?.();
432
+ redirectAfterSuccess(redirectTo);
433
+ }
434
+ } catch (cause) {
435
+ setError(getErrorMessage(cause, messages));
436
+ } finally {
437
+ setLoading(false);
438
+ }
439
+ };
440
+ return /* @__PURE__ */ jsxs("section", {
441
+ className: `puzzle-auth ${className ?? ""}`,
442
+ children: [
443
+ /* @__PURE__ */ jsx("header", {
444
+ className: "puzzle-auth__header",
445
+ children: /* @__PURE__ */ jsx("h1", { children: messages.signUp })
446
+ }),
447
+ error && /* @__PURE__ */ jsx(Alert, {
448
+ severity: "error",
449
+ children: error
450
+ }),
451
+ /* @__PURE__ */ jsxs("form", {
452
+ className: "puzzle-auth__form",
453
+ onSubmit: handleSubmit,
454
+ noValidate: true,
455
+ children: [
456
+ /* @__PURE__ */ jsx(FormField, {
457
+ label: messages.name,
458
+ controlId: "puzzle-auth-name",
459
+ children: /* @__PURE__ */ jsx(TextInput, {
460
+ name: "name",
461
+ autoComplete: "name",
462
+ value: name,
463
+ onChange: (event) => setName(event.currentTarget.value),
464
+ disabled: loading
465
+ })
466
+ }),
467
+ /* @__PURE__ */ jsx(FormField, {
468
+ label: messages.email,
469
+ required: true,
470
+ controlId: "puzzle-auth-signup-email",
471
+ children: /* @__PURE__ */ jsx(TextInput, {
472
+ type: "email",
473
+ name: "email",
474
+ autoComplete: "email",
475
+ value: email,
476
+ onChange: (event) => setEmail(event.currentTarget.value),
477
+ required: true,
478
+ disabled: loading
479
+ })
480
+ }),
481
+ /* @__PURE__ */ jsx(FormField, {
482
+ label: messages.password,
483
+ required: true,
484
+ controlId: "puzzle-auth-signup-password",
485
+ children: /* @__PURE__ */ jsx(TextInput, {
486
+ type: "password",
487
+ name: "new-password",
488
+ autoComplete: "new-password",
489
+ value: password,
490
+ onChange: (event) => setPassword(event.currentTarget.value),
491
+ required: true,
492
+ disabled: loading
493
+ })
494
+ }),
495
+ /* @__PURE__ */ jsx(FormField, {
496
+ label: messages.confirmPassword,
497
+ required: true,
498
+ controlId: "puzzle-auth-confirm-password",
499
+ children: /* @__PURE__ */ jsx(TextInput, {
500
+ type: "password",
501
+ name: "confirm-password",
502
+ autoComplete: "new-password",
503
+ value: confirmation,
504
+ onChange: (event) => setConfirmation(event.currentTarget.value),
505
+ required: true,
506
+ disabled: loading
507
+ })
508
+ }),
509
+ /* @__PURE__ */ jsx(Button, {
510
+ type: "submit",
511
+ fullWidth: true,
512
+ loading,
513
+ disabled: !email.trim() || !password || !confirmation,
514
+ children: loading ? messages.signingUp : messages.createAccount
515
+ })
516
+ ]
517
+ }),
518
+ onSignIn && /* @__PURE__ */ jsxs("footer", {
519
+ className: "puzzle-auth__footer",
520
+ children: [/* @__PURE__ */ jsx("span", { children: messages.existingAccount }), /* @__PURE__ */ jsx("button", {
521
+ type: "button",
522
+ className: "puzzle-auth__text-button",
523
+ onClick: onSignIn,
524
+ children: messages.signIn
525
+ })]
526
+ })
527
+ ]
528
+ });
529
+ };
530
+ const MfaChallengeForm = ({ className, redirectTo, onSuccess }) => {
531
+ const { snapshot, verifyMfaTotp, verifyMfaRecoveryCode, verifyMfaWithPasskey, client, messages } = useAuth();
532
+ const [code, setCode] = useState("");
533
+ const [recoveryMode, setRecoveryMode] = useState(false);
534
+ const [error, setError] = useState(null);
535
+ const [loading, setLoading] = useState(false);
536
+ const finish = (result) => {
537
+ if (result.status === "authenticated") {
538
+ onSuccess?.();
539
+ redirectAfterSuccess(redirectTo);
540
+ }
541
+ };
542
+ const handleSubmit = async (event) => {
543
+ event.preventDefault();
544
+ setError(null);
545
+ setLoading(true);
546
+ try {
547
+ const result = recoveryMode ? await verifyMfaRecoveryCode(code) : await verifyMfaTotp(code);
548
+ finish(result);
549
+ } catch (cause) {
550
+ setError(getErrorMessage(cause, messages));
551
+ } finally {
552
+ setLoading(false);
553
+ }
554
+ };
555
+ const handlePasskey = async () => {
556
+ setError(null);
557
+ setLoading(true);
558
+ try {
559
+ finish(await verifyMfaWithPasskey());
560
+ } catch (cause) {
561
+ setError(getErrorMessage(cause, messages));
562
+ } finally {
563
+ setLoading(false);
564
+ }
565
+ };
566
+ if (snapshot.status !== "mfa-pending" && snapshot.status !== "step-up-pending") return null;
567
+ const availableFactors = snapshot.mfa?.availableFactors;
568
+ return /* @__PURE__ */ jsxs("section", {
569
+ className: `puzzle-auth ${className ?? ""}`,
570
+ children: [
571
+ /* @__PURE__ */ jsxs("header", {
572
+ className: "puzzle-auth__header",
573
+ children: [/* @__PURE__ */ jsx("h1", { children: messages.mfaTitle }), /* @__PURE__ */ jsx("p", { children: messages.mfaDescription })]
574
+ }),
575
+ error && /* @__PURE__ */ jsx(Alert, {
576
+ severity: "error",
577
+ children: error
578
+ }),
579
+ /* @__PURE__ */ jsxs("form", {
580
+ className: "puzzle-auth__form",
581
+ onSubmit: handleSubmit,
582
+ noValidate: true,
583
+ children: [/* @__PURE__ */ jsx(FormField, {
584
+ label: recoveryMode ? messages.recoveryCode : messages.verificationCode,
585
+ required: true,
586
+ controlId: "puzzle-auth-mfa-code",
587
+ children: /* @__PURE__ */ jsx(TextInput, {
588
+ name: "code",
589
+ type: "text",
590
+ autoComplete: "one-time-code",
591
+ value: code,
592
+ onChange: (event) => setCode(event.currentTarget.value),
593
+ required: true,
594
+ disabled: loading
595
+ })
596
+ }), /* @__PURE__ */ jsx(Button, {
597
+ type: "submit",
598
+ fullWidth: true,
599
+ loading,
600
+ disabled: !code.trim(),
601
+ children: loading ? messages.verifying : messages.verify
602
+ })]
603
+ }),
604
+ /* @__PURE__ */ jsxs("div", {
605
+ className: "puzzle-auth__actions",
606
+ children: [availableFactors?.includes("recovery-code") && /* @__PURE__ */ jsx("button", {
607
+ type: "button",
608
+ className: "puzzle-auth__text-button",
609
+ onClick: () => {
610
+ setRecoveryMode((current) => !current);
611
+ setCode("");
612
+ setError(null);
613
+ },
614
+ children: recoveryMode ? messages.useAuthenticator : messages.useRecoveryCode
615
+ }), availableFactors?.includes("passkey") && client.isPasskeySupported() && /* @__PURE__ */ jsx(Button, {
616
+ variant: "outline",
617
+ fullWidth: true,
618
+ disabled: loading,
619
+ onClick: handlePasskey,
620
+ children: messages.usePasskey
621
+ })]
622
+ })
623
+ ]
624
+ });
625
+ };
626
+ const AuthScreen = ({ mode = "sign-in", className, redirectTo, onSuccess, onForgotPassword, children }) => {
627
+ const { snapshot, messages, signOut } = useAuth();
628
+ if (snapshot.status === "loading") return /* @__PURE__ */ jsx("div", {
629
+ className: `puzzle-auth ${className ?? ""}`,
630
+ role: "status",
631
+ "aria-live": "polite",
632
+ children: messages.loading
633
+ });
634
+ if (snapshot.status === "mfa-pending" || snapshot.status === "step-up-pending") return /* @__PURE__ */ jsx(MfaChallengeForm, {
635
+ className,
636
+ redirectTo,
637
+ onSuccess
638
+ });
639
+ if (snapshot.status === "authenticated") return /* @__PURE__ */ jsxs("section", {
640
+ className: `puzzle-auth ${className ?? ""}`,
641
+ children: [children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("p", { children: messages.authenticated }), snapshot.user && /* @__PURE__ */ jsx("p", { children: snapshot.user.email })] }), /* @__PURE__ */ jsx(Button, {
642
+ variant: "outline",
643
+ onClick: () => void signOut(),
644
+ children: messages.signOut
645
+ })]
646
+ });
647
+ if (snapshot.status === "mfa-enrollment-required" || snapshot.status === "mfa-enrollment-pending") return /* @__PURE__ */ jsx(RequiredMfaEnrollment, { className });
648
+ return mode === "sign-up" ? /* @__PURE__ */ jsx(SignUpForm, {
649
+ className,
650
+ redirectTo,
651
+ onSuccess
652
+ }) : /* @__PURE__ */ jsx(SignInForm, {
653
+ className,
654
+ redirectTo,
655
+ onSuccess,
656
+ onForgotPassword
657
+ });
658
+ };
659
+ const MfaSettings = ({ className }) => {
660
+ const { client, messages } = useAuth();
661
+ const [factors, setFactors] = useState(null);
662
+ const [enrollment, setEnrollment] = useState(null);
663
+ const [recoveryCodes, setRecoveryCodes] = useState([]);
664
+ const [password, setPassword] = useState("");
665
+ const [code, setCode] = useState("");
666
+ const [error, setError] = useState(null);
667
+ const [loading, setLoading] = useState(false);
668
+ const loadFactors = useCallback(async () => {
669
+ setFactors(await client.listMfaFactors());
670
+ }, [client]);
671
+ useEffect(() => {
672
+ loadFactors().catch((cause) => {
673
+ setError(cause instanceof Error ? cause.message : messages.unexpectedError);
674
+ });
675
+ }, [loadFactors, messages.unexpectedError]);
676
+ const beginTotp = async () => {
677
+ setError(null);
678
+ setLoading(true);
679
+ try {
680
+ setEnrollment(await client.beginTotpEnrollment(password));
681
+ setPassword("");
682
+ } catch (cause) {
683
+ setError(getErrorMessage(cause, messages));
684
+ } finally {
685
+ setLoading(false);
686
+ }
687
+ };
688
+ const finishTotp = async (event) => {
689
+ event.preventDefault();
690
+ if (!enrollment) return;
691
+ setError(null);
692
+ setLoading(true);
693
+ try {
694
+ const result = await client.verifyTotpEnrollment(enrollment.factorId, code);
695
+ setRecoveryCodes(result.recoveryCodes);
696
+ setEnrollment(null);
697
+ setCode("");
698
+ await loadFactors();
699
+ } catch (cause) {
700
+ setError(getErrorMessage(cause, messages));
701
+ } finally {
702
+ setLoading(false);
703
+ }
704
+ };
705
+ const addPasskey = async () => {
706
+ setError(null);
707
+ setLoading(true);
708
+ try {
709
+ const result = await client.registerPasskey(password);
710
+ setRecoveryCodes(result.recoveryCodes);
711
+ setPassword("");
712
+ await loadFactors();
713
+ } catch (cause) {
714
+ setError(getErrorMessage(cause, messages));
715
+ } finally {
716
+ setLoading(false);
717
+ }
718
+ };
719
+ const removeFactor = async (factorId) => {
720
+ setError(null);
721
+ setLoading(true);
722
+ try {
723
+ await client.removeMfaFactor(factorId, password);
724
+ setPassword("");
725
+ await loadFactors();
726
+ } catch (cause) {
727
+ setError(getErrorMessage(cause, messages));
728
+ } finally {
729
+ setLoading(false);
730
+ }
731
+ };
732
+ const regenerateCodes = async () => {
733
+ setError(null);
734
+ setLoading(true);
735
+ try {
736
+ const result = await client.regenerateRecoveryCodes(password);
737
+ setRecoveryCodes(result.recoveryCodes);
738
+ setPassword("");
739
+ await loadFactors();
740
+ } catch (cause) {
741
+ setError(getErrorMessage(cause, messages));
742
+ } finally {
743
+ setLoading(false);
744
+ }
745
+ };
746
+ return /* @__PURE__ */ jsxs("section", {
747
+ className: `puzzle-auth puzzle-auth--settings ${className ?? ""}`,
748
+ children: [
749
+ /* @__PURE__ */ jsx("h2", { children: messages.mfaSettings }),
750
+ error && /* @__PURE__ */ jsx(Alert, {
751
+ severity: "error",
752
+ children: error
753
+ }),
754
+ factors && /* @__PURE__ */ jsx("ul", {
755
+ className: "puzzle-auth__factor-list",
756
+ children: factors.factors.map((factor) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("strong", { children: factor.label }), /* @__PURE__ */ jsx("span", { children: factor.type })] }), /* @__PURE__ */ jsx(Button, {
757
+ variant: "ghost",
758
+ size: "sm",
759
+ disabled: loading,
760
+ onClick: () => void removeFactor(factor.id),
761
+ children: messages.removeFactor
762
+ })] }, factor.id))
763
+ }),
764
+ !enrollment && /* @__PURE__ */ jsx(FormField, {
765
+ label: messages.currentPassword,
766
+ controlId: "puzzle-auth-settings-password",
767
+ children: /* @__PURE__ */ jsx(TextInput, {
768
+ type: "password",
769
+ autoComplete: "current-password",
770
+ value: password,
771
+ onChange: (event) => setPassword(event.currentTarget.value),
772
+ disabled: loading
773
+ })
774
+ }),
775
+ enrollment ? /* @__PURE__ */ jsxs("form", {
776
+ className: "puzzle-auth__form",
777
+ onSubmit: finishTotp,
778
+ children: [
779
+ /* @__PURE__ */ jsx(FormField, {
780
+ label: messages.totpSecret,
781
+ children: /* @__PURE__ */ jsx(TextInput, {
782
+ readOnly: true,
783
+ value: enrollment.secret,
784
+ "aria-label": messages.totpSecret
785
+ })
786
+ }),
787
+ /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("a", {
788
+ href: enrollment.otpauthUrl,
789
+ children: "Open authenticator application"
790
+ }) }),
791
+ /* @__PURE__ */ jsx(FormField, {
792
+ label: messages.verificationCode,
793
+ controlId: "puzzle-auth-enrollment-code",
794
+ children: /* @__PURE__ */ jsx(TextInput, {
795
+ value: code,
796
+ autoComplete: "one-time-code",
797
+ onChange: (event) => setCode(event.currentTarget.value),
798
+ required: true
799
+ })
800
+ }),
801
+ /* @__PURE__ */ jsx(Button, {
802
+ type: "submit",
803
+ loading,
804
+ disabled: !code.trim(),
805
+ children: messages.verifyTotp
806
+ }),
807
+ /* @__PURE__ */ jsx(Button, {
808
+ variant: "ghost",
809
+ disabled: loading,
810
+ onClick: () => setEnrollment(null),
811
+ children: messages.cancel
812
+ })
813
+ ]
814
+ }) : /* @__PURE__ */ jsxs("div", {
815
+ className: "puzzle-auth__actions",
816
+ children: [
817
+ /* @__PURE__ */ jsx(Button, {
818
+ variant: "outline",
819
+ disabled: loading || !password,
820
+ onClick: () => void beginTotp(),
821
+ children: messages.addTotp
822
+ }),
823
+ client.isPasskeySupported() && /* @__PURE__ */ jsx(Button, {
824
+ variant: "outline",
825
+ disabled: loading || !password,
826
+ onClick: () => void addPasskey(),
827
+ children: messages.addPasskey
828
+ }),
829
+ /* @__PURE__ */ jsx(Button, {
830
+ variant: "ghost",
831
+ disabled: loading || !password,
832
+ onClick: () => void regenerateCodes(),
833
+ children: messages.recoveryCodes
834
+ })
835
+ ]
836
+ }),
837
+ recoveryCodes.length > 0 && /* @__PURE__ */ jsxs("div", {
838
+ className: "puzzle-auth__recovery-codes",
839
+ children: [/* @__PURE__ */ jsx(Alert, {
840
+ severity: "warning",
841
+ title: messages.recoveryCodes,
842
+ children: messages.saveRecoveryCodes
843
+ }), /* @__PURE__ */ jsx("ul", { children: recoveryCodes.map((recoveryCode) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: recoveryCode }) }, recoveryCode)) })]
844
+ })
845
+ ]
846
+ });
847
+ };
848
+ export { AuthProvider, AuthScreen, MfaChallengeForm, MfaSettings, RequiredMfaEnrollment, SignInForm, SignUpForm, useAuth, useSession, useUser };
849
+
850
+ //# sourceMappingURL=react.js.map