cauthy-auth 0.10.0 → 0.11.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/vue.js CHANGED
@@ -29,6 +29,73 @@ __export(vue_exports, {
29
29
  module.exports = __toCommonJS(vue_exports);
30
30
  var import_vue = require("vue");
31
31
 
32
+ // src/passkey.ts
33
+ function canUsePasskeys() {
34
+ return typeof window !== "undefined" && typeof window.PublicKeyCredential === "function" && typeof navigator !== "undefined" && typeof navigator.credentials?.get === "function";
35
+ }
36
+ function bytes(text2) {
37
+ const b64 = text2.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(text2.length / 4) * 4, "=");
38
+ const bin = atob(b64);
39
+ const out = new Uint8Array(bin.length);
40
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
41
+ return out.buffer;
42
+ }
43
+ function text(buf) {
44
+ if (!buf) return null;
45
+ const view = new Uint8Array(buf);
46
+ let bin = "";
47
+ for (let i = 0; i < view.byteLength; i++) bin += String.fromCharCode(view[i]);
48
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
49
+ }
50
+ var withId = (list) => (list ?? []).map((c) => ({ ...c, id: bytes(c.id) }));
51
+ async function getPasskey(options) {
52
+ const publicKey = { ...options, challenge: bytes(options.challenge), allowCredentials: withId(options.allowCredentials) };
53
+ const cred = await navigator.credentials.get({ publicKey });
54
+ if (cred === null) throw new DOMException("No passkey was chosen.", "NotAllowedError");
55
+ const r = cred.response;
56
+ return {
57
+ id: cred.id,
58
+ rawId: text(cred.rawId),
59
+ type: cred.type,
60
+ response: {
61
+ authenticatorData: text(r.authenticatorData),
62
+ clientDataJSON: text(r.clientDataJSON),
63
+ signature: text(r.signature),
64
+ userHandle: text(r.userHandle)
65
+ },
66
+ clientExtensionResults: cred.getClientExtensionResults?.() ?? {}
67
+ };
68
+ }
69
+ async function makePasskey(options) {
70
+ const publicKey = {
71
+ ...options,
72
+ challenge: bytes(options.challenge),
73
+ user: { ...options.user, id: bytes(options.user.id) },
74
+ excludeCredentials: withId(options.excludeCredentials)
75
+ };
76
+ const cred = await navigator.credentials.create({ publicKey });
77
+ if (cred === null) throw new DOMException("No passkey was made.", "NotAllowedError");
78
+ const r = cred.response;
79
+ return {
80
+ id: cred.id,
81
+ rawId: text(cred.rawId),
82
+ type: cred.type,
83
+ response: {
84
+ clientDataJSON: text(r.clientDataJSON),
85
+ attestationObject: text(r.attestationObject),
86
+ transports: typeof r.getTransports === "function" ? r.getTransports() : []
87
+ },
88
+ clientExtensionResults: cred.getClientExtensionResults?.() ?? {}
89
+ };
90
+ }
91
+ function ceremonyError(e) {
92
+ const name = e instanceof Error || e instanceof DOMException ? e.name : "";
93
+ if (name === "NotAllowedError" || name === "AbortError") return { code: "cancelled", message: "The passkey was not used. Try again when you are ready." };
94
+ if (name === "InvalidStateError") return { code: "passkey_exists", message: "This device already has a passkey for this account." };
95
+ if (name === "SecurityError") return { code: "passkey_unavailable", message: "Passkeys cannot be used on this page." };
96
+ return { code: "passkey_failed", message: "The passkey could not be used. Try again, or sign in another way." };
97
+ }
98
+
32
99
  // src/theme.ts
33
100
  function fromWire(wire) {
34
101
  const names = {
@@ -145,6 +212,28 @@ function createAuthClient(options = {}) {
145
212
  if (o.locale) q.set("locale", o.locale);
146
213
  return `${base}/login?${q}`;
147
214
  };
215
+ async function methods() {
216
+ try {
217
+ const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
218
+ if (res.ok) {
219
+ const m = await res.json();
220
+ return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true };
221
+ }
222
+ } catch {
223
+ }
224
+ return { providers: [], signUp: true, magicLink: false, passkey: false, passkeyHere: false, passkeyOnCauthy: false };
225
+ }
226
+ async function ceremony(begin, finish, form, run, more = {}) {
227
+ const begun = await post(begin, form, null);
228
+ if (!begun.ok) return begun;
229
+ let credential;
230
+ try {
231
+ credential = await run(begun.options);
232
+ } catch (e) {
233
+ return { ok: false, error: ceremonyError(e) };
234
+ }
235
+ return post(finish, { credential, ...more }, null);
236
+ }
148
237
  return {
149
238
  signIn: {
150
239
  email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password))),
@@ -153,8 +242,16 @@ function createAuthClient(options = {}) {
153
242
  magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
154
243
  emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
155
244
  passkeyUrl,
156
- passkey(o) {
245
+ async passkey(o) {
246
+ const m = await methods();
247
+ if (m.passkeyHere && canUsePasskeys()) {
248
+ const r = await ceremony("/sign-in/passkey/begin", "/sign-in/passkey/finish", {}, getPasskey);
249
+ if (r.ok || r.error.code !== "passkey_unavailable" || !m.passkeyOnCauthy) return r;
250
+ } else if (!m.passkeyOnCauthy) {
251
+ return { ok: false, error: { code: "passkey_unavailable", message: "Passkeys cannot be used here." } };
252
+ }
157
253
  window.location.assign(passkeyUrl(o));
254
+ return { ok: true, status: "redirected" };
158
255
  },
159
256
  socialUrl,
160
257
  social(provider, o) {
@@ -198,7 +295,16 @@ function createAuthClient(options = {}) {
198
295
  const body = await read("/me/sessions");
199
296
  return body && Array.isArray(body.sessions) ? body.sessions : [];
200
297
  },
201
- signOutEverywhere: () => post("/me/sessions/end", {}, null)
298
+ signOutEverywhere: () => post("/me/sessions/end", {}, null),
299
+ async passkeys() {
300
+ const body = await read("/me/passkeys");
301
+ return body && Array.isArray(body.passkeys) ? body.passkeys : [];
302
+ },
303
+ addPasskey(f = {}) {
304
+ if (!canUsePasskeys()) return Promise.resolve({ ok: false, error: { code: "passkey_unavailable", message: "This browser cannot make passkeys." } });
305
+ return ceremony("/me/passkeys/add/begin", "/me/passkeys/add/finish", { password: f.password ?? "" }, makePasskey, { name: f.name ?? "" });
306
+ },
307
+ removePasskey: (id) => post("/me/passkeys/remove", { id }, null)
202
308
  },
203
309
  async getSession() {
204
310
  try {
@@ -213,14 +319,7 @@ function createAuthClient(options = {}) {
213
319
  looked ??= read("/look").then((wire) => wire && typeof wire === "object" ? fromWire(wire) : null);
214
320
  return looked;
215
321
  },
216
- async methods() {
217
- try {
218
- const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
219
- if (res.ok) return await res.json();
220
- } catch {
221
- }
222
- return { providers: [], signUp: true, magicLink: false, passkey: false };
223
- }
322
+ methods
224
323
  };
225
324
  }
226
325
 
@@ -316,7 +415,8 @@ function createSessionStore(options = {}) {
316
415
  email: (f) => then(client.signIn.email(f)),
317
416
  mfa: (f) => then(client.signIn.mfa(f)),
318
417
  magicLinkVerify: (f) => then(client.signIn.magicLinkVerify(f)),
319
- emailCode: (f) => then(client.signIn.emailCode(f))
418
+ emailCode: (f) => then(client.signIn.emailCode(f)),
419
+ passkey: (o) => then(client.signIn.passkey(o))
320
420
  },
321
421
  signUp: { email: (f) => then(client.signUp.email(f)) },
322
422
  signOut: () => then(client.signOut()),