cauthy-auth 0.10.0 → 0.12.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/README.md +3 -2
- package/dist/cli.js +299 -20
- package/dist/client.d.mts +88 -5
- package/dist/client.d.ts +88 -5
- package/dist/client.js +202 -11
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +200 -11
- package/dist/client.mjs.map +1 -1
- package/dist/index.js +128 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +128 -5
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +110 -5
- package/dist/react.d.ts +110 -5
- package/dist/react.js +338 -20
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +335 -20
- package/dist/react.mjs.map +1 -1
- package/dist/svelte.d.mts +1 -1
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +198 -11
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +198 -11
- package/dist/svelte.mjs.map +1 -1
- package/dist/vue.d.mts +1 -1
- package/dist/vue.d.ts +1 -1
- package/dist/vue.js +198 -11
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +198 -11
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -921,7 +921,11 @@ function createCauthy(given = {}) {
|
|
|
921
921
|
if (prompt === "login" || prompt === "none") q.set("prompt", prompt);
|
|
922
922
|
const locale = url.searchParams.get("locale") ?? url.searchParams.get("ui_locales");
|
|
923
923
|
if (locale && /^[a-zA-Z-]{2,10}$/.test(locale)) q.set("ui_locales", locale);
|
|
924
|
-
|
|
924
|
+
let passkey = "";
|
|
925
|
+
if (url.searchParams.get("method") === "passkey") {
|
|
926
|
+
q.set("method", "passkey");
|
|
927
|
+
passkey = "#passkey";
|
|
928
|
+
}
|
|
925
929
|
return redirect(`${issuer}/v1/oauth/authorize?${q}${passkey}`, [
|
|
926
930
|
writeCookie(flowCookie, await seal(flow, await key), attrs(request, FLOW_SECONDS))
|
|
927
931
|
]);
|
|
@@ -946,6 +950,10 @@ function createCauthy(given = {}) {
|
|
|
946
950
|
const error = url.searchParams.get("error");
|
|
947
951
|
if (error !== null) {
|
|
948
952
|
if (error === "login_required") return redirect(flow.returnTo, [writeCookie(flowCookie, "", attrs(request, 0))]);
|
|
953
|
+
if (error === "access_denied") {
|
|
954
|
+
const back = pages.signIn ?? flow.returnTo;
|
|
955
|
+
return redirect(`${back}${back.includes("?") ? "&" : "?"}auth_error=cancelled`, [writeCookie(flowCookie, "", attrs(request, 0))]);
|
|
956
|
+
}
|
|
949
957
|
return failed(request, error.slice(0, 60));
|
|
950
958
|
}
|
|
951
959
|
const code = url.searchParams.get("code");
|
|
@@ -976,6 +984,14 @@ function createCauthy(given = {}) {
|
|
|
976
984
|
return redirect(`${issuer}/v1/oauth/logout?${q}`, clearParts(cookieName, attrs(request, 0), had));
|
|
977
985
|
}
|
|
978
986
|
const STEP_SECONDS = 600;
|
|
987
|
+
const PASSKEY_SECONDS = 300;
|
|
988
|
+
const isCredential = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && typeof v.id === "string" && typeof v.response === "object";
|
|
989
|
+
const passkeyOf = (d) => ({
|
|
990
|
+
id: String(d?.id ?? ""),
|
|
991
|
+
name: String(d?.nickname ?? ""),
|
|
992
|
+
createdAt: String(d?.created_at ?? ""),
|
|
993
|
+
lastUsedAt: d?.last_used_at ? String(d.last_used_at) : null
|
|
994
|
+
});
|
|
979
995
|
const problem = (status2, code, message, field, headers) => json({ error: { code, message, ...field ? { field } : {} } }, status2, headers);
|
|
980
996
|
const invalid = (p) => problem(400, p.code, p.message, p.field);
|
|
981
997
|
function ownRequest(request) {
|
|
@@ -1060,7 +1076,39 @@ function createCauthy(given = {}) {
|
|
|
1060
1076
|
let lookCache = null;
|
|
1061
1077
|
async function ownRoute(request, url, route) {
|
|
1062
1078
|
const post = request.method === "POST";
|
|
1063
|
-
const own = [
|
|
1079
|
+
const own = [
|
|
1080
|
+
"/sign-in/email",
|
|
1081
|
+
"/sign-in/mfa",
|
|
1082
|
+
"/sign-up/email",
|
|
1083
|
+
"/forgot-password",
|
|
1084
|
+
"/reset-password",
|
|
1085
|
+
"/verify-email",
|
|
1086
|
+
"/sign-in/magic-link",
|
|
1087
|
+
"/sign-in/magic-link/verify",
|
|
1088
|
+
"/sign-in/email-code",
|
|
1089
|
+
"/me",
|
|
1090
|
+
"/me/password",
|
|
1091
|
+
"/me/mfa/setup",
|
|
1092
|
+
"/me/mfa/verify",
|
|
1093
|
+
"/me/mfa/disable",
|
|
1094
|
+
"/me/mfa/recovery-codes",
|
|
1095
|
+
"/me/sessions",
|
|
1096
|
+
"/me/sessions/end",
|
|
1097
|
+
"/me/email",
|
|
1098
|
+
"/me/identities",
|
|
1099
|
+
"/me/identities/unlink",
|
|
1100
|
+
"/sign-out",
|
|
1101
|
+
"/methods",
|
|
1102
|
+
"/look",
|
|
1103
|
+
"/sign-in/passkey/begin",
|
|
1104
|
+
"/sign-in/passkey/finish",
|
|
1105
|
+
"/sign-in/google-one-tap/begin",
|
|
1106
|
+
"/sign-in/google-one-tap",
|
|
1107
|
+
"/me/passkeys",
|
|
1108
|
+
"/me/passkeys/add/begin",
|
|
1109
|
+
"/me/passkeys/add/finish",
|
|
1110
|
+
"/me/passkeys/remove"
|
|
1111
|
+
].includes(route) || route.startsWith("/sign-in/social/") || route.startsWith("/callback/") || route.startsWith("/me/identities/link/");
|
|
1064
1112
|
if (!own) return null;
|
|
1065
1113
|
if (tenant === "") {
|
|
1066
1114
|
return problem(500, "not_configured", "cauthy-auth: tenant (CAUTHY_TENANT) is required for the application's own sign-in screens");
|
|
@@ -1073,15 +1121,21 @@ function createCauthy(given = {}) {
|
|
|
1073
1121
|
return json(lookCache.look);
|
|
1074
1122
|
}
|
|
1075
1123
|
if (route === "/methods" && !post) {
|
|
1076
|
-
const [providers, settings] = await Promise.all([
|
|
1124
|
+
const [providers, settings, place] = await Promise.all([
|
|
1125
|
+
ask(request, "GET", "/v1/auth/social/providers"),
|
|
1126
|
+
ask(request, "GET", "/v1/auth/settings"),
|
|
1127
|
+
ask(request, "GET", `/v1/auth/webauthn/place?origin=${encodeURIComponent(originOf(request))}`)
|
|
1128
|
+
]);
|
|
1077
1129
|
const list = Array.isArray(providers.data) ? providers.data : [];
|
|
1130
|
+
const here = place.data?.here === true;
|
|
1078
1131
|
return json({
|
|
1079
1132
|
providers: list.map((p) => ({ id: String(p.provider ?? p.id ?? ""), name: String(p.label ?? p.name ?? p.provider ?? "") })).filter((p) => p.id !== ""),
|
|
1080
1133
|
signUp: settings.data?.allow_signup !== false,
|
|
1081
1134
|
// A sign-in link needs a page of the application to open.
|
|
1082
1135
|
magicLink: settings.data?.allow_magic_link === true && pages.signIn !== void 0,
|
|
1083
|
-
|
|
1084
|
-
|
|
1136
|
+
passkey: place.error ? true : here || place.data?.on_cauthy === true,
|
|
1137
|
+
passkeyHere: here,
|
|
1138
|
+
passkeyOnCauthy: place.error ? true : place.data?.on_cauthy === true
|
|
1085
1139
|
});
|
|
1086
1140
|
}
|
|
1087
1141
|
if (route.startsWith("/sign-in/social/") && !post) {
|
|
@@ -1143,6 +1197,14 @@ function createCauthy(given = {}) {
|
|
|
1143
1197
|
const list = Array.isArray(res.data) ? res.data : [];
|
|
1144
1198
|
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
1199
|
}
|
|
1200
|
+
if (route === "/me/passkeys" && !post) {
|
|
1201
|
+
const mine = await signedInAs(request);
|
|
1202
|
+
if ("response" in mine) return mine.response;
|
|
1203
|
+
const res = await ask(request, "GET", "/v1/auth/webauthn/credentials", void 0, void 0, mine.token);
|
|
1204
|
+
if (res.error) return problem(res.status, res.error.code, res.error.message, void 0, mine.headers);
|
|
1205
|
+
const list = Array.isArray(res.data) ? res.data : [];
|
|
1206
|
+
return json({ passkeys: list.map(passkeyOf) }, 200, mine.headers);
|
|
1207
|
+
}
|
|
1146
1208
|
if ((route === "/me" || route === "/me/sessions") && !post) {
|
|
1147
1209
|
const mine = await signedInAs(request);
|
|
1148
1210
|
if ("response" in mine) return mine.response;
|
|
@@ -1274,6 +1336,33 @@ function createCauthy(given = {}) {
|
|
|
1274
1336
|
if (res.error) return refused(res);
|
|
1275
1337
|
return json({ status: "unlinked", provider }, 200, mine.headers);
|
|
1276
1338
|
}
|
|
1339
|
+
if (route === "/me/passkeys/add/begin") {
|
|
1340
|
+
const res = await as("/v1/auth/webauthn/register/begin", { password });
|
|
1341
|
+
if (res.error) return refused(res, res.error.code === "step_up_required" ? "password" : void 0);
|
|
1342
|
+
const step = { kind: "enrol", token: String(res.data?.session_id ?? ""), returnTo: afterSignIn };
|
|
1343
|
+
const h = new Headers(mine.headers);
|
|
1344
|
+
h.append("set-cookie", writeCookie(stepCookie, await seal(step, await key), attrs(request, PASSKEY_SECONDS)));
|
|
1345
|
+
return json({ status: "started", options: res.data?.options?.publicKey ?? res.data?.options ?? null }, 200, h);
|
|
1346
|
+
}
|
|
1347
|
+
if (route === "/me/passkeys/add/finish") {
|
|
1348
|
+
const sealed = readCookies(request.headers.get("cookie")).get(stepCookie);
|
|
1349
|
+
const step = sealed ? await unseal(sealed, await key) : null;
|
|
1350
|
+
if (step === null || step.kind !== "enrol") return problem(400, "step_expired", "Start adding the passkey again: it was asked for too long ago.", void 0, mine.headers);
|
|
1351
|
+
if (!isCredential(b.credential)) return problem(400, "bad_request", "Send the passkey the browser made.", void 0, mine.headers);
|
|
1352
|
+
const name = typeof b.name === "string" ? b.name.trim().slice(0, 60) : "";
|
|
1353
|
+
const res = await as("/v1/auth/webauthn/register/finish", { session_id: step.token, nickname: name, credential: b.credential });
|
|
1354
|
+
const h = new Headers(mine.headers);
|
|
1355
|
+
h.append("set-cookie", writeCookie(stepCookie, "", attrs(request, 0)));
|
|
1356
|
+
if (res.error) return problem(res.status, res.error.code === "attestation_invalid" ? "passkey_failed" : res.error.code, res.error.code === "attestation_invalid" ? "The passkey could not be added. Try again." : res.error.message, void 0, h);
|
|
1357
|
+
return json({ status: "added", passkey: passkeyOf(res.data) }, 200, h);
|
|
1358
|
+
}
|
|
1359
|
+
if (route === "/me/passkeys/remove") {
|
|
1360
|
+
const id = typeof b.id === "string" ? b.id : "";
|
|
1361
|
+
if (!/^[0-9a-f-]{36}$/i.test(id)) return problem(400, "invalid_passkey", "Say which passkey to remove.", void 0, mine.headers);
|
|
1362
|
+
const res = await as(`/v1/auth/webauthn/credentials/${id}`, void 0, "DELETE");
|
|
1363
|
+
if (res.error) return refused(res);
|
|
1364
|
+
return json({ status: "removed", id }, 200, mine.headers);
|
|
1365
|
+
}
|
|
1277
1366
|
if (route === "/me/sessions/end") {
|
|
1278
1367
|
const res = await as("/v1/auth/me/sessions", void 0, "DELETE");
|
|
1279
1368
|
if (res.error) return refused(res);
|
|
@@ -1300,6 +1389,40 @@ function createCauthy(given = {}) {
|
|
|
1300
1389
|
if (res.error) return problem(res.status, res.error.code, res.error.message);
|
|
1301
1390
|
return json({ status: "verified" });
|
|
1302
1391
|
}
|
|
1392
|
+
if (route === "/sign-in/passkey/begin") {
|
|
1393
|
+
const res = await ask(request, "POST", "/v1/auth/webauthn/login/begin", {});
|
|
1394
|
+
if (res.error) return problem(res.status, res.error.code, res.error.message);
|
|
1395
|
+
const step = { kind: "passkey", token: String(res.data?.session_id ?? ""), returnTo: afterSignIn };
|
|
1396
|
+
return json(
|
|
1397
|
+
{ status: "started", options: res.data?.options?.publicKey ?? res.data?.options ?? null },
|
|
1398
|
+
200,
|
|
1399
|
+
new Headers({ "set-cookie": writeCookie(stepCookie, await seal(step, await key), attrs(request, PASSKEY_SECONDS)) })
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
if (route === "/sign-in/passkey/finish") {
|
|
1403
|
+
const sealed = readCookies(request.headers.get("cookie")).get(stepCookie);
|
|
1404
|
+
const step = sealed ? await unseal(sealed, await key) : null;
|
|
1405
|
+
if (step === null || step.kind !== "passkey") return problem(400, "step_expired", "Try the passkey again: it was asked for too long ago.");
|
|
1406
|
+
if (!isCredential(b.credential)) return problem(400, "bad_request", "Send the passkey the browser answered with.");
|
|
1407
|
+
const res = await ask(request, "POST", "/v1/auth/webauthn/login/finish", { session_id: step.token, credential: b.credential });
|
|
1408
|
+
const clear = writeCookie(stepCookie, "", attrs(request, 0));
|
|
1409
|
+
if (res.error) {
|
|
1410
|
+
const failed2 = ["assertion_invalid", "session_expired", "clone_detected", "not_found"].includes(res.error.code);
|
|
1411
|
+
return problem(res.status, failed2 ? "passkey_failed" : res.error.code, failed2 ? "The passkey sign-in could not be completed. Try again, or sign in another way." : res.error.message, void 0, new Headers({ "set-cookie": clear }));
|
|
1412
|
+
}
|
|
1413
|
+
return signedIn(request, res.data, [clear]);
|
|
1414
|
+
}
|
|
1415
|
+
if (route === "/sign-in/google-one-tap/begin") {
|
|
1416
|
+
const res = await ask(request, "POST", "/v1/auth/google/one-tap/begin", {});
|
|
1417
|
+
if (res.error) return problem(res.status, res.error.code, res.error.message);
|
|
1418
|
+
return json({ status: "started", clientId: String(res.data?.client_id ?? ""), nonce: String(res.data?.nonce ?? "") });
|
|
1419
|
+
}
|
|
1420
|
+
if (route === "/sign-in/google-one-tap") {
|
|
1421
|
+
if (typeof b.credential !== "string" || b.credential === "") return problem(400, "bad_request", "Send the credential Google gave the page.");
|
|
1422
|
+
const res = await ask(request, "POST", "/v1/auth/google/one-tap", { credential: b.credential });
|
|
1423
|
+
if (res.error) return problem(res.status, res.error.code, res.error.message);
|
|
1424
|
+
return signedIn(request, res.data);
|
|
1425
|
+
}
|
|
1303
1426
|
if (route === "/sign-in/magic-link") {
|
|
1304
1427
|
const bad = checkEmail(b.email);
|
|
1305
1428
|
if (bad) return invalid(bad);
|