cauthy-auth 0.19.0 → 0.20.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
@@ -1134,7 +1134,7 @@ function createCauthy(given = {}) {
1134
1134
  "/me/passkeys/remove",
1135
1135
  "/me/agents",
1136
1136
  "/me/agents/disconnect"
1137
- ].includes(route) || route.startsWith("/sign-in/social/") || route.startsWith("/callback/") || route.startsWith("/me/identities/link/");
1137
+ ].includes(route) || route.startsWith("/sign-in/social/") || route.startsWith("/callback/") || route.startsWith("/me/identities/link/") || route === "/sign-in/sso" || route === "/sign-in/sso/discover" || route === "/sso/callback";
1138
1138
  if (!own) return null;
1139
1139
  if (tenant === "") {
1140
1140
  return problem(500, "not_configured", "cauthy-auth: tenant (CAUTHY_TENANT) is required for the application's own sign-in screens");
@@ -1147,10 +1147,11 @@ function createCauthy(given = {}) {
1147
1147
  return json(lookCache.look);
1148
1148
  }
1149
1149
  if (route === "/methods" && !post) {
1150
- const [providers, settings, place] = await Promise.all([
1150
+ const [providers, settings, place, sso] = await Promise.all([
1151
1151
  ask(request, "GET", "/v1/auth/social/providers"),
1152
1152
  ask(request, "GET", "/v1/auth/settings"),
1153
- ask(request, "GET", `/v1/auth/webauthn/place?origin=${encodeURIComponent(originOf(request))}`)
1153
+ ask(request, "GET", `/v1/auth/webauthn/place?origin=${encodeURIComponent(originOf(request))}`),
1154
+ ask(request, "GET", "/v1/auth/sso/connections")
1154
1155
  ]);
1155
1156
  const list = Array.isArray(providers.data) ? providers.data : [];
1156
1157
  const here = place.data?.here === true;
@@ -1162,9 +1163,46 @@ function createCauthy(given = {}) {
1162
1163
  passkey: place.error ? true : here || place.data?.on_cauthy === true,
1163
1164
  passkeyHere: here,
1164
1165
  passkeyOnCauthy: place.error ? true : place.data?.on_cauthy === true,
1166
+ // A company's sign-in, found by the person's work address.
1167
+ sso: Array.isArray(sso.data) && sso.data.some((c) => c?.discoverable === true),
1165
1168
  ...settings.data?.captcha_enabled === true && settings.data?.captcha_site_key ? { captchaSiteKey: String(settings.data.captcha_site_key) } : {}
1166
1169
  });
1167
1170
  }
1171
+ if (route === "/sign-in/sso" && !post) {
1172
+ const returnTo = localPath(url.searchParams.get("return_to"), afterSignIn);
1173
+ const failed2 = (code) => redirect(`${returnTo}${returnTo.includes("?") ? "&" : "?"}auth_error=${encodeURIComponent(code)}`);
1174
+ const email = (url.searchParams.get("email") ?? "").trim();
1175
+ const connection = (url.searchParams.get("connection") ?? "").trim();
1176
+ if (email === "" && connection === "") return failed2("sso_not_found");
1177
+ const verifier = random(48);
1178
+ const challenge = b64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))));
1179
+ const callback2 = `${basePath}/sso/callback`;
1180
+ const started = await ask(request, "POST", "/v1/auth/sso/start", {
1181
+ ...connection ? { connection } : { email },
1182
+ redirect_uri: `${originOf(request)}${callback2}`,
1183
+ code_challenge: challenge
1184
+ }, callback2);
1185
+ if (started.error || typeof started.data?.redirect_url !== "string") return failed2(started.error?.code ?? "sso_unavailable");
1186
+ const step = { kind: "sso", token: verifier, returnTo };
1187
+ return redirect(started.data.redirect_url, [writeCookie(stepCookie, await seal(step, await key), attrs(request, STEP_SECONDS))]);
1188
+ }
1189
+ if (route === "/sso/callback" && !post) {
1190
+ const sealed = readCookies(request.headers.get("cookie")).get(stepCookie);
1191
+ const step = sealed ? await unseal(sealed, await key) : null;
1192
+ const clear = writeCookie(stepCookie, "", attrs(request, 0));
1193
+ const back = step?.returnTo ?? afterSignIn;
1194
+ const failedTo = (code2) => redirect(`${back}${back.includes("?") ? "&" : "?"}auth_error=${encodeURIComponent(code2)}`, [clear]);
1195
+ if (step === null || step.kind !== "sso") return failedTo("state_mismatch");
1196
+ const refused = url.searchParams.get("error");
1197
+ if (refused) return failedTo(refused);
1198
+ const code = url.searchParams.get("code");
1199
+ if (!code) return failedTo("sso_failed");
1200
+ const done = await ask(request, "POST", "/v1/auth/sso/token", { code, redirect_uri: `${originOf(request)}${basePath}/sso/callback`, code_verifier: step.token });
1201
+ if (done.error) return failedTo(done.error.code);
1202
+ const s = await stored(done.data);
1203
+ if (s === null) return failedTo("unavailable");
1204
+ return redirect(back, [...await write(request, s, readCookies(request.headers.get("cookie"))), clear]);
1205
+ }
1168
1206
  if (route.startsWith("/sign-in/social/") && !post) {
1169
1207
  const provider = providerIn(route.slice("/sign-in/social/".length));
1170
1208
  if (!PROVIDER_ID.test(provider)) return new Response("Not found", { status: 404 });
@@ -1274,6 +1312,14 @@ function createCauthy(given = {}) {
1274
1312
  }
1275
1313
  const b = await body(request);
1276
1314
  if (b === null) return problem(400, "bad_request", "Send the form as JSON.");
1315
+ if (route === "/sign-in/sso/discover") {
1316
+ const bad = checkEmail(b.email);
1317
+ if (bad) return invalid(bad);
1318
+ const res = await ask(request, "POST", "/v1/auth/sso/discover", { email: String(b.email).trim() });
1319
+ if (res.error) return problem(res.status, res.error.code, res.error.message);
1320
+ const c = res.data?.connection;
1321
+ return json(c ? { status: "found", connection: { id: String(c.slug ?? ""), name: String(c.display_name ?? ""), enforced: c.enforced === true } } : { status: "none" });
1322
+ }
1277
1323
  if (route === "/sign-in/email") {
1278
1324
  const bad = firstProblem(checkEmail(b.email), checkPassword(b.password));
1279
1325
  if (bad) return invalid(bad);