cauthy-auth 0.8.0 → 0.10.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/index.mjs CHANGED
@@ -1013,10 +1013,15 @@ function createCauthy(given = {}) {
1013
1013
  accept: "application/json",
1014
1014
  // A link Cauthy mails opens the application's page for it when it
1015
1015
  // has one; otherwise Cauthy's own. Either way in the reader's language.
1016
- ...page ? { "x-cauthy-link-to": `${originOf(request)}${page}`, "x-cauthy-client-id": clientId } : { "x-cauthy-links": "hosted" },
1016
+ ...page ? { "x-cauthy-link-to": `${originOf(request)}${page}` } : { "x-cauthy-links": "hosted" },
1017
+ // Whose request it is: what Cauthy mails goes in this application's
1018
+ // name, under its logo.
1019
+ ...clientId ? { "x-cauthy-client-id": clientId } : {},
1017
1020
  ...await passedOn(request),
1018
1021
  ...bearer2 ? { authorization: `Bearer ${bearer2}` } : {},
1019
1022
  ...request.headers.get("accept-language") ? { "accept-language": request.headers.get("accept-language") } : {},
1023
+ // The language the application says its person reads, over the browser's.
1024
+ ...request.headers.get("x-cauthy-locale") ? { "x-cauthy-locale": request.headers.get("x-cauthy-locale") } : {},
1020
1025
  ...request.headers.get("user-agent") ? { "user-agent": request.headers.get("user-agent") } : {},
1021
1026
  ...payload === void 0 ? {} : { "content-type": "application/json" }
1022
1027
  },
