@deque/axe-auth 1.1.0-next.97bcb8e6 → 1.1.0-next.fb07beab
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 +10 -0
- package/dist/index.js +32 -25
- package/dist/oauth/callbackServer.d.ts +23 -0
- package/dist/oauth/callbackServer.js +234 -0
- package/dist/oauth/errors.d.ts +22 -0
- package/dist/oauth/errors.js +14 -0
- package/dist/oauth/index.d.ts +4 -0
- package/dist/oauth/index.js +7 -0
- package/dist/oauth/logo.generated.d.ts +1 -0
- package/dist/oauth/logo.generated.js +7 -0
- package/dist/oauth/renderHtml.d.ts +9 -0
- package/dist/oauth/renderHtml.js +60 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -26,3 +26,13 @@ axe-auth [options]
|
|
|
26
26
|
| ----------------- | ------------------- |
|
|
27
27
|
| `-v`, `--version` | Show version number |
|
|
28
28
|
| `-h`, `--help` | Show help message |
|
|
29
|
+
|
|
30
|
+
> Login, token exchange, and keychain storage are owned by sibling issues under epic [#410](https://github.com/dequelabs/axe-mcp-server/issues/410). This package currently ships only the loopback callback server module; see the architecture docs below.
|
|
31
|
+
|
|
32
|
+
## Architecture
|
|
33
|
+
|
|
34
|
+
Design notes live in the repository under [`packages/axe-auth/docs/`](https://github.com/dequelabs/axe-mcp-server/tree/develop/packages/axe-auth/docs):
|
|
35
|
+
|
|
36
|
+
- [`oauth-flow.md`](https://github.com/dequelabs/axe-mcp-server/blob/develop/packages/axe-auth/docs/oauth-flow.md) — high-level OAuth 2.0 + PKCE flow and where each concern lives.
|
|
37
|
+
- [`callback-server.md`](https://github.com/dequelabs/axe-mcp-server/blob/develop/packages/axe-auth/docs/callback-server.md) — `startCallbackServer` API, error codes, and RFC 8252 conformance.
|
|
38
|
+
- [`callback-page.md`](https://github.com/dequelabs/axe-mcp-server/blob/develop/packages/axe-auth/docs/callback-page.md) — HTML response design, branding, and CSP rationale.
|
package/dist/index.js
CHANGED
|
@@ -5,26 +5,7 @@ const node_util_1 = require("node:util");
|
|
|
5
5
|
const node_fs_1 = require("node:fs");
|
|
6
6
|
const node_path_1 = require("node:path");
|
|
7
7
|
const pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
({ values } = (0, node_util_1.parseArgs)({
|
|
11
|
-
options: {
|
|
12
|
-
version: { type: "boolean", short: "v" },
|
|
13
|
-
help: { type: "boolean", short: "h" },
|
|
14
|
-
},
|
|
15
|
-
strict: true,
|
|
16
|
-
}));
|
|
17
|
-
}
|
|
18
|
-
catch (err) {
|
|
19
|
-
console.error(err.message);
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
if (values.version) {
|
|
23
|
-
console.log(pkg.version);
|
|
24
|
-
process.exit(0);
|
|
25
|
-
}
|
|
26
|
-
if (values.help) {
|
|
27
|
-
console.log(`${pkg.name} v${pkg.version}
|
|
8
|
+
const helpText = `${pkg.name} v${pkg.version}
|
|
28
9
|
|
|
29
10
|
${pkg.description}
|
|
30
11
|
|
|
@@ -33,8 +14,34 @@ Usage:
|
|
|
33
14
|
|
|
34
15
|
Options:
|
|
35
16
|
-v, --version Show version number
|
|
36
|
-
-h, --help Show this help message
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
17
|
+
-h, --help Show this help message`;
|
|
18
|
+
const main = async () => {
|
|
19
|
+
let values;
|
|
20
|
+
try {
|
|
21
|
+
({ values } = (0, node_util_1.parseArgs)({
|
|
22
|
+
options: {
|
|
23
|
+
version: { type: "boolean", short: "v" },
|
|
24
|
+
help: { type: "boolean", short: "h" },
|
|
25
|
+
},
|
|
26
|
+
strict: true,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
console.error(err.message);
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
if (values.version) {
|
|
34
|
+
console.log(pkg.version);
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
if (values.help) {
|
|
38
|
+
console.log(helpText);
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
console.log("No arguments provided. Use --help for usage information.");
|
|
42
|
+
return 1;
|
|
43
|
+
};
|
|
44
|
+
main().then((code) => process.exit(code), (err) => {
|
|
45
|
+
console.error(err);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
2
|
+
export type CallbackServerOptions = {
|
|
3
|
+
expectedState: string;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
};
|
|
7
|
+
export type CallbackResult = {
|
|
8
|
+
code: string;
|
|
9
|
+
state: string;
|
|
10
|
+
};
|
|
11
|
+
export type CallbackServerHandle = {
|
|
12
|
+
redirectUri: string;
|
|
13
|
+
result: Promise<CallbackResult>;
|
|
14
|
+
close: () => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
type RequestHandler = (req: IncomingMessage, res: ServerResponse) => void;
|
|
17
|
+
type LoopbackListener = (handler: RequestHandler, host: string) => Promise<Server>;
|
|
18
|
+
export declare const bindLoopback: (handler: RequestHandler, listenFn?: LoopbackListener) => Promise<{
|
|
19
|
+
server: Server;
|
|
20
|
+
host: "127.0.0.1" | "::1";
|
|
21
|
+
}>;
|
|
22
|
+
export declare const startCallbackServer: (opts: CallbackServerOptions) => Promise<CallbackServerHandle>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startCallbackServer = exports.bindLoopback = void 0;
|
|
4
|
+
const node_events_1 = require("node:events");
|
|
5
|
+
const node_http_1 = require("node:http");
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
const renderHtml_1 = require("./renderHtml");
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
9
|
+
const LOOPBACK_ADDRESSES = new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
|
|
10
|
+
const isLoopback = (addr) => !!addr && LOOPBACK_ADDRESSES.has(addr);
|
|
11
|
+
// Errors from bind(2) that mean "this address family isn't configured on this
|
|
12
|
+
// host" — the signal to fall back to the other loopback family rather than
|
|
13
|
+
// fail the caller.
|
|
14
|
+
const FAMILY_UNAVAILABLE = new Set(["EAFNOSUPPORT", "EADDRNOTAVAIL"]);
|
|
15
|
+
const listen = async (handler, host) => {
|
|
16
|
+
const server = (0, node_http_1.createServer)(handler);
|
|
17
|
+
server.listen(0, host);
|
|
18
|
+
// events.once rejects if the emitter fires 'error' before 'listening'.
|
|
19
|
+
await (0, node_events_1.once)(server, "listening");
|
|
20
|
+
return server;
|
|
21
|
+
};
|
|
22
|
+
// RFC 8252 §7.3: "use whichever is available". Prefer IPv4; fall back to
|
|
23
|
+
// IPv6 only when the IPv4 loopback isn't configured on this host. `listenFn`
|
|
24
|
+
// is an injection seam for tests — production callers use the default.
|
|
25
|
+
const bindLoopback = async (handler, listenFn = listen) => {
|
|
26
|
+
try {
|
|
27
|
+
return {
|
|
28
|
+
server: await listenFn(handler, "127.0.0.1"),
|
|
29
|
+
host: "127.0.0.1",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
const code = err.code;
|
|
34
|
+
if (!code || !FAMILY_UNAVAILABLE.has(code)) {
|
|
35
|
+
throw new errors_1.OAuthCallbackError("BIND_FAILED", `Failed to bind loopback server on 127.0.0.1: ${err.message}`, { cause: err });
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
return {
|
|
39
|
+
server: await listenFn(handler, "::1"),
|
|
40
|
+
host: "::1",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (ipv6Err) {
|
|
44
|
+
throw new errors_1.OAuthCallbackError("BIND_FAILED", `Failed to bind loopback server on 127.0.0.1 (${code}) and [::1]: ${ipv6Err.message}`, { cause: ipv6Err });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
exports.bindLoopback = bindLoopback;
|
|
49
|
+
const closeServer = async (server) => {
|
|
50
|
+
if (!server.listening)
|
|
51
|
+
return;
|
|
52
|
+
server.close();
|
|
53
|
+
server.closeAllConnections?.();
|
|
54
|
+
await (0, node_events_1.once)(server, "close");
|
|
55
|
+
};
|
|
56
|
+
const writeHtml = (res, status, html) => {
|
|
57
|
+
res.writeHead(status, {
|
|
58
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
59
|
+
"Content-Security-Policy": renderHtml_1.CSP_HEADER,
|
|
60
|
+
"X-Content-Type-Options": "nosniff",
|
|
61
|
+
});
|
|
62
|
+
res.end(html);
|
|
63
|
+
};
|
|
64
|
+
const writeText = (res, status, body, extraHeaders = {}) => {
|
|
65
|
+
res.writeHead(status, {
|
|
66
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
67
|
+
"X-Content-Type-Options": "nosniff",
|
|
68
|
+
...extraHeaders,
|
|
69
|
+
});
|
|
70
|
+
res.end(body + "\n");
|
|
71
|
+
};
|
|
72
|
+
const startCallbackServer = async (opts) => {
|
|
73
|
+
const { expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal } = opts;
|
|
74
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
75
|
+
throw new TypeError(`timeoutMs must be a positive finite number (received ${timeoutMs}).`);
|
|
76
|
+
}
|
|
77
|
+
let resolveResult;
|
|
78
|
+
let rejectResult;
|
|
79
|
+
const result = new Promise((resolve, reject) => {
|
|
80
|
+
resolveResult = resolve;
|
|
81
|
+
rejectResult = reject;
|
|
82
|
+
});
|
|
83
|
+
// Avoid unhandled rejection warnings if the caller defers attaching a
|
|
84
|
+
// .catch() until after the timer fires.
|
|
85
|
+
result.catch(() => { });
|
|
86
|
+
let consumed = false;
|
|
87
|
+
let closed = false;
|
|
88
|
+
let server = null;
|
|
89
|
+
let timeoutHandle = null;
|
|
90
|
+
let abortListener = null;
|
|
91
|
+
const close = async () => {
|
|
92
|
+
if (closed)
|
|
93
|
+
return;
|
|
94
|
+
closed = true;
|
|
95
|
+
if (timeoutHandle) {
|
|
96
|
+
clearTimeout(timeoutHandle);
|
|
97
|
+
timeoutHandle = null;
|
|
98
|
+
}
|
|
99
|
+
if (abortListener && signal) {
|
|
100
|
+
signal.removeEventListener("abort", abortListener);
|
|
101
|
+
abortListener = null;
|
|
102
|
+
}
|
|
103
|
+
if (server)
|
|
104
|
+
await closeServer(server);
|
|
105
|
+
};
|
|
106
|
+
// Test-and-set on the one-shot slot. Returns true if the caller won the
|
|
107
|
+
// claim (and must settle the promise); false if another caller already
|
|
108
|
+
// consumed it (and must not touch the promise).
|
|
109
|
+
const tryConsume = () => {
|
|
110
|
+
if (consumed)
|
|
111
|
+
return false;
|
|
112
|
+
consumed = true;
|
|
113
|
+
return true;
|
|
114
|
+
};
|
|
115
|
+
// For out-of-band settlements (timeout, abort) that don't involve a response
|
|
116
|
+
// the client needs to see. Closes the server immediately.
|
|
117
|
+
const settleRejectNow = (err) => {
|
|
118
|
+
if (!tryConsume())
|
|
119
|
+
return;
|
|
120
|
+
rejectResult(err);
|
|
121
|
+
void close();
|
|
122
|
+
};
|
|
123
|
+
// For in-handler settlements: defer the promise + server-close until the
|
|
124
|
+
// response has been flushed to the client, so the browser actually sees the
|
|
125
|
+
// success/error page before we tear down the socket. "finish" is the event
|
|
126
|
+
// that fires when the response body has been handed to the OS; "close" and
|
|
127
|
+
// "error" are fallbacks for early aborts so we never leak an unsettled
|
|
128
|
+
// promise.
|
|
129
|
+
const deferSettleOnClose = (res, finalize) => {
|
|
130
|
+
let settled = false;
|
|
131
|
+
const settle = () => {
|
|
132
|
+
if (settled)
|
|
133
|
+
return;
|
|
134
|
+
settled = true;
|
|
135
|
+
res.off("finish", settle);
|
|
136
|
+
res.off("close", settle);
|
|
137
|
+
res.off("error", settle);
|
|
138
|
+
finalize();
|
|
139
|
+
void close();
|
|
140
|
+
};
|
|
141
|
+
res.once("finish", settle);
|
|
142
|
+
res.once("close", settle);
|
|
143
|
+
res.once("error", settle);
|
|
144
|
+
};
|
|
145
|
+
const handler = (req, res) => {
|
|
146
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
147
|
+
if (url.pathname === "/favicon.ico") {
|
|
148
|
+
writeText(res, 404, "Not Found");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (req.method !== "GET") {
|
|
152
|
+
writeText(res, 405, "Method Not Allowed", { Allow: "GET" });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (!isLoopback(req.socket.remoteAddress)) {
|
|
156
|
+
writeText(res, 403, "Forbidden");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (url.pathname !== "/callback") {
|
|
160
|
+
writeText(res, 404, "Not Found");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Atomically decide whether this request owns the one-shot slot. Claiming
|
|
164
|
+
// up front means once we start composing the response, no concurrent
|
|
165
|
+
// settlement (timer/abort) can race us into a dropped connection.
|
|
166
|
+
if (!tryConsume()) {
|
|
167
|
+
writeHtml(res, 409, (0, renderHtml_1.renderHtml)({
|
|
168
|
+
kind: "error",
|
|
169
|
+
reason: "This callback server has already handled a response.",
|
|
170
|
+
}));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const providerError = url.searchParams.get("error");
|
|
174
|
+
if (providerError) {
|
|
175
|
+
const description = url.searchParams.get("error_description") ?? undefined;
|
|
176
|
+
const error = new errors_1.OAuthCallbackError("PROVIDER_ERROR", `Authorization server returned error: ${providerError}`, {
|
|
177
|
+
details: description
|
|
178
|
+
? { error: providerError, error_description: description }
|
|
179
|
+
: { error: providerError },
|
|
180
|
+
});
|
|
181
|
+
deferSettleOnClose(res, () => rejectResult(error));
|
|
182
|
+
writeHtml(res, 400, (0, renderHtml_1.renderHtml)({
|
|
183
|
+
kind: "error",
|
|
184
|
+
reason: providerError,
|
|
185
|
+
description,
|
|
186
|
+
}));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const code = url.searchParams.get("code");
|
|
190
|
+
const state = url.searchParams.get("state");
|
|
191
|
+
if (!code) {
|
|
192
|
+
const error = new errors_1.OAuthCallbackError("MISSING_CODE", "Authorization response missing 'code' parameter.");
|
|
193
|
+
deferSettleOnClose(res, () => rejectResult(error));
|
|
194
|
+
writeHtml(res, 400, (0, renderHtml_1.renderHtml)({
|
|
195
|
+
kind: "error",
|
|
196
|
+
reason: "The authorization response was missing the 'code' parameter.",
|
|
197
|
+
}));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (state !== expectedState) {
|
|
201
|
+
const error = new errors_1.OAuthCallbackError("STATE_MISMATCH", "Authorization response 'state' did not match expected value.");
|
|
202
|
+
deferSettleOnClose(res, () => rejectResult(error));
|
|
203
|
+
writeHtml(res, 400, (0, renderHtml_1.renderHtml)({
|
|
204
|
+
kind: "error",
|
|
205
|
+
reason: "The authorization response failed state validation.",
|
|
206
|
+
}));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
deferSettleOnClose(res, () => resolveResult({ code, state }));
|
|
210
|
+
writeHtml(res, 200, (0, renderHtml_1.renderHtml)({ kind: "success" }));
|
|
211
|
+
};
|
|
212
|
+
const bound = await (0, exports.bindLoopback)(handler);
|
|
213
|
+
server = bound.server;
|
|
214
|
+
const port = server.address().port;
|
|
215
|
+
const redirectUri = bound.host === "::1"
|
|
216
|
+
? `http://[::1]:${port}/callback`
|
|
217
|
+
: `http://127.0.0.1:${port}/callback`;
|
|
218
|
+
timeoutHandle = setTimeout(() => {
|
|
219
|
+
settleRejectNow(new errors_1.OAuthCallbackError("TIMEOUT", `No OAuth callback received within ${timeoutMs}ms.`));
|
|
220
|
+
}, timeoutMs);
|
|
221
|
+
if (signal) {
|
|
222
|
+
if (signal.aborted) {
|
|
223
|
+
settleRejectNow(new errors_1.OAuthCallbackError("ABORTED", "Callback server aborted."));
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
abortListener = () => {
|
|
227
|
+
settleRejectNow(new errors_1.OAuthCallbackError("ABORTED", "Callback server aborted."));
|
|
228
|
+
};
|
|
229
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { redirectUri, result, close };
|
|
233
|
+
};
|
|
234
|
+
exports.startCallbackServer = startCallbackServer;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type OAuthCallbackErrorCode =
|
|
2
|
+
/** No callback arrived within `timeoutMs`; a retry is reasonable. */
|
|
3
|
+
"TIMEOUT"
|
|
4
|
+
/** Callback `state` did not match `expectedState`. Surface a generic failure — do NOT echo the expected value. */
|
|
5
|
+
| "STATE_MISMATCH"
|
|
6
|
+
/** Callback carried `?error=...`. `details` holds `error` and optionally `error_description` for display. */
|
|
7
|
+
| "PROVIDER_ERROR"
|
|
8
|
+
/** Callback had no `code` parameter; treat as a malformed response. */
|
|
9
|
+
| "MISSING_CODE"
|
|
10
|
+
/** Could not bind the loopback port. No retry — environment likely blocked. */
|
|
11
|
+
| "BIND_FAILED"
|
|
12
|
+
/** Caller's `AbortSignal` fired. Expected; no user-facing message. */
|
|
13
|
+
| "ABORTED";
|
|
14
|
+
export type OAuthCallbackErrorOptions = {
|
|
15
|
+
details?: Record<string, string>;
|
|
16
|
+
cause?: unknown;
|
|
17
|
+
};
|
|
18
|
+
export declare class OAuthCallbackError extends Error {
|
|
19
|
+
readonly code: OAuthCallbackErrorCode;
|
|
20
|
+
readonly details?: Record<string, string>;
|
|
21
|
+
constructor(code: OAuthCallbackErrorCode, message: string, options?: OAuthCallbackErrorOptions);
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OAuthCallbackError = void 0;
|
|
4
|
+
class OAuthCallbackError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
details;
|
|
7
|
+
constructor(code, message, options = {}) {
|
|
8
|
+
super(message, { cause: options.cause });
|
|
9
|
+
this.name = "OAuthCallbackError";
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.details = options.details;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.OAuthCallbackError = OAuthCallbackError;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { startCallbackServer } from "./callbackServer";
|
|
2
|
+
export type { CallbackServerOptions, CallbackServerHandle, CallbackResult, } from "./callbackServer";
|
|
3
|
+
export { OAuthCallbackError } from "./errors";
|
|
4
|
+
export type { OAuthCallbackErrorCode, OAuthCallbackErrorOptions, } from "./errors";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OAuthCallbackError = exports.startCallbackServer = void 0;
|
|
4
|
+
var callbackServer_1 = require("./callbackServer");
|
|
5
|
+
Object.defineProperty(exports, "startCallbackServer", { enumerable: true, get: function () { return callbackServer_1.startCallbackServer; } });
|
|
6
|
+
var errors_1 = require("./errors");
|
|
7
|
+
Object.defineProperty(exports, "OAuthCallbackError", { enumerable: true, get: function () { return errors_1.OAuthCallbackError; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const LOGO_PNG_BASE64 = "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";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Generated by scripts/encode-logo.js — do not edit by hand.
|
|
3
|
+
// Source: assets/logo.png
|
|
4
|
+
// To regenerate: node scripts/encode-logo.js
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LOGO_PNG_BASE64 = void 0;
|
|
7
|
+
exports.LOGO_PNG_BASE64 = "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";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderHtml = exports.CSP_HEADER = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const logo_generated_1 = require("./logo.generated");
|
|
6
|
+
const CSS = `:root{--off-black:#666;--off-white:#fdfdfe;--white:#fff;--pink:#d71ef7;--blue:#3c7aae;--space-small:12px;--space-medium:24px;--space-large:36px;--space-huge:48px;--border-radius:3px}
|
|
7
|
+
html{box-sizing:border-box}
|
|
8
|
+
*,*::before,*::after{box-sizing:inherit}
|
|
9
|
+
*:focus{outline:2px solid var(--pink);outline-offset:2px}
|
|
10
|
+
body{margin:0;padding:0;width:100%;height:100%;font-family:Roboto,Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;color:var(--off-black);background-color:var(--off-white);font-size:15px}
|
|
11
|
+
h1{padding:0;margin:0;font-size:34px;font-weight:700}
|
|
12
|
+
main{margin:var(--space-huge) auto;padding-left:var(--space-medium);padding-right:var(--space-medium);max-width:400px;text-align:center}
|
|
13
|
+
.hero{margin-bottom:var(--space-medium)}
|
|
14
|
+
.hero img{width:180px;max-width:100%;margin-bottom:var(--space-medium)}
|
|
15
|
+
.reason{margin-top:var(--space-medium);font-weight:500}
|
|
16
|
+
.description{margin-top:var(--space-small);font-size:13px}
|
|
17
|
+
.close{margin-top:var(--space-large)}`;
|
|
18
|
+
// CSP uses a SHA-256 hash of the <style> block contents instead of
|
|
19
|
+
// 'unsafe-inline'. Any drift between this digest and the rendered <style> tag
|
|
20
|
+
// causes the browser to refuse the stylesheet — see docs/callback-page.md.
|
|
21
|
+
const STYLE_HASH = (0, node_crypto_1.createHash)("sha256").update(CSS, "utf8").digest("base64");
|
|
22
|
+
exports.CSP_HEADER = `default-src 'none'; img-src data:; style-src 'sha256-${STYLE_HASH}'; frame-ancestors 'none'`;
|
|
23
|
+
// OWASP-recommended 5-character set for HTML body/attribute contexts;
|
|
24
|
+
// & must come first to avoid double-escaping. Not safe for JS/CSS/URL contexts.
|
|
25
|
+
// https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#output-encoding-for-html-contexts
|
|
26
|
+
const escape = (s) => s
|
|
27
|
+
.replace(/&/g, "&")
|
|
28
|
+
.replace(/</g, "<")
|
|
29
|
+
.replace(/>/g, ">")
|
|
30
|
+
.replace(/"/g, """)
|
|
31
|
+
.replace(/'/g, "'");
|
|
32
|
+
const page = (title, body) => `<!doctype html>
|
|
33
|
+
<html lang="en">
|
|
34
|
+
<head>
|
|
35
|
+
<meta charset="UTF-8">
|
|
36
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
37
|
+
<title>${escape(title)}</title>
|
|
38
|
+
<style>${CSS}</style>
|
|
39
|
+
</head>
|
|
40
|
+
<body>
|
|
41
|
+
<main>
|
|
42
|
+
<div class="hero"><img src="data:image/png;base64,${logo_generated_1.LOGO_PNG_BASE64}" alt="Deque"></div>
|
|
43
|
+
${body}
|
|
44
|
+
</main>
|
|
45
|
+
</body>
|
|
46
|
+
</html>`;
|
|
47
|
+
const renderHtml = (input) => {
|
|
48
|
+
if (input.kind === "success") {
|
|
49
|
+
return page("Authenticated", `<h1>Authenticated</h1>
|
|
50
|
+
<p class="close">You can close this tab and return to your terminal.</p>`);
|
|
51
|
+
}
|
|
52
|
+
const descriptionBlock = input.description
|
|
53
|
+
? `<p class="description">${escape(input.description)}</p>`
|
|
54
|
+
: "";
|
|
55
|
+
return page("Authentication failed", `<h1>Authentication failed</h1>
|
|
56
|
+
<p class="reason">${escape(input.reason)}</p>
|
|
57
|
+
${descriptionBlock}
|
|
58
|
+
<p class="close">You can close this tab and return to your terminal.</p>`);
|
|
59
|
+
};
|
|
60
|
+
exports.renderHtml = renderHtml;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deque/axe-auth",
|
|
3
|
-
"version": "1.1.0-next.
|
|
3
|
+
"version": "1.1.0-next.fb07beab",
|
|
4
4
|
"description": "CLI authentication utility for Deque services",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -22,11 +22,13 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^22.13.10",
|
|
25
|
+
"c8": "^10.1.3",
|
|
25
26
|
"tsx": "^4.20.6",
|
|
26
27
|
"typescript": "^5.9.3"
|
|
27
28
|
},
|
|
28
29
|
"scripts": {
|
|
29
30
|
"build": "tsc",
|
|
30
|
-
"test": "tsx --test 'src/**/*.test.ts'"
|
|
31
|
+
"test": "tsx --test 'src/**/*.test.ts'",
|
|
32
|
+
"coverage": "c8 pnpm test"
|
|
31
33
|
}
|
|
32
34
|
}
|