@manta-eu/auth 0.12.0 → 0.13.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/request-origin.d.ts +4 -0
- package/dist/request-origin.d.ts.map +1 -0
- package/dist/request-origin.js +17 -0
- package/dist/request-origin.js.map +1 -0
- package/dist/sealed-cookies.d.ts +43 -0
- package/dist/sealed-cookies.d.ts.map +1 -0
- package/dist/sealed-cookies.js +95 -0
- package/dist/sealed-cookies.js.map +1 -0
- package/dist/web-routes.d.ts.map +1 -1
- package/dist/web-routes.js +22 -86
- package/dist/web-routes.js.map +1 -1
- package/package.json +10 -2
- package/src/request-origin.ts +21 -0
- package/src/sealed-cookies.ts +145 -0
- package/src/web-routes.ts +61 -93
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const crossSiteSignOutMessage = "Sign out from the app";
|
|
2
|
+
export declare function isWebSocket(request: Request): boolean;
|
|
3
|
+
export declare function originAllowsRequest(request: Request, ownOrigin?: string): boolean;
|
|
4
|
+
//# sourceMappingURL=request-origin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-origin.d.ts","sourceRoot":"","sources":["../src/request-origin.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,uBAAuB,0BAA0B,CAAC;AAE/D,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAErD;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,SAA8B,GAAG,OAAO,CAYtG"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
2
|
+
export const crossSiteSignOutMessage = "Sign out from the app";
|
|
3
|
+
export function isWebSocket(request) {
|
|
4
|
+
return request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
5
|
+
}
|
|
6
|
+
export function originAllowsRequest(request, ownOrigin = new URL(request.url).origin) {
|
|
7
|
+
const fetchSite = request.headers.get("sec-fetch-site");
|
|
8
|
+
if (fetchSite === "same-origin")
|
|
9
|
+
return true;
|
|
10
|
+
if (fetchSite === "cross-site" || fetchSite === "same-site")
|
|
11
|
+
return false;
|
|
12
|
+
const origin = request.headers.get("origin");
|
|
13
|
+
if (isPresent(origin))
|
|
14
|
+
return origin === ownOrigin;
|
|
15
|
+
return !isWebSocket(request) && (request.method === "GET" || request.method === "HEAD");
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=request-origin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-origin.js","sourceRoot":"","sources":["../src/request-origin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD,MAAM,CAAC,MAAM,uBAAuB,GAAG,uBAAuB,CAAC;AAE/D,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,KAAK,WAAW,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAgB,EAAE,SAAS,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM;IAC3F,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAExD,IAAI,SAAS,KAAK,aAAa;QAAE,OAAO,IAAI,CAAC;IAE7C,IAAI,SAAS,KAAK,YAAY,IAAI,SAAS,KAAK,WAAW;QAAE,OAAO,KAAK,CAAC;IAE1E,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAE7C,IAAI,SAAS,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,KAAK,SAAS,CAAC;IAEnD,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;AAC1F,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type JWTPayload } from "jose";
|
|
2
|
+
export type SealingKey = Awaited<ReturnType<typeof crypto.subtle.deriveKey>>;
|
|
3
|
+
export declare function purposeKey(secret: string, purpose: string): Promise<SealingKey>;
|
|
4
|
+
export type SealArgs = {
|
|
5
|
+
key: SealingKey;
|
|
6
|
+
value: JWTPayload;
|
|
7
|
+
ttlMs?: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function seal(args: SealArgs): Promise<string>;
|
|
10
|
+
export declare function unseal(key: SealingKey, value: string | undefined): Promise<JWTPayload | undefined>;
|
|
11
|
+
export declare function cookiesOf(request: Request): Map<string, string>;
|
|
12
|
+
export type ReturnToArgs = {
|
|
13
|
+
raw: string | null;
|
|
14
|
+
fallback: string;
|
|
15
|
+
appUrl: URL;
|
|
16
|
+
};
|
|
17
|
+
export declare function safeReturnTo(args: ReturnToArgs): string;
|
|
18
|
+
export declare const sessionMaxAgeSeconds: number;
|
|
19
|
+
export type CookieArgs = {
|
|
20
|
+
name: string;
|
|
21
|
+
value: string;
|
|
22
|
+
path?: string;
|
|
23
|
+
maxAge?: number;
|
|
24
|
+
};
|
|
25
|
+
export type SetCookie = (cookie: CookieArgs) => string;
|
|
26
|
+
export declare function httpOnlyCookie(secure: boolean): SetCookie;
|
|
27
|
+
export type ChunkCookiesArgs = {
|
|
28
|
+
cookies: Map<string, string>;
|
|
29
|
+
name: string;
|
|
30
|
+
setCookie: SetCookie;
|
|
31
|
+
};
|
|
32
|
+
export type SealedChunkCookiesArgs = ChunkCookiesArgs & {
|
|
33
|
+
key: SealingKey;
|
|
34
|
+
value: JWTPayload;
|
|
35
|
+
maxAge?: number;
|
|
36
|
+
};
|
|
37
|
+
export declare function sealedChunkCookies(args: SealedChunkCookiesArgs): Promise<string[]>;
|
|
38
|
+
export declare function expiredChunkCookies(args: ChunkCookiesArgs): string[];
|
|
39
|
+
export type UnsealedChunksArgs = Pick<ChunkCookiesArgs, "cookies" | "name"> & {
|
|
40
|
+
key: SealingKey;
|
|
41
|
+
};
|
|
42
|
+
export declare function unsealedChunks(args: UnsealedChunksArgs): Promise<JWTPayload | undefined>;
|
|
43
|
+
//# sourceMappingURL=sealed-cookies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sealed-cookies.d.ts","sourceRoot":"","sources":["../src/sealed-cookies.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE/D,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;AAO7E,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAKrF;AAED,MAAM,MAAM,QAAQ,GAAG;IAAE,GAAG,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E,wBAAsB,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ1D;AAED,wBAAsB,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAUxG;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ/D;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC;AAEjF,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAUvD;AAgCD,eAAO,MAAM,oBAAoB,QAAoB,CAAC;AAEtD,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzF,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC;AAEvD,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,CAazD;AAED,MAAM,MAAM,gBAAgB,GAAG;IAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,SAAS,CAAA;CAAE,CAAC;AAEpG,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,GAAG;IAAE,GAAG,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAUxF;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAIpE;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,MAAM,CAAC,GAAG;IAAE,GAAG,EAAE,UAAU,CAAA;CAAE,CAAC;AAElG,wBAAsB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAI9F"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
2
|
+
import { EncryptJWT, jwtDecrypt } from "jose";
|
|
3
|
+
const chunkSize = 3800;
|
|
4
|
+
const maxChunks = 10;
|
|
5
|
+
const encoded = (text) => new TextEncoder().encode(text);
|
|
6
|
+
export async function purposeKey(secret, purpose) {
|
|
7
|
+
const material = await crypto.subtle.importKey("raw", encoded(secret), "HKDF", false, ["deriveKey"]);
|
|
8
|
+
const derivation = { name: "HKDF", hash: "SHA-256", salt: encoded("manta-web-login"), info: encoded(purpose) };
|
|
9
|
+
return crypto.subtle.deriveKey(derivation, material, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
|
|
10
|
+
}
|
|
11
|
+
export async function seal(args) {
|
|
12
|
+
const { key, value, ttlMs } = args;
|
|
13
|
+
const jwt = new EncryptJWT({ ...value }).setProtectedHeader({ alg: "dir", enc: "A256GCM" });
|
|
14
|
+
if (ttlMs !== undefined)
|
|
15
|
+
jwt.setExpirationTime(new Date(Date.now() + ttlMs));
|
|
16
|
+
return jwt.encrypt(key);
|
|
17
|
+
}
|
|
18
|
+
export async function unseal(key, value) {
|
|
19
|
+
if (!isPresent(value))
|
|
20
|
+
return undefined;
|
|
21
|
+
try {
|
|
22
|
+
const { payload } = await jwtDecrypt(value, key);
|
|
23
|
+
return payload;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function cookiesOf(request) {
|
|
30
|
+
const pairs = (request.headers.get("cookie") ?? "").split(";").flatMap((part) => {
|
|
31
|
+
const separator = part.indexOf("=");
|
|
32
|
+
return separator > 0 ? [[part.slice(0, separator).trim(), part.slice(separator + 1).trim()]] : [];
|
|
33
|
+
});
|
|
34
|
+
return new Map(pairs);
|
|
35
|
+
}
|
|
36
|
+
export function safeReturnTo(args) {
|
|
37
|
+
const { raw, fallback, appUrl } = args;
|
|
38
|
+
if (!raw?.startsWith("/") || /[\p{Cc}\\]/u.test(raw))
|
|
39
|
+
return fallback;
|
|
40
|
+
const { origin, pathname, search, hash } = new URL(raw, appUrl);
|
|
41
|
+
if (origin !== appUrl.origin || pathname.startsWith("//"))
|
|
42
|
+
return fallback;
|
|
43
|
+
return `${pathname}${search}${hash}`;
|
|
44
|
+
}
|
|
45
|
+
function cookieChunks(value) {
|
|
46
|
+
const count = Math.ceil(value.length / chunkSize);
|
|
47
|
+
if (count > maxChunks)
|
|
48
|
+
throw new Error("The session is too large to keep in cookies");
|
|
49
|
+
return Array.from({ length: count }, (_, index) => value.slice(index * chunkSize, (index + 1) * chunkSize));
|
|
50
|
+
}
|
|
51
|
+
function joinedChunks(cookies, name) {
|
|
52
|
+
const names = Array.from({ length: maxChunks }, (_, index) => `${name}.${index}`);
|
|
53
|
+
const firstMissing = names.findIndex((chunk) => !cookies.has(chunk));
|
|
54
|
+
return names
|
|
55
|
+
.slice(0, firstMissing === -1 ? undefined : firstMissing)
|
|
56
|
+
.map((chunk) => cookies.get(chunk))
|
|
57
|
+
.join("");
|
|
58
|
+
}
|
|
59
|
+
function chunkNamesFrom(args) {
|
|
60
|
+
const { cookies, name, first } = args;
|
|
61
|
+
const prefix = `${name}.`;
|
|
62
|
+
return [...cookies.keys()].filter((cookie) => cookie.startsWith(prefix) && Number(cookie.slice(prefix.length)) >= first);
|
|
63
|
+
}
|
|
64
|
+
export const sessionMaxAgeSeconds = 30 * 24 * 60 * 60;
|
|
65
|
+
export function httpOnlyCookie(secure) {
|
|
66
|
+
return (cookie) => {
|
|
67
|
+
const { name, value, path = "/", maxAge } = cookie;
|
|
68
|
+
return [
|
|
69
|
+
`${name}=${value}`,
|
|
70
|
+
`Path=${path}`,
|
|
71
|
+
"HttpOnly",
|
|
72
|
+
"SameSite=Lax",
|
|
73
|
+
...(secure ? ["Secure"] : []),
|
|
74
|
+
...(maxAge === undefined ? [] : [`Max-Age=${maxAge}`]),
|
|
75
|
+
].join("; ");
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export async function sealedChunkCookies(args) {
|
|
79
|
+
const { cookies, name, setCookie, key, value, maxAge } = args;
|
|
80
|
+
const chunks = cookieChunks(await seal({ key, value, ttlMs: maxAge === undefined ? undefined : maxAge * 1000 }));
|
|
81
|
+
const stale = chunkNamesFrom({ cookies, name, first: chunks.length });
|
|
82
|
+
return [
|
|
83
|
+
...chunks.map((chunk, index) => setCookie({ name: `${name}.${index}`, value: chunk, maxAge })),
|
|
84
|
+
...stale.map((chunk) => setCookie({ name: chunk, value: "", maxAge: 0 })),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
export function expiredChunkCookies(args) {
|
|
88
|
+
const { cookies, name, setCookie } = args;
|
|
89
|
+
return chunkNamesFrom({ cookies, name, first: 0 }).map((chunk) => setCookie({ name: chunk, value: "", maxAge: 0 }));
|
|
90
|
+
}
|
|
91
|
+
export async function unsealedChunks(args) {
|
|
92
|
+
const { cookies, name, key } = args;
|
|
93
|
+
return unseal(key, joinedChunks(cookies, name));
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=sealed-cookies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sealed-cookies.js","sourceRoot":"","sources":["../src/sealed-cookies.ts"],"names":[],"mappings":"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"}
|
package/dist/web-routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-routes.d.ts","sourceRoot":"","sources":["../src/web-routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"web-routes.d.ts","sourceRoot":"","sources":["../src/web-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAwB5C,MAAM,MAAM,qBAAqB,GAAG;IAClC,gHAAgH;IAChH,MAAM,EAAE,UAAU,GAAG;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,uHAAuH;IACvH,MAAM,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/D,yGAAyG;IACzG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2GAA2G;IAC3G,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,uFAAuF;IACvF,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IACxD,+EAA+E;IAC/E,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAChD,CAAC;AA+BF,yGAAyG;AACzG,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAuM7E"}
|
package/dist/web-routes.js
CHANGED
|
@@ -1,57 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { crossSiteSignOutMessage, originAllowsRequest } from "./request-origin.js";
|
|
2
|
+
import { cookiesOf, expiredChunkCookies, purposeKey, safeReturnTo, seal, sealedChunkCookies, sessionMaxAgeSeconds, unseal, unsealedChunks, httpOnlyCookie, } from "./sealed-cookies.js";
|
|
2
3
|
import { accessTokenExpiresAt } from "./token.js";
|
|
3
4
|
import { endWebLogin, finishWebLogin, refreshWebLogin, startWebLogin, webLogoutUrl, } from "./web-login.js";
|
|
4
5
|
const loginTtlMs = 10 * 60 * 1000;
|
|
5
|
-
const offlineCookieMaxAgeSeconds = 30 * 24 * 60 * 60;
|
|
6
|
-
const chunkSize = 3800;
|
|
7
|
-
const maxChunks = 10;
|
|
8
6
|
const refreshReuseMs = 10_000;
|
|
9
|
-
// One key per purpose, so a login cookie can never be replayed as a session cookie or the other way round.
|
|
10
|
-
async function purposeKey(secret, purpose) {
|
|
11
|
-
const material = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), "HKDF", false, ["deriveKey"]);
|
|
12
|
-
return crypto.subtle.deriveKey({
|
|
13
|
-
name: "HKDF",
|
|
14
|
-
hash: "SHA-256",
|
|
15
|
-
salt: new TextEncoder().encode("manta-web-login"),
|
|
16
|
-
info: new TextEncoder().encode(purpose),
|
|
17
|
-
}, material, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
|
|
18
|
-
}
|
|
19
|
-
async function seal(key, value, ttlMs) {
|
|
20
|
-
const jwt = new EncryptJWT({ ...value }).setProtectedHeader({ alg: "dir", enc: "A256GCM" });
|
|
21
|
-
if (ttlMs !== undefined)
|
|
22
|
-
jwt.setExpirationTime(new Date(Date.now() + ttlMs));
|
|
23
|
-
return jwt.encrypt(key);
|
|
24
|
-
}
|
|
25
|
-
async function unseal(key, value) {
|
|
26
|
-
if (!value)
|
|
27
|
-
return undefined;
|
|
28
|
-
try {
|
|
29
|
-
return (await jwtDecrypt(value, key)).payload;
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function cookiesOf(request) {
|
|
36
|
-
const cookies = new Map();
|
|
37
|
-
for (const part of (request.headers.get("cookie") ?? "").split(";")) {
|
|
38
|
-
const separator = part.indexOf("=");
|
|
39
|
-
if (separator > 0)
|
|
40
|
-
cookies.set(part.slice(0, separator).trim(), part.slice(separator + 1).trim());
|
|
41
|
-
}
|
|
42
|
-
return cookies;
|
|
43
|
-
}
|
|
44
|
-
// Only a path on the app itself, so `/login?returnTo=` can never send people to another site.
|
|
45
|
-
// A browser drops tabs and newlines from a Location, so `/\t/evil.example` would land on `//evil.example`.
|
|
46
|
-
function safeReturnTo(raw, fallback, appUrl) {
|
|
47
|
-
if (!raw?.startsWith("/") || /[\p{Cc}\\]/u.test(raw))
|
|
48
|
-
return fallback;
|
|
49
|
-
const resolved = new URL(raw, appUrl);
|
|
50
|
-
// Resolving drops dot segments, so `/.//evil.example` comes out as the protocol-relative `//evil.example`.
|
|
51
|
-
if (resolved.origin !== appUrl.origin || resolved.pathname.startsWith("//"))
|
|
52
|
-
return fallback;
|
|
53
|
-
return `${resolved.pathname}${resolved.search}${resolved.hash}`;
|
|
54
|
-
}
|
|
55
7
|
function isStoredSession(value) {
|
|
56
8
|
return typeof value === "object" && value !== null && typeof value.accessToken === "string";
|
|
57
9
|
}
|
|
@@ -72,7 +24,7 @@ export function webLoginRoutes(options) {
|
|
|
72
24
|
if (options.sessionSecret.length < 32)
|
|
73
25
|
throw new Error("sessionSecret must be at least 32 characters");
|
|
74
26
|
const appUrl = new URL(options.appUrl);
|
|
75
|
-
const
|
|
27
|
+
const cookie = httpOnlyCookie(appUrl.protocol === "https:");
|
|
76
28
|
const paths = { login: "/login", callback: "/callback", logout: "/logout", ...options.paths };
|
|
77
29
|
const afterLogin = options.afterLogin ?? "/";
|
|
78
30
|
const afterLogout = options.afterLogout ?? "/";
|
|
@@ -114,55 +66,40 @@ export function webLoginRoutes(options) {
|
|
|
114
66
|
}
|
|
115
67
|
}
|
|
116
68
|
}
|
|
117
|
-
function cookie(name, value, extra = {}) {
|
|
118
|
-
const attributes = [`${name}=${value}`, `Path=${extra.path ?? "/"}`, "HttpOnly", "SameSite=Lax"];
|
|
119
|
-
if (secure)
|
|
120
|
-
attributes.push("Secure");
|
|
121
|
-
if (extra.maxAge !== undefined)
|
|
122
|
-
attributes.push(`Max-Age=${extra.maxAge}`);
|
|
123
|
-
return attributes.join("; ");
|
|
124
|
-
}
|
|
125
69
|
function clearedSession(cookies) {
|
|
126
|
-
return
|
|
127
|
-
.filter((name) => name.startsWith(`${sessionName}.`))
|
|
128
|
-
.map((name) => cookie(name, "", { maxAge: 0 }));
|
|
70
|
+
return expiredChunkCookies({ cookies, name: sessionName, setCookie: cookie });
|
|
129
71
|
}
|
|
130
72
|
async function sessionCookies(stored, cookies) {
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
.filter((name) => name.startsWith(`${sessionName}.`) && Number(name.slice(sessionName.length + 1)) >= chunks)
|
|
141
|
-
.map((name) => cookie(name, "", { maxAge: 0 }));
|
|
142
|
-
return [...written, ...stale];
|
|
73
|
+
const maxAge = options.offlineAccess === true ? sessionMaxAgeSeconds : undefined;
|
|
74
|
+
return sealedChunkCookies({
|
|
75
|
+
cookies,
|
|
76
|
+
name: sessionName,
|
|
77
|
+
setCookie: cookie,
|
|
78
|
+
key: await keys.session,
|
|
79
|
+
value: stored,
|
|
80
|
+
maxAge,
|
|
81
|
+
});
|
|
143
82
|
}
|
|
144
83
|
async function storedSession(cookies) {
|
|
145
|
-
|
|
146
|
-
for (let index = 0; cookies.has(`${sessionName}.${index}`); index += 1) {
|
|
147
|
-
value += cookies.get(`${sessionName}.${index}`);
|
|
148
|
-
}
|
|
149
|
-
const stored = await unseal(await keys.session, value);
|
|
84
|
+
const stored = await unsealedChunks({ cookies, name: sessionName, key: await keys.session });
|
|
150
85
|
return isStoredSession(stored) ? stored : undefined;
|
|
151
86
|
}
|
|
152
87
|
async function login(request) {
|
|
153
|
-
const
|
|
88
|
+
const raw = new URL(request.url).searchParams.get("returnTo");
|
|
89
|
+
const returnTo = safeReturnTo({ raw, fallback: afterLogin, appUrl });
|
|
154
90
|
const { url, state, verifier } = await startWebLogin(client, {
|
|
155
91
|
redirectUri,
|
|
156
92
|
...(options.offlineAccess === true ? { offlineAccess: true } : {}),
|
|
157
93
|
...(options.idpHint ? { idpHint: options.idpHint } : {}),
|
|
158
94
|
});
|
|
159
|
-
const
|
|
160
|
-
|
|
95
|
+
const value = { state, verifier, returnTo };
|
|
96
|
+
const sealed = await seal({ key: await keys.login, value, ttlMs: loginTtlMs });
|
|
97
|
+
return redirect(url, [cookie({ name: loginName, value: sealed, path: paths.callback, maxAge: loginTtlMs / 1000 })]);
|
|
161
98
|
}
|
|
162
99
|
async function callback(request) {
|
|
163
100
|
const url = new URL(request.url);
|
|
164
101
|
const cookies = cookiesOf(request);
|
|
165
|
-
const clearLogin = cookie(loginName, "",
|
|
102
|
+
const clearLogin = cookie({ name: loginName, value: "", path: paths.callback, maxAge: 0 });
|
|
166
103
|
const pending = await unseal(await keys.login, cookies.get(loginName));
|
|
167
104
|
const failed = (reason) => new Response(`Sign-in failed: ${reason}`, { status: 400, headers: { "set-cookie": clearLogin } });
|
|
168
105
|
if (url.searchParams.has("error")) {
|
|
@@ -209,9 +146,8 @@ export function webLoginRoutes(options) {
|
|
|
209
146
|
return callback(request);
|
|
210
147
|
if (pathname === paths.logout && (request.method === "GET" || request.method === "POST")) {
|
|
211
148
|
// The session cookies are SameSite=Lax, so a link from another site would otherwise sign the person out.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
return new Response("Sign out from the app", { status: 403 });
|
|
149
|
+
if (!originAllowsRequest(request, appUrl.origin))
|
|
150
|
+
return new Response(crossSiteSignOutMessage, { status: 403 });
|
|
215
151
|
return logout(request);
|
|
216
152
|
}
|
|
217
153
|
return undefined;
|
package/dist/web-routes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-routes.js","sourceRoot":"","sources":["../src/web-routes.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"web-routes.js","sourceRoot":"","sources":["../src/web-routes.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manta-eu/auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Keycloak login for the SDK CLIs: browser and device-code login, silent token renewal, and credential storage; plus the default deployment the SDK targets",
|
|
5
5
|
"homepage": "https://developers.manta.eu",
|
|
6
6
|
"keywords": [
|
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
"./web": {
|
|
24
24
|
"types": "./dist/web.d.ts",
|
|
25
25
|
"import": "./dist/web.js"
|
|
26
|
+
},
|
|
27
|
+
"./sealed-cookies": {
|
|
28
|
+
"types": "./dist/sealed-cookies.d.ts",
|
|
29
|
+
"import": "./dist/sealed-cookies.js"
|
|
30
|
+
},
|
|
31
|
+
"./request-origin": {
|
|
32
|
+
"types": "./dist/request-origin.d.ts",
|
|
33
|
+
"import": "./dist/request-origin.js"
|
|
26
34
|
}
|
|
27
35
|
},
|
|
28
36
|
"author": "",
|
|
@@ -31,7 +39,7 @@
|
|
|
31
39
|
"commander": "^15.0.0",
|
|
32
40
|
"jose": "^6.2.12",
|
|
33
41
|
"openid-client": "^6.8.8",
|
|
34
|
-
"@manta-eu/lib": "0.
|
|
42
|
+
"@manta-eu/lib": "0.13.0"
|
|
35
43
|
},
|
|
36
44
|
"devDependencies": {
|
|
37
45
|
"@types/node": "^24.13.3",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
2
|
+
|
|
3
|
+
export const crossSiteSignOutMessage = "Sign out from the app";
|
|
4
|
+
|
|
5
|
+
export function isWebSocket(request: Request): boolean {
|
|
6
|
+
return request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function originAllowsRequest(request: Request, ownOrigin = new URL(request.url).origin): boolean {
|
|
10
|
+
const fetchSite = request.headers.get("sec-fetch-site");
|
|
11
|
+
|
|
12
|
+
if (fetchSite === "same-origin") return true;
|
|
13
|
+
|
|
14
|
+
if (fetchSite === "cross-site" || fetchSite === "same-site") return false;
|
|
15
|
+
|
|
16
|
+
const origin = request.headers.get("origin");
|
|
17
|
+
|
|
18
|
+
if (isPresent(origin)) return origin === ownOrigin;
|
|
19
|
+
|
|
20
|
+
return !isWebSocket(request) && (request.method === "GET" || request.method === "HEAD");
|
|
21
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
2
|
+
import { EncryptJWT, jwtDecrypt, type JWTPayload } from "jose";
|
|
3
|
+
|
|
4
|
+
export type SealingKey = Awaited<ReturnType<typeof crypto.subtle.deriveKey>>;
|
|
5
|
+
|
|
6
|
+
const chunkSize = 3800;
|
|
7
|
+
const maxChunks = 10;
|
|
8
|
+
|
|
9
|
+
const encoded = (text: string) => new TextEncoder().encode(text);
|
|
10
|
+
|
|
11
|
+
export async function purposeKey(secret: string, purpose: string): Promise<SealingKey> {
|
|
12
|
+
const material = await crypto.subtle.importKey("raw", encoded(secret), "HKDF", false, ["deriveKey"]);
|
|
13
|
+
const derivation = { name: "HKDF", hash: "SHA-256", salt: encoded("manta-web-login"), info: encoded(purpose) };
|
|
14
|
+
|
|
15
|
+
return crypto.subtle.deriveKey(derivation, material, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type SealArgs = { key: SealingKey; value: JWTPayload; ttlMs?: number };
|
|
19
|
+
|
|
20
|
+
export async function seal(args: SealArgs): Promise<string> {
|
|
21
|
+
const { key, value, ttlMs } = args;
|
|
22
|
+
|
|
23
|
+
const jwt = new EncryptJWT({ ...value }).setProtectedHeader({ alg: "dir", enc: "A256GCM" });
|
|
24
|
+
|
|
25
|
+
if (ttlMs !== undefined) jwt.setExpirationTime(new Date(Date.now() + ttlMs));
|
|
26
|
+
|
|
27
|
+
return jwt.encrypt(key);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function unseal(key: SealingKey, value: string | undefined): Promise<JWTPayload | undefined> {
|
|
31
|
+
if (!isPresent(value)) return undefined;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const { payload } = await jwtDecrypt(value, key);
|
|
35
|
+
|
|
36
|
+
return payload;
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function cookiesOf(request: Request): Map<string, string> {
|
|
43
|
+
const pairs = (request.headers.get("cookie") ?? "").split(";").flatMap((part) => {
|
|
44
|
+
const separator = part.indexOf("=");
|
|
45
|
+
|
|
46
|
+
return separator > 0 ? [[part.slice(0, separator).trim(), part.slice(separator + 1).trim()] as const] : [];
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return new Map(pairs);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type ReturnToArgs = { raw: string | null; fallback: string; appUrl: URL };
|
|
53
|
+
|
|
54
|
+
export function safeReturnTo(args: ReturnToArgs): string {
|
|
55
|
+
const { raw, fallback, appUrl } = args;
|
|
56
|
+
|
|
57
|
+
if (!raw?.startsWith("/") || /[\p{Cc}\\]/u.test(raw)) return fallback;
|
|
58
|
+
|
|
59
|
+
const { origin, pathname, search, hash } = new URL(raw, appUrl);
|
|
60
|
+
|
|
61
|
+
if (origin !== appUrl.origin || pathname.startsWith("//")) return fallback;
|
|
62
|
+
|
|
63
|
+
return `${pathname}${search}${hash}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function cookieChunks(value: string): string[] {
|
|
67
|
+
const count = Math.ceil(value.length / chunkSize);
|
|
68
|
+
|
|
69
|
+
if (count > maxChunks) throw new Error("The session is too large to keep in cookies");
|
|
70
|
+
|
|
71
|
+
return Array.from({ length: count }, (_, index) => value.slice(index * chunkSize, (index + 1) * chunkSize));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function joinedChunks(cookies: Map<string, string>, name: string): string {
|
|
75
|
+
const names = Array.from({ length: maxChunks }, (_, index) => `${name}.${index}`);
|
|
76
|
+
const firstMissing = names.findIndex((chunk) => !cookies.has(chunk));
|
|
77
|
+
|
|
78
|
+
return names
|
|
79
|
+
.slice(0, firstMissing === -1 ? undefined : firstMissing)
|
|
80
|
+
.map((chunk) => cookies.get(chunk))
|
|
81
|
+
.join("");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type ChunkNamesArgs = { cookies: Map<string, string>; name: string; first: number };
|
|
85
|
+
|
|
86
|
+
function chunkNamesFrom(args: ChunkNamesArgs): string[] {
|
|
87
|
+
const { cookies, name, first } = args;
|
|
88
|
+
|
|
89
|
+
const prefix = `${name}.`;
|
|
90
|
+
|
|
91
|
+
return [...cookies.keys()].filter(
|
|
92
|
+
(cookie) => cookie.startsWith(prefix) && Number(cookie.slice(prefix.length)) >= first,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const sessionMaxAgeSeconds = 30 * 24 * 60 * 60;
|
|
97
|
+
|
|
98
|
+
export type CookieArgs = { name: string; value: string; path?: string; maxAge?: number };
|
|
99
|
+
|
|
100
|
+
export type SetCookie = (cookie: CookieArgs) => string;
|
|
101
|
+
|
|
102
|
+
export function httpOnlyCookie(secure: boolean): SetCookie {
|
|
103
|
+
return (cookie) => {
|
|
104
|
+
const { name, value, path = "/", maxAge } = cookie;
|
|
105
|
+
|
|
106
|
+
return [
|
|
107
|
+
`${name}=${value}`,
|
|
108
|
+
`Path=${path}`,
|
|
109
|
+
"HttpOnly",
|
|
110
|
+
"SameSite=Lax",
|
|
111
|
+
...(secure ? ["Secure"] : []),
|
|
112
|
+
...(maxAge === undefined ? [] : [`Max-Age=${maxAge}`]),
|
|
113
|
+
].join("; ");
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type ChunkCookiesArgs = { cookies: Map<string, string>; name: string; setCookie: SetCookie };
|
|
118
|
+
|
|
119
|
+
export type SealedChunkCookiesArgs = ChunkCookiesArgs & { key: SealingKey; value: JWTPayload; maxAge?: number };
|
|
120
|
+
|
|
121
|
+
export async function sealedChunkCookies(args: SealedChunkCookiesArgs): Promise<string[]> {
|
|
122
|
+
const { cookies, name, setCookie, key, value, maxAge } = args;
|
|
123
|
+
|
|
124
|
+
const chunks = cookieChunks(await seal({ key, value, ttlMs: maxAge === undefined ? undefined : maxAge * 1000 }));
|
|
125
|
+
const stale = chunkNamesFrom({ cookies, name, first: chunks.length });
|
|
126
|
+
|
|
127
|
+
return [
|
|
128
|
+
...chunks.map((chunk, index) => setCookie({ name: `${name}.${index}`, value: chunk, maxAge })),
|
|
129
|
+
...stale.map((chunk) => setCookie({ name: chunk, value: "", maxAge: 0 })),
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function expiredChunkCookies(args: ChunkCookiesArgs): string[] {
|
|
134
|
+
const { cookies, name, setCookie } = args;
|
|
135
|
+
|
|
136
|
+
return chunkNamesFrom({ cookies, name, first: 0 }).map((chunk) => setCookie({ name: chunk, value: "", maxAge: 0 }));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type UnsealedChunksArgs = Pick<ChunkCookiesArgs, "cookies" | "name"> & { key: SealingKey };
|
|
140
|
+
|
|
141
|
+
export async function unsealedChunks(args: UnsealedChunksArgs): Promise<JWTPayload | undefined> {
|
|
142
|
+
const { cookies, name, key } = args;
|
|
143
|
+
|
|
144
|
+
return unseal(key, joinedChunks(cookies, name));
|
|
145
|
+
}
|
package/src/web-routes.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import { EncryptJWT, jwtDecrypt, type JWTPayload } from "jose";
|
|
2
1
|
import type { FetchLike } from "./http.js";
|
|
3
2
|
import type { OidcClient } from "./oidc.js";
|
|
3
|
+
import { crossSiteSignOutMessage, originAllowsRequest } from "./request-origin.js";
|
|
4
|
+
import {
|
|
5
|
+
cookiesOf,
|
|
6
|
+
expiredChunkCookies,
|
|
7
|
+
purposeKey,
|
|
8
|
+
safeReturnTo,
|
|
9
|
+
seal,
|
|
10
|
+
sealedChunkCookies,
|
|
11
|
+
sessionMaxAgeSeconds,
|
|
12
|
+
unseal,
|
|
13
|
+
unsealedChunks,
|
|
14
|
+
httpOnlyCookie,
|
|
15
|
+
} from "./sealed-cookies.js";
|
|
4
16
|
import { accessTokenExpiresAt } from "./token.js";
|
|
5
17
|
import {
|
|
6
18
|
endWebLogin,
|
|
@@ -51,78 +63,26 @@ type PendingLogin = { state: string; verifier: string; returnTo: string };
|
|
|
51
63
|
|
|
52
64
|
type SharedRefresh = { renewed: Promise<WebTokens | undefined>; settledAt?: number; nextRefreshToken?: string };
|
|
53
65
|
|
|
54
|
-
type SealingKey = Awaited<ReturnType<typeof crypto.subtle.deriveKey>>;
|
|
55
|
-
|
|
56
66
|
const loginTtlMs = 10 * 60 * 1000;
|
|
57
|
-
const offlineCookieMaxAgeSeconds = 30 * 24 * 60 * 60;
|
|
58
|
-
const chunkSize = 3800;
|
|
59
|
-
const maxChunks = 10;
|
|
60
67
|
const refreshReuseMs = 10_000;
|
|
61
68
|
|
|
62
|
-
// One key per purpose, so a login cookie can never be replayed as a session cookie or the other way round.
|
|
63
|
-
async function purposeKey(secret: string, purpose: string): Promise<SealingKey> {
|
|
64
|
-
const material = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), "HKDF", false, ["deriveKey"]);
|
|
65
|
-
return crypto.subtle.deriveKey(
|
|
66
|
-
{
|
|
67
|
-
name: "HKDF",
|
|
68
|
-
hash: "SHA-256",
|
|
69
|
-
salt: new TextEncoder().encode("manta-web-login"),
|
|
70
|
-
info: new TextEncoder().encode(purpose),
|
|
71
|
-
},
|
|
72
|
-
material,
|
|
73
|
-
{ name: "AES-GCM", length: 256 },
|
|
74
|
-
false,
|
|
75
|
-
["encrypt", "decrypt"],
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function seal(key: SealingKey, value: StoredSession | PendingLogin, ttlMs?: number): Promise<string> {
|
|
80
|
-
const jwt = new EncryptJWT({ ...value }).setProtectedHeader({ alg: "dir", enc: "A256GCM" });
|
|
81
|
-
if (ttlMs !== undefined) jwt.setExpirationTime(new Date(Date.now() + ttlMs));
|
|
82
|
-
return jwt.encrypt(key);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function unseal(key: SealingKey, value: string | undefined): Promise<JWTPayload | undefined> {
|
|
86
|
-
if (!value) return undefined;
|
|
87
|
-
try {
|
|
88
|
-
return (await jwtDecrypt(value, key)).payload;
|
|
89
|
-
} catch {
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function cookiesOf(request: Request): Map<string, string> {
|
|
95
|
-
const cookies = new Map<string, string>();
|
|
96
|
-
for (const part of (request.headers.get("cookie") ?? "").split(";")) {
|
|
97
|
-
const separator = part.indexOf("=");
|
|
98
|
-
if (separator > 0) cookies.set(part.slice(0, separator).trim(), part.slice(separator + 1).trim());
|
|
99
|
-
}
|
|
100
|
-
return cookies;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Only a path on the app itself, so `/login?returnTo=` can never send people to another site.
|
|
104
|
-
// A browser drops tabs and newlines from a Location, so `/\t/evil.example` would land on `//evil.example`.
|
|
105
|
-
function safeReturnTo(raw: string | null, fallback: string, appUrl: URL): string {
|
|
106
|
-
if (!raw?.startsWith("/") || /[\p{Cc}\\]/u.test(raw)) return fallback;
|
|
107
|
-
const resolved = new URL(raw, appUrl);
|
|
108
|
-
// Resolving drops dot segments, so `/.//evil.example` comes out as the protocol-relative `//evil.example`.
|
|
109
|
-
if (resolved.origin !== appUrl.origin || resolved.pathname.startsWith("//")) return fallback;
|
|
110
|
-
return `${resolved.pathname}${resolved.search}${resolved.hash}`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
69
|
function isStoredSession(value: unknown): value is StoredSession {
|
|
114
70
|
return typeof value === "object" && value !== null && typeof (value as StoredSession).accessToken === "string";
|
|
115
71
|
}
|
|
116
72
|
|
|
117
73
|
function isPendingLogin(value: unknown): value is PendingLogin {
|
|
118
74
|
if (typeof value !== "object" || value === null) return false;
|
|
75
|
+
|
|
119
76
|
const login = value as PendingLogin;
|
|
77
|
+
|
|
120
78
|
return typeof login.state === "string" && typeof login.verifier === "string" && typeof login.returnTo === "string";
|
|
121
79
|
}
|
|
122
80
|
|
|
123
81
|
function redirect(location: string, setCookies: string[] = []): Response {
|
|
124
82
|
const headers = new Headers({ location, "cache-control": "no-store" });
|
|
83
|
+
|
|
125
84
|
for (const cookie of setCookies) headers.append("set-cookie", cookie);
|
|
85
|
+
|
|
126
86
|
return new Response(null, { status: 302, headers });
|
|
127
87
|
}
|
|
128
88
|
|
|
@@ -131,7 +91,7 @@ export function webLoginRoutes(options: WebLoginRoutesOptions): WebLoginRoutes {
|
|
|
131
91
|
if (options.sessionSecret.length < 32) throw new Error("sessionSecret must be at least 32 characters");
|
|
132
92
|
|
|
133
93
|
const appUrl = new URL(options.appUrl);
|
|
134
|
-
const
|
|
94
|
+
const cookie = httpOnlyCookie(appUrl.protocol === "https:");
|
|
135
95
|
const paths = { login: "/login", callback: "/callback", logout: "/logout", ...options.paths };
|
|
136
96
|
const afterLogin = options.afterLogin ?? "/";
|
|
137
97
|
const afterLogout = options.afterLogout ?? "/";
|
|
@@ -143,17 +103,22 @@ export function webLoginRoutes(options: WebLoginRoutesOptions): WebLoginRoutes {
|
|
|
143
103
|
login: purposeKey(options.sessionSecret, "login"),
|
|
144
104
|
};
|
|
145
105
|
const { client, fetchImpl } = options;
|
|
106
|
+
|
|
146
107
|
// Keycloak takes a refresh token at most twice, so requests racing one expired session share one refresh,
|
|
147
108
|
// and so do requests that arrive with the old cookie shortly after it.
|
|
148
109
|
const refreshes = new Map<string, SharedRefresh>();
|
|
149
110
|
|
|
150
111
|
function refreshOnce(refreshToken: string): Promise<WebTokens | undefined> {
|
|
151
112
|
const now = Date.now();
|
|
113
|
+
|
|
152
114
|
for (const [token, { settledAt }] of refreshes) {
|
|
153
115
|
if (settledAt !== undefined && now - settledAt > refreshReuseMs) refreshes.delete(token);
|
|
154
116
|
}
|
|
117
|
+
|
|
155
118
|
const known = refreshes.get(refreshToken);
|
|
119
|
+
|
|
156
120
|
if (known) return known.renewed;
|
|
121
|
+
|
|
157
122
|
const entry: SharedRefresh = { renewed: refreshWebLogin(client, refreshToken, fetchImpl) };
|
|
158
123
|
refreshes.set(refreshToken, entry);
|
|
159
124
|
void entry.renewed.then(
|
|
@@ -163,6 +128,7 @@ export function webLoginRoutes(options: WebLoginRoutesOptions): WebLoginRoutes {
|
|
|
163
128
|
},
|
|
164
129
|
() => refreshes.delete(refreshToken),
|
|
165
130
|
);
|
|
131
|
+
|
|
166
132
|
return entry.renewed;
|
|
167
133
|
}
|
|
168
134
|
|
|
@@ -177,59 +143,48 @@ export function webLoginRoutes(options: WebLoginRoutesOptions): WebLoginRoutes {
|
|
|
177
143
|
}
|
|
178
144
|
}
|
|
179
145
|
|
|
180
|
-
function cookie(name: string, value: string, extra: { path?: string; maxAge?: number } = {}): string {
|
|
181
|
-
const attributes = [`${name}=${value}`, `Path=${extra.path ?? "/"}`, "HttpOnly", "SameSite=Lax"];
|
|
182
|
-
if (secure) attributes.push("Secure");
|
|
183
|
-
if (extra.maxAge !== undefined) attributes.push(`Max-Age=${extra.maxAge}`);
|
|
184
|
-
return attributes.join("; ");
|
|
185
|
-
}
|
|
186
|
-
|
|
187
146
|
function clearedSession(cookies: Map<string, string>): string[] {
|
|
188
|
-
return
|
|
189
|
-
.filter((name) => name.startsWith(`${sessionName}.`))
|
|
190
|
-
.map((name) => cookie(name, "", { maxAge: 0 }));
|
|
147
|
+
return expiredChunkCookies({ cookies, name: sessionName, setCookie: cookie });
|
|
191
148
|
}
|
|
192
149
|
|
|
193
150
|
async function sessionCookies(stored: StoredSession, cookies: Map<string, string>): Promise<string[]> {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
cookie
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
.filter((name) => name.startsWith(`${sessionName}.`) && Number(name.slice(sessionName.length + 1)) >= chunks)
|
|
205
|
-
.map((name) => cookie(name, "", { maxAge: 0 }));
|
|
206
|
-
return [...written, ...stale];
|
|
151
|
+
const maxAge = options.offlineAccess === true ? sessionMaxAgeSeconds : undefined;
|
|
152
|
+
|
|
153
|
+
return sealedChunkCookies({
|
|
154
|
+
cookies,
|
|
155
|
+
name: sessionName,
|
|
156
|
+
setCookie: cookie,
|
|
157
|
+
key: await keys.session,
|
|
158
|
+
value: stored,
|
|
159
|
+
maxAge,
|
|
160
|
+
});
|
|
207
161
|
}
|
|
208
162
|
|
|
209
163
|
async function storedSession(cookies: Map<string, string>): Promise<StoredSession | undefined> {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
value += cookies.get(`${sessionName}.${index}`);
|
|
213
|
-
}
|
|
214
|
-
const stored = await unseal(await keys.session, value);
|
|
164
|
+
const stored = await unsealedChunks({ cookies, name: sessionName, key: await keys.session });
|
|
165
|
+
|
|
215
166
|
return isStoredSession(stored) ? stored : undefined;
|
|
216
167
|
}
|
|
217
168
|
|
|
218
169
|
async function login(request: Request): Promise<Response> {
|
|
219
|
-
const
|
|
170
|
+
const raw = new URL(request.url).searchParams.get("returnTo");
|
|
171
|
+
const returnTo = safeReturnTo({ raw, fallback: afterLogin, appUrl });
|
|
220
172
|
const { url, state, verifier } = await startWebLogin(client, {
|
|
221
173
|
redirectUri,
|
|
222
174
|
...(options.offlineAccess === true ? { offlineAccess: true } : {}),
|
|
223
175
|
...(options.idpHint ? { idpHint: options.idpHint } : {}),
|
|
224
176
|
});
|
|
225
|
-
|
|
226
|
-
|
|
177
|
+
|
|
178
|
+
const value = { state, verifier, returnTo } satisfies PendingLogin;
|
|
179
|
+
const sealed = await seal({ key: await keys.login, value, ttlMs: loginTtlMs });
|
|
180
|
+
|
|
181
|
+
return redirect(url, [cookie({ name: loginName, value: sealed, path: paths.callback, maxAge: loginTtlMs / 1000 })]);
|
|
227
182
|
}
|
|
228
183
|
|
|
229
184
|
async function callback(request: Request): Promise<Response> {
|
|
230
185
|
const url = new URL(request.url);
|
|
231
186
|
const cookies = cookiesOf(request);
|
|
232
|
-
const clearLogin = cookie(loginName, "",
|
|
187
|
+
const clearLogin = cookie({ name: loginName, value: "", path: paths.callback, maxAge: 0 });
|
|
233
188
|
const pending = await unseal(await keys.login, cookies.get(loginName));
|
|
234
189
|
const failed = (reason: string) =>
|
|
235
190
|
new Response(`Sign-in failed: ${reason}`, { status: 400, headers: { "set-cookie": clearLogin } });
|
|
@@ -237,9 +192,12 @@ export function webLoginRoutes(options: WebLoginRoutesOptions): WebLoginRoutes {
|
|
|
237
192
|
if (url.searchParams.has("error")) {
|
|
238
193
|
return failed(url.searchParams.get("error_description") ?? url.searchParams.get("error") ?? "refused");
|
|
239
194
|
}
|
|
195
|
+
|
|
240
196
|
if (!isPendingLogin(pending)) return failed("the sign-in expired, start again");
|
|
241
197
|
if (url.searchParams.get("state") !== pending.state) return failed("the sign-in does not match this browser");
|
|
198
|
+
|
|
242
199
|
const code = url.searchParams.get("code");
|
|
200
|
+
|
|
243
201
|
if (!code) return failed("no code");
|
|
244
202
|
|
|
245
203
|
const tokens = await finishWebLogin(
|
|
@@ -252,16 +210,19 @@ export function webLoginRoutes(options: WebLoginRoutesOptions): WebLoginRoutes {
|
|
|
252
210
|
...(tokens.refreshToken ? { refreshToken: tokens.refreshToken } : {}),
|
|
253
211
|
...(tokens.idToken ? { idToken: tokens.idToken } : {}),
|
|
254
212
|
};
|
|
213
|
+
|
|
255
214
|
return redirect(pending.returnTo, [...(await sessionCookies(stored, cookies)), clearLogin]);
|
|
256
215
|
}
|
|
257
216
|
|
|
258
217
|
async function logout(request: Request): Promise<Response> {
|
|
259
218
|
const cookies = cookiesOf(request);
|
|
260
219
|
const stored = await storedSession(cookies);
|
|
220
|
+
|
|
261
221
|
if (stored?.refreshToken) {
|
|
262
222
|
forgetRefreshes(stored.refreshToken);
|
|
263
223
|
await endWebLogin(client, stored.refreshToken, fetchImpl);
|
|
264
224
|
}
|
|
225
|
+
|
|
265
226
|
// Keycloak matches this exactly against the app's redirect URLs and home URL, which has no trailing slash.
|
|
266
227
|
const landing = afterLogout === "/" ? options.appUrl.replace(/\/+$/, "") : new URL(afterLogout, appUrl).toString();
|
|
267
228
|
const target =
|
|
@@ -271,30 +232,35 @@ export function webLoginRoutes(options: WebLoginRoutesOptions): WebLoginRoutes {
|
|
|
271
232
|
...(stored?.idToken ? { idToken: stored.idToken } : {}),
|
|
272
233
|
postLogoutRedirectUri: landing,
|
|
273
234
|
});
|
|
235
|
+
|
|
274
236
|
return redirect(target, clearedSession(cookies));
|
|
275
237
|
}
|
|
276
238
|
|
|
277
239
|
return {
|
|
278
240
|
async handle(request) {
|
|
279
241
|
const { pathname } = new URL(request.url);
|
|
242
|
+
|
|
280
243
|
if (pathname === paths.login && request.method === "GET") return login(request);
|
|
281
244
|
if (pathname === paths.callback && request.method === "GET") return callback(request);
|
|
245
|
+
|
|
282
246
|
if (pathname === paths.logout && (request.method === "GET" || request.method === "POST")) {
|
|
283
247
|
// The session cookies are SameSite=Lax, so a link from another site would otherwise sign the person out.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return new Response("Sign out from the app", { status: 403 });
|
|
248
|
+
if (!originAllowsRequest(request, appUrl.origin)) return new Response(crossSiteSignOutMessage, { status: 403 });
|
|
249
|
+
|
|
287
250
|
return logout(request);
|
|
288
251
|
}
|
|
252
|
+
|
|
289
253
|
return undefined;
|
|
290
254
|
},
|
|
291
255
|
|
|
292
256
|
async session(request) {
|
|
293
257
|
const cookies = cookiesOf(request);
|
|
294
258
|
const stored = await storedSession(cookies);
|
|
259
|
+
|
|
295
260
|
if (!stored) return { accessToken: undefined, setCookies: [] };
|
|
296
261
|
|
|
297
262
|
const now = Date.now();
|
|
263
|
+
|
|
298
264
|
if (accessTokenExpiresAt(stored.accessToken, now) > now) {
|
|
299
265
|
return {
|
|
300
266
|
accessToken: stored.accessToken,
|
|
@@ -304,6 +270,7 @@ export function webLoginRoutes(options: WebLoginRoutesOptions): WebLoginRoutes {
|
|
|
304
270
|
}
|
|
305
271
|
|
|
306
272
|
const renewed = stored.refreshToken ? await refreshOnce(stored.refreshToken) : undefined;
|
|
273
|
+
|
|
307
274
|
if (!renewed) return { accessToken: undefined, setCookies: clearedSession(cookies) };
|
|
308
275
|
|
|
309
276
|
const next: StoredSession = {
|
|
@@ -311,6 +278,7 @@ export function webLoginRoutes(options: WebLoginRoutesOptions): WebLoginRoutes {
|
|
|
311
278
|
...(renewed.refreshToken ? { refreshToken: renewed.refreshToken } : {}),
|
|
312
279
|
...((renewed.idToken ?? stored.idToken) ? { idToken: renewed.idToken ?? stored.idToken } : {}),
|
|
313
280
|
};
|
|
281
|
+
|
|
314
282
|
return {
|
|
315
283
|
accessToken: next.accessToken,
|
|
316
284
|
...(next.idToken ? { idToken: next.idToken } : {}),
|