cauthy-auth 0.11.0 → 0.13.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.
package/dist/styles.css CHANGED
@@ -269,3 +269,8 @@
269
269
  border: 1px solid var(--cauthy-border);
270
270
  border-radius: var(--cauthy-radius);
271
271
  }
272
+
273
+ /* The workspace's Turnstile challenge, when it asks for one. */
274
+ :where([data-cauthy] .cauthy-challenge) {
275
+ min-height: 65px;
276
+ }
package/dist/svelte.js CHANGED
@@ -30,6 +30,15 @@ module.exports = __toCommonJS(svelte_exports);
30
30
  function canUsePasskeys() {
31
31
  return typeof window !== "undefined" && typeof window.PublicKeyCredential === "function" && typeof navigator !== "undefined" && typeof navigator.credentials?.get === "function";
32
32
  }
33
+ async function canAutofillPasskeys() {
34
+ if (!canUsePasskeys()) return false;
35
+ const pkc = window.PublicKeyCredential;
36
+ try {
37
+ return typeof pkc.isConditionalMediationAvailable === "function" && await pkc.isConditionalMediationAvailable() === true;
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
33
42
  function bytes(text2) {
34
43
  const b64 = text2.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(text2.length / 4) * 4, "=");
35
44
  const bin = atob(b64);
@@ -45,9 +54,9 @@ function text(buf) {
45
54
  return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
46
55
  }
47
56
  var withId = (list) => (list ?? []).map((c) => ({ ...c, id: bytes(c.id) }));
48
- async function getPasskey(options) {
57
+ async function getPasskey(options, how = {}) {
49
58
  const publicKey = { ...options, challenge: bytes(options.challenge), allowCredentials: withId(options.allowCredentials) };
50
- const cred = await navigator.credentials.get({ publicKey });
59
+ const cred = await navigator.credentials.get({ publicKey, ...how });
51
60
  if (cred === null) throw new DOMException("No passkey was chosen.", "NotAllowedError");
52
61
  const r = cred.response;
53
62
  return {
@@ -167,6 +176,24 @@ function firstProblem(...checks) {
167
176
  }
168
177
 
169
178
  // src/client.ts
179
+ var googleScript = null;
180
+ function loadGoogleIdentity() {
181
+ const ready = () => window.google?.accounts?.id;
182
+ if (ready()) return Promise.resolve(ready());
183
+ googleScript ??= new Promise((resolve, reject) => {
184
+ const script = document.createElement("script");
185
+ script.src = "https://accounts.google.com/gsi/client";
186
+ script.async = true;
187
+ script.onload = () => ready() ? resolve(ready()) : reject(new Error("google identity services did not load"));
188
+ script.onerror = () => {
189
+ googleScript = null;
190
+ reject(new Error("google identity services did not load"));
191
+ };
192
+ document.head.appendChild(script);
193
+ });
194
+ return googleScript;
195
+ }
196
+ var PASSKEY_SECONDS = 300;
170
197
  var UNAVAILABLE = { code: "unavailable", message: "Sign-in is unavailable right now. Try again in a moment." };
171
198
  function createAuthClient(options = {}) {
172
199
  const base = (options.basePath ?? "/api/auth").replace(/\/+$/, "");
@@ -175,13 +202,13 @@ function createAuthClient(options = {}) {
175
202
  const said = typeof options.locale === "function" ? options.locale() : options.locale;
176
203
  return (said ?? (typeof document === "undefined" ? "" : document.documentElement?.lang) ?? "").trim();
177
204
  };
178
- async function post(path, form, bad) {
205
+ async function post(path, form, bad, captchaToken) {
179
206
  if (bad) return { ok: false, error: { code: bad.code, message: bad.message, field: bad.field } };
180
207
  try {
181
208
  const res = await doFetch(`${base}${path}`, {
182
209
  method: "POST",
183
210
  credentials: "same-origin",
184
- headers: { "content-type": "application/json", accept: "application/json", ...locale() ? { "x-cauthy-locale": locale() } : {} },
211
+ headers: { "content-type": "application/json", accept: "application/json", ...locale() ? { "x-cauthy-locale": locale() } : {}, ...captchaToken ? { "x-cauthy-captcha": captchaToken } : {} },
185
212
  body: JSON.stringify(form)
186
213
  });
187
214
  const body = await res.json().catch(() => null);
@@ -233,9 +260,11 @@ function createAuthClient(options = {}) {
233
260
  }
234
261
  return {
235
262
  signIn: {
236
- email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password))),
263
+ email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password)), f.captchaToken),
237
264
  mfa: (f) => post("/sign-in/mfa", { code: f.code }, checkCode(f.code)),
238
- magicLink: (f) => post("/sign-in/magic-link", { email: f.email }, checkEmail(f.email)),
265
+ mfaMethods: () => post("/sign-in/mfa/methods", {}, null),
266
+ mfaSendCode: () => post("/sign-in/mfa/send", {}, null),
267
+ magicLink: (f) => post("/sign-in/magic-link", { email: f.email }, checkEmail(f.email), f.captchaToken),
239
268
  magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
240
269
  emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
241
270
  passkeyUrl,
@@ -250,15 +279,75 @@ function createAuthClient(options = {}) {
250
279
  window.location.assign(passkeyUrl(o));
251
280
  return { ok: true, status: "redirected" };
252
281
  },
282
+ async passkeyAutofill(o = {}) {
283
+ const cancelled = { ok: false, error: { code: "cancelled", message: "No passkey was chosen." } };
284
+ const m = await methods();
285
+ if (!m.passkeyHere || !await canAutofillPasskeys()) return cancelled;
286
+ for (; ; ) {
287
+ if (o.signal?.aborted) return cancelled;
288
+ const begun = await post("/sign-in/passkey/begin", {}, null);
289
+ if (!begun.ok) return begun;
290
+ const renew = new AbortController();
291
+ const stop = () => renew.abort();
292
+ o.signal?.addEventListener("abort", stop, { once: true });
293
+ const timer = setTimeout(stop, (PASSKEY_SECONDS - 30) * 1e3);
294
+ let credential;
295
+ try {
296
+ credential = await getPasskey(begun.options, { mediation: "conditional", signal: renew.signal });
297
+ } catch (e) {
298
+ if (o.signal?.aborted) return cancelled;
299
+ if (renew.signal.aborted) continue;
300
+ return { ok: false, error: ceremonyError(e) };
301
+ } finally {
302
+ clearTimeout(timer);
303
+ o.signal?.removeEventListener("abort", stop);
304
+ }
305
+ return post("/sign-in/passkey/finish", { credential }, null);
306
+ }
307
+ },
308
+ async googleOneTap(o = {}) {
309
+ const cancelled = { ok: false, error: { code: "cancelled", message: "No Google account was chosen." } };
310
+ if (typeof window === "undefined" || o.signal?.aborted) return cancelled;
311
+ const begun = await post("/sign-in/google-one-tap/begin", {}, null);
312
+ if (!begun.ok) return begun;
313
+ let gis;
314
+ try {
315
+ gis = await loadGoogleIdentity();
316
+ } catch {
317
+ return { ok: false, error: { code: "unavailable", message: "Google could not be reached. Try again, or sign in another way." } };
318
+ }
319
+ const credential = await new Promise((done) => {
320
+ o.signal?.addEventListener("abort", () => {
321
+ gis.cancel();
322
+ done(null);
323
+ }, { once: true });
324
+ gis.initialize({
325
+ client_id: begun.clientId,
326
+ nonce: begun.nonce,
327
+ context: o.context ?? "signin",
328
+ callback: (r) => done(r.credential ?? null),
329
+ auto_select: false,
330
+ cancel_on_tap_outside: true,
331
+ itp_support: true,
332
+ use_fedcm_for_prompt: true
333
+ });
334
+ gis.prompt((moment) => {
335
+ if (moment.isNotDisplayed?.() || moment.isSkippedMoment?.()) done(null);
336
+ else if (moment.isDismissedMoment?.() && moment.getDismissedReason?.() !== "credential_returned") done(null);
337
+ });
338
+ });
339
+ if (credential === null) return cancelled;
340
+ return post("/sign-in/google-one-tap", { credential }, null);
341
+ },
253
342
  socialUrl,
254
343
  social(provider, o) {
255
344
  window.location.assign(socialUrl(provider, o));
256
345
  }
257
346
  },
258
347
  signUp: {
259
- 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)))
348
+ 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)), f.captchaToken)
260
349
  },
