cauthy-auth 0.5.2 → 0.8.0

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 (72) hide show
  1. package/README.md +68 -3
  2. package/dist/{cauthy-BCTCqubt.d.ts → cauthy-Bn3iv50y.d.ts} +1 -1
  3. package/dist/{cauthy-hswMoS7B.d.mts → cauthy-C29HCvag.d.mts} +1 -1
  4. package/dist/cli.js +490 -25
  5. package/dist/client.d.mts +236 -0
  6. package/dist/client.d.ts +236 -0
  7. package/dist/client.js +331 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/client.mjs +297 -0
  10. package/dist/client.mjs.map +1 -0
  11. package/dist/express.d.mts +24 -0
  12. package/dist/express.d.ts +24 -0
  13. package/dist/express.js +102 -0
  14. package/dist/express.js.map +1 -0
  15. package/dist/express.mjs +76 -0
  16. package/dist/express.mjs.map +1 -0
  17. package/dist/fastify.d.mts +2 -2
  18. package/dist/fastify.d.ts +2 -2
  19. package/dist/fastify.js +3 -1
  20. package/dist/fastify.js.map +1 -1
  21. package/dist/fastify.mjs +3 -1
  22. package/dist/fastify.mjs.map +1 -1
  23. package/dist/hono.d.mts +23 -0
  24. package/dist/hono.d.ts +23 -0
  25. package/dist/hono.js +50 -0
  26. package/dist/hono.js.map +1 -0
  27. package/dist/hono.mjs +24 -0
  28. package/dist/hono.mjs.map +1 -0
  29. package/dist/index.d.mts +44 -6
  30. package/dist/index.d.ts +44 -6
  31. package/dist/index.js +668 -37
  32. package/dist/index.js.map +1 -1
  33. package/dist/index.mjs +656 -37
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/next.d.mts +24 -0
  36. package/dist/next.d.ts +24 -0
  37. package/dist/next.js +77 -0
  38. package/dist/next.js.map +1 -0
  39. package/dist/next.mjs +52 -0
  40. package/dist/next.mjs.map +1 -0
  41. package/dist/react.d.mts +546 -7
  42. package/dist/react.d.ts +546 -7
  43. package/dist/react.js +925 -14
  44. package/dist/react.js.map +1 -1
  45. package/dist/react.mjs +913 -10
  46. package/dist/react.mjs.map +1 -1
  47. package/dist/styles.css +271 -0
  48. package/dist/svelte.d.mts +26 -0
  49. package/dist/svelte.d.ts +26 -0
  50. package/dist/svelte.js +402 -0
  51. package/dist/svelte.js.map +1 -0
  52. package/dist/svelte.mjs +373 -0
  53. package/dist/svelte.mjs.map +1 -0
  54. package/dist/{system-DvcQjORr.d.ts → system-DhRr_WxR.d.ts} +139 -1
  55. package/dist/{system-CR8p4BjB.d.mts → system-ZD_Wa8Bm.d.mts} +139 -1
  56. package/dist/theme.d.mts +56 -1
  57. package/dist/theme.d.ts +56 -1
  58. package/dist/theme.js +181 -5
  59. package/dist/theme.js.map +1 -1
  60. package/dist/theme.mjs +176 -4
  61. package/dist/theme.mjs.map +1 -1
  62. package/dist/validate-B_iC-4y8.d.mts +19 -0
  63. package/dist/validate-B_iC-4y8.d.ts +19 -0
  64. package/dist/vue.d.mts +28 -0
  65. package/dist/vue.d.ts +28 -0
  66. package/dist/vue.js +420 -0
  67. package/dist/vue.js.map +1 -0
  68. package/dist/vue.mjs +391 -0
  69. package/dist/vue.mjs.map +1 -0
  70. package/package.json +68 -7
  71. package/dist/permissions-B-a3eN0h.d.mts +0 -52
  72. package/dist/permissions-B-a3eN0h.d.ts +0 -52
package/dist/react.js CHANGED
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -20,12 +21,225 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
21
  // src/react.tsx
21
22
  var react_exports = {};
22
23
  __export(react_exports, {
24
+ Account: () => Account,
23
25
  CauthyProvider: () => CauthyProvider,
26
+ ForgotPassword: () => ForgotPassword,
27
+ ResetPassword: () => ResetPassword,
28
+ SignIn: () => SignIn,
29
+ SignUp: () => SignUp,
30
+ VerifyEmail: () => VerifyEmail,
24
31
  can: () => can,
32
+ createAuthClient: () => createAuthClient,
33
+ definePolicy: () => definePolicy,
34
+ useAuth: () => useAuth,
25
35
  useSession: () => useSession
26
36
  });
27
37
  module.exports = __toCommonJS(react_exports);
