cauthy-auth 0.9.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/svelte.mjs CHANGED
@@ -1,3 +1,70 @@
1
+ // src/passkey.ts
2
+ function canUsePasskeys() {
3
+ return typeof window !== "undefined" && typeof window.PublicKeyCredential === "function" && typeof navigator !== "undefined" && typeof navigator.credentials?.get === "function";
4
+ }
5
+ function bytes(text2) {
6
+ const b64 = text2.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(text2.length / 4) * 4, "=");
7
+ const bin = atob(b64);
8
+ const out = new Uint8Array(bin.length);
9
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
10
+ return out.buffer;
11
+ }
12
+ function text(buf) {
13
+ if (!buf) return null;
14
+ const view = new Uint8Array(buf);
15
+ let bin = "";
16
+ for (let i = 0; i < view.byteLength; i++) bin += String.fromCharCode(view[i]);
17
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
18
+ }
19
+ var withId = (list) => (list ?? []).map((c) => ({ ...c, id: bytes(c.id) }));
20
+ async function getPasskey(options) {
21
+ const publicKey = { ...options, challenge: bytes(options.challenge), allowCredentials: withId(options.allowCredentials) };
22
+ const cred = await navigator.credentials.get({ publicKey });
23
+ if (cred === null) throw new DOMException("No passkey was chosen.", "NotAllowedError");
24
+ const r = cred.response;
25
+ return {
26
+ id: cred.id,
27
+ rawId: text(cred.rawId),
28
+ type: cred.type,
29
+ response: {
30
+ authenticatorData: text(r.authenticatorData),
31
+ clientDataJSON: text(r.clientDataJSON),
32
+ signature: text(r.signature),
33
+ userHandle: text(r.userHandle)
34
+ },
35
+ clientExtensionResults: cred.getClientExtensionResults?.() ?? {}
36
+ };
37
+ }
38
+ async function makePasskey(options) {
39
+ const publicKey = {
40
+ ...options,
41
+ challenge: bytes(options.challenge),
42
+ user: { ...options.user, id: bytes(options.user.id) },
43
+ excludeCredentials: withId(options.excludeCredentials)
44
+ };
45
+ const cred = await navigator.credentials.create({ publicKey });
46
+ if (cred === null) throw new DOMException("No passkey was made.", "NotAllowedError");
47
+ const r = cred.response;
48
+ return {
49
+ id: cred.id,
50
+ rawId: text(cred.rawId),
51
+ type: cred.type,
52
+ response: {
53
+ clientDataJSON: text(r.clientDataJSON),
54
+ attestationObject: text(r.attestationObject),
55
+ transports: typeof r.getTransports === "function" ? r.getTransports() : []
56
+ },
57
+ clientExtensionResults: cred.getClientExtensionResults?.() ?? {}
58
+ };
59
+ }
60
+ function ceremonyError(e) {
61
+ const name = e instanceof Error || e instanceof DOMException ? e.name : "";
62
+ if (name === "NotAllowedError" || name === "AbortError") return { code: "cancelled", message: "The passkey was not used. Try again when you are ready." };
63
+ if (name === "InvalidStateError") return { code: "passkey_exists", message: "This device already has a passkey for this account." };
64
+ if (name === "SecurityError") return { code: "passkey_unavailable", message: "Passkeys cannot be used on this page." };
65
+ return { code: "passkey_failed", message: "The passkey could not be used. Try again, or sign in another way." };
66
+ }
67
+
1
68
  // src/theme.ts
