@ratio-app/cli 0.1.1
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 +504 -0
- package/bin/run.mjs +5 -0
- package/dist/commands/__test__/throw.d.ts +25 -0
- package/dist/commands/__test__/throw.js +238 -0
- package/dist/commands/__test__/throw.js.map +1 -0
- package/dist/commands/app/create.d.ts +55 -0
- package/dist/commands/app/create.js +609 -0
- package/dist/commands/app/create.js.map +1 -0
- package/dist/commands/app/deploy.d.ts +14 -0
- package/dist/commands/app/deploy.js +179 -0
- package/dist/commands/app/deploy.js.map +1 -0
- package/dist/commands/app/dev.d.ts +28 -0
- package/dist/commands/app/dev.js +701 -0
- package/dist/commands/app/dev.js.map +1 -0
- package/dist/commands/app/generate.d.ts +14 -0
- package/dist/commands/app/generate.js +179 -0
- package/dist/commands/app/generate.js.map +1 -0
- package/dist/commands/app/info.d.ts +14 -0
- package/dist/commands/app/info.js +179 -0
- package/dist/commands/app/info.js.map +1 -0
- package/dist/commands/app/link.d.ts +118 -0
- package/dist/commands/app/link.js +1120 -0
- package/dist/commands/app/link.js.map +1 -0
- package/dist/commands/auth/login.d.ts +43 -0
- package/dist/commands/auth/login.js +987 -0
- package/dist/commands/auth/login.js.map +1 -0
- package/dist/commands/auth/logout.d.ts +15 -0
- package/dist/commands/auth/logout.js +486 -0
- package/dist/commands/auth/logout.js.map +1 -0
- package/dist/commands/auth/whoami.d.ts +16 -0
- package/dist/commands/auth/whoami.js +531 -0
- package/dist/commands/auth/whoami.js.map +1 -0
- package/dist/commands/dev/listen.d.ts +84 -0
- package/dist/commands/dev/listen.js +1187 -0
- package/dist/commands/dev/listen.js.map +1 -0
- package/dist/commands/dev/trigger.d.ts +88 -0
- package/dist/commands/dev/trigger.js +729 -0
- package/dist/commands/dev/trigger.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +617 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/auth/authorize-code-exchange.d.ts +50 -0
- package/dist/lib/auth/authorize-code-exchange.js +114 -0
- package/dist/lib/auth/authorize-code-exchange.js.map +1 -0
- package/dist/lib/auth/authorize-url.d.ts +42 -0
- package/dist/lib/auth/authorize-url.js +35 -0
- package/dist/lib/auth/authorize-url.js.map +1 -0
- package/dist/lib/auth/browser.d.ts +16 -0
- package/dist/lib/auth/browser.js +48 -0
- package/dist/lib/auth/browser.js.map +1 -0
- package/dist/lib/auth/config.d.ts +41 -0
- package/dist/lib/auth/config.js +21 -0
- package/dist/lib/auth/config.js.map +1 -0
- package/dist/lib/auth/http-refresher.d.ts +30 -0
- package/dist/lib/auth/http-refresher.js +103 -0
- package/dist/lib/auth/http-refresher.js.map +1 -0
- package/dist/lib/auth/identity.d.ts +26 -0
- package/dist/lib/auth/identity.js +10 -0
- package/dist/lib/auth/identity.js.map +1 -0
- package/dist/lib/auth/index.d.ts +13 -0
- package/dist/lib/auth/index.js +527 -0
- package/dist/lib/auth/index.js.map +1 -0
- package/dist/lib/auth/loopback-server.d.ts +65 -0
- package/dist/lib/auth/loopback-server.js +245 -0
- package/dist/lib/auth/loopback-server.js.map +1 -0
- package/dist/lib/auth/manual-prompt.d.ts +17 -0
- package/dist/lib/auth/manual-prompt.js +20 -0
- package/dist/lib/auth/manual-prompt.js.map +1 -0
- package/dist/lib/auth/pkce.d.ts +24 -0
- package/dist/lib/auth/pkce.js +15 -0
- package/dist/lib/auth/pkce.js.map +1 -0
- package/dist/lib/auth/state.d.ts +20 -0
- package/dist/lib/auth/state.js +19 -0
- package/dist/lib/auth/state.js.map +1 -0
- package/dist/lib/auth/verbose-trace.d.ts +25 -0
- package/dist/lib/auth/verbose-trace.js +17 -0
- package/dist/lib/auth/verbose-trace.js.map +1 -0
- package/dist/lib/credentials/clock.d.ts +18 -0
- package/dist/lib/credentials/clock.js +10 -0
- package/dist/lib/credentials/clock.js.map +1 -0
- package/dist/lib/credentials/index.d.ts +5 -0
- package/dist/lib/credentials/index.js +340 -0
- package/dist/lib/credentials/index.js.map +1 -0
- package/dist/lib/credentials/path.d.ts +26 -0
- package/dist/lib/credentials/path.js +32 -0
- package/dist/lib/credentials/path.js.map +1 -0
- package/dist/lib/credentials/refresher.d.ts +26 -0
- package/dist/lib/credentials/refresher.js +34 -0
- package/dist/lib/credentials/refresher.js.map +1 -0
- package/dist/lib/credentials/schema.d.ts +138 -0
- package/dist/lib/credentials/schema.js +85 -0
- package/dist/lib/credentials/schema.js.map +1 -0
- package/dist/lib/credentials/store.d.ts +102 -0
- package/dist/lib/credentials/store.js +337 -0
- package/dist/lib/credentials/store.js.map +1 -0
- package/dist/lib/credentials/types.d.ts +65 -0
- package/dist/lib/credentials/types.js +1 -0
- package/dist/lib/credentials/types.js.map +1 -0
- package/dist/lib/dev-store-gate.d.ts +66 -0
- package/dist/lib/dev-store-gate.js +15 -0
- package/dist/lib/dev-store-gate.js.map +1 -0
- package/dist/lib/errors.d.ts +60 -0
- package/dist/lib/errors.js +101 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/manifest/index.d.ts +2 -0
- package/dist/lib/manifest/index.js +144 -0
- package/dist/lib/manifest/index.js.map +1 -0
- package/dist/lib/manifest/read-manifest.d.ts +32 -0
- package/dist/lib/manifest/read-manifest.js +87 -0
- package/dist/lib/manifest/read-manifest.js.map +1 -0
- package/dist/lib/manifest/write-client-id.d.ts +33 -0
- package/dist/lib/manifest/write-client-id.js +94 -0
- package/dist/lib/manifest/write-client-id.js.map +1 -0
- package/dist/lib/messages.d.ts +15 -0
- package/dist/lib/messages.js +16 -0
- package/dist/lib/messages.js.map +1 -0
- package/dist/lib/orchestrator/child-runner.d.ts +140 -0
- package/dist/lib/orchestrator/child-runner.js +471 -0
- package/dist/lib/orchestrator/child-runner.js.map +1 -0
- package/dist/lib/preview/index.d.ts +11 -0
- package/dist/lib/preview/index.js +17 -0
- package/dist/lib/preview/index.js.map +1 -0
- package/dist/lib/preview/types.d.ts +16 -0
- package/dist/lib/preview/types.js +11 -0
- package/dist/lib/preview/types.js.map +1 -0
- package/dist/lib/ratio-command.d.ts +52 -0
- package/dist/lib/ratio-command.js +141 -0
- package/dist/lib/ratio-command.js.map +1 -0
- package/dist/lib/relay/index.d.ts +7 -0
- package/dist/lib/relay/index.js +362 -0
- package/dist/lib/relay/index.js.map +1 -0
- package/dist/lib/relay/relay-client.d.ts +91 -0
- package/dist/lib/relay/relay-client.js +362 -0
- package/dist/lib/relay/relay-client.js.map +1 -0
- package/dist/lib/relay/types.d.ts +71 -0
- package/dist/lib/relay/types.js +1 -0
- package/dist/lib/relay/types.js.map +1 -0
- package/dist/lib/render-error.d.ts +35 -0
- package/dist/lib/render-error.js +115 -0
- package/dist/lib/render-error.js.map +1 -0
- package/dist/lib/verbose-flag.d.ts +12 -0
- package/dist/lib/verbose-flag.js +17 -0
- package/dist/lib/verbose-flag.js.map +1 -0
- package/dist/render-CrHGqTPH.d.ts +115 -0
- package/dist/templates/.gitkeep +0 -0
- package/dist/templates/minimal/.env.example.tmpl +4 -0
- package/dist/templates/minimal/README.md.tmpl +26 -0
- package/dist/templates/minimal/gitignore +5 -0
- package/dist/templates/minimal/manifest.json +5 -0
- package/dist/templates/minimal/package.json.tmpl +25 -0
- package/dist/templates/minimal/ratio.config.jsonc.tmpl +17 -0
- package/dist/templates/minimal/src/index.ts.tmpl +26 -0
- package/dist/templates/minimal/src/install.ts.tmpl +37 -0
- package/dist/templates/minimal/src/webhooks.ts.tmpl +57 -0
- package/dist/templates/minimal/tsconfig.json +17 -0
- package/dist/templates/serverless/README.md.tmpl +28 -0
- package/dist/templates/serverless/gitignore +6 -0
- package/dist/templates/serverless/manifest.json +5 -0
- package/dist/templates/serverless/package.json.tmpl +22 -0
- package/dist/templates/serverless/ratio.config.jsonc.tmpl +17 -0
- package/dist/templates/serverless/src/index.ts.tmpl +20 -0
- package/dist/templates/serverless/src/install.ts.tmpl +20 -0
- package/dist/templates/serverless/src/webhooks.ts.tmpl +36 -0
- package/dist/templates/serverless/tsconfig.json +18 -0
- package/dist/templates/serverless/wrangler.toml.tmpl +9 -0
- package/dist/templates/with-admin-ui/.env.example.tmpl +4 -0
- package/dist/templates/with-admin-ui/README.md.tmpl +28 -0
- package/dist/templates/with-admin-ui/gitignore +6 -0
- package/dist/templates/with-admin-ui/manifest.json +5 -0
- package/dist/templates/with-admin-ui/package.json.tmpl +31 -0
- package/dist/templates/with-admin-ui/ratio.config.jsonc.tmpl +17 -0
- package/dist/templates/with-admin-ui/src/admin/app/globals.css +19 -0
- package/dist/templates/with-admin-ui/src/admin/app/layout.tsx.tmpl +13 -0
- package/dist/templates/with-admin-ui/src/admin/app/page.tsx.tmpl +8 -0
- package/dist/templates/with-admin-ui/src/admin/next-env.d.ts.tmpl +5 -0
- package/dist/templates/with-admin-ui/src/admin/next.config.mjs +3 -0
- package/dist/templates/with-admin-ui/src/admin/tsconfig.json +20 -0
- package/dist/templates/with-admin-ui/src/index.ts.tmpl +26 -0
- package/dist/templates/with-admin-ui/src/install.ts.tmpl +37 -0
- package/dist/templates/with-admin-ui/src/webhooks.ts.tmpl +57 -0
- package/dist/templates/with-admin-ui/tsconfig.json +17 -0
- package/package.json +78 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { VerboseTracer } from './verbose-trace.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ephemeral loopback HTTP server for the OAuth browser callback.
|
|
5
|
+
*
|
|
6
|
+
* Binds 127.0.0.1:0 (OS-assigned port). Awaits ONE GET /callback
|
|
7
|
+
* request with ?code=...&state=... query parameters, verifies the state
|
|
8
|
+
* via timingSafeEqualState, closes the server, and resolves.
|
|
9
|
+
*
|
|
10
|
+
* Security properties:
|
|
11
|
+
* - Binds 127.0.0.1 only — never 0.0.0.0 or ::
|
|
12
|
+
* - Port is OS-assigned (listen(0)) — never hardcoded
|
|
13
|
+
* - State compared via timingSafeEqual — no fast-fail timing side channel
|
|
14
|
+
* - Server closes on success, timeout, or state mismatch — no port leak
|
|
15
|
+
* - Non-callback paths receive a 404 response
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
interface LoopbackAwaitOptions {
|
|
19
|
+
readonly expectedState: string;
|
|
20
|
+
readonly timeoutMs: number;
|
|
21
|
+
readonly signal?: AbortSignal;
|
|
22
|
+
/** Verbose diagnostic sink — never receives code/state values. */
|
|
23
|
+
readonly trace?: VerboseTracer;
|
|
24
|
+
}
|
|
25
|
+
interface LoopbackAwaitResult {
|
|
26
|
+
readonly code: string;
|
|
27
|
+
readonly redirectUri: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Binds 127.0.0.1:0, awaits ONE GET /callback?code=...&state=..., verifies
|
|
31
|
+
* state via timingSafeEqual against expectedState, closes the server cleanly,
|
|
32
|
+
* resolves with { code, redirectUri }. On timeout or state mismatch or
|
|
33
|
+
* non-callback request path, rejects with the appropriate typed error.
|
|
34
|
+
*/
|
|
35
|
+
declare function awaitLoopbackCallback(o: LoopbackAwaitOptions): Promise<LoopbackAwaitResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Return the loopback callback URI for a given port.
|
|
38
|
+
*/
|
|
39
|
+
declare function buildLoopbackRedirectUri(port: number): string;
|
|
40
|
+
interface LoopbackSession {
|
|
41
|
+
/** The OS-assigned port the server is bound to. */
|
|
42
|
+
readonly port: number;
|
|
43
|
+
/** The full redirect URI to send in the authorize URL. */
|
|
44
|
+
readonly redirectUri: string;
|
|
45
|
+
/**
|
|
46
|
+
* Await the single callback. Resolves with the code, rejects on timeout
|
|
47
|
+
* or state mismatch. The server is closed when this promise settles.
|
|
48
|
+
*/
|
|
49
|
+
awaitCallback(opts: Omit<LoopbackAwaitOptions, 'timeoutMs'> & {
|
|
50
|
+
timeoutMs?: number;
|
|
51
|
+
}): Promise<LoopbackAwaitResult>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Two-phase API: start the server first (to get the port for the authorize URL),
|
|
55
|
+
* then await the callback after the browser has been opened.
|
|
56
|
+
*
|
|
57
|
+
* Usage:
|
|
58
|
+
* const session = await startLoopbackSession();
|
|
59
|
+
* const url = buildBrowserAuthorizeUrl({ redirectUri: session.redirectUri, ... });
|
|
60
|
+
* await opener.open(url);
|
|
61
|
+
* const { code } = await session.awaitCallback({ expectedState, timeoutMs: 30_000 });
|
|
62
|
+
*/
|
|
63
|
+
declare function startLoopbackSession(trace?: VerboseTracer): Promise<LoopbackSession>;
|
|
64
|
+
|
|
65
|
+
export { type LoopbackAwaitOptions, type LoopbackAwaitResult, type LoopbackSession, awaitLoopbackCallback, buildLoopbackRedirectUri, startLoopbackSession };
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// src/lib/auth/loopback-server.ts
|
|
2
|
+
import http from "http";
|
|
3
|
+
|
|
4
|
+
// src/lib/errors.ts
|
|
5
|
+
var RatioCLIError = class extends Error {
|
|
6
|
+
code;
|
|
7
|
+
exitCode;
|
|
8
|
+
hint;
|
|
9
|
+
docsUrl;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
super(opts.message);
|
|
12
|
+
this.name = new.target.name;
|
|
13
|
+
this.code = opts.code;
|
|
14
|
+
this.exitCode = opts.exitCode;
|
|
15
|
+
if (opts.hint !== void 0) this.hint = opts.hint;
|
|
16
|
+
if (opts.docsUrl !== void 0) this.docsUrl = opts.docsUrl;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var NotLoggedInError = class extends RatioCLIError {
|
|
20
|
+
constructor(message, hint = "Run 'ratio auth login' to sign in.") {
|
|
21
|
+
super({ code: "NOT_LOGGED_IN", exitCode: 3, message, hint });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var ValidationError = class extends RatioCLIError {
|
|
25
|
+
constructor(message, hint) {
|
|
26
|
+
const opts = {
|
|
27
|
+
code: "VALIDATION_ERROR",
|
|
28
|
+
exitCode: 8,
|
|
29
|
+
message
|
|
30
|
+
};
|
|
31
|
+
if (hint !== void 0) opts.hint = hint;
|
|
32
|
+
super(opts);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/lib/auth/state.ts
|
|
37
|
+
import crypto from "crypto";
|
|
38
|
+
function timingSafeEqualState(a, b) {
|
|
39
|
+
const aBuf = Buffer.from(a, "utf8");
|
|
40
|
+
const bBuf = Buffer.from(b, "utf8");
|
|
41
|
+
if (aBuf.length !== bBuf.length) {
|
|
42
|
+
crypto.timingSafeEqual(aBuf, aBuf);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return crypto.timingSafeEqual(aBuf, bBuf);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/lib/auth/verbose-trace.ts
|
|
49
|
+
var noopTracer = () => {
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/lib/auth/loopback-server.ts
|
|
53
|
+
function awaitLoopbackCallback(o) {
|
|
54
|
+
const trace = o.trace ?? noopTracer;
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
let settled = false;
|
|
57
|
+
function settle(fn) {
|
|
58
|
+
if (settled) return;
|
|
59
|
+
settled = true;
|
|
60
|
+
server.close(() => fn());
|
|
61
|
+
}
|
|
62
|
+
const server = http.createServer((req, res) => {
|
|
63
|
+
const reqUrl = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
64
|
+
if (reqUrl.pathname !== "/callback") {
|
|
65
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
66
|
+
res.end("Not found");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const code = reqUrl.searchParams.get("code");
|
|
70
|
+
const state = reqUrl.searchParams.get("state");
|
|
71
|
+
if (!state || !timingSafeEqualState(state, o.expectedState)) {
|
|
72
|
+
trace("callback: state mismatch");
|
|
73
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
74
|
+
res.end("State mismatch");
|
|
75
|
+
settle(
|
|
76
|
+
() => reject(
|
|
77
|
+
new ValidationError("Authorization callback failed a security check.")
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!code || code.length === 0) {
|
|
83
|
+
trace("callback: missing code");
|
|
84
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
85
|
+
res.end("Missing code");
|
|
86
|
+
settle(
|
|
87
|
+
() => reject(
|
|
88
|
+
new ValidationError("Authorization callback did not return a code.")
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
94
|
+
res.end("<html><body><h1>Signed in! You can close this tab.</h1></body></html>");
|
|
95
|
+
const addr = server.address();
|
|
96
|
+
const port = typeof addr === "object" && addr !== null ? addr.port : 0;
|
|
97
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
98
|
+
trace(`callback: received on port ${port} (state ok)`);
|
|
99
|
+
settle(() => resolve({ code, redirectUri }));
|
|
100
|
+
});
|
|
101
|
+
server.on("error", (err) => {
|
|
102
|
+
settle(() => reject(err));
|
|
103
|
+
});
|
|
104
|
+
server.listen(0, "127.0.0.1", () => {
|
|
105
|
+
const boundAddr = server.address();
|
|
106
|
+
const boundPort = typeof boundAddr === "object" && boundAddr !== null ? boundAddr.port : 0;
|
|
107
|
+
trace(`loopback: bound 127.0.0.1:${boundPort}`);
|
|
108
|
+
const timer = setTimeout(() => {
|
|
109
|
+
trace(`callback: timeout fired after ${o.timeoutMs}ms`);
|
|
110
|
+
settle(
|
|
111
|
+
() => reject(
|
|
112
|
+
new NotLoggedInError(
|
|
113
|
+
"Browser flow timed out. Run 'ratio auth login --manual' to complete without a browser."
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
}, o.timeoutMs);
|
|
118
|
+
if (typeof timer.unref === "function") {
|
|
119
|
+
timer.unref();
|
|
120
|
+
}
|
|
121
|
+
if (o.signal) {
|
|
122
|
+
o.signal.addEventListener("abort", () => {
|
|
123
|
+
trace("callback: cancelled");
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
settle(
|
|
126
|
+
() => reject(
|
|
127
|
+
new NotLoggedInError(
|
|
128
|
+
"Browser flow was cancelled. Run 'ratio auth login --manual' to complete without a browser."
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function buildLoopbackRedirectUri(port) {
|
|
138
|
+
return `http://127.0.0.1:${port}/callback`;
|
|
139
|
+
}
|
|
140
|
+
function startLoopbackSession(trace = noopTracer) {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
let settled = false;
|
|
143
|
+
function settle(fn) {
|
|
144
|
+
if (settled) return;
|
|
145
|
+
settled = true;
|
|
146
|
+
fn();
|
|
147
|
+
}
|
|
148
|
+
const server = http.createServer();
|
|
149
|
+
server.on("error", reject);
|
|
150
|
+
server.listen(0, "127.0.0.1", () => {
|
|
151
|
+
const addr = server.address();
|
|
152
|
+
const port = typeof addr === "object" && addr !== null ? addr.port : 0;
|
|
153
|
+
const redirectUri = buildLoopbackRedirectUri(port);
|
|
154
|
+
trace(`loopback: bound 127.0.0.1:${port}`);
|
|
155
|
+
resolve({
|
|
156
|
+
port,
|
|
157
|
+
redirectUri,
|
|
158
|
+
awaitCallback(opts) {
|
|
159
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
160
|
+
return new Promise((res, rej) => {
|
|
161
|
+
server.on("request", (req, response) => {
|
|
162
|
+
const reqUrl = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
163
|
+
if (reqUrl.pathname !== "/callback") {
|
|
164
|
+
response.writeHead(404, { "Content-Type": "text/plain" });
|
|
165
|
+
response.end("Not found");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const code = reqUrl.searchParams.get("code");
|
|
169
|
+
const state = reqUrl.searchParams.get("state");
|
|
170
|
+
if (!state || !timingSafeEqualState(state, opts.expectedState)) {
|
|
171
|
+
trace("callback: state mismatch");
|
|
172
|
+
response.writeHead(400, { "Content-Type": "text/plain" });
|
|
173
|
+
response.end("State mismatch");
|
|
174
|
+
settle(() => {
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
server.close(
|
|
177
|
+
() => rej(new ValidationError("Authorization callback failed a security check."))
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (!code || code.length === 0) {
|
|
183
|
+
trace("callback: missing code");
|
|
184
|
+
response.writeHead(400, { "Content-Type": "text/plain" });
|
|
185
|
+
response.end("Missing code");
|
|
186
|
+
settle(() => {
|
|
187
|
+
clearTimeout(timer);
|
|
188
|
+
server.close(
|
|
189
|
+
() => rej(new ValidationError("Authorization callback did not return a code."))
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
response.writeHead(200, { "Content-Type": "text/html" });
|
|
195
|
+
response.end(
|
|
196
|
+
"<html><body><h1>Signed in! You can close this tab.</h1></body></html>"
|
|
197
|
+
);
|
|
198
|
+
trace(`callback: received on port ${port} (state ok)`);
|
|
199
|
+
settle(() => {
|
|
200
|
+
clearTimeout(timer);
|
|
201
|
+
server.close(() => res({ code, redirectUri }));
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
const timer = setTimeout(() => {
|
|
205
|
+
trace(`callback: timeout fired after ${timeoutMs}ms`);
|
|
206
|
+
settle(() => {
|
|
207
|
+
server.close(
|
|
208
|
+
() => rej(
|
|
209
|
+
new NotLoggedInError(
|
|
210
|
+
"Browser flow timed out. Run 'ratio auth login --manual' to complete without a browser."
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
}, timeoutMs);
|
|
216
|
+
if (typeof timer.unref === "function") {
|
|
217
|
+
timer.unref();
|
|
218
|
+
}
|
|
219
|
+
if (opts.signal) {
|
|
220
|
+
opts.signal.addEventListener("abort", () => {
|
|
221
|
+
trace("callback: cancelled");
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
settle(() => {
|
|
224
|
+
server.close(
|
|
225
|
+
() => rej(
|
|
226
|
+
new NotLoggedInError(
|
|
227
|
+
"Browser flow was cancelled. Run 'ratio auth login --manual' to complete without a browser."
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
export {
|
|
241
|
+
awaitLoopbackCallback,
|
|
242
|
+
buildLoopbackRedirectUri,
|
|
243
|
+
startLoopbackSession
|
|
244
|
+
};
|
|
245
|
+
//# sourceMappingURL=loopback-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/auth/loopback-server.ts","../../../src/lib/errors.ts","../../../src/lib/auth/state.ts","../../../src/lib/auth/verbose-trace.ts"],"sourcesContent":["/**\n * Ephemeral loopback HTTP server for the OAuth browser callback.\n *\n * Binds 127.0.0.1:0 (OS-assigned port). Awaits ONE GET /callback\n * request with ?code=...&state=... query parameters, verifies the state\n * via timingSafeEqualState, closes the server, and resolves.\n *\n * Security properties:\n * - Binds 127.0.0.1 only — never 0.0.0.0 or ::\n * - Port is OS-assigned (listen(0)) — never hardcoded\n * - State compared via timingSafeEqual — no fast-fail timing side channel\n * - Server closes on success, timeout, or state mismatch — no port leak\n * - Non-callback paths receive a 404 response\n */\n\nimport http from 'node:http';\nimport { NotLoggedInError, ValidationError } from '../errors.js';\nimport { timingSafeEqualState } from './state.js';\nimport { noopTracer } from './verbose-trace.js';\nimport type { VerboseTracer } from './verbose-trace.js';\n\nexport interface LoopbackAwaitOptions {\n readonly expectedState: string;\n readonly timeoutMs: number; // default 30_000 for prod, injectable for tests\n readonly signal?: AbortSignal;\n /** Verbose diagnostic sink — never receives code/state values. */\n readonly trace?: VerboseTracer;\n}\n\nexport interface LoopbackAwaitResult {\n readonly code: string;\n readonly redirectUri: string; // http://127.0.0.1:<actualPort>/callback\n}\n\n/**\n * Binds 127.0.0.1:0, awaits ONE GET /callback?code=...&state=..., verifies\n * state via timingSafeEqual against expectedState, closes the server cleanly,\n * resolves with { code, redirectUri }. On timeout or state mismatch or\n * non-callback request path, rejects with the appropriate typed error.\n */\nexport function awaitLoopbackCallback(o: LoopbackAwaitOptions): Promise<LoopbackAwaitResult> {\n const trace = o.trace ?? noopTracer;\n return new Promise((resolve, reject) => {\n let settled = false;\n\n function settle(fn: () => void): void {\n if (settled) return;\n settled = true;\n server.close(() => fn());\n }\n\n const server = http.createServer((req, res) => {\n const reqUrl = new URL(req.url ?? '/', `http://127.0.0.1`);\n\n if (reqUrl.pathname !== '/callback') {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not found');\n return;\n }\n\n const code = reqUrl.searchParams.get('code');\n const state = reqUrl.searchParams.get('state');\n\n if (!state || !timingSafeEqualState(state, o.expectedState)) {\n trace('callback: state mismatch');\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('State mismatch');\n settle(() =>\n reject(\n new ValidationError('Authorization callback failed a security check.'),\n ),\n );\n return;\n }\n\n if (!code || code.length === 0) {\n trace('callback: missing code');\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Missing code');\n settle(() =>\n reject(\n new ValidationError('Authorization callback did not return a code.'),\n ),\n );\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end('<html><body><h1>Signed in! You can close this tab.</h1></body></html>');\n\n const addr = server.address();\n const port = typeof addr === 'object' && addr !== null ? addr.port : 0;\n const redirectUri = `http://127.0.0.1:${port}/callback`;\n\n trace(`callback: received on port ${port} (state ok)`);\n settle(() => resolve({ code, redirectUri }));\n });\n\n server.on('error', (err) => {\n settle(() => reject(err));\n });\n\n server.listen(0, '127.0.0.1', () => {\n const boundAddr = server.address();\n const boundPort =\n typeof boundAddr === 'object' && boundAddr !== null ? boundAddr.port : 0;\n trace(`loopback: bound 127.0.0.1:${boundPort}`);\n\n const timer = setTimeout(() => {\n trace(`callback: timeout fired after ${o.timeoutMs}ms`);\n settle(() =>\n reject(\n new NotLoggedInError(\n \"Browser flow timed out. Run 'ratio auth login --manual' to complete without a browser.\",\n ),\n ),\n );\n }, o.timeoutMs);\n\n // Allow process to exit if only this timer remains.\n if (typeof timer.unref === 'function') {\n timer.unref();\n }\n\n if (o.signal) {\n o.signal.addEventListener('abort', () => {\n trace('callback: cancelled');\n clearTimeout(timer);\n settle(() =>\n reject(\n new NotLoggedInError(\n \"Browser flow was cancelled. Run 'ratio auth login --manual' to complete without a browser.\",\n ),\n ),\n );\n });\n }\n });\n });\n}\n\n/**\n * Return the loopback callback URI for a given port.\n */\nexport function buildLoopbackRedirectUri(port: number): string {\n return `http://127.0.0.1:${port}/callback`;\n}\n\nexport interface LoopbackSession {\n /** The OS-assigned port the server is bound to. */\n readonly port: number;\n /** The full redirect URI to send in the authorize URL. */\n readonly redirectUri: string;\n /**\n * Await the single callback. Resolves with the code, rejects on timeout\n * or state mismatch. The server is closed when this promise settles.\n */\n awaitCallback(opts: Omit<LoopbackAwaitOptions, 'timeoutMs'> & { timeoutMs?: number }): Promise<LoopbackAwaitResult>;\n}\n\n/**\n * Two-phase API: start the server first (to get the port for the authorize URL),\n * then await the callback after the browser has been opened.\n *\n * Usage:\n * const session = await startLoopbackSession();\n * const url = buildBrowserAuthorizeUrl({ redirectUri: session.redirectUri, ... });\n * await opener.open(url);\n * const { code } = await session.awaitCallback({ expectedState, timeoutMs: 30_000 });\n */\nexport function startLoopbackSession(trace: VerboseTracer = noopTracer): Promise<LoopbackSession> {\n return new Promise((resolve, reject) => {\n let settled = false;\n\n function settle(fn: () => void): void {\n if (settled) return;\n settled = true;\n fn();\n }\n\n const server = http.createServer();\n server.on('error', reject);\n server.listen(0, '127.0.0.1', () => {\n const addr = server.address();\n const port = typeof addr === 'object' && addr !== null ? addr.port : 0;\n const redirectUri = buildLoopbackRedirectUri(port);\n\n trace(`loopback: bound 127.0.0.1:${port}`);\n\n resolve({\n port,\n redirectUri,\n awaitCallback(\n opts: Omit<LoopbackAwaitOptions, 'timeoutMs'> & { timeoutMs?: number },\n ): Promise<LoopbackAwaitResult> {\n const timeoutMs = opts.timeoutMs ?? 30_000;\n\n return new Promise((res, rej) => {\n server.on('request', (req, response) => {\n const reqUrl = new URL(req.url ?? '/', `http://127.0.0.1`);\n\n if (reqUrl.pathname !== '/callback') {\n response.writeHead(404, { 'Content-Type': 'text/plain' });\n response.end('Not found');\n return;\n }\n\n const code = reqUrl.searchParams.get('code');\n const state = reqUrl.searchParams.get('state');\n\n if (!state || !timingSafeEqualState(state, opts.expectedState)) {\n trace('callback: state mismatch');\n response.writeHead(400, { 'Content-Type': 'text/plain' });\n response.end('State mismatch');\n settle(() => {\n clearTimeout(timer);\n server.close(() =>\n rej(new ValidationError('Authorization callback failed a security check.')),\n );\n });\n return;\n }\n\n if (!code || code.length === 0) {\n trace('callback: missing code');\n response.writeHead(400, { 'Content-Type': 'text/plain' });\n response.end('Missing code');\n settle(() => {\n clearTimeout(timer);\n server.close(() =>\n rej(new ValidationError('Authorization callback did not return a code.')),\n );\n });\n return;\n }\n\n response.writeHead(200, { 'Content-Type': 'text/html' });\n response.end(\n '<html><body><h1>Signed in! You can close this tab.</h1></body></html>',\n );\n\n trace(`callback: received on port ${port} (state ok)`);\n settle(() => {\n clearTimeout(timer);\n server.close(() => res({ code, redirectUri }));\n });\n });\n\n const timer = setTimeout(() => {\n trace(`callback: timeout fired after ${timeoutMs}ms`);\n settle(() => {\n server.close(() =>\n rej(\n new NotLoggedInError(\n \"Browser flow timed out. Run 'ratio auth login --manual' to complete without a browser.\",\n ),\n ),\n );\n });\n }, timeoutMs);\n\n if (typeof timer.unref === 'function') {\n timer.unref();\n }\n\n if (opts.signal) {\n opts.signal.addEventListener('abort', () => {\n trace('callback: cancelled');\n clearTimeout(timer);\n settle(() => {\n server.close(() =>\n rej(\n new NotLoggedInError(\n \"Browser flow was cancelled. Run 'ratio auth login --manual' to complete without a browser.\",\n ),\n ),\n );\n });\n });\n }\n });\n },\n });\n });\n });\n}\n","/**\n * Error hierarchy for the Ratio CLI. Every error thrown from a command's\n * run() is expected to be an instance of RatioCLIError so the global\n * render-error handler can map it to a deterministic stderr line and exit\n * code. Uncaught Error subclasses render as \"INTERNAL: <msg>\" with exit 1.\n *\n * No import from @oclif/core — this file is dependency-free so tests\n * can import it without oclif overhead.\n */\n\nexport class RatioCLIError extends Error {\n readonly code: string;\n readonly exitCode: number;\n readonly hint?: string;\n readonly docsUrl?: string;\n\n constructor(opts: {\n code: string;\n exitCode: number;\n message: string;\n hint?: string;\n docsUrl?: string;\n }) {\n super(opts.message);\n this.name = new.target.name;\n this.code = opts.code;\n this.exitCode = opts.exitCode;\n if (opts.hint !== undefined) this.hint = opts.hint;\n if (opts.docsUrl !== undefined) this.docsUrl = opts.docsUrl;\n }\n}\n\n/** Coming-soon stub UX. Renders on stdout with exit 0. */\nexport class ComingSoonError extends RatioCLIError {\n constructor(message: string) {\n super({ code: 'COMING_SOON', exitCode: 0, message });\n }\n}\n\n/** User has no credentials and command requires auth. */\nexport class NotLoggedInError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to sign in.\",\n ) {\n super({ code: 'NOT_LOGGED_IN', exitCode: 3, message, hint });\n }\n}\n\nexport class TokenExpiredError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to refresh your session.\",\n ) {\n super({ code: 'TOKEN_EXPIRED', exitCode: 3, message, hint });\n }\n}\n\nexport class ScopeDeniedError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'SCOPE_DENIED',\n exitCode: 4,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class NetworkError extends RatioCLIError {\n constructor(message: string, hint: string = 'Check your network and retry.') {\n super({ code: 'NETWORK_ERROR', exitCode: 5, message, hint });\n }\n}\n\nexport class RelayConnectError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Verify the tunnel URL and that the auth-server is reachable.',\n ) {\n super({ code: 'RELAY_CONNECT', exitCode: 6, message, hint });\n }\n}\n\nexport class TemplateExistsError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Choose a different name or remove the existing directory.',\n ) {\n super({ code: 'TEMPLATE_EXISTS', exitCode: 7, message, hint });\n }\n}\n\nexport class ValidationError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'VALIDATION_ERROR',\n exitCode: 8,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class RelayResumeGapError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Reconnect from the current cursor; older events are outside the buffer window.',\n ) {\n super({ code: 'RELAY_RESUME_GAP', exitCode: 9, message, hint });\n }\n}\n\nexport class OrchestratorSpawnError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Could not start your app's dev command — check that the command in ratio.config.jsonc runs from this directory.\",\n ) {\n super({ code: 'ORCHESTRATOR_SPAWN', exitCode: 10, message, hint });\n }\n}\n\nexport class OrchestratorDrainTimeoutError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Your dev command did not exit cleanly within 5 seconds — it was forcibly terminated.',\n ) {\n super({ code: 'ORCHESTRATOR_DRAIN', exitCode: 11, message, hint });\n }\n}\n\nexport function isRatioCLIError(err: unknown): err is RatioCLIError {\n return err instanceof RatioCLIError;\n}\n","/**\n * OAuth state parameter helpers.\n *\n * State is generated once per authorization request and must be compared\n * in constant time to prevent timing-oracle CSRF attacks.\n */\n\nimport crypto from 'node:crypto';\n\n/**\n * Generate a fresh state value: 32 random bytes encoded as base64url.\n * 32 bytes = 256 bits of entropy, well above the 128-bit minimum.\n */\nexport function generateState(): string {\n return crypto.randomBytes(32).toString('base64url');\n}\n\n/**\n * Constant-time comparison of two state strings.\n * Returns true iff a === b without leaking timing information.\n * Both strings are converted to UTF-8 Buffers before comparison;\n * length-mismatch is handled without a fast-fail branch.\n */\nexport function timingSafeEqualState(a: string, b: string): boolean {\n const aBuf = Buffer.from(a, 'utf8');\n const bBuf = Buffer.from(b, 'utf8');\n if (aBuf.length !== bBuf.length) {\n // Perform a dummy comparison to avoid timing-observable fast-path.\n crypto.timingSafeEqual(aBuf, aBuf);\n return false;\n }\n return crypto.timingSafeEqual(aBuf, bBuf);\n}\n","/**\n * Minimal verbose diagnostic seam for the auth flow.\n *\n * When `--verbose` is on, the auth HTTP/loopback boundaries emit ONE line\n * per boundary crossing (method, path, status, timing, flow events) so a\n * failing login/refresh can be diagnosed from the CLI transcript alone.\n *\n * Safety contract (enforced by tests):\n * - Lines go to STDERR only — never stdout — so `--json` consumers and\n * shell pipelines are never corrupted.\n * - NO secret material is ever passed to a tracer: no access/refresh\n * token, no authorization code, no code_verifier, no state value.\n * Only method, path, HTTP status, port numbers, and durations.\n */\n\n/** A tracer receives one pre-formatted diagnostic message per event. */\nexport type VerboseTracer = (msg: string) => void;\n\n/** Shared no-op used when `--verbose` is off (default everywhere). */\nexport const noopTracer: VerboseTracer = () => {};\n\n/**\n * Build a tracer. Returns the shared no-op unless `verbose` is true.\n * Output sink is injectable for tests; defaults to process.stderr.\n */\nexport function createVerboseTracer(\n verbose: boolean,\n write: (line: string) => void = (line) => {\n process.stderr.write(line);\n },\n): VerboseTracer {\n if (!verbose) return noopTracer;\n return (msg: string) => {\n write(`[verbose] ${msg}\\n`);\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdin prompt for the --manual copy-paste OAuth flow.
|
|
3
|
+
*
|
|
4
|
+
* Uses node:readline/promises so it is awaitable and testable with
|
|
5
|
+
* an injectable interface. The defaultStdinPrompt() function is the
|
|
6
|
+
* production implementation; tests inject a fake.
|
|
7
|
+
*/
|
|
8
|
+
interface StdinPrompt {
|
|
9
|
+
askForCode(prompt: string): Promise<string>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create the default stdin prompter backed by node:readline/promises.
|
|
13
|
+
* Trims whitespace from the pasted code.
|
|
14
|
+
*/
|
|
15
|
+
declare function defaultStdinPrompt(): StdinPrompt;
|
|
16
|
+
|
|
17
|
+
export { type StdinPrompt, defaultStdinPrompt };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/lib/auth/manual-prompt.ts
|
|
2
|
+
import { createInterface } from "readline/promises";
|
|
3
|
+
import { stdin as input, stdout as output } from "process";
|
|
4
|
+
function defaultStdinPrompt() {
|
|
5
|
+
return {
|
|
6
|
+
async askForCode(prompt) {
|
|
7
|
+
const rl = createInterface({ input, output });
|
|
8
|
+
try {
|
|
9
|
+
const answer = await rl.question(prompt);
|
|
10
|
+
return answer.trim();
|
|
11
|
+
} finally {
|
|
12
|
+
rl.close();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
defaultStdinPrompt
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=manual-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/auth/manual-prompt.ts"],"sourcesContent":["/**\n * Stdin prompt for the --manual copy-paste OAuth flow.\n *\n * Uses node:readline/promises so it is awaitable and testable with\n * an injectable interface. The defaultStdinPrompt() function is the\n * production implementation; tests inject a fake.\n */\n\nimport { createInterface } from 'node:readline/promises';\nimport { stdin as input, stdout as output } from 'node:process';\n\nexport interface StdinPrompt {\n askForCode(prompt: string): Promise<string>;\n}\n\n/**\n * Create the default stdin prompter backed by node:readline/promises.\n * Trims whitespace from the pasted code.\n */\nexport function defaultStdinPrompt(): StdinPrompt {\n return {\n async askForCode(prompt: string): Promise<string> {\n const rl = createInterface({ input, output });\n try {\n const answer = await rl.question(prompt);\n return answer.trim();\n } finally {\n rl.close();\n }\n },\n };\n}\n"],"mappings":";AAQA,SAAS,uBAAuB;AAChC,SAAS,SAAS,OAAO,UAAU,cAAc;AAU1C,SAAS,qBAAkC;AAChD,SAAO;AAAA,IACL,MAAM,WAAW,QAAiC;AAChD,YAAM,KAAK,gBAAgB,EAAE,OAAO,OAAO,CAAC;AAC5C,UAAI;AACF,cAAM,SAAS,MAAM,GAAG,SAAS,MAAM;AACvC,eAAO,OAAO,KAAK;AAAA,MACrB,UAAE;AACA,WAAG,MAAM;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE (RFC 7636) helpers — S256 code challenge only.
|
|
3
|
+
*
|
|
4
|
+
* The "plain" method is rejected by the backend DTO validator and is
|
|
5
|
+
* never generated here. All operations are synchronous and pure.
|
|
6
|
+
*/
|
|
7
|
+
interface PkcePair {
|
|
8
|
+
readonly codeVerifier: string;
|
|
9
|
+
readonly codeChallenge: string;
|
|
10
|
+
readonly codeChallengeMethod: 'S256';
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Compute the S256 code challenge for a given code verifier.
|
|
14
|
+
* BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) without padding.
|
|
15
|
+
* Algorithm matches the backend pkce utility byte-for-byte.
|
|
16
|
+
*/
|
|
17
|
+
declare function computeS256Challenge(codeVerifier: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Generate a fresh PKCE pair for one authorization request.
|
|
20
|
+
* codeVerifier is 96 random base64url bytes → 128 chars (within RFC 7636 43-128 range).
|
|
21
|
+
*/
|
|
22
|
+
declare function generatePkcePair(): PkcePair;
|
|
23
|
+
|
|
24
|
+
export { type PkcePair, computeS256Challenge, generatePkcePair };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/lib/auth/pkce.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
function computeS256Challenge(codeVerifier) {
|
|
4
|
+
return crypto.createHash("sha256").update(codeVerifier, "ascii").digest("base64url");
|
|
5
|
+
}
|
|
6
|
+
function generatePkcePair() {
|
|
7
|
+
const codeVerifier = crypto.randomBytes(96).toString("base64url");
|
|
8
|
+
const codeChallenge = computeS256Challenge(codeVerifier);
|
|
9
|
+
return { codeVerifier, codeChallenge, codeChallengeMethod: "S256" };
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
computeS256Challenge,
|
|
13
|
+
generatePkcePair
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/auth/pkce.ts"],"sourcesContent":["/**\n * PKCE (RFC 7636) helpers — S256 code challenge only.\n *\n * The \"plain\" method is rejected by the backend DTO validator and is\n * never generated here. All operations are synchronous and pure.\n */\n\nimport crypto from 'node:crypto';\n\nexport interface PkcePair {\n readonly codeVerifier: string; // 43-128 chars, unreserved-only (RFC 7636 §4.1)\n readonly codeChallenge: string; // base64url(SHA-256(codeVerifier))\n readonly codeChallengeMethod: 'S256';\n}\n\n/**\n * Compute the S256 code challenge for a given code verifier.\n * BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) without padding.\n * Algorithm matches the backend pkce utility byte-for-byte.\n */\nexport function computeS256Challenge(codeVerifier: string): string {\n return crypto.createHash('sha256').update(codeVerifier, 'ascii').digest('base64url');\n}\n\n/**\n * Generate a fresh PKCE pair for one authorization request.\n * codeVerifier is 96 random base64url bytes → 128 chars (within RFC 7636 43-128 range).\n */\nexport function generatePkcePair(): PkcePair {\n const codeVerifier = crypto.randomBytes(96).toString('base64url');\n const codeChallenge = computeS256Challenge(codeVerifier);\n return { codeVerifier, codeChallenge, codeChallengeMethod: 'S256' };\n}\n"],"mappings":";AAOA,OAAO,YAAY;AAaZ,SAAS,qBAAqB,cAA8B;AACjE,SAAO,OAAO,WAAW,QAAQ,EAAE,OAAO,cAAc,OAAO,EAAE,OAAO,WAAW;AACrF;AAMO,SAAS,mBAA6B;AAC3C,QAAM,eAAe,OAAO,YAAY,EAAE,EAAE,SAAS,WAAW;AAChE,QAAM,gBAAgB,qBAAqB,YAAY;AACvD,SAAO,EAAE,cAAc,eAAe,qBAAqB,OAAO;AACpE;","names":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth state parameter helpers.
|
|
3
|
+
*
|
|
4
|
+
* State is generated once per authorization request and must be compared
|
|
5
|
+
* in constant time to prevent timing-oracle CSRF attacks.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Generate a fresh state value: 32 random bytes encoded as base64url.
|
|
9
|
+
* 32 bytes = 256 bits of entropy, well above the 128-bit minimum.
|
|
10
|
+
*/
|
|
11
|
+
declare function generateState(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Constant-time comparison of two state strings.
|
|
14
|
+
* Returns true iff a === b without leaking timing information.
|
|
15
|
+
* Both strings are converted to UTF-8 Buffers before comparison;
|
|
16
|
+
* length-mismatch is handled without a fast-fail branch.
|
|
17
|
+
*/
|
|
18
|
+
declare function timingSafeEqualState(a: string, b: string): boolean;
|
|
19
|
+
|
|
20
|
+
export { generateState, timingSafeEqualState };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/lib/auth/state.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
function generateState() {
|
|
4
|
+
return crypto.randomBytes(32).toString("base64url");
|
|
5
|
+
}
|
|
6
|
+
function timingSafeEqualState(a, b) {
|
|
7
|
+
const aBuf = Buffer.from(a, "utf8");
|
|
8
|
+
const bBuf = Buffer.from(b, "utf8");
|
|
9
|
+
if (aBuf.length !== bBuf.length) {
|
|
10
|
+
crypto.timingSafeEqual(aBuf, aBuf);
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return crypto.timingSafeEqual(aBuf, bBuf);
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
generateState,
|
|
17
|
+
timingSafeEqualState
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/auth/state.ts"],"sourcesContent":["/**\n * OAuth state parameter helpers.\n *\n * State is generated once per authorization request and must be compared\n * in constant time to prevent timing-oracle CSRF attacks.\n */\n\nimport crypto from 'node:crypto';\n\n/**\n * Generate a fresh state value: 32 random bytes encoded as base64url.\n * 32 bytes = 256 bits of entropy, well above the 128-bit minimum.\n */\nexport function generateState(): string {\n return crypto.randomBytes(32).toString('base64url');\n}\n\n/**\n * Constant-time comparison of two state strings.\n * Returns true iff a === b without leaking timing information.\n * Both strings are converted to UTF-8 Buffers before comparison;\n * length-mismatch is handled without a fast-fail branch.\n */\nexport function timingSafeEqualState(a: string, b: string): boolean {\n const aBuf = Buffer.from(a, 'utf8');\n const bBuf = Buffer.from(b, 'utf8');\n if (aBuf.length !== bBuf.length) {\n // Perform a dummy comparison to avoid timing-observable fast-path.\n crypto.timingSafeEqual(aBuf, aBuf);\n return false;\n }\n return crypto.timingSafeEqual(aBuf, bBuf);\n}\n"],"mappings":";AAOA,OAAO,YAAY;AAMZ,SAAS,gBAAwB;AACtC,SAAO,OAAO,YAAY,EAAE,EAAE,SAAS,WAAW;AACpD;AAQO,SAAS,qBAAqB,GAAW,GAAoB;AAClE,QAAM,OAAO,OAAO,KAAK,GAAG,MAAM;AAClC,QAAM,OAAO,OAAO,KAAK,GAAG,MAAM;AAClC,MAAI,KAAK,WAAW,KAAK,QAAQ;AAE/B,WAAO,gBAAgB,MAAM,IAAI;AACjC,WAAO;AAAA,EACT;AACA,SAAO,OAAO,gBAAgB,MAAM,IAAI;AAC1C;","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal verbose diagnostic seam for the auth flow.
|
|
3
|
+
*
|
|
4
|
+
* When `--verbose` is on, the auth HTTP/loopback boundaries emit ONE line
|
|
5
|
+
* per boundary crossing (method, path, status, timing, flow events) so a
|
|
6
|
+
* failing login/refresh can be diagnosed from the CLI transcript alone.
|
|
7
|
+
*
|
|
8
|
+
* Safety contract (enforced by tests):
|
|
9
|
+
* - Lines go to STDERR only — never stdout — so `--json` consumers and
|
|
10
|
+
* shell pipelines are never corrupted.
|
|
11
|
+
* - NO secret material is ever passed to a tracer: no access/refresh
|
|
12
|
+
* token, no authorization code, no code_verifier, no state value.
|
|
13
|
+
* Only method, path, HTTP status, port numbers, and durations.
|
|
14
|
+
*/
|
|
15
|
+
/** A tracer receives one pre-formatted diagnostic message per event. */
|
|
16
|
+
type VerboseTracer = (msg: string) => void;
|
|
17
|
+
/** Shared no-op used when `--verbose` is off (default everywhere). */
|
|
18
|
+
declare const noopTracer: VerboseTracer;
|
|
19
|
+
/**
|
|
20
|
+
* Build a tracer. Returns the shared no-op unless `verbose` is true.
|
|
21
|
+
* Output sink is injectable for tests; defaults to process.stderr.
|
|
22
|
+
*/
|
|
23
|
+
declare function createVerboseTracer(verbose: boolean, write?: (line: string) => void): VerboseTracer;
|
|
24
|
+
|
|
25
|
+
export { type VerboseTracer, createVerboseTracer, noopTracer };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/lib/auth/verbose-trace.ts
|
|
2
|
+
var noopTracer = () => {
|
|
3
|
+
};
|
|
4
|
+
function createVerboseTracer(verbose, write = (line) => {
|
|
5
|
+
process.stderr.write(line);
|
|
6
|
+
}) {
|
|
7
|
+
if (!verbose) return noopTracer;
|
|
8
|
+
return (msg) => {
|
|
9
|
+
write(`[verbose] ${msg}
|
|
10
|
+
`);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
createVerboseTracer,
|
|
15
|
+
noopTracer
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=verbose-trace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/auth/verbose-trace.ts"],"sourcesContent":["/**\n * Minimal verbose diagnostic seam for the auth flow.\n *\n * When `--verbose` is on, the auth HTTP/loopback boundaries emit ONE line\n * per boundary crossing (method, path, status, timing, flow events) so a\n * failing login/refresh can be diagnosed from the CLI transcript alone.\n *\n * Safety contract (enforced by tests):\n * - Lines go to STDERR only — never stdout — so `--json` consumers and\n * shell pipelines are never corrupted.\n * - NO secret material is ever passed to a tracer: no access/refresh\n * token, no authorization code, no code_verifier, no state value.\n * Only method, path, HTTP status, port numbers, and durations.\n */\n\n/** A tracer receives one pre-formatted diagnostic message per event. */\nexport type VerboseTracer = (msg: string) => void;\n\n/** Shared no-op used when `--verbose` is off (default everywhere). */\nexport const noopTracer: VerboseTracer = () => {};\n\n/**\n * Build a tracer. Returns the shared no-op unless `verbose` is true.\n * Output sink is injectable for tests; defaults to process.stderr.\n */\nexport function createVerboseTracer(\n verbose: boolean,\n write: (line: string) => void = (line) => {\n process.stderr.write(line);\n },\n): VerboseTracer {\n if (!verbose) return noopTracer;\n return (msg: string) => {\n write(`[verbose] ${msg}\\n`);\n };\n}\n"],"mappings":";AAmBO,IAAM,aAA4B,MAAM;AAAC;AAMzC,SAAS,oBACd,SACA,QAAgC,CAAC,SAAS;AACxC,UAAQ,OAAO,MAAM,IAAI;AAC3B,GACe;AACf,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,CAAC,QAAgB;AACtB,UAAM,aAAa,GAAG;AAAA,CAAI;AAAA,EAC5B;AACF;","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injectable wall-clock seam for the credential store.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONLY file in the credentials module allowed to sample the
|
|
5
|
+
* real wall clock. Every other file receives a {@link Clock} instance so
|
|
6
|
+
* tests can drive expiry-window logic deterministically with a fake.
|
|
7
|
+
*/
|
|
8
|
+
/** Injectable time source. */
|
|
9
|
+
interface Clock {
|
|
10
|
+
/** Milliseconds since Unix epoch. */
|
|
11
|
+
now(): number;
|
|
12
|
+
}
|
|
13
|
+
/** Production {@link Clock} backed by the system wall clock. */
|
|
14
|
+
declare class SystemClock implements Clock {
|
|
15
|
+
now(): number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { type Clock, SystemClock };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/credentials/clock.ts"],"sourcesContent":["/**\n * Injectable wall-clock seam for the credential store.\n *\n * This is the ONLY file in the credentials module allowed to sample the\n * real wall clock. Every other file receives a {@link Clock} instance so\n * tests can drive expiry-window logic deterministically with a fake.\n */\n\n/** Injectable time source. */\nexport interface Clock {\n /** Milliseconds since Unix epoch. */\n now(): number;\n}\n\n/** Production {@link Clock} backed by the system wall clock. */\nexport class SystemClock implements Clock {\n now(): number {\n return Date.now();\n }\n}\n"],"mappings":";AAeO,IAAM,cAAN,MAAmC;AAAA,EACxC,MAAc;AACZ,WAAO,KAAK,IAAI;AAAA,EAClB;AACF;","names":[]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Credentials, DeveloperSession, Identity, Profile, TokenSet } from './types.js';
|
|
2
|
+
export { Clock, SystemClock } from './clock.js';
|
|
3
|
+
export { PathEnv, defaultCredentialsPath } from './path.js';
|
|
4
|
+
export { NotImplementedRefresher, TokenRefresher } from './refresher.js';
|
|
5
|
+
export { CredentialStore, CredentialStoreOptions, FileCredentialStore } from './store.js';
|