28
- var import_react = require("react");
38
+ var import_react3 = require("react");
39
+
40
+ // src/theme.ts
41
+ function themeVariables(theme) {
42
+ const vars = {};
43
+ const put = (name, v) => {
44
+ if (v === void 0 || v === "") return;
45
+ vars[`--cauthy-${name}`] = typeof v === "number" ? `${v}px` : v;
46
+ };
47
+ put("surface", theme.surface);
48
+ put("text", theme.text);
49
+ put("text-muted", theme.textMuted);
50
+ put("border", theme.border);
51
+ put("input-background", theme.inputBackground);
52
+ put("input-border", theme.inputBorder);
53
+ put("primary", theme.primary);
54
+ put("primary-text", theme.primaryText);
55
+ put("accent", theme.accent);
56
+ put("error", theme.error);
57
+ put("error-background", theme.errorBackground);
58
+ put("success", theme.success);
59
+ put("success-background", theme.successBackground);
60
+ put("radius", theme.radius);
61
+ put("button-radius", theme.buttonRadius);
62
+ put("font", theme.fontFamily);
63
+ return vars;
64
+ }
65
+ function fromWire(wire) {
66
+ const names = {
67
+ background: "background",
68
+ surface: "surface",
69
+ text: "text",
70
+ text_muted: "textMuted",
71
+ border: "border",
72
+ primary: "primary",
73
+ primary_text: "primaryText",
74
+ accent: "accent",
75
+ error: "error",
76
+ error_background: "errorBackground",
77
+ success: "success",
78
+ success_background: "successBackground",
79
+ radius: "radius",
80
+ button_radius: "buttonRadius",
81
+ font_family: "fontFamily",
82
+ logo_url: "logoUrl",
83
+ fonts: "fonts",
84
+ layout: "layout",
85
+ panel_colors: "panelColors",
86
+ panel_text: "panelText",
87
+ panel_icon_url: "panelIconUrl",
88
+ texts: "texts",
89
+ input_background: "inputBackground",
90
+ input_border: "inputBorder",
91
+ tokens: "tokens"
92
+ };
93
+ const theme = {};
94
+ for (const [k, v] of Object.entries(wire ?? {})) if (names[k] && v !== void 0 && v !== null && v !== "") theme[names[k]] = v;
95
+ return theme;
96
+ }
97
+
98
+ // src/validate.ts
99
+ var MIN_PASSWORD = 8;
100
+ var MAX_PASSWORD = 256;
101
+ var MAX_NAME = 120;
102
+ var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
103
+ function checkEmail(value) {
104
+ const v = typeof value === "string" ? value.trim() : "";
105
+ if (v === "") return { field: "email", code: "required", message: "Enter your email address." };
106
+ if (v.length > 254 || !EMAIL.test(v)) return { field: "email", code: "invalid_email", message: "That does not look like an email address." };
107
+ return null;
108
+ }
109
+ function checkPassword(value) {
110
+ if (typeof value !== "string" || value === "") return { field: "password", code: "required", message: "Enter your password." };
111
+ if (value.length > MAX_PASSWORD) return { field: "password", code: "too_long", message: "That password is too long." };
112
+ return null;
113
+ }
114
+ function checkNewPassword(value) {
115
+ const basic = checkPassword(value);
116
+ if (basic) return basic;
117
+ if (value.length < MIN_PASSWORD) {
118
+ return { field: "password", code: "too_short", message: `Use at least ${MIN_PASSWORD} characters.` };
119
+ }
120
+ return null;
121
+ }
122
+ function checkName(value) {
123
+ if (value === void 0 || value === null || value === "") return null;
124
+ if (typeof value !== "string" || value.trim().length > MAX_NAME) return { field: "name", code: "too_long", message: `Use at most ${MAX_NAME} characters.` };
125
+ return null;
126
+ }
127
+ function checkCode(value) {
128
+ const v = typeof value === "string" ? value.trim() : "";
129
+ if (v === "") return { field: "code", code: "required", message: "Enter the code." };
130
+ if (v.length > 40) return { field: "code", code: "too_long", message: "That is not a code." };
131
+ return null;
132
+ }
133
+ function firstProblem(...checks) {
134
+ return checks.find((c) => c !== null) ?? null;
135
+ }
136
+
137
+ // src/client.ts
138
+ var UNAVAILABLE = { code: "unavailable", message: "Sign-in is unavailable right now. Try again in a moment." };
139
+ function createAuthClient(options = {}) {
140
+ const base = (options.basePath ?? "/api/auth").replace(/\/+$/, "");
141
+ const doFetch = (...args) => (options.fetch ?? fetch)(...args);
142
+ async function post(path, form, bad) {
143
+ if (bad) return { ok: false, error: { code: bad.code, message: bad.message, field: bad.field } };
144
+ try {
145
+ const res = await doFetch(`${base}${path}`, {
146
+ method: "POST",
147
+ credentials: "same-origin",
148
+ headers: { "content-type": "application/json", accept: "application/json" },
149
+ body: JSON.stringify(form)
150
+ });
151
+ const body = await res.json().catch(() => null);
152
+ if (res.ok && body && typeof body.status === "string") return { ok: true, ...body };
153
+ const e = body?.error;
154
+ return { ok: false, error: e && typeof e.code === "string" ? { code: e.code, message: String(e.message ?? ""), ...e.field ? { field: e.field } : {} } : UNAVAILABLE };
155
+ } catch {
156
+ return { ok: false, error: UNAVAILABLE };
157
+ }
158
+ }
159
+ let looked;
160
+ async function read(path) {
161
+ try {
162
+ const res = await doFetch(`${base}${path}`, { credentials: "same-origin", headers: { accept: "application/json" } });
163
+ return res.ok ? await res.json() : null;
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+ const socialUrl = (provider, o = {}) => `${base}/sign-in/social/${encodeURIComponent(provider)}${o.returnTo ? `?return_to=${encodeURIComponent(o.returnTo)}` : ""}`;
169
+ const passkeyUrl = (o = {}) => {
170
+ const q = new URLSearchParams({ method: "passkey" });
171
+ if (o.returnTo) q.set("return_to", o.returnTo);
172
+ if (o.locale) q.set("locale", o.locale);
173
+ return `${base}/login?${q}`;
174
+ };
175
+ return {
176
+ signIn: {
177
+ email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password))),
178
+ mfa: (f) => post("/sign-in/mfa", { code: f.code }, checkCode(f.code)),
179
+ magicLink: (f) => post("/sign-in/magic-link", { email: f.email }, checkEmail(f.email)),
180
+ magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
181
+ emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
182
+ passkeyUrl,
183
+ passkey(o) {
184
+ window.location.assign(passkeyUrl(o));
185
+ },
186
+ socialUrl,
187
+ social(provider, o) {
188
+ window.location.assign(socialUrl(provider, o));
189
+ }
190
+ },
191
+ signUp: {
192
+ email: (f) => post("/sign-up/email", { email: f.email, password: f.password, name: f.name }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password)))
193
+ },
194
+ forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email)),
195
+ resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password)),
196
+ verifyEmail: (f) => post("/verify-email", { token: f.token }, null),
197
+ signOut: () => post("/sign-out", {}, null),
198
+ account: {
199
+ async get() {
200
+ const body = await read("/me");
201
+ return body && typeof body.account === "object" ? body.account : null;
202
+ },
203
+ update: (f) => post("/me", { name: f.name }, checkName(f.name)),
204
+ changePassword: (f) => {
205
+ const bad = checkNewPassword(f.newPassword);
206
+ return post("/me/password", { password: f.password, newPassword: f.newPassword }, bad ? { ...bad, field: "newPassword" } : null);
207
+ },
208
+ mfa: {
209
+ setup: (f) => post("/me/mfa/setup", { password: f.password }, null),
210
+ verify: (f) => post("/me/mfa/verify", { code: f.code }, checkCode(f.code)),
211
+ disable: (f) => post("/me/mfa/disable", { password: f.password }, null),
212
+ recoveryCodes: (f) => post("/me/mfa/recovery-codes", { password: f.password }, null)
213
+ },
214
+ async sessions() {
215
+ const body = await read("/me/sessions");
216
+ return body && Array.isArray(body.sessions) ? body.sessions : [];
217
+ },
218
+ signOutEverywhere: () => post("/me/sessions/end", {}, null)
219
+ },
220
+ async getSession() {
221
+ try {
222
+ const res = await doFetch(`${base}/session`, { credentials: "same-origin", headers: { accept: "application/json" } });
223
+ if (!res.ok) return null;
224
+ return (await res.json()).session;
225
+ } catch {
226
+ return null;
227
+ }
228
+ },
229
+ look() {
230
+ looked ??= read("/look").then((wire) => wire && typeof wire === "object" ? fromWire(wire) : null);
231
+ return looked;
232
+ },
233
+ async methods() {
234
+ try {
235
+ const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
236
+ if (res.ok) return await res.json();
237
+ } catch {
238
+ }
239
+ return { providers: [], signUp: true, magicLink: false, passkey: false };
240
+ }
241
+ };
242
+ }
29
243
 