261
- forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email)),
350
+ forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email), f.captchaToken),
262
351
  resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password)),
263
352
  verifyEmail: (f) => post("/verify-email", { token: f.token }, null),
264
353
  signOut: () => post("/sign-out", {}, null),
@@ -276,7 +365,14 @@ function createAuthClient(options = {}) {
276
365
  setup: (f) => post("/me/mfa/setup", { password: f.password }, null),
277
366
  verify: (f) => post("/me/mfa/verify", { code: f.code }, checkCode(f.code)),
278
367
  disable: (f) => post("/me/mfa/disable", { password: f.password }, null),
279
- recoveryCodes: (f) => post("/me/mfa/recovery-codes", { password: f.password }, null)
368
+ recoveryCodes: (f) => post("/me/mfa/recovery-codes", { password: f.password }, null),
369
+ async factors() {
370
+ const body = await read("/me/mfa/factors");
371
+ return body && typeof body.totp === "boolean" ? body : null;
372
+ },
373
+ addPhone: (f) => post("/me/mfa/phone", { phone: f.phone, channel: f.channel, password: f.password }, null),
374
+ verifyPhone: (f) => post("/me/mfa/phone/verify", { code: f.code }, checkCode(f.code)),
375
+ removePhone: (f) => post("/me/mfa/phone/remove", { password: f.password }, null)
280
376
  },
281
377
  changeEmail: (f) => post("/me/email", { email: f.email, password: f.password }, checkEmail(f.email)),
282
378
  async identities() {