@@ -1055,7 +1060,7 @@ function createCauthy(given = {}) {
1055
1060
  let lookCache = null;
1056
1061
  async function ownRoute(request, url, route) {
1057
1062
  const post = request.method === "POST";
1058
- const own = ["/sign-in/email", "/sign-in/mfa", "/sign-up/email", "/forgot-password", "/reset-password", "/verify-email", "/sign-in/magic-link", "/sign-in/magic-link/verify", "/sign-in/email-code", "/me", "/me/password", "/me/mfa/setup", "/me/mfa/verify", "/me/mfa/disable", "/me/mfa/recovery-codes", "/me/sessions", "/me/sessions/end", "/sign-out", "/methods", "/look"].includes(route) || route.startsWith("/sign-in/social/") || route.startsWith("/callback/");
1063
+ const own = ["/sign-in/email", "/sign-in/mfa", "/sign-up/email", "/forgot-password", "/reset-password", "/verify-email", "/sign-in/magic-link", "/sign-in/magic-link/verify", "/sign-in/email-code", "/me", "/me/password", "/me/mfa/setup", "/me/mfa/verify", "/me/mfa/disable", "/me/mfa/recovery-codes", "/me/sessions", "/me/sessions/end", "/me/email", "/me/identities", "/me/identities/unlink", "/sign-out", "/methods", "/look"].includes(route) || route.startsWith("/sign-in/social/") || route.startsWith("/callback/") || route.startsWith("/me/identities/link/");
1059
1064
  if (!own) return null;
1060
1065
  if (tenant === "") {
1061
1066
  return problem(500, "not_configured", "cauthy-auth: tenant (CAUTHY_TENANT) is required for the application's own sign-in screens");
@@ -1098,16 +1103,46 @@ function createCauthy(given = {}) {
1098
1103
  const clear = writeCookie(stepCookie, "", attrs(request, 0));
1099
1104
  const back = step?.returnTo ?? afterSignIn;
1100
1105
  const failedTo = (code2) => redirect(`${back}${back.includes("?") ? "&" : "?"}auth_error=${encodeURIComponent(code2)}`, [clear]);
1101
- if (step === null || step.kind !== "social" || step.provider !== provider || url.searchParams.get("state") !== step.token) return failedTo("state_mismatch");
1106
+ if (step === null || step.kind !== "social" && step.kind !== "link" || step.provider !== provider || url.searchParams.get("state") !== step.token) return failedTo("state_mismatch");
1102
1107
  const code = url.searchParams.get("code");
1103
1108
  if (!code) return failedTo(url.searchParams.get("error") === "access_denied" ? "cancelled" : "provider_refused");
1104
1109
  const q = new URLSearchParams({ code, state: step.token, redirect_uri: `${originOf(request)}${basePath}/callback/${provider}` });
1110
+ if (step.kind === "link") {
1111
+ const mine = await signedInAs(request);
1112
+ if ("response" in mine) return failedTo("unauthenticated");
1113
+ const linked = await ask(request, "GET", `/v1/auth/identities/${provider}/link/callback?${q}`, void 0, void 0, mine.token);
1114
+ if (linked.error) return failedTo(linked.error.code);
1115
+ const h = new Headers(mine.headers);
1116
+ h.append("set-cookie", clear);
1117
+ return redirect(`${back}${back.includes("?") ? "&" : "?"}linked=${encodeURIComponent(provider)}`, [...h.getSetCookie()]);
1118
+ }
1105
1119
  const done = await ask(request, "GET", `/v1/auth/social/${provider}/callback?${q}`);
1106
1120
  if (done.error) return failedTo(done.error.code);
1107
1121
  const s = await stored(done.data);
1108
1122
  if (s === null) return failedTo("unavailable");
1109
1123
  return redirect(back, [...await write(request, s, readCookies(request.headers.get("cookie"))), clear]);
1110
1124
  }
1125
+ if (route.startsWith("/me/identities/link/") && !post) {
1126
+ const provider = providerIn(route.slice("/me/identities/link/".length));
1127
+ if (!PROVIDER_ID.test(provider)) return new Response("Not found", { status: 404 });
1128
+ const returnTo = localPath(url.searchParams.get("return_to"), afterSignIn);
1129
+ const failed2 = (code) => redirect(`${returnTo}${returnTo.includes("?") ? "&" : "?"}auth_error=${encodeURIComponent(code)}`);
1130
+ const mine = await signedInAs(request);
1131
+ if ("response" in mine) return failed2("unauthenticated");
1132
+ const callback2 = `${basePath}/callback/${provider}`;
1133
+ const started = await ask(request, "GET", `/v1/auth/identities/${provider}/link?redirect_uri=${encodeURIComponent(`${originOf(request)}${callback2}`)}`, void 0, callback2, mine.token);
1134
+ if (started.error || typeof started.data?.redirect_url !== "string") return failed2(started.error?.code ?? "provider_unavailable");
1135
+ const step = { kind: "link", token: String(started.data.state ?? ""), provider, returnTo };
1136
+ return redirect(started.data.redirect_url, [...mine.headers.getSetCookie(), writeCookie(stepCookie, await seal(step, await key), attrs(request, STEP_SECONDS))]);
1137
+ }
1138
+ if (route === "/me/identities" && !post) {
1139
+ const mine = await signedInAs(request);
1140
+ if ("response" in mine) return mine.response;
1141
+ const res = await ask(request, "GET", "/v1/auth/identities", void 0, void 0, mine.token);
1142
+ if (res.error) return problem(res.status, res.error.code, res.error.message, void 0, mine.headers);
1143
+ const list = Array.isArray(res.data) ? res.data : [];
1144
+ return json({ identities: list.map((x) => ({ provider: String(x.provider ?? ""), email: String(x.email ?? ""), name: String(x.name ?? ""), linkedAt: String(x.linked_at ?? "") })) }, 200, mine.headers);
1145
+ }
1111
1146
  if ((route === "/me" || route === "/me/sessions") && !post) {
1112
1147
  const mine = await signedInAs(request);
1113
1148
  if ("response" in mine) return mine.response;
@@ -1224,6 +1259,21 @@ function createCauthy(given = {}) {
1224
1259
  if (res.error) return refused(res, res.error.code === "step_up_required" ? "password" : void 0);
1225
1260
  return json({ status: "renewed", recoveryCodes: Array.isArray(res.data?.recovery_codes) ? res.data.recovery_codes.map(String) : [] }, 200, mine.headers);
1226
1261
  }
1262
+ if (route === "/me/email") {
1263
+ const bad = checkEmail(b.email);
1264
+ if (bad) return invalid(bad);
1265
+ const email = String(b.email).trim();
1266
+ const res = await as("/v1/auth/me/email", { email, password });
1267
+ if (res.error) return refused(res, res.error.code === "step_up_required" ? "password" : ["invalid_email", "email_unchanged", "email_taken"].includes(res.error.code) ? "email" : void 0);
1268
+ return json({ status: "sent", email }, 200, mine.headers);
1269
+ }
1270
+ if (route === "/me/identities/unlink") {
1271
+ const provider = typeof b.provider === "string" ? b.provider : "";
1272
+ if (!PROVIDER_ID.test(provider)) return problem(400, "invalid_provider", "Say which provider to unlink.", "provider", mine.headers);
1273
+ const res = await as(`/v1/auth/identities/${provider}`, void 0, "DELETE");
1274
+ if (res.error) return refused(res);
1275
+ return json({ status: "unlinked", provider }, 200, mine.headers);
1276
+ }
1227
1277
  if (route === "/me/sessions/end") {
1228
1278
  const res = await as("/v1/auth/me/sessions", void 0, "DELETE");
1229
1279
  if (res.error) return refused(res);