2
69
  function fromWire(wire) {
3
70
  const names = {
@@ -76,13 +143,17 @@ var UNAVAILABLE = { code: "unavailable", message: "Sign-in is unavailable right
76
143
  function createAuthClient(options = {}) {
77
144
  const base = (options.basePath ?? "/api/auth").replace(/\/+$/, "");
78
145
  const doFetch = (...args) => (options.fetch ?? fetch)(...args);
146
+ const locale = () => {
147
+ const said = typeof options.locale === "function" ? options.locale() : options.locale;
148
+ return (said ?? (typeof document === "undefined" ? "" : document.documentElement?.lang) ?? "").trim();
149
+ };
79
150
  async function post(path, form, bad) {
80
151
  if (bad) return { ok: false, error: { code: bad.code, message: bad.message, field: bad.field } };
81
152
  try {
82
153
  const res = await doFetch(`${base}${path}`, {
83
154
  method: "POST",
84
155
  credentials: "same-origin",
85
- headers: { "content-type": "application/json", accept: "application/json" },
156
+ headers: { "content-type": "application/json", accept: "application/json", ...locale() ? { "x-cauthy-locale": locale() } : {} },
86
157
  body: JSON.stringify(form)
87
158
  });
88
159
  const body = await res.json().catch(() => null);
@@ -110,6 +181,28 @@ function createAuthClient(options = {}) {
110
181
  if (o.locale) q.set("locale", o.locale);
111
182
  return `${base}/login?${q}`;
112
183
  };
184
+ async function methods() {
185
+ try {
186
+ const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
187
+ if (res.ok) {
188
+ const m = await res.json();
189
+ return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true };
190
+ }
191
+ } catch {
192
+ }
193
+ return { providers: [], signUp: true, magicLink: false, passkey: false, passkeyHere: false, passkeyOnCauthy: false };
194
+ }
195
+ async function ceremony(begin, finish, form, run, more = {}) {
196
+ const begun = await post(begin, form, null);
197
+ if (!begun.ok) return begun;
198
+ let credential;
199
+ try {
200
+ credential = await run(begun.options);
201
+ } catch (e) {
202
+ return { ok: false, error: ceremonyError(e) };
203
+ }
204
+ return post(finish, { credential, ...more }, null);
205
+ }
113
206
  return {
114
207
  signIn: {
115
208
  email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password))),
@@ -118,8 +211,16 @@ function createAuthClient(options = {}) {
118
211
  magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
119
212
  emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
120
213
  passkeyUrl,
121
- passkey(o) {
214
+ async passkey(o) {
215
+ const m = await methods();
216
+ if (m.passkeyHere && canUsePasskeys()) {
217
+ const r = await ceremony("/sign-in/passkey/begin", "/sign-in/passkey/finish", {}, getPasskey);
218
+ if (r.ok || r.error.code !== "passkey_unavailable" || !m.passkeyOnCauthy) return r;
219
+ } else if (!m.passkeyOnCauthy) {
220
+ return { ok: false, error: { code: "passkey_unavailable", message: "Passkeys cannot be used here." } };
221
+ }
122
222
  window.location.assign(passkeyUrl(o));
223
+ return { ok: true, status: "redirected" };
123
224
  },
124
225
  socialUrl,
125
226
  social(provider, o) {
@@ -163,7 +264,16 @@ function createAuthClient(options = {}) {
163
264
  const body = await read("/me/sessions");
164
265
  return body && Array.isArray(body.sessions) ? body.sessions : [];
165
266
  },
166
- signOutEverywhere: () => post("/me/sessions/end", {}, null)
267
+ signOutEverywhere: () => post("/me/sessions/end", {}, null),
268
+ async passkeys() {
269
+ const body = await read("/me/passkeys");
270
+ return body && Array.isArray(body.passkeys) ? body.passkeys : [];
271
+ },
272
+ addPasskey(f = {}) {
273
+ if (!canUsePasskeys()) return Promise.resolve({ ok: false, error: { code: "passkey_unavailable", message: "This browser cannot make passkeys." } });
274
+ return ceremony("/me/passkeys/add/begin", "/me/passkeys/add/finish", { password: f.password ?? "" }, makePasskey, { name: f.name ?? "" });
275
+ },
276
+ removePasskey: (id) => post("/me/passkeys/remove", { id }, null)
167
277
  },
168
278
  async getSession() {
169
279
  try {
@@ -178,14 +288,7 @@ function createAuthClient(options = {}) {
178
288
  looked ??= read("/look").then((wire) => wire && typeof wire === "object" ? fromWire(wire) : null);
179
289
  return looked;
180
290
  },
181
- async methods() {
182
- try {
183
- const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
184
- if (res.ok) return await res.json();
185
- } catch {
186
- }
187
- return { providers: [], signUp: true, magicLink: false, passkey: false };
188
- }
291
+ methods
189
292
  };
190
293
  }
191
294
 
@@ -281,7 +384,8 @@ function createSessionStore(options = {}) {
281
384
  email: (f) => then(client.signIn.email(f)),
282
385
  mfa: (f) => then(client.signIn.mfa(f)),
283
386
  magicLinkVerify: (f) => then(client.signIn.magicLinkVerify(f)),
284
- emailCode: (f) => then(client.signIn.emailCode(f))
387
+ emailCode: (f) => then(client.signIn.emailCode(f)),
388
+ passkey: (o) => then(client.signIn.passkey(o))
285
389
  },
286
390
  signUp: { email: (f) => then(client.signUp.email(f)) },
287
391
  signOut: () => then(client.signOut()),