@manta-eu/auth 0.1.8 → 0.1.10
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 +67 -4
- package/dist/auth-commands.js +1 -0
- package/dist/auth-commands.js.map +1 -0
- package/dist/browser-login.js +1 -0
- package/dist/browser-login.js.map +1 -0
- package/dist/browser.js +1 -0
- package/dist/browser.js.map +1 -0
- package/dist/credential-store.js +1 -0
- package/dist/credential-store.js.map +1 -0
- package/dist/default-deployment.d.ts +8 -0
- package/dist/default-deployment.d.ts.map +1 -1
- package/dist/default-deployment.js +7 -0
- package/dist/default-deployment.js.map +1 -0
- package/dist/device-login.js +1 -0
- package/dist/device-login.js.map +1 -0
- package/dist/http.js +1 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/oidc.d.ts +15 -0
- package/dist/oidc.d.ts.map +1 -0
- package/dist/oidc.js +27 -0
- package/dist/oidc.js.map +1 -0
- package/dist/stored-login.js +1 -0
- package/dist/stored-login.js.map +1 -0
- package/dist/token.d.ts +3 -4
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js +32 -10
- package/dist/token.js.map +1 -0
- package/dist/web-login.d.ts +40 -0
- package/dist/web-login.d.ts.map +1 -0
- package/dist/web-login.js +82 -0
- package/dist/web-login.js.map +1 -0
- package/dist/web-routes.d.ts +42 -0
- package/dist/web-routes.d.ts.map +1 -0
- package/dist/web-routes.js +248 -0
- package/dist/web-routes.js.map +1 -0
- package/dist/web.d.ts +8 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +7 -0
- package/dist/web.js.map +1 -0
- package/package.json +14 -3
- package/src/auth-commands.ts +239 -0
- package/src/browser-login.ts +203 -0
- package/src/browser.ts +32 -0
- package/src/credential-store.ts +225 -0
- package/src/default-deployment.ts +15 -0
- package/src/device-login.ts +181 -0
- package/src/http.ts +47 -0
- package/src/index.ts +60 -0
- package/src/oidc.ts +40 -0
- package/src/stored-login.ts +63 -0
- package/src/token.ts +151 -0
- package/src/web-login.ts +106 -0
- package/src/web-routes.ts +321 -0
- package/src/web.ts +25 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { FetchLike } from "./http.js";
|
|
2
|
+
import type { OidcClient } from "./oidc.js";
|
|
3
|
+
export type WebLoginRoutesOptions = {
|
|
4
|
+
/** A confidential client: a custom app's client ID and secret, plus `baseUrl` and `realm` off hiremanta.com. */
|
|
5
|
+
client: OidcClient & {
|
|
6
|
+
clientSecret: string;
|
|
7
|
+
};
|
|
8
|
+
/** Where the app runs, e.g. `https://app.example.com`; `paths.callback` under it must be a registered redirect URL. */
|
|
9
|
+
appUrl: string;
|
|
10
|
+
/** At least 32 characters, kept on the server. Rotating it signs everyone out. */
|
|
11
|
+
sessionSecret: string;
|
|
12
|
+
paths?: {
|
|
13
|
+
login?: string;
|
|
14
|
+
callback?: string;
|
|
15
|
+
logout?: string;
|
|
16
|
+
};
|
|
17
|
+
/** Where people land after signing in (unless `/login?returnTo=` names a page) and after signing out. */
|
|
18
|
+
afterLogin?: string;
|
|
19
|
+
afterLogout?: string;
|
|
20
|
+
offlineAccess?: boolean;
|
|
21
|
+
idpHint?: string;
|
|
22
|
+
/** Off signs people out of this app only; on (the default) ends their Manta session too. */
|
|
23
|
+
signOutOfManta?: boolean;
|
|
24
|
+
cookieName?: string;
|
|
25
|
+
fetchImpl?: FetchLike;
|
|
26
|
+
};
|
|
27
|
+
export type WebSession = {
|
|
28
|
+
/** Absent when no one is signed in, or the session could not be renewed. */
|
|
29
|
+
accessToken: string | undefined;
|
|
30
|
+
idToken?: string;
|
|
31
|
+
/** Set-Cookie values to add to the response: a renewed session, or one cleared after a refused renewal. */
|
|
32
|
+
setCookies: string[];
|
|
33
|
+
};
|
|
34
|
+
export type WebLoginRoutes = {
|
|
35
|
+
/** Answers the login, callback and logout paths; `undefined` for any other request. */
|
|
36
|
+
handle(request: Request): Promise<Response | undefined>;
|
|
37
|
+
/** The signed-in person's access token, renewed when it is about to expire. */
|
|
38
|
+
session(request: Request): Promise<WebSession>;
|
|
39
|
+
};
|
|
40
|
+
/** Sign-in, callback and sign-out routes for a custom app's server, on standard Request and Response. */
|
|
41
|
+
export declare function webLoginRoutes(options: WebLoginRoutesOptions): WebLoginRoutes;
|
|
42
|
+
//# sourceMappingURL=web-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-routes.d.ts","sourceRoot":"","sources":["../src/web-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAW5C,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;AAmFF,yGAAyG;AACzG,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CA+L7E"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { EncryptJWT, jwtDecrypt } from "jose";
|
|
2
|
+
import { accessTokenExpiresAt } from "./token.js";
|
|
3
|
+
import { endWebLogin, finishWebLogin, refreshWebLogin, startWebLogin, webLogoutUrl, } from "./web-login.js";
|
|
4
|
+
const loginTtlMs = 10 * 60 * 1000;
|
|
5
|
+
const offlineCookieMaxAgeSeconds = 30 * 24 * 60 * 60;
|
|
6
|
+
const chunkSize = 3800;
|
|
7
|
+
const maxChunks = 10;
|
|
8
|
+
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
|
+
function isStoredSession(value) {
|
|
56
|
+
return typeof value === "object" && value !== null && typeof value.accessToken === "string";
|
|
57
|
+
}
|
|
58
|
+
function isPendingLogin(value) {
|
|
59
|
+
if (typeof value !== "object" || value === null)
|
|
60
|
+
return false;
|
|
61
|
+
const login = value;
|
|
62
|
+
return typeof login.state === "string" && typeof login.verifier === "string" && typeof login.returnTo === "string";
|
|
63
|
+
}
|
|
64
|
+
function redirect(location, setCookies = []) {
|
|
65
|
+
const headers = new Headers({ location, "cache-control": "no-store" });
|
|
66
|
+
for (const cookie of setCookies)
|
|
67
|
+
headers.append("set-cookie", cookie);
|
|
68
|
+
return new Response(null, { status: 302, headers });
|
|
69
|
+
}
|
|
70
|
+
/** Sign-in, callback and sign-out routes for a custom app's server, on standard Request and Response. */
|
|
71
|
+
export function webLoginRoutes(options) {
|
|
72
|
+
if (options.sessionSecret.length < 32)
|
|
73
|
+
throw new Error("sessionSecret must be at least 32 characters");
|
|
74
|
+
const appUrl = new URL(options.appUrl);
|
|
75
|
+
const secure = appUrl.protocol === "https:";
|
|
76
|
+
const paths = { login: "/login", callback: "/callback", logout: "/logout", ...options.paths };
|
|
77
|
+
const afterLogin = options.afterLogin ?? "/";
|
|
78
|
+
const afterLogout = options.afterLogout ?? "/";
|
|
79
|
+
const redirectUri = new URL(paths.callback, appUrl).toString();
|
|
80
|
+
const sessionName = options.cookieName ?? "manta_session";
|
|
81
|
+
const loginName = `${sessionName}_login`;
|
|
82
|
+
const keys = {
|
|
83
|
+
session: purposeKey(options.sessionSecret, "session"),
|
|
84
|
+
login: purposeKey(options.sessionSecret, "login"),
|
|
85
|
+
};
|
|
86
|
+
const { client, fetchImpl } = options;
|
|
87
|
+
// Keycloak takes a refresh token at most twice, so requests racing one expired session share one refresh,
|
|
88
|
+
// and so do requests that arrive with the old cookie shortly after it.
|
|
89
|
+
const refreshes = new Map();
|
|
90
|
+
function refreshOnce(refreshToken) {
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
for (const [token, { settledAt }] of refreshes) {
|
|
93
|
+
if (settledAt !== undefined && now - settledAt > refreshReuseMs)
|
|
94
|
+
refreshes.delete(token);
|
|
95
|
+
}
|
|
96
|
+
const known = refreshes.get(refreshToken);
|
|
97
|
+
if (known)
|
|
98
|
+
return known.renewed;
|
|
99
|
+
const entry = { renewed: refreshWebLogin(client, refreshToken, fetchImpl) };
|
|
100
|
+
refreshes.set(refreshToken, entry);
|
|
101
|
+
void entry.renewed.then((tokens) => {
|
|
102
|
+
entry.settledAt = Date.now();
|
|
103
|
+
entry.nextRefreshToken = tokens?.refreshToken;
|
|
104
|
+
}, () => refreshes.delete(refreshToken));
|
|
105
|
+
return entry.renewed;
|
|
106
|
+
}
|
|
107
|
+
// A signed-out session must not come back through a refresh shared with a request that still holds an older cookie.
|
|
108
|
+
function forgetRefreshes(endedRefreshToken) {
|
|
109
|
+
const ended = new Set([endedRefreshToken]);
|
|
110
|
+
for (const [token, { nextRefreshToken }] of [...refreshes].reverse()) {
|
|
111
|
+
if (ended.has(token) || (nextRefreshToken !== undefined && ended.has(nextRefreshToken))) {
|
|
112
|
+
ended.add(token);
|
|
113
|
+
refreshes.delete(token);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
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
|
+
function clearedSession(cookies) {
|
|
126
|
+
return [...cookies.keys()]
|
|
127
|
+
.filter((name) => name.startsWith(`${sessionName}.`))
|
|
128
|
+
.map((name) => cookie(name, "", { maxAge: 0 }));
|
|
129
|
+
}
|
|
130
|
+
async function sessionCookies(stored, cookies) {
|
|
131
|
+
const value = await seal(await keys.session, stored);
|
|
132
|
+
const chunks = Math.ceil(value.length / chunkSize);
|
|
133
|
+
if (chunks > maxChunks)
|
|
134
|
+
throw new Error("The session is too large to keep in cookies");
|
|
135
|
+
const maxAge = options.offlineAccess === true ? offlineCookieMaxAgeSeconds : undefined;
|
|
136
|
+
const written = Array.from({ length: chunks }, (_, index) => cookie(`${sessionName}.${index}`, value.slice(index * chunkSize, (index + 1) * chunkSize), {
|
|
137
|
+
...(maxAge === undefined ? {} : { maxAge }),
|
|
138
|
+
}));
|
|
139
|
+
const stale = [...cookies.keys()]
|
|
140
|
+
.filter((name) => name.startsWith(`${sessionName}.`) && Number(name.slice(sessionName.length + 1)) >= chunks)
|
|
141
|
+
.map((name) => cookie(name, "", { maxAge: 0 }));
|
|
142
|
+
return [...written, ...stale];
|
|
143
|
+
}
|
|
144
|
+
async function storedSession(cookies) {
|
|
145
|
+
let value = "";
|
|
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);
|
|
150
|
+
return isStoredSession(stored) ? stored : undefined;
|
|
151
|
+
}
|
|
152
|
+
async function login(request) {
|
|
153
|
+
const returnTo = safeReturnTo(new URL(request.url).searchParams.get("returnTo"), afterLogin, appUrl);
|
|
154
|
+
const { url, state, verifier } = await startWebLogin(client, {
|
|
155
|
+
redirectUri,
|
|
156
|
+
...(options.offlineAccess === true ? { offlineAccess: true } : {}),
|
|
157
|
+
...(options.idpHint ? { idpHint: options.idpHint } : {}),
|
|
158
|
+
});
|
|
159
|
+
const sealed = await seal(await keys.login, { state, verifier, returnTo }, loginTtlMs);
|
|
160
|
+
return redirect(url, [cookie(loginName, sealed, { path: paths.callback, maxAge: loginTtlMs / 1000 })]);
|
|
161
|
+
}
|
|
162
|
+
async function callback(request) {
|
|
163
|
+
const url = new URL(request.url);
|
|
164
|
+
const cookies = cookiesOf(request);
|
|
165
|
+
const clearLogin = cookie(loginName, "", { path: paths.callback, maxAge: 0 });
|
|
166
|
+
const pending = await unseal(await keys.login, cookies.get(loginName));
|
|
167
|
+
const failed = (reason) => new Response(`Sign-in failed: ${reason}`, { status: 400, headers: { "set-cookie": clearLogin } });
|
|
168
|
+
if (url.searchParams.has("error")) {
|
|
169
|
+
return failed(url.searchParams.get("error_description") ?? url.searchParams.get("error") ?? "refused");
|
|
170
|
+
}
|
|
171
|
+
if (!isPendingLogin(pending))
|
|
172
|
+
return failed("the sign-in expired, start again");
|
|
173
|
+
if (url.searchParams.get("state") !== pending.state)
|
|
174
|
+
return failed("the sign-in does not match this browser");
|
|
175
|
+
const code = url.searchParams.get("code");
|
|
176
|
+
if (!code)
|
|
177
|
+
return failed("no code");
|
|
178
|
+
const tokens = await finishWebLogin(client, { code, redirectUri, verifier: pending.verifier, callbackUrl: url.toString(), state: pending.state }, fetchImpl);
|
|
179
|
+
const stored = {
|
|
180
|
+
accessToken: tokens.accessToken,
|
|
181
|
+
...(tokens.refreshToken ? { refreshToken: tokens.refreshToken } : {}),
|
|
182
|
+
...(tokens.idToken ? { idToken: tokens.idToken } : {}),
|
|
183
|
+
};
|
|
184
|
+
return redirect(pending.returnTo, [...(await sessionCookies(stored, cookies)), clearLogin]);
|
|
185
|
+
}
|
|
186
|
+
async function logout(request) {
|
|
187
|
+
const cookies = cookiesOf(request);
|
|
188
|
+
const stored = await storedSession(cookies);
|
|
189
|
+
if (stored?.refreshToken) {
|
|
190
|
+
forgetRefreshes(stored.refreshToken);
|
|
191
|
+
await endWebLogin(client, stored.refreshToken, fetchImpl);
|
|
192
|
+
}
|
|
193
|
+
// Keycloak matches this exactly against the app's redirect URLs and home URL, which has no trailing slash.
|
|
194
|
+
const landing = afterLogout === "/" ? options.appUrl.replace(/\/+$/, "") : new URL(afterLogout, appUrl).toString();
|
|
195
|
+
const target = options.signOutOfManta === false
|
|
196
|
+
? afterLogout
|
|
197
|
+
: webLogoutUrl(client, {
|
|
198
|
+
...(stored?.idToken ? { idToken: stored.idToken } : {}),
|
|
199
|
+
postLogoutRedirectUri: landing,
|
|
200
|
+
});
|
|
201
|
+
return redirect(target, clearedSession(cookies));
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
async handle(request) {
|
|
205
|
+
const { pathname } = new URL(request.url);
|
|
206
|
+
if (pathname === paths.login && request.method === "GET")
|
|
207
|
+
return login(request);
|
|
208
|
+
if (pathname === paths.callback && request.method === "GET")
|
|
209
|
+
return callback(request);
|
|
210
|
+
if (pathname === paths.logout && (request.method === "GET" || request.method === "POST")) {
|
|
211
|
+
// The session cookies are SameSite=Lax, so a link from another site would otherwise sign the person out.
|
|
212
|
+
const site = request.headers.get("sec-fetch-site");
|
|
213
|
+
if (site === "cross-site" || site === "same-site")
|
|
214
|
+
return new Response("Sign out from the app", { status: 403 });
|
|
215
|
+
return logout(request);
|
|
216
|
+
}
|
|
217
|
+
return undefined;
|
|
218
|
+
},
|
|
219
|
+
async session(request) {
|
|
220
|
+
const cookies = cookiesOf(request);
|
|
221
|
+
const stored = await storedSession(cookies);
|
|
222
|
+
if (!stored)
|
|
223
|
+
return { accessToken: undefined, setCookies: [] };
|
|
224
|
+
const now = Date.now();
|
|
225
|
+
if (accessTokenExpiresAt(stored.accessToken, now) > now) {
|
|
226
|
+
return {
|
|
227
|
+
accessToken: stored.accessToken,
|
|
228
|
+
...(stored.idToken ? { idToken: stored.idToken } : {}),
|
|
229
|
+
setCookies: [],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const renewed = stored.refreshToken ? await refreshOnce(stored.refreshToken) : undefined;
|
|
233
|
+
if (!renewed)
|
|
234
|
+
return { accessToken: undefined, setCookies: clearedSession(cookies) };
|
|
235
|
+
const next = {
|
|
236
|
+
accessToken: renewed.accessToken,
|
|
237
|
+
...(renewed.refreshToken ? { refreshToken: renewed.refreshToken } : {}),
|
|
238
|
+
...((renewed.idToken ?? stored.idToken) ? { idToken: renewed.idToken ?? stored.idToken } : {}),
|
|
239
|
+
};
|
|
240
|
+
return {
|
|
241
|
+
accessToken: next.accessToken,
|
|
242
|
+
...(next.idToken ? { idToken: next.idToken } : {}),
|
|
243
|
+
setCookies: await sessionCookies(next, cookies),
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=web-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-routes.js","sourceRoot":"","sources":["../src/web-routes.ts"],"names":[],"mappings":"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"}
|
package/dist/web.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { defaultDeployment } from "./default-deployment.js";
|
|
2
|
+
export type { PublicClient } from "./device-login.js";
|
|
3
|
+
export { describeError, isTransientStatus, RefusedRequestError, type FetchLike } from "./http.js";
|
|
4
|
+
export { discoveryUrl, issuerUrl, type OidcClient, type Realm } from "./oidc.js";
|
|
5
|
+
export { accessTokenExpiresAt, cachedToken, mintWithClientCredentials, withRenewal, type ClientCredentials, type RenewableTokenSource, type TokenSource, } from "./token.js";
|
|
6
|
+
export { endWebLogin, finishWebLogin, refreshWebLogin, startWebLogin, webLogoutUrl, type StartWebLoginOptions, type WebLoginStart, type WebLogoutOptions, type WebTokens, } from "./web-login.js";
|
|
7
|
+
export { webLoginRoutes, type WebLoginRoutes, type WebLoginRoutesOptions, type WebSession } from "./web-routes.js";
|
|
8
|
+
//# sourceMappingURL=web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAClG,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,KAAK,EAAE,MAAM,WAAW,CAAC;AACjF,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,yBAAyB,EACzB,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,WAAW,EACX,cAAc,EACd,eAAe,EACf,aAAa,EACb,YAAY,EACZ,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,SAAS,GACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,qBAAqB,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/web.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { defaultDeployment } from "./default-deployment.js";
|
|
2
|
+
export { describeError, isTransientStatus, RefusedRequestError } from "./http.js";
|
|
3
|
+
export { discoveryUrl, issuerUrl } from "./oidc.js";
|
|
4
|
+
export { accessTokenExpiresAt, cachedToken, mintWithClientCredentials, withRenewal, } from "./token.js";
|
|
5
|
+
export { endWebLogin, finishWebLogin, refreshWebLogin, startWebLogin, webLogoutUrl, } from "./web-login.js";
|
|
6
|
+
export { webLoginRoutes } from "./web-routes.js";
|
|
7
|
+
//# sourceMappingURL=web.js.map
|
package/dist/web.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,mBAAmB,EAAkB,MAAM,WAAW,CAAC;AAClG,OAAO,EAAE,YAAY,EAAE,SAAS,EAA+B,MAAM,WAAW,CAAC;AACjF,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,yBAAyB,EACzB,WAAW,GAIZ,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,WAAW,EACX,cAAc,EACd,eAAe,EACf,aAAa,EACb,YAAY,GAKb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,cAAc,EAAoE,MAAM,iBAAiB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manta-eu/auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
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": [
|
|
@@ -19,12 +19,18 @@
|
|
|
19
19
|
"./default-deployment": {
|
|
20
20
|
"types": "./dist/default-deployment.d.ts",
|
|
21
21
|
"import": "./dist/default-deployment.js"
|
|
22
|
+
},
|
|
23
|
+
"./web": {
|
|
24
|
+
"types": "./dist/web.d.ts",
|
|
25
|
+
"import": "./dist/web.js"
|
|
22
26
|
}
|
|
23
27
|
},
|
|
24
28
|
"author": "",
|
|
25
29
|
"license": "MIT",
|
|
26
30
|
"dependencies": {
|
|
27
|
-
"commander": "^15.0.0"
|
|
31
|
+
"commander": "^15.0.0",
|
|
32
|
+
"jose": "^6.2.12",
|
|
33
|
+
"openid-client": "^6.8.8"
|
|
28
34
|
},
|
|
29
35
|
"devDependencies": {
|
|
30
36
|
"@types/node": "^24.13.3",
|
|
@@ -38,7 +44,12 @@
|
|
|
38
44
|
"main": "./dist/index.js",
|
|
39
45
|
"types": "./dist/index.d.ts",
|
|
40
46
|
"files": [
|
|
41
|
-
"dist"
|
|
47
|
+
"dist",
|
|
48
|
+
"src",
|
|
49
|
+
"!src/**/*.test.*",
|
|
50
|
+
"!src/**/*.stories.*",
|
|
51
|
+
"!src/**/__fixtures__",
|
|
52
|
+
"!src/coverage"
|
|
42
53
|
],
|
|
43
54
|
"publishConfig": {
|
|
44
55
|
"access": "public"
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { browserLogin, loginScope } from "./browser-login.js";
|
|
3
|
+
import { hasLocalBrowser, openInBrowser } from "./browser.js";
|
|
4
|
+
import { type CredentialStore, defaultCredentialStore } from "./credential-store.js";
|
|
5
|
+
import { defaultDeployment } from "./default-deployment.js";
|
|
6
|
+
import {
|
|
7
|
+
createPkcePair,
|
|
8
|
+
type DeviceTokens,
|
|
9
|
+
pollForDeviceTokens,
|
|
10
|
+
type PublicClient,
|
|
11
|
+
startDeviceLogin,
|
|
12
|
+
} from "./device-login.js";
|
|
13
|
+
import { describeError, type FetchLike, formRequest, postTo } from "./http.js";
|
|
14
|
+
import { saveLogin, storedLoginTokenSource } from "./stored-login.js";
|
|
15
|
+
import { accessTokenUsername, type RenewableTokenSource } from "./token.js";
|
|
16
|
+
|
|
17
|
+
export type ClientOptions = { keycloakUrl: string; keycloakRealm: string; clientId?: string };
|
|
18
|
+
|
|
19
|
+
export function cliClientId(realm: string): string {
|
|
20
|
+
return `${realm}-cli`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The confidential client a CI job authenticates with through MANTA_CLIENT_SECRET.
|
|
24
|
+
export const ciClientId = "manta-codegen";
|
|
25
|
+
|
|
26
|
+
export const clientIdHelp = `Keycloak client id (default: <realm>-cli, or ${ciClientId} with MANTA_CLIENT_SECRET)`;
|
|
27
|
+
|
|
28
|
+
export type LoginOptions = { useDeviceCode?: boolean };
|
|
29
|
+
|
|
30
|
+
export function publicClientFrom({ keycloakUrl, keycloakRealm, clientId }: ClientOptions): PublicClient {
|
|
31
|
+
return {
|
|
32
|
+
baseUrl: keycloakUrl.replace(/\/+$/, ""),
|
|
33
|
+
realm: keycloakRealm,
|
|
34
|
+
clientId: clientId ?? cliClientId(keycloakRealm),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function warnAs(label: string): (message: string) => void {
|
|
39
|
+
return (message) => console.error(`${label}: ${message}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function where(client: PublicClient): string {
|
|
43
|
+
return `${client.realm} at ${client.baseUrl}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function interactiveLogin(
|
|
47
|
+
client: PublicClient,
|
|
48
|
+
{
|
|
49
|
+
label,
|
|
50
|
+
useDeviceCode = false,
|
|
51
|
+
fetchImpl = fetch,
|
|
52
|
+
}: { label: string; useDeviceCode?: boolean; fetchImpl?: FetchLike },
|
|
53
|
+
): Promise<DeviceTokens> {
|
|
54
|
+
const localBrowser = hasLocalBrowser();
|
|
55
|
+
|
|
56
|
+
if (useDeviceCode || !localBrowser) {
|
|
57
|
+
const login = await startDeviceLogin(client, fetchImpl, createPkcePair(), loginScope);
|
|
58
|
+
|
|
59
|
+
console.error(`${label}: open ${login.verificationUri} and enter the code ${login.userCode}`);
|
|
60
|
+
if (localBrowser) openInBrowser(login.verificationUri);
|
|
61
|
+
|
|
62
|
+
return pollForDeviceTokens(client, login, { fetchImpl });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return browserLogin(client, {
|
|
66
|
+
fetchImpl,
|
|
67
|
+
openUrl: (url) => {
|
|
68
|
+
console.error(`${label}: log in with the browser that opened. If none did, open ${url}`);
|
|
69
|
+
openInBrowser(url);
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function reportLogin(label: string, client: PublicClient, tokens: DeviceTokens, saved: boolean): void {
|
|
75
|
+
const username = accessTokenUsername(tokens.accessToken);
|
|
76
|
+
|
|
77
|
+
console.error(`${label}: logged in to ${where(client)}${username ? ` as ${username}` : ""}`);
|
|
78
|
+
if (!saved) console.error(`${label}: Keycloak sent no refresh token, so the next run logs in again.`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function notLoggedIn(label: string, client: PublicClient): Error {
|
|
82
|
+
const flags = [
|
|
83
|
+
client.baseUrl === defaultDeployment.keycloakUrl ? "" : ` --keycloak-url ${client.baseUrl}`,
|
|
84
|
+
client.realm === defaultDeployment.keycloakRealm ? "" : ` --keycloak-realm ${client.realm}`,
|
|
85
|
+
client.clientId === cliClientId(client.realm) ? "" : ` --client-id ${client.clientId}`,
|
|
86
|
+
].join("");
|
|
87
|
+
|
|
88
|
+
return new Error(`not logged in to ${where(client)}. Run: ${label} login${flags}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// A run without a terminal cannot answer a browser, so it names the command to run instead of waiting.
|
|
92
|
+
export function userTokenSource(
|
|
93
|
+
client: PublicClient,
|
|
94
|
+
{
|
|
95
|
+
label,
|
|
96
|
+
useDeviceCode,
|
|
97
|
+
store = defaultCredentialStore({ warn: warnAs(label) }),
|
|
98
|
+
interactive = Boolean(process.stdin.isTTY),
|
|
99
|
+
fetchImpl = fetch,
|
|
100
|
+
}: {
|
|
101
|
+
label: string;
|
|
102
|
+
useDeviceCode?: boolean;
|
|
103
|
+
store?: CredentialStore;
|
|
104
|
+
interactive?: boolean;
|
|
105
|
+
fetchImpl?: FetchLike;
|
|
106
|
+
},
|
|
107
|
+
): RenewableTokenSource {
|
|
108
|
+
return storedLoginTokenSource({
|
|
109
|
+
client,
|
|
110
|
+
store,
|
|
111
|
+
fetchImpl,
|
|
112
|
+
warn: warnAs(label),
|
|
113
|
+
login: async () => {
|
|
114
|
+
if (!interactive) throw notLoggedIn(label, client);
|
|
115
|
+
|
|
116
|
+
const tokens = await interactiveLogin(client, { label, useDeviceCode, fetchImpl });
|
|
117
|
+
reportLogin(label, client, tokens, Boolean(tokens.refreshToken));
|
|
118
|
+
|
|
119
|
+
return tokens;
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function revocationEndpoint(baseUrl: string, realm: string): string {
|
|
125
|
+
return `${baseUrl}/realms/${realm}/protocol/openid-connect/revoke`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Revoking the refresh token ends its offline session in Keycloak, not just this machine's copy.
|
|
129
|
+
export async function revokeRefreshToken(
|
|
130
|
+
client: PublicClient,
|
|
131
|
+
refreshToken: string,
|
|
132
|
+
fetchImpl: FetchLike = fetch,
|
|
133
|
+
): Promise<boolean> {
|
|
134
|
+
const request = formRequest({ token: refreshToken, token_type_hint: "refresh_token", client_id: client.clientId });
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
return (await postTo(revocationEndpoint(client.baseUrl, client.realm), request, fetchImpl)).ok;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function logIn(
|
|
144
|
+
client: PublicClient,
|
|
145
|
+
{
|
|
146
|
+
label,
|
|
147
|
+
useDeviceCode,
|
|
148
|
+
store,
|
|
149
|
+
fetchImpl = fetch,
|
|
150
|
+
}: { label: string; useDeviceCode?: boolean; store: CredentialStore; fetchImpl?: FetchLike },
|
|
151
|
+
): Promise<void> {
|
|
152
|
+
const tokens = await interactiveLogin(client, { label, useDeviceCode, fetchImpl });
|
|
153
|
+
|
|
154
|
+
reportLogin(label, client, tokens, await saveLogin(store, client, tokens));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// The local copy goes even when Keycloak is unreachable, since a login the user asked to end must not linger here.
|
|
158
|
+
export async function logOut(
|
|
159
|
+
client: PublicClient,
|
|
160
|
+
{ label, store, fetchImpl = fetch }: { label: string; store: CredentialStore; fetchImpl?: FetchLike },
|
|
161
|
+
): Promise<void> {
|
|
162
|
+
const stored = await store.read(client);
|
|
163
|
+
|
|
164
|
+
if (!stored) {
|
|
165
|
+
console.error(`${label}: not logged in to ${where(client)}`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const revoked = await revokeRefreshToken(client, stored.refreshToken, fetchImpl);
|
|
170
|
+
await store.remove(client);
|
|
171
|
+
|
|
172
|
+
console.error(
|
|
173
|
+
revoked
|
|
174
|
+
? `${label}: logged out of ${where(client)}`
|
|
175
|
+
: `${label}: deleted the stored login for ${where(client)}, but Keycloak did not confirm the revocation.`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Reads the store only, so it answers offline; the next run finds out whether Keycloak still accepts the login.
|
|
180
|
+
export async function loginStatus(
|
|
181
|
+
client: PublicClient,
|
|
182
|
+
{ store }: { store: CredentialStore },
|
|
183
|
+
): Promise<{ loggedIn: boolean; line: string }> {
|
|
184
|
+
const stored = await store.read(client);
|
|
185
|
+
|
|
186
|
+
if (!stored) return { loggedIn: false, line: `not logged in to ${where(client)}` };
|
|
187
|
+
|
|
188
|
+
return { loggedIn: true, line: `logged in to ${where(client)}${stored.username ? ` as ${stored.username}` : ""}` };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function clientCommand(program: Command, name: string, description: string): Command {
|
|
192
|
+
return program
|
|
193
|
+
.command(name)
|
|
194
|
+
.description(description)
|
|
195
|
+
.option("--keycloak-url <url>", "Keycloak base URL", defaultDeployment.keycloakUrl)
|
|
196
|
+
.option("--keycloak-realm <realm>", "Keycloak realm", defaultDeployment.keycloakRealm)
|
|
197
|
+
.option("--client-id <id>", "Keycloak client id (default: <realm>-cli)");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function reportingFailure(label: string, action: () => Promise<void>): Promise<void> {
|
|
201
|
+
try {
|
|
202
|
+
await action();
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error(`${label}: ${describeError(error)}`);
|
|
205
|
+
process.exitCode = 1;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Both CLIs register the same commands over the same store, so one login serves both.
|
|
210
|
+
export function registerAuthCommands(
|
|
211
|
+
program: Command,
|
|
212
|
+
{
|
|
213
|
+
label,
|
|
214
|
+
store = defaultCredentialStore({ warn: warnAs(label) }),
|
|
215
|
+
fetchImpl = fetch,
|
|
216
|
+
}: { label: string; store?: CredentialStore; fetchImpl?: FetchLike },
|
|
217
|
+
): void {
|
|
218
|
+
clientCommand(program, "login", "log in once; later runs renew the stored login without a browser")
|
|
219
|
+
.option("--use-device-code", "log in with a one-time code instead of a browser redirect, e.g. over SSH")
|
|
220
|
+
.action((options: ClientOptions & LoginOptions) =>
|
|
221
|
+
reportingFailure(label, () =>
|
|
222
|
+
logIn(publicClientFrom(options), { label, useDeviceCode: options.useDeviceCode, store, fetchImpl }),
|
|
223
|
+
),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
clientCommand(program, "logout", "revoke the stored login at Keycloak and delete it").action(
|
|
227
|
+
(options: ClientOptions) =>
|
|
228
|
+
reportingFailure(label, () => logOut(publicClientFrom(options), { label, store, fetchImpl })),
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
clientCommand(program, "status", "show whether a login is stored").action((options: ClientOptions) =>
|
|
232
|
+
reportingFailure(label, async () => {
|
|
233
|
+
const { loggedIn, line } = await loginStatus(publicClientFrom(options), { store });
|
|
234
|
+
|
|
235
|
+
console.log(line);
|
|
236
|
+
if (!loggedIn) process.exitCode = 1;
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
}
|