30
244
  // src/permissions.ts
31
245
  function matches(entry, resource, action) {
@@ -40,19 +254,679 @@ function can(organization, resource, action) {
40
254
  if ((organization.denied ?? []).some((d) => matches(d, resource, action))) return false;
41
255
  return organization.permissions.some((p) => matches(p, resource, action));
42
256
  }
257
+ function definePolicy(roles) {
258
+ const entry = (p) => `${p.resource}:${p.action}`;
259
+ const byName = new Map(roles.map((r) => [r.role, r]));
260
+ const split = (permission) => {
261
+ const i = permission.lastIndexOf(":");
262
+ return i < 0 ? [permission, ""] : [permission.slice(0, i), permission.slice(i + 1)];
263
+ };
264
+ return {
265
+ roles: roles.map((r) => ({
266
+ role: r.role,
267
+ ...r.name !== void 0 ? { name: r.name } : {},
268
+ ...r.description !== void 0 ? { description: r.description } : {},
269
+ ...r.inherits ? { inherits: [...r.inherits] } : {},
270
+ ...r.permissions ? { permissions: r.permissions.map((p) => ({ ...p })) } : {}
271
+ })),
272
+ roleNames: roles.map((r) => r.role),
273
+ permissions: [...new Set(roles.flatMap((r) => (r.permissions ?? []).map(entry)))],
274
+ can(organization, permission) {
275
+ const [resource, action] = split(permission);
276
+ return can(organization, resource, action);
277
+ },
278
+ hasRole(organization, ...wanted) {
279
+ return organization != null && wanted.includes(organization.role);
280
+ },
281
+ permissionsOf(role) {
282
+ const seen = /* @__PURE__ */ new Set();
283
+ const out = /* @__PURE__ */ new Set();
284
+ const walk = (name) => {
285
+ if (seen.has(name)) return;
286
+ seen.add(name);
287
+ const r = byName.get(name);
288
+ for (const p of r?.permissions ?? []) out.add(entry(p));
289
+ for (const parent of r?.inherits ?? []) walk(parent);
290
+ };
291
+ walk(role);
292
+ return [...out];
293
+ }
294
+ };
295
+ }
43
296
 
44
- // src/react.tsx
297
+ // src/components.tsx
298
+ var import_react = require("react");
45
299
  var import_jsx_runtime = require("react/jsx-runtime");
46
- var Ctx = (0, import_react.createContext)(null);
300
+ var TEXTS = {
301
+ en: {
302
+ email: "Email",
303
+ password: "Password",
304
+ name: "Name",
305
+ code: "Code",
306
+ signIn: "Sign in",
307
+ signUp: "Create account",
308
+ continueWith: "Continue with",
309
+ orWithEmail: "or with your email",
310
+ forgotPassword: "Forgot your password?",
311
+ noAccount: "No account yet?",
312
+ createAccount: "Create one",
313
+ haveAccount: "Already have an account?",
314
+ sendLink: "Send the link",
315
+ linkSent: "If that address has an account, a link to choose a new password is on its way.",
316
+ codeHelp: "Enter the code from your authenticator app, or a recovery code.",
317
+ verify: "Verify",
318
+ checkEmail: "Almost there: open the link we sent to confirm your address, then sign in.",
319
+ backToSignIn: "Back to sign in",
320
+ emailMeALink: "Email me a sign-in link",
321
+ usePasskey: "Sign in with a passkey",
322
+ codeFromMail: "Reading it on another device? Enter the code from the email.",
323
+ signInLinkSent: "If that address has an account, a link to sign in is on its way.",
324
+ newPassword: "New password",
325
+ savePassword: "Save the password",
326
+ passwordChanged: "Your password was changed. You can sign in with it now.",
327
+ confirming: "Confirming your address\u2026",
328
+ emailConfirmed: "Your address is confirmed. You can sign in now.",
329
+ profile: "Profile",
330
+ save: "Save",
331
+ saved: "Saved.",
332
+ currentPassword: "Current password",
333
+ changePassword: "Change password",
334
+ passwordChangedHere: "Your password was changed. Every other session of your account was signed out.",
335
+ secondStep: "Second step",
336
+ secondStepOn: "On: signing in also asks for a code from your authenticator app.",
337
+ secondStepOff: "Off: signing in asks only for your password.",
338
+ turnOn: "Turn on",
339
+ turnOff: "Turn off",
340
+ addToApp: "Add this key to your authenticator app, then enter the code it shows.",
341
+ openInApp: "Open in an authenticator app",
342
+ recoveryCodes: "Recovery codes",
343
+ newRecoveryCodes: "New recovery codes",
344
+ keepCodes: "Keep these codes somewhere safe. Each works once, and they are not shown again.",
345
+ passkeys: "Passkeys",
346
+ managePasskeys: "Add or remove a passkey",
347
+ sessions: "Where you are signed in",
348
+ signOutEverywhere: "Sign out everywhere",
349
+ unknownDevice: "Unknown device",
350
+ errors: {
351
+ required: "This is required.",
352
+ invalid_email: "That does not look like an email address.",
353
+ too_short: "Use at least 8 characters.",
354
+ invalid_credentials: "The address or the password is not right.",
355
+ unavailable: "Sign-in is unavailable right now. Try again in a moment.",
356
+ state_mismatch: "That sign-in could not be completed. Try again.",
357
+ cancelled: "The sign-in was cancelled.",
358
+ invalid_token: "This link is not valid, or it has expired. Ask for a new one.",
359
+ invalid_code: "That code is not right, or it has expired.",
360
+ wrong_password: "The current password is not right.",
361
+ step_up_required: "Enter your current password.",
362
+ weak_password: "Choose a stronger password.",
363
+ unauthenticated: "Sign in first."
364
+ }
365
+ },
366
+ es: {
367
+ email: "Correo",
368
+ password: "Contrase\xF1a",
369
+ name: "Nombre",
370
+ code: "C\xF3digo",
371
+ signIn: "Entrar",
372
+ signUp: "Crear cuenta",
373
+ continueWith: "Continuar con",
374
+ orWithEmail: "o con tu correo",
375
+ forgotPassword: "\xBFOlvidaste tu contrase\xF1a?",
376
+ noAccount: "\xBFNo tienes cuenta?",
377
+ createAccount: "Crear una",
378
+ haveAccount: "\xBFYa tienes cuenta?",
379
+ sendLink: "Enviar el enlace",
380
+ linkSent: "Si ese correo tiene una cuenta, ya va en camino un enlace para elegir una contrase\xF1a nueva.",
381
+ codeHelp: "Escribe el c\xF3digo de tu aplicaci\xF3n de autenticaci\xF3n, o un c\xF3digo de recuperaci\xF3n.",
382
+ verify: "Verificar",
383
+ checkEmail: "Ya casi: abre el enlace que te enviamos para confirmar tu correo y luego entra.",
384
+ backToSignIn: "Volver a entrar",
385
+ emailMeALink: "Env\xEDame un enlace para entrar",
386
+ usePasskey: "Entrar con una llave de acceso",
387
+ codeFromMail: "\xBFLo est\xE1s leyendo en otro dispositivo? Escribe el c\xF3digo que trae el correo.",
388
+ signInLinkSent: "Si ese correo tiene una cuenta, ya va en camino un enlace para entrar.",
389
+ newPassword: "Contrase\xF1a nueva",
390
+ savePassword: "Guardar la contrase\xF1a",
391
+ passwordChanged: "Tu contrase\xF1a se cambi\xF3. Ya puedes entrar con ella.",
392
+ confirming: "Confirmando tu correo\u2026",
393
+ emailConfirmed: "Tu correo qued\xF3 confirmado. Ya puedes entrar.",
394
+ profile: "Perfil",
395
+ save: "Guardar",
396
+ saved: "Guardado.",
397
+ currentPassword: "Contrase\xF1a actual",
398
+ changePassword: "Cambiar contrase\xF1a",
399
+ passwordChangedHere: "Tu contrase\xF1a se cambi\xF3. Se cerraron todas las dem\xE1s sesiones de tu cuenta.",
400
+ secondStep: "Segundo paso",
401
+ secondStepOn: "Activo: al entrar tambi\xE9n se pide un c\xF3digo de tu aplicaci\xF3n de autenticaci\xF3n.",
402
+ secondStepOff: "Inactivo: al entrar solo se pide tu contrase\xF1a.",
403
+ turnOn: "Activar",
404
+ turnOff: "Desactivar",
405
+ addToApp: "Agrega esta clave a tu aplicaci\xF3n de autenticaci\xF3n y escribe el c\xF3digo que muestra.",
406
+ openInApp: "Abrir en una aplicaci\xF3n de autenticaci\xF3n",
407
+ recoveryCodes: "C\xF3digos de recuperaci\xF3n",
408
+ newRecoveryCodes: "Nuevos c\xF3digos de recuperaci\xF3n",
409
+ keepCodes: "Guarda estos c\xF3digos en un lugar seguro. Cada uno sirve una vez y no se vuelven a mostrar.",
410
+ passkeys: "Llaves de acceso",
411
+ managePasskeys: "Agregar o quitar una llave de acceso",
412
+ sessions: "D\xF3nde tienes la sesi\xF3n abierta",
413
+ signOutEverywhere: "Cerrar sesi\xF3n en todas partes",
414
+ unknownDevice: "Dispositivo desconocido",
415
+ errors: {
416
+ required: "Este dato es obligatorio.",
417
+ invalid_email: "Eso no parece un correo.",
418
+ too_short: "Usa al menos 8 caracteres.",
419
+ invalid_credentials: "El correo o la contrase\xF1a no son correctos.",
420
+ unavailable: "No se puede entrar en este momento. Intenta de nuevo en un momento.",
421
+ state_mismatch: "No se pudo completar el ingreso. Intenta de nuevo.",
422
+ cancelled: "El ingreso se cancel\xF3.",
423
+ invalid_token: "Este enlace no es v\xE1lido o ya venci\xF3. Pide uno nuevo.",
424
+ invalid_code: "Ese c\xF3digo no es correcto o ya venci\xF3.",
425
+ wrong_password: "La contrase\xF1a actual no es correcta.",
426
+ step_up_required: "Escribe tu contrase\xF1a actual.",
427
+ weak_password: "Elige una contrase\xF1a m\xE1s fuerte.",
428
+ unauthenticated: "Inicia sesi\xF3n primero."
429
+ }
430
+ }
431
+ };
432
+ var PlainButton = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { ...p });
433
+ var PlainInput = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { ...p });
434
+ var looks = /* @__PURE__ */ new Map();
435
+ var loaded = /* @__PURE__ */ new Set();
436
+ function loadFonts(fonts) {
437
+ if (typeof document === "undefined" || typeof FontFace === "undefined") return;
438
+ for (const f of fonts ?? []) {
439
+ const key = `${f.family}|${f.url}|${f.weight ?? ""}|${f.style ?? ""}`;
440
+ if (loaded.has(key) || !/^[A-Za-z0-9][A-Za-z0-9 _-]*$/.test(f.family) || !/^https:\/\/[^\s"'()]+\.woff2$/i.test(f.url)) continue;
441
+ loaded.add(key);
442
+ const face = new FontFace(f.family, `url("${f.url}") format("woff2")`, { weight: String(f.weight ?? "400"), style: f.style === "italic" ? "italic" : "normal", display: "swap" });
443
+ void face.load().then((ready) => document.fonts.add(ready), () => loaded.delete(key));
444
+ }
445
+ }
446
+ function useRoot(props) {
447
+ const { basePath } = useSession();
448
+ const [vars, setVars] = (0, import_react.useState)({});
449
+ const wanted = !props.unstyled && props.workspaceLook !== false;
450
+ (0, import_react.useEffect)(() => {
451
+ if (!wanted) return;
452
+ let live = true;
453
+ let look = looks.get(basePath);
454
+ if (!look) {
455
+ look = createAuthClient({ basePath }).look();
456
+ looks.set(basePath, look);
457
+ }
458
+ void look.then((theme) => {
459
+ if (!live || !theme) return;
460
+ setVars(themeVariables(theme));
461
+ loadFonts(theme.fonts);
462
+ });
463
+ return () => {
464
+ live = false;
465
+ };
466
+ }, [basePath, wanted]);
467
+ if (props.unstyled) return {};
468
+ return { "data-cauthy": "", style: vars };
469
+ }
470
+ function useForm(props) {
471
+ const t = { ...TEXTS[props.locale ?? "en"], ...props.texts, errors: { ...TEXTS[props.locale ?? "en"].errors, ...props.texts?.errors } };
472
+ const cx = (slot) => props.classNames?.[slot] ?? `cauthy-${slot}`;
473
+ const Button = props.components?.Button ?? PlainButton;
474
+ const Input = props.components?.Input ?? PlainInput;
475
+ const [error, setError] = (0, import_react.useState)(null);
476
+ const [busy, setBusy] = (0, import_react.useState)(false);
477
+ const say = (e) => t.errors[e.code] ?? e.message;
478
+ const done = () => {
479
+ if (props.onSuccess) props.onSuccess();
480
+ else window.location.assign(props.returnTo ?? "/");
481
+ };
482
+ const root = useRoot(props);
483
+ return { t, cx, Button, Input, error, setError, busy, setBusy, say, done, root };
484
+ }
485
+ function Field(p) {
486
+ const id = (0, import_react.useId)();
487
+ const { f } = p;
488
+ const bad = f.error?.field === p.name ? f.error : null;
489
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: f.cx("field"), children: [
490
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: f.cx("label"), htmlFor: id, children: [
491
+ p.label,
492
+ p.aside
493
+ ] }),
494
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
495
+ f.Input,
496
+ {
497
+ id,
498
+ name: p.name,
499
+ type: p.type,
500
+ value: p.value,
501
+ autoComplete: p.autoComplete,
502
+ required: p.required,
503
+ className: f.cx("input"),
504
+ "aria-invalid": bad ? true : void 0,
505
+ "aria-describedby": bad ? `${id}-error` : void 0,
506
+ onChange: (e) => p.onChange(e.target.value)
507
+ }
508
+ ),
509
+ bad && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { id: `${id}-error`, role: "alert", className: f.cx("fieldError"), children: f.say(bad) })
510
+ ] });
511
+ }
512
+ function returnedError() {
513
+ if (typeof window === "undefined") return null;
514
+ const code = new URLSearchParams(window.location.search).get("auth_error");
515
+ return code ? { code, message: "That sign-in could not be completed. Try again." } : null;
516
+ }
517
+ function takeToken(name) {
518
+ if (typeof window === "undefined") return null;
519
+ const url = new URL(window.location.href);
520
+ const token = url.searchParams.get(name);
521
+ if (token === null) return null;
522
+ url.searchParams.delete(name);
523
+ window.history.replaceState(window.history.state, "", url.pathname + url.search + url.hash);
524
+ return token;
525
+ }
526
+ function useMailedToken(name, open) {
527
+ const opened = (0, import_react.useRef)(false);
528
+ (0, import_react.useEffect)(() => {
529
+ if (opened.current) return;
530
+ opened.current = true;
531
+ open(takeToken(name));
532
+ }, []);
533
+ }
534
+ function SignIn(props) {
535
+ const auth = useAuth();
536
+ const f = useForm(props);
537
+ const [methods, setMethods] = (0, import_react.useState)(null);
538
+ const [email, setEmail] = (0, import_react.useState)("");
539
+ const [password, setPassword] = (0, import_react.useState)("");
540
+ const [code, setCode] = (0, import_react.useState)("");
541
+ const [step, setStep] = (0, import_react.useState)("password");
542
+ (0, import_react.useEffect)(() => {
543
+ void auth.methods().then(setMethods);
544
+ const back = returnedError();
545
+ if (back) f.setError(back);
546
+ }, []);
547
+ useMailedToken("ml_token", (token) => {
548
+ if (!token) return;
549
+ setStep("opening");
550
+ void auth.signIn.magicLinkVerify({ token }).then((r) => {
551
+ if (r.ok) return f.done();
552
+ f.setError(r.error);
553
+ setStep("password");
554
+ });
555
+ });
556
+ const submit = async (e) => {
557
+ e.preventDefault();
558
+ f.setBusy(true);
559
+ f.setError(null);
560
+ const r = step === "code" ? await auth.signIn.mfa({ code }) : step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password });
561
+ f.setBusy(false);
562
+ if (!r.ok) return f.setError(r.error);
563
+ if (r.status === "mfa_required") return setStep("code");
564
+ f.done();
565
+ };
566
+ const sendLink = async () => {
567
+ f.setBusy(true);
568
+ f.setError(null);
569
+ const r = await auth.signIn.magicLink({ email });
570
+ f.setBusy(false);
571
+ if (!r.ok) return f.setError(r.error);
572
+ setStep("sent");
573
+ };
574
+ const general = f.error && !f.error.field ? f.error : null;
575
+ if (step === "opening") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: f.cx("form"), ...f.root, "aria-busy": "true" });
576
+ if (step === "sent") {
577
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
578
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.signInLinkSent }),
579
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
580
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.codeFromMail }),
581
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
583
+ ] });
584
+ }
585
+ if (step === "code") {
586
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
587
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.codeHelp }),
588
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
589
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
591
+ ] });
592
+ }
593
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
594
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
595
+ (methods?.providers ?? []).map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(f.Button, { type: "button", className: f.cx("providerButton"), onClick: () => auth.signIn.social(p.id, { returnTo: props.returnTo }), children: [
596
+ f.t.continueWith,
597
+ " ",
598
+ p.name
599
+ ] }, p.id)),
600
+ methods?.passkey && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", className: f.cx("providerButton"), onClick: () => auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale }), children: f.t.usePasskey }),
601
+ ((methods?.providers.length ?? 0) > 0 || methods?.passkey) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("separator"), children: f.t.orWithEmail }),
602
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
604
+ Field,
605
+ {
606
+ f,
607
+ name: "password",
608
+ type: "password",
609
+ label: f.t.password,
610
+ value: password,
611
+ autoComplete: "current-password",
612
+ required: true,
613
+ onChange: setPassword,
614
+ aside: props.links?.forgotPassword ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: props.links.forgotPassword, children: f.t.forgotPassword }) : void 0
615
+ }
616
+ ),
617
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signIn }),
618
+ methods?.magicLink && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendLink(), children: f.t.emailMeALink }),
619
+ methods?.signUp !== false && props.links?.signUp && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: f.cx("footer"), children: [
620
+ f.t.noAccount,
621
+ " ",
622
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: props.links.signUp, children: f.t.createAccount })
623
+ ] })
624
+ ] });
625
+ }
626
+ function SignUp(props) {
627
+ const auth = useAuth();
628
+ const f = useForm(props);
629
+ const [name, setName] = (0, import_react.useState)("");
630
+ const [email, setEmail] = (0, import_react.useState)("");
631
+ const [password, setPassword] = (0, import_react.useState)("");
632
+ const [sent, setSent] = (0, import_react.useState)(false);
633
+ const submit = async (e) => {
634
+ e.preventDefault();
635
+ f.setBusy(true);
636
+ f.setError(null);
637
+ const r = await auth.signUp.email({ name, email, password });
638
+ f.setBusy(false);
639
+ if (!r.ok) return f.setError(r.error);
640
+ if (r.status === "verification_required") return setSent(true);
641
+ f.done();
642
+ };
643
+ const general = f.error && !f.error.field ? f.error : null;
644
+ if (sent) {
645
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: f.cx("form"), ...f.root, children: [
646
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.checkEmail }),
647
+ props.links?.signIn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: props.links.signIn, children: f.t.backToSignIn })
648
+ ] });
649
+ }
650
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
651
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
652
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "name", onChange: setName }),
653
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
654
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "password", type: "password", label: f.t.password, value: password, autoComplete: "new-password", required: true, onChange: setPassword }),
655
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signUp }),
656
+ props.links?.signIn && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: f.cx("footer"), children: [
657
+ f.t.haveAccount,
658
+ " ",
659
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: props.links.signIn, children: f.t.signIn })
660
+ ] })
661
+ ] });
662
+ }
663
+ function ForgotPassword(props) {
664
+ const auth = useAuth();
665
+ const f = useForm(props);
666
+ const [email, setEmail] = (0, import_react.useState)("");
667
+ const [sent, setSent] = (0, import_react.useState)(false);
668
+ const submit = async (e) => {
669
+ e.preventDefault();
670
+ f.setBusy(true);
671
+ f.setError(null);
672
+ const r = await auth.forgotPassword({ email });
673
+ f.setBusy(false);
674
+ if (!r.ok) return f.setError(r.error);
675
+ setSent(true);
676
+ };
677
+ const general = f.error && !f.error.field ? f.error : null;
678
+ if (sent) {
679
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: f.cx("form"), ...f.root, children: [
680
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.linkSent }),
681
+ props.links?.signIn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: props.links.signIn, children: f.t.backToSignIn })
682
+ ] });
683
+ }
684
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
685
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
686
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
687
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.sendLink }),
688
+ props.links?.signIn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("footer"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: props.links.signIn, children: f.t.backToSignIn }) })
689
+ ] });
690
+ }
691
+ function ResetPassword(props) {
692
+ const auth = useAuth();
693
+ const f = useForm(props);
694
+ const [token, setToken] = (0, import_react.useState)(null);
695
+ const [password, setPassword] = (0, import_react.useState)("");
696
+ const [changed, setChanged] = (0, import_react.useState)(false);
697
+ useMailedToken("token", (t) => {
698
+ setToken(t ?? "");
699
+ if (!t) f.setError({ code: "invalid_token", message: "This link is not valid, or it has expired." });
700
+ });
701
+ const submit = async (e) => {
702
+ e.preventDefault();
703
+ f.setBusy(true);
704
+ f.setError(null);
705
+ const r = await auth.resetPassword({ token: token ?? "", password });
706
+ f.setBusy(false);
707
+ if (!r.ok) return f.setError(r.error);
708
+ setChanged(true);
709
+ };
710
+ const general = f.error && !f.error.field ? f.error : null;
711
+ const back = props.links?.signIn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: props.links.signIn, children: f.t.backToSignIn });
712
+ if (changed) {
713
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: f.cx("form"), ...f.root, children: [
714
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.passwordChanged }),
715
+ back
716
+ ] });
717
+ }
718
+ if (token === "") {
719
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: f.cx("form"), ...f.root, children: [
720
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
721
+ props.links?.forgotPassword && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: props.links.forgotPassword, children: f.t.forgotPassword })
722
+ ] });
723
+ }
724
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
725
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
726
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "password", type: "password", label: f.t.newPassword, value: password, autoComplete: "new-password", required: true, onChange: setPassword }),
727
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy || token === null, className: f.cx("button"), children: f.t.savePassword })
728
+ ] });
729
+ }
730
+ function VerifyEmail(props) {
731
+ const auth = useAuth();
732
+ const f = useForm(props);
733
+ const [state, setState] = (0, import_react.useState)("confirming");
734
+ useMailedToken("token", (token) => {
735
+ if (!token) {
736
+ f.setError({ code: "invalid_token", message: "This link is not valid, or it has expired." });
737
+ return setState("failed");
738
+ }
739
+ void auth.verifyEmail({ token }).then((r) => {
740
+ if (r.ok) return setState("confirmed");
741
+ f.setError(r.error);
742
+ setState("failed");
743
+ });
744
+ });
745
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: f.cx("form"), ...f.root, "aria-busy": state === "confirming", children: [
746
+ state === "confirming" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.confirming }),
747
+ state === "confirmed" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.emailConfirmed }),
748
+ state === "failed" && f.error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(f.error) }),
749
+ state !== "confirming" && props.links?.signIn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: props.links.signIn, children: f.t.backToSignIn })
750
+ ] });
751
+ }
752
+ function useSection(props) {
753
+ const f = useForm({ ...props, unstyled: true });
754
+ const [done, setDone] = (0, import_react.useState)(null);
755
+ const run = async (action) => {
756
+ f.setBusy(true);
757
+ f.setError(null);
758
+ setDone(null);
759
+ const r = await action();
760
+ f.setBusy(false);
761
+ if (!r.ok) f.setError(r.error);
762
+ return r;
763
+ };
764
+ const general = f.error && !f.error.field ? f.error : null;
765
+ return { f, done, setDone, run, general };
766
+ }
767
+ function Account(props) {
768
+ const auth = useAuth();
769
+ const { accountUrl } = useSession();
770
+ const { t, root } = useForm(props);
771
+ const cx = (slot) => props.classNames?.[slot] ?? `cauthy-${slot}`;
772
+ const [account, setAccount] = (0, import_react.useState)(null);
773
+ const [sessions, setSessions] = (0, import_react.useState)([]);
774
+ (0, import_react.useEffect)(() => {
775
+ void auth.account.get().then(setAccount);
776
+ void auth.account.sessions().then(setSessions);
777
+ }, []);
778
+ if (account === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cx("account"), ...root, "aria-busy": "true" });
779
+ const when = props.formatDate ?? ((iso) => new Date(iso).toLocaleString());
780
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cx("account"), ...root, children: [
781
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProfilePart, { props, account, onSaved: setAccount }),
782
+ account.hasPassword && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PasswordPart, { props }),
783
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SecondStepPart, { props, account, onChanged: (mfaEnabled) => setAccount({ ...account, mfaEnabled }) }),
784
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: cx("section"), children: [
785
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: cx("heading"), children: t.passkeys }),
786
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: cx("link"), href: accountUrl(), children: t.managePasskeys })
787
+ ] }),
788
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: cx("section"), children: [
789
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: cx("heading"), children: t.sessions }),
790
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: cx("sessions"), children: sessions.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: cx("session"), children: [
791
+ s.device || t.unknownDevice,
792
+ " \xB7 ",
793
+ s.ip,
794
+ " \xB7 ",
795
+ when(s.createdAt)
796
+ ] }, i)) }),
797
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SignOutEverywhere, { props })
798
+ ] })
799
+ ] });
800
+ }
801
+ function ProfilePart({ props, account, onSaved }) {
802
+ const auth = useAuth();
803
+ const { f, done, setDone, run, general } = useSection(props);
804
+ const [name, setName] = (0, import_react.useState)(account.name);
805
+ const submit = async (e) => {
806
+ e.preventDefault();
807
+ const r = await run(() => auth.account.update({ name }));
808
+ if (!r.ok) return;
809
+ onSaved(r.account);
810
+ setDone(f.t.saved);
811
+ };
812
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("section"), onSubmit: submit, noValidate: true, children: [
813
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: f.cx("heading"), children: f.t.profile }),
814
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: account.email }),
815
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
816
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "name", onChange: setName }),
817
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.save }),
818
+ done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", className: f.cx("notice"), children: done })
819
+ ] });
820
+ }
821
+ function PasswordPart({ props }) {
822
+ const auth = useAuth();
823
+ const { f, done, setDone, run, general } = useSection(props);
824
+ const [password, setPassword] = (0, import_react.useState)("");
825
+ const [newPassword, setNewPassword] = (0, import_react.useState)("");
826
+ const submit = async (e) => {
827
+ e.preventDefault();
828
+ const r = await run(() => auth.account.changePassword({ password, newPassword }));
829
+ if (!r.ok) return;
830
+ setPassword("");
831
+ setNewPassword("");
832
+ setDone(f.t.passwordChangedHere);
833
+ };
834
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("section"), onSubmit: submit, noValidate: true, children: [
835
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: f.cx("heading"), children: f.t.changePassword }),
836
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
837
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword }),
838
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "newPassword", type: "password", label: f.t.newPassword, value: newPassword, autoComplete: "new-password", required: true, onChange: setNewPassword }),
839
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.changePassword }),
840
+ done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", className: f.cx("notice"), children: done })
841
+ ] });
842
+ }
843
+ function SecondStepPart({ props, account, onChanged }) {
844
+ const auth = useAuth();
845
+ const { f, run, general } = useSection(props);
846
+ const [password, setPassword] = (0, import_react.useState)("");
847
+ const [code, setCode] = (0, import_react.useState)("");
848
+ const [setup, setSetup] = (0, import_react.useState)(null);
849
+ const [codes, setCodes] = (0, import_react.useState)(null);
850
+ const start = async (e) => {
851
+ e.preventDefault();
852
+ const r = await run(() => auth.account.mfa.setup({ password }));
853
+ if (r.ok) setSetup({ secret: r.secret, otpauthUri: r.otpauthUri });
854
+ };
855
+ const confirm = async (e) => {
856
+ e.preventDefault();
857
+ const r = await run(() => auth.account.mfa.verify({ code }));
858
+ if (!r.ok) return;
859
+ setSetup(null);
860
+ setCode("");
861
+ setPassword("");
862
+ setCodes(r.recoveryCodes);
863
+ onChanged(true);
864
+ };
865
+ const turnOff = async (e) => {
866
+ e.preventDefault();
867
+ const r = await run(() => auth.account.mfa.disable({ password }));
868
+ if (!r.ok) return;
869
+ setPassword("");
870
+ setCodes(null);
871
+ onChanged(false);
872
+ };
873
+ const renew = async () => {
874
+ const r = await run(() => auth.account.mfa.recoveryCodes({ password }));
875
+ if (r.ok) setCodes(r.recoveryCodes);
876
+ };
877
+ const passwordField = account.hasPassword && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword });
878
+ const shown = codes && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
879
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.keepCodes }),
880
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: f.cx("codes"), children: codes.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: c }) }, c)) })
881
+ ] });
882
+ if (setup) {
883
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("section"), onSubmit: confirm, noValidate: true, children: [
884
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: f.cx("heading"), children: f.t.secondStep }),
885
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.addToApp }),
886
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: f.cx("secret"), children: setup.secret }),
887
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: setup.otpauthUri, children: f.t.openInApp }),
888
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
889
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
890
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
891
+ ] });
892
+ }
893
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("section"), onSubmit: account.mfaEnabled ? turnOff : start, noValidate: true, children: [
894
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: f.cx("heading"), children: f.t.secondStep }),
895
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: account.mfaEnabled ? f.t.secondStepOn : f.t.secondStepOff }),
896
+ shown,
897
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
898
+ passwordField,
899
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: account.mfaEnabled ? f.t.turnOff : f.t.turnOn }),
900
+ account.mfaEnabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void renew(), children: f.t.newRecoveryCodes })
901
+ ] });
902
+ }
903
+ function SignOutEverywhere({ props }) {
904
+ const auth = useAuth();
905
+ const { f, run, general } = useSection(props);
906
+ const end = async () => {
907
+ const r = await run(() => auth.account.signOutEverywhere());
908
+ if (!r.ok) return;
909
+ if (props.onSignedOut) props.onSignedOut();
910
+ else window.location.assign("/");
911
+ };
912
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
913
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
914
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("button"), onClick: () => void end(), children: f.t.signOutEverywhere })
915
+ ] });
916
+ }
917
+
918
+ // src/react.tsx
919
+ var import_jsx_runtime2 = require("react/jsx-runtime");
920
+ var Ctx = (0, import_react3.createContext)(null);
47
921
  var AHEAD_MS = 6e4;
