cauthy-auth 0.8.0 → 0.9.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
@@ -1055,7 +1055,7 @@ function createCauthy(given = {}) {
1055
1055
  let lookCache = null;
1056
1056
  async function ownRoute(request, url, route) {
1057
1057
  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/");
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", "/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
1059
  if (!own) return null;
1060
1060
  if (tenant === "") {
1061
1061
  return problem(500, "not_configured", "cauthy-auth: tenant (CAUTHY_TENANT) is required for the application's own sign-in screens");
@@ -1098,16 +1098,46 @@ function createCauthy(given = {}) {
1098
1098
  const clear = writeCookie(stepCookie, "", attrs(request, 0));
1099
1099
  const back = step?.returnTo ?? afterSignIn;
1100
1100
  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");
1101
+ if (step === null || step.kind !== "social" && step.kind !== "link" || step.provider !== provider || url.searchParams.get("state") !== step.token) return failedTo("state_mismatch");
1102
1102
  const code = url.searchParams.get("code");
1103
1103
  if (!code) return failedTo(url.searchParams.get("error") === "access_denied" ? "cancelled" : "provider_refused");
1104
1104
  const q = new URLSearchParams({ code, state: step.token, redirect_uri: `${originOf(request)}${basePath}/callback/${provider}` });
1105
+ if (step.kind === "link") {
1106
+ const mine = await signedInAs(request);
1107
+ if ("response" in mine) return failedTo("unauthenticated");
1108
+ const linked = await ask(request, "GET", `/v1/auth/identities/${provider}/link/callback?${q}`, void 0, void 0, mine.token);
1109
+ if (linked.error) return failedTo(linked.error.code);
1110
+ const h = new Headers(mine.headers);
1111
+ h.append("set-cookie", clear);
1112
+ return redirect(`${back}${back.includes("?") ? "&" : "?"}linked=${encodeURIComponent(provider)}`, [...h.getSetCookie()]);
1113
+ }
1105
1114
  const done = await ask(request, "GET", `/v1/auth/social/${provider}/callback?${q}`);
1106
1115
  if (done.error) return failedTo(done.error.code);
1107
1116
  const s = await stored(done.data);
1108
1117
  if (s === null) return failedTo("unavailable");
1109
1118
  return redirect(back, [...await write(request, s, readCookies(request.headers.get("cookie"))), clear]);
1110
1119
  }
1120
+ if (route.startsWith("/me/identities/link/") && !post) {
1121
+ const provider = providerIn(route.slice("/me/identities/link/".length));
1122
+ if (!PROVIDER_ID.test(provider)) return new Response("Not found", { status: 404 });
1123
+ const returnTo = localPath(url.searchParams.get("return_to"), afterSignIn);
1124
+ const failed2 = (code) => redirect(`${returnTo}${returnTo.includes("?") ? "&" : "?"}auth_error=${encodeURIComponent(code)}`);
1125
+ const mine = await signedInAs(request);
1126
+ if ("response" in mine) return failed2("unauthenticated");
1127
+ const callback2 = `${basePath}/callback/${provider}`;
1128
+ const started = await ask(request, "GET", `/v1/auth/identities/${provider}/link?redirect_uri=${encodeURIComponent(`${originOf(request)}${callback2}`)}`, void 0, callback2, mine.token);
1129
+ if (started.error || typeof started.data?.redirect_url !== "string") return failed2(started.error?.code ?? "provider_unavailable");
1130
+ const step = { kind: "link", token: String(started.data.state ?? ""), provider, returnTo };
1131
+ return redirect(started.data.redirect_url, [...mine.headers.getSetCookie(), writeCookie(stepCookie, await seal(step, await key), attrs(request, STEP_SECONDS))]);
1132
+ }
1133
+ if (route === "/me/identities" && !post) {
1134
+ const mine = await signedInAs(request);
1135
+ if ("response" in mine) return mine.response;
1136
+ const res = await ask(request, "GET", "/v1/auth/identities", void 0, void 0, mine.token);
1137
+ if (res.error) return problem(res.status, res.error.code, res.error.message, void 0, mine.headers);
1138
+ const list = Array.isArray(res.data) ? res.data : [];
1139
+ 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);
1140
+ }
1111
1141
  if ((route === "/me" || route === "/me/sessions") && !post) {
1112
1142
  const mine = await signedInAs(request);
1113
1143
  if ("response" in mine) return mine.response;
@@ -1224,6 +1254,21 @@ function createCauthy(given = {}) {
1224
1254
  if (res.error) return refused(res, res.error.code === "step_up_required" ? "password" : void 0);
1225
1255
  return json({ status: "renewed", recoveryCodes: Array.isArray(res.data?.recovery_codes) ? res.data.recovery_codes.map(String) : [] }, 200, mine.headers);
1226
1256
  }
1257
+ if (route === "/me/email") {
1258
+ const bad = checkEmail(b.email);
1259
+ if (bad) return invalid(bad);
1260
+ const email = String(b.email).trim();
1261
+ const res = await as("/v1/auth/me/email", { email, password });
1262
+ 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);
1263
+ return json({ status: "sent", email }, 200, mine.headers);
1264
+ }
1265
+ if (route === "/me/identities/unlink") {
1266
+ const provider = typeof b.provider === "string" ? b.provider : "";
1267
+ if (!PROVIDER_ID.test(provider)) return problem(400, "invalid_provider", "Say which provider to unlink.", "provider", mine.headers);
1268
+ const res = await as(`/v1/auth/identities/${provider}`, void 0, "DELETE");
1269
+ if (res.error) return refused(res);
1270
+ return json({ status: "unlinked", provider }, 200, mine.headers);
1271
+ }
1227
1272
  if (route === "/me/sessions/end") {
1228
1273
  const res = await as("/v1/auth/me/sessions", void 0, "DELETE");
1229
1274
  if (res.error) return refused(res);