cauthy-auth 0.10.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/client.d.mts +54 -5
- package/dist/client.d.ts +54 -5
- package/dist/client.js +113 -11
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +112 -11
- package/dist/client.mjs.map +1 -1
- package/dist/index.js +115 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +115 -5
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +61 -5
- package/dist/react.d.ts +61 -5
- package/dist/react.js +198 -17
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +197 -17
- 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 +111 -11
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +111 -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 +111 -11
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +111 -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,37 @@ 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
|
+
"/me/passkeys",
|
|
1106
|
+
"/me/passkeys/add/begin",
|
|
1107
|
+
"/me/passkeys/add/finish",
|
|
1108
|
+
"/me/passkeys/remove"
|
|
1109
|
+
].includes(route) || route.startsWith("/sign-in/social/") || route.startsWith("/callback/") || route.startsWith("/me/identities/link/");
|
|
1064
1110
|
if (!own) return null;
|
|
1065
1111
|
if (tenant === "") {
|
|
1066
1112
|
return problem(500, "not_configured", "cauthy-auth: tenant (CAUTHY_TENANT) is required for the application's own sign-in screens");
|
|
@@ -1073,15 +1119,21 @@ function createCauthy(given = {}) {
|
|
|
1073
1119
|
return json(lookCache.look);
|
|
1074
1120
|
}
|
|
1075
1121
|
if (route === "/methods" && !post) {
|
|
1076
|
-
const [providers, settings] = await Promise.all([
|
|
1122
|
+
const [providers, settings, place] = await Promise.all([
|
|
1123
|
+
ask(request, "GET", "/v1/auth/social/providers"),
|
|
1124
|
+
ask(request, "GET", "/v1/auth/settings"),
|
|
1125
|
+
ask(request, "GET", `/v1/auth/webauthn/place?origin=${encodeURIComponent(originOf(request))}`)
|
|
1126
|
+
]);
|
|
1077
1127
|
const list = Array.isArray(providers.data) ? providers.data : [];
|
|
1128
|
+
const here = place.data?.here === true;
|
|
1078
1129
|
return json({
|
|
1079
1130
|
providers: list.map((p) => ({ id: String(p.provider ?? p.id ?? ""), name: String(p.label ?? p.name ?? p.provider ?? "") })).filter((p) => p.id !== ""),
|
|
1080
1131
|
signUp: settings.data?.allow_signup !== false,
|
|
1081
1132
|
// A sign-in link needs a page of the application to open.
|
|
1082
1133
|
magicLink: settings.data?.allow_magic_link === true && pages.signIn !== void 0,
|
|
1083
|
-
|
|
1084
|
-
|
|
1134
|
+
passkey: place.error ? true : here || place.data?.on_cauthy === true,
|
|
1135
|
+
passkeyHere: here,
|
|
1136
|
+
passkeyOnCauthy: place.error ? true : place.data?.on_cauthy === true
|
|
1085
1137
|
});
|
|
1086
1138
|
}
|
|
1087
1139
|
if (route.startsWith("/sign-in/social/") && !post) {
|
|
@@ -1143,6 +1195,14 @@ function createCauthy(given = {}) {
|
|
|
1143
1195
|
const list = Array.isArray(res.data) ? res.data : [];
|
|
1144
1196
|
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
1197
|
}
|
|
1198
|
+
if (route === "/me/passkeys" && !post) {
|
|
1199
|
+
const mine = await signedInAs(request);
|
|
1200
|
+
if ("response" in mine) return mine.response;
|
|
1201
|
+
const res = await ask(request, "GET", "/v1/auth/webauthn/credentials", void 0, void 0, mine.token);
|
|
1202
|
+
if (res.error) return problem(res.status, res.error.code, res.error.message, void 0, mine.headers);
|
|
1203
|
+
const list = Array.isArray(res.data) ? res.data : [];
|
|
1204
|
+
return json({ passkeys: list.map(passkeyOf) }, 200, mine.headers);
|
|
1205
|
+
}
|
|
1146
1206
|
if ((route === "/me" || route === "/me/sessions") && !post) {
|
|
1147
1207
|
const mine = await signedInAs(request);
|
|
1148
1208
|
if ("response" in mine) return mine.response;
|
|
@@ -1274,6 +1334,33 @@ function createCauthy(given = {}) {
|
|
|
1274
1334
|
if (res.error) return refused(res);
|
|
1275
1335
|
return json({ status: "unlinked", provider }, 200, mine.headers);
|
|
1276
1336
|
}
|
|
1337
|
+
if (route === "/me/passkeys/add/begin") {
|
|
1338
|
+
const res = await as("/v1/auth/webauthn/register/begin", { password });
|
|
1339
|
+
if (res.error) return refused(res, res.error.code === "step_up_required" ? "password" : void 0);
|
|
1340
|
+
const step = { kind: "enrol", token: String(res.data?.session_id ?? ""), returnTo: afterSignIn };
|
|
1341
|
+
const h = new Headers(mine.headers);
|
|
1342
|
+
h.append("set-cookie", writeCookie(stepCookie, await seal(step, await key), attrs(request, PASSKEY_SECONDS)));
|
|
1343
|
+
return json({ status: "started", options: res.data?.options?.publicKey ?? res.data?.options ?? null }, 200, h);
|
|
1344
|
+
}
|
|
1345
|
+
if (route === "/me/passkeys/add/finish") {
|
|
1346
|
+
const sealed = readCookies(request.headers.get("cookie")).get(stepCookie);
|
|
1347
|
+
const step = sealed ? await unseal(sealed, await key) : null;
|
|
1348
|
+
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);
|
|
1349
|
+
if (!isCredential(b.credential)) return problem(400, "bad_request", "Send the passkey the browser made.", void 0, mine.headers);
|
|
1350
|
+
const name = typeof b.name === "string" ? b.name.trim().slice(0, 60) : "";
|
|
1351
|
+
const res = await as("/v1/auth/webauthn/register/finish", { session_id: step.token, nickname: name, credential: b.credential });
|
|
1352
|
+
const h = new Headers(mine.headers);
|
|
1353
|
+
h.append("set-cookie", writeCookie(stepCookie, "", attrs(request, 0)));
|
|
1354
|
+
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);
|
|
1355
|
+
return json({ status: "added", passkey: passkeyOf(res.data) }, 200, h);
|
|
1356
|
+
}
|
|
1357
|
+
if (route === "/me/passkeys/remove") {
|
|
1358
|
+
const id = typeof b.id === "string" ? b.id : "";
|
|
1359
|
+
if (!/^[0-9a-f-]{36}$/i.test(id)) return problem(400, "invalid_passkey", "Say which passkey to remove.", void 0, mine.headers);
|
|
1360
|
+
const res = await as(`/v1/auth/webauthn/credentials/${id}`, void 0, "DELETE");
|
|
1361
|
+
if (res.error) return refused(res);
|
|
1362
|
+
return json({ status: "removed", id }, 200, mine.headers);
|
|
1363
|
+
}
|
|
1277
1364
|
if (route === "/me/sessions/end") {
|
|
1278
1365
|
const res = await as("/v1/auth/me/sessions", void 0, "DELETE");
|
|
1279
1366
|
if (res.error) return refused(res);
|
|
@@ -1300,6 +1387,29 @@ function createCauthy(given = {}) {
|
|
|
1300
1387
|
if (res.error) return problem(res.status, res.error.code, res.error.message);
|
|
1301
1388
|
return json({ status: "verified" });
|
|
1302
1389
|
}
|
|
1390
|
+
if (route === "/sign-in/passkey/begin") {
|
|
1391
|
+
const res = await ask(request, "POST", "/v1/auth/webauthn/login/begin", {});
|
|
1392
|
+
if (res.error) return problem(res.status, res.error.code, res.error.message);
|
|
1393
|
+
const step = { kind: "passkey", token: String(res.data?.session_id ?? ""), returnTo: afterSignIn };
|
|
1394
|
+
return json(
|
|
1395
|
+
{ status: "started", options: res.data?.options?.publicKey ?? res.data?.options ?? null },
|
|
1396
|
+
200,
|
|
1397
|
+
new Headers({ "set-cookie": writeCookie(stepCookie, await seal(step, await key), attrs(request, PASSKEY_SECONDS)) })
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
if (route === "/sign-in/passkey/finish") {
|
|
1401
|
+
const sealed = readCookies(request.headers.get("cookie")).get(stepCookie);
|
|
1402
|
+
const step = sealed ? await unseal(sealed, await key) : null;
|
|
1403
|
+
if (step === null || step.kind !== "passkey") return problem(400, "step_expired", "Try the passkey again: it was asked for too long ago.");
|
|
1404
|
+
if (!isCredential(b.credential)) return problem(400, "bad_request", "Send the passkey the browser answered with.");
|
|
1405
|
+
const res = await ask(request, "POST", "/v1/auth/webauthn/login/finish", { session_id: step.token, credential: b.credential });
|
|
1406
|
+
const clear = writeCookie(stepCookie, "", attrs(request, 0));
|
|
1407
|
+
if (res.error) {
|
|
1408
|
+
const failed2 = ["assertion_invalid", "session_expired", "clone_detected", "not_found"].includes(res.error.code);
|
|
1409
|
+
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 }));
|
|
1410
|
+
}
|
|
1411
|
+
return signedIn(request, res.data, [clear]);
|
|
1412
|
+
}
|
|
1303
1413
|
if (route === "/sign-in/magic-link") {
|
|
1304
1414
|
const bad = checkEmail(b.email);
|
|
1305
1415
|
if (bad) return invalid(bad);
|