48
922
  function CauthyProvider({ children, basePath = "/api/auth", initialSession }) {
49
923
  const base = basePath.replace(/\/+$/, "");
50
- const [session, setSession] = (0, import_react.useState)(initialSession ?? null);
51
- const [status, setStatus] = (0, import_react.useState)(
924
+ const [session, setSession] = (0, import_react3.useState)(initialSession ?? null);
925
+ const [status, setStatus] = (0, import_react3.useState)(
52
926
  initialSession === void 0 ? "loading" : initialSession === null ? "unauthenticated" : "authenticated"
53
927
  );
54
- const asking = (0, import_react.useRef)(null);
55
- const refresh = (0, import_react.useCallback)(() => {
928
+ const asking = (0, import_react3.useRef)(null);
929
+ const refresh = (0, import_react3.useCallback)(() => {
56
930
  asking.current ??= (async () => {
57
931
  try {
58
932
  const res = await fetch(`${base}/session`, { credentials: "same-origin", headers: { accept: "application/json" } });
@@ -72,23 +946,23 @@ function CauthyProvider({ children, basePath = "/api/auth", initialSession }) {
72
946
  })();
73
947
  return asking.current;
74
948
  }, [base]);
75
- (0, import_react.useEffect)(() => {
949
+ (0, import_react3.useEffect)(() => {
76
950
  if (initialSession === void 0) void refresh();
77
951
  }, [initialSession, refresh]);
78
- (0, import_react.useEffect)(() => {
952
+ (0, import_react3.useEffect)(() => {
79
953
  if (!session) return;
80
954
  const wait = Math.max(new Date(session.expiresAt).getTime() - Date.now() - AHEAD_MS, 5e3);
81
955
  const timer = setTimeout(() => void refresh(), wait);
82
956
  return () => clearTimeout(timer);
83
957
  }, [session, refresh]);
84
- (0, import_react.useEffect)(() => {
958
+ (0, import_react3.useEffect)(() => {
85
959
  const onVisible = () => {
86
960
  if (document.visibilityState === "visible") void refresh();
87
961
  };
88
962
  document.addEventListener("visibilitychange", onVisible);
89
963
  return () => document.removeEventListener("visibilitychange", onVisible);
90
964
  }, [refresh]);
91
- const value = (0, import_react.useMemo)(
965
+ const value = (0, import_react3.useMemo)(
92
966
  () => ({
93
967
  session,
94
968
  status,
@@ -105,21 +979,58 @@ function CauthyProvider({ children, basePath = "/api/auth", initialSession }) {
105
979
  accountUrl(returnTo) {
106
980
  const back = returnTo ?? (typeof window === "undefined" ? "/" : window.location.pathname + window.location.search);
107
981
  return `${base}/account?return_to=${encodeURIComponent(back)}`;
108
- }
982
+ },
983
+ basePath: base
109
984
  }),
110
985
  [session, status, refresh, base]
111
986
  );
112
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ctx.Provider, { value, children });
987
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Ctx.Provider, { value, children });
113
988
  }
114
989
  function useSession() {
115
- const ctx = (0, import_react.useContext)(Ctx);
990
+ const ctx = (0, import_react3.useContext)(Ctx);
116
991
  if (ctx === null) throw new Error("cauthy-auth: useSession needs <CauthyProvider> above it");
117
992
  return ctx;
118
993
  }
994
+ function useAuth() {
995
+ const { session, status, refresh, basePath } = useSession();
996
+ return (0, import_react3.useMemo)(() => {
997
+ const client = createAuthClient({ basePath });
998
+ const then = async (running) => {
999
+ const r = await running;
1000
+ await refresh();
1001
+ return r;
1002
+ };
1003
+ return {
1004
+ ...client,
1005
+ signIn: { ...client.signIn, email: (f) => then(client.signIn.email(f)), mfa: (f) => then(client.signIn.mfa(f)), magicLinkVerify: (f) => then(client.signIn.magicLinkVerify(f)), emailCode: (f) => then(client.signIn.emailCode(f)) },
1006
+ signUp: { email: (f) => then(client.signUp.email(f)) },
1007
+ signOut: () => then(client.signOut()),
1008
+ account: {
1009
+ ...client.account,
1010
+ // What is on screen says the new name, and knows when the session ended.
1011
+ update: (f) => then(client.account.update(f)),
1012
+ changePassword: (f) => then(client.account.changePassword(f)),
1013
+ signOutEverywhere: () => then(client.account.signOutEverywhere())
1014
+ },
1015
+ session,
1016
+ status,
1017
+ refresh
1018
+ };
1019
+ }, [basePath, refresh, session, status]);
1020
+ }
119
1021
  // Annotate the CommonJS export names for ESM import in node:
120
1022
  0 && (module.exports = {
1023
+ Account,
121
1024
  CauthyProvider,
1025
+ ForgotPassword,
1026
+ ResetPassword,
1027
+ SignIn,
1028
+ SignUp,
1029
+ VerifyEmail,
122
1030
  can,
1031
+ createAuthClient,
1032
+ definePolicy,
1033
+ useAuth,
123
1034
  useSession
124
1035
  });
125
1036
  //# sourceMappingURL=react.js.map