@grackle-ai/server 0.72.1 → 0.72.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/grpc-service.d.ts.map +1 -1
- package/dist/grpc-service.js +3 -5
- package/dist/grpc-service.js.map +1 -1
- package/dist/index.js +19 -16
- package/dist/index.js.map +1 -1
- package/dist/ws-bridge.d.ts.map +1 -1
- package/dist/ws-bridge.js +2 -3
- package/dist/ws-bridge.js.map +1 -1
- package/package.json +12 -7
- package/dist/adapters/codespace.d.ts +0 -40
- package/dist/adapters/codespace.d.ts.map +0 -1
- package/dist/adapters/codespace.js +0 -212
- package/dist/adapters/codespace.js.map +0 -1
- package/dist/adapters/docker.d.ts +0 -39
- package/dist/adapters/docker.d.ts.map +0 -1
- package/dist/adapters/docker.js +0 -366
- package/dist/adapters/docker.js.map +0 -1
- package/dist/adapters/local.d.ts +0 -15
- package/dist/adapters/local.d.ts.map +0 -1
- package/dist/adapters/local.js +0 -57
- package/dist/adapters/local.js.map +0 -1
- package/dist/adapters/ssh.d.ts +0 -45
- package/dist/adapters/ssh.d.ts.map +0 -1
- package/dist/adapters/ssh.js +0 -214
- package/dist/adapters/ssh.js.map +0 -1
- package/dist/api-key.d.ts +0 -8
- package/dist/api-key.d.ts.map +0 -1
- package/dist/api-key.js +0 -58
- package/dist/api-key.js.map +0 -1
- package/dist/oauth.d.ts +0 -100
- package/dist/oauth.d.ts.map +0 -1
- package/dist/oauth.js +0 -225
- package/dist/oauth.js.map +0 -1
- package/dist/pairing.d.ts +0 -22
- package/dist/pairing.d.ts.map +0 -1
- package/dist/pairing.js +0 -136
- package/dist/pairing.js.map +0 -1
- package/dist/security-headers.d.ts +0 -17
- package/dist/security-headers.d.ts.map +0 -1
- package/dist/security-headers.js +0 -31
- package/dist/security-headers.js.map +0 -1
- package/dist/session.d.ts +0 -36
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -143
- package/dist/session.js.map +0 -1
package/dist/adapters/ssh.js
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_POWERLINE_PORT, DEFAULT_MCP_PORT } from "@grackle-ai/common";
|
|
2
|
-
import { ProcessTunnel, bootstrapPowerLine, connectThroughTunnel, registerTunnel, getTunnel, closeTunnel, findFreePort, remoteStop, remoteDestroy, remoteHealthCheck, startRemotePowerLine, SSH_CONNECTIVITY_TIMEOUT_MS, REMOTE_EXEC_DEFAULT_TIMEOUT_MS, } from "@grackle-ai/adapter-sdk";
|
|
3
|
-
import { getCredentialProviders } from "../credential-providers.js";
|
|
4
|
-
import { exec } from "../utils/exec.js";
|
|
5
|
-
import { sleep } from "../utils/sleep.js";
|
|
6
|
-
const REMOTE_COPY_TIMEOUT_MS = 120_000;
|
|
7
|
-
/** Delay for reverse tunnels to wait for SSH to establish. */
|
|
8
|
-
const REVERSE_TUNNEL_SETTLE_MS = 3_000;
|
|
9
|
-
// ─── SSH Helpers ────────────────────────────────────────────
|
|
10
|
-
/** Build the common SSH flags shared across exec, scp, and tunnel commands. */
|
|
11
|
-
function buildSshFlags(cfg) {
|
|
12
|
-
const flags = ["-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new"];
|
|
13
|
-
if (cfg.sshPort) {
|
|
14
|
-
flags.push("-p", String(cfg.sshPort));
|
|
15
|
-
}
|
|
16
|
-
if (cfg.identityFile) {
|
|
17
|
-
flags.push("-i", cfg.identityFile);
|
|
18
|
-
}
|
|
19
|
-
if (cfg.sshOptions) {
|
|
20
|
-
for (const [key, value] of Object.entries(cfg.sshOptions)) {
|
|
21
|
-
flags.push("-o", `${key}=${value}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return flags;
|
|
25
|
-
}
|
|
26
|
-
/** Build the `user@host` destination string. */
|
|
27
|
-
function buildDestination(cfg) {
|
|
28
|
-
return cfg.user ? `${cfg.user}@${cfg.host}` : cfg.host;
|
|
29
|
-
}
|
|
30
|
-
// ─── Executor ───────────────────────────────────────────────
|
|
31
|
-
/** Execute commands on a remote host via SSH. */
|
|
32
|
-
class SshExecutor {
|
|
33
|
-
cfg;
|
|
34
|
-
constructor(cfg) {
|
|
35
|
-
this.cfg = cfg;
|
|
36
|
-
}
|
|
37
|
-
/** Execute a shell command on the remote host and return trimmed stdout. */
|
|
38
|
-
async exec(command, opts) {
|
|
39
|
-
const args = [...buildSshFlags(this.cfg), buildDestination(this.cfg), command];
|
|
40
|
-
const result = await exec("ssh", args, { timeout: opts?.timeout ?? REMOTE_EXEC_DEFAULT_TIMEOUT_MS });
|
|
41
|
-
return result.stdout;
|
|
42
|
-
}
|
|
43
|
-
/** Copy a local file or directory to the remote host via scp. */
|
|
44
|
-
async copyTo(localPath, remotePath) {
|
|
45
|
-
const flags = buildSshFlags(this.cfg);
|
|
46
|
-
// scp uses -P (uppercase) instead of -p for port
|
|
47
|
-
const scpFlags = flags.map((f, i) => (f === "-p" && i > 0 && flags[i - 1] !== "-o") ? "-P" : f);
|
|
48
|
-
const args = ["-r", ...scpFlags, localPath, `${buildDestination(this.cfg)}:${remotePath}`];
|
|
49
|
-
await exec("scp", args, { timeout: REMOTE_COPY_TIMEOUT_MS });
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// ─── Tunnel ─────────────────────────────────────────────────
|
|
53
|
-
/** SSH tunnel that forwards a local port to the remote PowerLine port. */
|
|
54
|
-
class SshTunnel extends ProcessTunnel {
|
|
55
|
-
cfg;
|
|
56
|
-
constructor(localPort, cfg, processFactory, portProbe) {
|
|
57
|
-
super(localPort, undefined, processFactory, portProbe);
|
|
58
|
-
this.cfg = cfg;
|
|
59
|
-
}
|
|
60
|
-
/** Return the ssh command and arguments for the tunnel process. */
|
|
61
|
-
spawnArgs() {
|
|
62
|
-
const flags = buildSshFlags(this.cfg);
|
|
63
|
-
const args = [
|
|
64
|
-
"-N",
|
|
65
|
-
"-L", `${this.localPort}:127.0.0.1:${DEFAULT_POWERLINE_PORT}`,
|
|
66
|
-
"-o", "ExitOnForwardFailure=yes",
|
|
67
|
-
"-o", "ServerAliveInterval=15",
|
|
68
|
-
"-o", "ServerAliveCountMax=3",
|
|
69
|
-
...flags,
|
|
70
|
-
buildDestination(this.cfg),
|
|
71
|
-
];
|
|
72
|
-
return { command: "ssh", args };
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Reverse SSH tunnel: binds a port on the remote host that tunnels back to a local port.
|
|
77
|
-
* Used so agents (running on the remote host) can reach the Grackle MCP server (on the host).
|
|
78
|
-
*/
|
|
79
|
-
class SshReverseTunnel extends ProcessTunnel {
|
|
80
|
-
cfg;
|
|
81
|
-
remotePort;
|
|
82
|
-
constructor(localPort, remotePort, cfg, processFactory, portProbe) {
|
|
83
|
-
super(localPort, undefined, processFactory, portProbe);
|
|
84
|
-
this.cfg = cfg;
|
|
85
|
-
this.remotePort = remotePort;
|
|
86
|
-
}
|
|
87
|
-
/** Return the ssh command with -R for reverse port forwarding. */
|
|
88
|
-
spawnArgs() {
|
|
89
|
-
const flags = buildSshFlags(this.cfg);
|
|
90
|
-
const args = [
|
|
91
|
-
"-N",
|
|
92
|
-
"-R", `${this.remotePort}:127.0.0.1:${this.localPort}`,
|
|
93
|
-
"-o", "ExitOnForwardFailure=yes",
|
|
94
|
-
"-o", "ServerAliveInterval=15",
|
|
95
|
-
"-o", "ServerAliveCountMax=3",
|
|
96
|
-
...flags,
|
|
97
|
-
buildDestination(this.cfg),
|
|
98
|
-
];
|
|
99
|
-
return { command: "ssh", args };
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Reverse tunnels bind on the remote side, not locally.
|
|
103
|
-
* We can't probe the remote port, so wait a fixed delay for SSH to establish.
|
|
104
|
-
*/
|
|
105
|
-
async waitForReady() {
|
|
106
|
-
await sleep(REVERSE_TUNNEL_SETTLE_MS);
|
|
107
|
-
if (this.process?.exitCode !== null) {
|
|
108
|
-
throw new Error(`Reverse tunnel exited immediately with code ${this.process?.exitCode}`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// ─── Adapter ────────────────────────────────────────────────
|
|
113
|
-
/** Environment adapter that provisions and manages remote environments via SSH. */
|
|
114
|
-
export class SshAdapter {
|
|
115
|
-
type = "ssh";
|
|
116
|
-
/** Provision the remote host: test connectivity, bootstrap PowerLine, open tunnel. */
|
|
117
|
-
async *provision(environmentId, config, powerlineToken) {
|
|
118
|
-
const cfg = config;
|
|
119
|
-
if (!cfg.host) {
|
|
120
|
-
throw new Error("SSH adapter requires a 'host' in the configuration");
|
|
121
|
-
}
|
|
122
|
-
const executor = new SshExecutor(cfg);
|
|
123
|
-
// Test SSH connectivity
|
|
124
|
-
yield { stage: "connecting", message: `Testing SSH connectivity to ${cfg.host}...`, progress: 0.05 };
|
|
125
|
-
try {
|
|
126
|
-
await executor.exec("echo ok", { timeout: SSH_CONNECTIVITY_TIMEOUT_MS });
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
throw new Error(`Cannot reach ${cfg.host} via SSH: ${err instanceof Error ? err.message : String(err)}`);
|
|
130
|
-
}
|
|
131
|
-
// Bootstrap PowerLine on the remote host
|
|
132
|
-
yield* bootstrapPowerLine(executor, powerlineToken, {
|
|
133
|
-
extraEnv: cfg.env,
|
|
134
|
-
isGitHubProviderEnabled: () => getCredentialProviders().github !== "off",
|
|
135
|
-
defaultRuntime: config.defaultRuntime || undefined,
|
|
136
|
-
});
|
|
137
|
-
// Open SSH tunnel
|
|
138
|
-
const localPort = cfg.localPort || await findFreePort();
|
|
139
|
-
yield { stage: "tunneling", message: `Opening SSH tunnel on local port ${localPort}...`, progress: 0.80 };
|
|
140
|
-
const tunnel = new SshTunnel(localPort, cfg);
|
|
141
|
-
await tunnel.open();
|
|
142
|
-
// Open reverse tunnel (remote → host MCP server) for agent tool calls
|
|
143
|
-
const mcpPort = parseInt(process.env.GRACKLE_MCP_PORT || String(DEFAULT_MCP_PORT), 10);
|
|
144
|
-
const reverseTunnel = new SshReverseTunnel(mcpPort, mcpPort, cfg);
|
|
145
|
-
await reverseTunnel.open();
|
|
146
|
-
registerTunnel(environmentId, { tunnel, reverseTunnel });
|
|
147
|
-
yield { stage: "connecting", message: `Tunnel open, connecting on port ${localPort}...`, progress: 0.90 };
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Attempt fast reconnect: probe PowerLine, restart if needed, re-open tunnel.
|
|
151
|
-
*
|
|
152
|
-
* Any failure (SSH unreachable, PowerLine won't start, tunnel error) throws
|
|
153
|
-
* and falls through to the caller, which should trigger a full provision.
|
|
154
|
-
*
|
|
155
|
-
* Minimizes SSH round trips — probe and conditional restart run in a single
|
|
156
|
-
* SSH call via `startRemotePowerLine({ probeFirst: true })`.
|
|
157
|
-
*/
|
|
158
|
-
async *reconnect(environmentId, config, powerlineToken) {
|
|
159
|
-
const cfg = config;
|
|
160
|
-
if (!cfg.host) {
|
|
161
|
-
throw new Error("SSH adapter requires a 'host' in the configuration");
|
|
162
|
-
}
|
|
163
|
-
const executor = new SshExecutor(cfg);
|
|
164
|
-
// 1. Close any stale tunnel
|
|
165
|
-
yield { stage: "reconnecting", message: "Closing stale tunnel...", progress: 0.10 };
|
|
166
|
-
await closeTunnel(environmentId);
|
|
167
|
-
// 2. Probe + conditional restart in a single SSH call.
|
|
168
|
-
yield { stage: "reconnecting", message: `Checking PowerLine on ${cfg.host}...`, progress: 0.30 };
|
|
169
|
-
const { alreadyRunning } = await startRemotePowerLine(executor, powerlineToken, {
|
|
170
|
-
extraEnv: cfg.env,
|
|
171
|
-
probeFirst: true,
|
|
172
|
-
});
|
|
173
|
-
if (!alreadyRunning) {
|
|
174
|
-
yield { stage: "reconnecting", message: "PowerLine restarted", progress: 0.50 };
|
|
175
|
-
}
|
|
176
|
-
// 3. Open new SSH tunnel + reverse tunnel for MCP
|
|
177
|
-
const localPort = cfg.localPort || await findFreePort();
|
|
178
|
-
yield { stage: "reconnecting", message: `Opening SSH tunnel on local port ${localPort}...`, progress: 0.70 };
|
|
179
|
-
const tunnel = new SshTunnel(localPort, cfg);
|
|
180
|
-
await tunnel.open();
|
|
181
|
-
const mcpPort = parseInt(process.env.GRACKLE_MCP_PORT || String(DEFAULT_MCP_PORT), 10);
|
|
182
|
-
const reverseTunnel = new SshReverseTunnel(mcpPort, mcpPort, cfg);
|
|
183
|
-
await reverseTunnel.open();
|
|
184
|
-
registerTunnel(environmentId, { tunnel, reverseTunnel });
|
|
185
|
-
yield { stage: "reconnecting", message: "Reconnected via SSH", progress: 0.90 };
|
|
186
|
-
}
|
|
187
|
-
/** Connect to the PowerLine through the SSH tunnel. */
|
|
188
|
-
async connect(environmentId, config, powerlineToken) {
|
|
189
|
-
const state = getTunnel(environmentId);
|
|
190
|
-
if (!state) {
|
|
191
|
-
throw new Error(`No tunnel registered for environment ${environmentId}`);
|
|
192
|
-
}
|
|
193
|
-
return connectThroughTunnel(environmentId, state.tunnel.localPort, powerlineToken);
|
|
194
|
-
}
|
|
195
|
-
/** Close the SSH tunnel without stopping the remote PowerLine. */
|
|
196
|
-
async disconnect(environmentId) {
|
|
197
|
-
await closeTunnel(environmentId);
|
|
198
|
-
}
|
|
199
|
-
/** Stop the remote PowerLine process and close the tunnel. */
|
|
200
|
-
async stop(environmentId, config) {
|
|
201
|
-
const cfg = config;
|
|
202
|
-
await remoteStop(environmentId, new SshExecutor(cfg));
|
|
203
|
-
}
|
|
204
|
-
/** Stop the remote PowerLine, remove artifacts, and close the tunnel. */
|
|
205
|
-
async destroy(environmentId, config) {
|
|
206
|
-
const cfg = config;
|
|
207
|
-
await remoteDestroy(environmentId, new SshExecutor(cfg));
|
|
208
|
-
}
|
|
209
|
-
/** Check that the tunnel is alive and the PowerLine responds to a ping. */
|
|
210
|
-
async healthCheck(connection) {
|
|
211
|
-
return remoteHealthCheck(connection);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
//# sourceMappingURL=ssh.js.map
|
package/dist/adapters/ssh.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ssh.js","sourceRoot":"","sources":["../../src/adapters/ssh.ts"],"names":[],"mappings":"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"}
|
package/dist/api-key.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Load or create the API key. On first run, a random 256-bit key is
|
|
3
|
-
* generated and written to ~/.grackle/api-key with 0600 permissions.
|
|
4
|
-
*/
|
|
5
|
-
export declare function loadOrCreateApiKey(): string;
|
|
6
|
-
/** Verify a bearer token matches the API key. */
|
|
7
|
-
export declare function verifyApiKey(token: string): boolean;
|
|
8
|
-
//# sourceMappingURL=api-key.d.ts.map
|
package/dist/api-key.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api-key.d.ts","sourceRoot":"","sources":["../src/api-key.ts"],"names":[],"mappings":"AAwCA;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAO3C;AAED,iDAAiD;AACjD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAYnD"}
|
package/dist/api-key.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { API_KEY_FILENAME } from "@grackle-ai/common";
|
|
5
|
-
import { logger } from "./logger.js";
|
|
6
|
-
import { grackleHome } from "./paths.js";
|
|
7
|
-
const API_KEY_BYTE_LENGTH = 32;
|
|
8
|
-
const keyPath = join(grackleHome, API_KEY_FILENAME);
|
|
9
|
-
let cachedKey = undefined;
|
|
10
|
-
/** Attempt to read an existing API key from disk. Returns undefined if none exists. */
|
|
11
|
-
function tryLoadApiKey() {
|
|
12
|
-
if (existsSync(keyPath)) {
|
|
13
|
-
const content = readFileSync(keyPath, "utf8").trim();
|
|
14
|
-
if (content.length > 0) {
|
|
15
|
-
return content;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
/** Generate a new random API key, write it to disk with 0600 permissions, and return it. */
|
|
21
|
-
function createApiKey() {
|
|
22
|
-
const key = randomBytes(API_KEY_BYTE_LENGTH).toString("hex");
|
|
23
|
-
mkdirSync(grackleHome, { recursive: true });
|
|
24
|
-
writeFileSync(keyPath, key + "\n", { mode: 0o600 });
|
|
25
|
-
// Ensure permissions on Windows (best-effort)
|
|
26
|
-
try {
|
|
27
|
-
chmodSync(keyPath, 0o600);
|
|
28
|
-
}
|
|
29
|
-
catch { /* Windows may not support this */ }
|
|
30
|
-
logger.info({ keyPath }, "Generated new API key");
|
|
31
|
-
return key;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Load or create the API key. On first run, a random 256-bit key is
|
|
35
|
-
* generated and written to ~/.grackle/api-key with 0600 permissions.
|
|
36
|
-
*/
|
|
37
|
-
export function loadOrCreateApiKey() {
|
|
38
|
-
if (cachedKey) {
|
|
39
|
-
return cachedKey;
|
|
40
|
-
}
|
|
41
|
-
cachedKey = tryLoadApiKey() ?? createApiKey();
|
|
42
|
-
return cachedKey;
|
|
43
|
-
}
|
|
44
|
-
/** Verify a bearer token matches the API key. */
|
|
45
|
-
export function verifyApiKey(token) {
|
|
46
|
-
const key = loadOrCreateApiKey();
|
|
47
|
-
// Constant-time comparison to prevent timing attacks
|
|
48
|
-
if (token.length !== key.length) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
let result = 0;
|
|
52
|
-
for (let i = 0; i < key.length; i++) {
|
|
53
|
-
// eslint-disable-next-line no-bitwise
|
|
54
|
-
result |= token.charCodeAt(i) ^ key.charCodeAt(i);
|
|
55
|
-
}
|
|
56
|
-
return result === 0;
|
|
57
|
-
}
|
|
58
|
-
//# sourceMappingURL=api-key.js.map
|
package/dist/api-key.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api-key.js","sourceRoot":"","sources":["../src/api-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,mBAAmB,GAAW,EAAE,CAAC;AAEvC,MAAM,OAAO,GAAW,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;AAE5D,IAAI,SAAS,GAAuB,SAAS,CAAC;AAE9C,uFAAuF;AACvF,SAAS,aAAa;IACpB,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,4FAA4F;AAC5F,SAAS,YAAY;IACnB,MAAM,GAAG,GAAG,WAAW,CAAC,mBAAmB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE7D,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,aAAa,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAEpD,8CAA8C;IAC9C,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC,CAAC,kCAAkC,CAAC,CAAC;IAE9C,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,uBAAuB,CAAC,CAAC;IAClD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,SAAS,GAAG,aAAa,EAAE,IAAI,YAAY,EAAE,CAAC;IAC9C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;IACjC,qDAAqD;IACrD,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,sCAAsC;QACtC,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,MAAM,KAAK,CAAC,CAAC;AACtB,CAAC"}
|
package/dist/oauth.d.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
interface ClientRecord {
|
|
2
|
-
clientId: string;
|
|
3
|
-
redirectUris: string[];
|
|
4
|
-
clientName: string;
|
|
5
|
-
createdAt: number;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Register a new OAuth client with dynamic client registration.
|
|
9
|
-
*
|
|
10
|
-
* @param redirectUris - List of allowed redirect URIs for this client.
|
|
11
|
-
* @param clientName - Human-readable name for the client (optional).
|
|
12
|
-
* @returns The newly registered client record.
|
|
13
|
-
*/
|
|
14
|
-
export declare function registerClient(redirectUris: string[], clientName?: string): ClientRecord | undefined;
|
|
15
|
-
/**
|
|
16
|
-
* Look up a registered client by ID.
|
|
17
|
-
*
|
|
18
|
-
* @param clientId - The client ID to look up.
|
|
19
|
-
* @returns The client record, or undefined if not found.
|
|
20
|
-
*/
|
|
21
|
-
export declare function getClient(clientId: string): ClientRecord | undefined;
|
|
22
|
-
interface AuthCodeRecord {
|
|
23
|
-
code: string;
|
|
24
|
-
clientId: string;
|
|
25
|
-
redirectUri: string;
|
|
26
|
-
codeChallenge: string;
|
|
27
|
-
resource: string;
|
|
28
|
-
createdAt: number;
|
|
29
|
-
expiresAt: number;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Create a single-use authorization code bound to the given parameters.
|
|
33
|
-
*
|
|
34
|
-
* @param clientId - The client that requested authorization.
|
|
35
|
-
* @param redirectUri - The redirect URI for this authorization.
|
|
36
|
-
* @param codeChallenge - PKCE S256 code challenge.
|
|
37
|
-
* @param resource - The resource URL the client wants to access.
|
|
38
|
-
* @returns The generated authorization code string.
|
|
39
|
-
*/
|
|
40
|
-
export declare function createAuthorizationCode(clientId: string, redirectUri: string, codeChallenge: string, resource: string): string;
|
|
41
|
-
/**
|
|
42
|
-
* Compute the S256 code challenge from a code verifier.
|
|
43
|
-
*
|
|
44
|
-
* @param codeVerifier - The PKCE code verifier string.
|
|
45
|
-
* @returns The base64url-encoded SHA-256 hash.
|
|
46
|
-
*/
|
|
47
|
-
export declare function computeCodeChallenge(codeVerifier: string): string;
|
|
48
|
-
/**
|
|
49
|
-
* Verify that a code verifier matches a code challenge using S256.
|
|
50
|
-
*
|
|
51
|
-
* @param codeVerifier - The PKCE code verifier from the token request.
|
|
52
|
-
* @param codeChallenge - The code challenge stored at authorization time.
|
|
53
|
-
* @returns True if the verifier matches the challenge.
|
|
54
|
-
*/
|
|
55
|
-
export declare function verifyCodeChallenge(codeVerifier: string, codeChallenge: string): boolean;
|
|
56
|
-
/**
|
|
57
|
-
* Consume an authorization code, validating all parameters.
|
|
58
|
-
*
|
|
59
|
-
* The code is deleted regardless of whether validation succeeds (single-use).
|
|
60
|
-
*
|
|
61
|
-
* @param code - The authorization code to consume.
|
|
62
|
-
* @param clientId - The client ID making the token request.
|
|
63
|
-
* @param redirectUri - The redirect URI from the token request.
|
|
64
|
-
* @param codeVerifier - The PKCE code verifier.
|
|
65
|
-
* @param resource - The resource URL from the token request.
|
|
66
|
-
* @returns The auth code record if valid, or undefined.
|
|
67
|
-
*/
|
|
68
|
-
export declare function consumeAuthorizationCode(code: string, clientId: string, redirectUri: string, codeVerifier: string, resource: string): AuthCodeRecord | undefined;
|
|
69
|
-
interface RefreshTokenRecord {
|
|
70
|
-
token: string;
|
|
71
|
-
clientId: string;
|
|
72
|
-
resource: string;
|
|
73
|
-
createdAt: number;
|
|
74
|
-
expiresAt: number;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Create a new refresh token for the given client and resource.
|
|
78
|
-
*
|
|
79
|
-
* @param clientId - The client this refresh token is issued to.
|
|
80
|
-
* @param resource - The resource URL this refresh token is scoped to.
|
|
81
|
-
* @returns The generated refresh token string.
|
|
82
|
-
*/
|
|
83
|
-
export declare function createRefreshToken(clientId: string, resource: string): string;
|
|
84
|
-
/**
|
|
85
|
-
* Consume a refresh token with rotation — the old token is invalidated and
|
|
86
|
-
* a new one must be issued in its place.
|
|
87
|
-
*
|
|
88
|
-
* @param token - The refresh token to consume.
|
|
89
|
-
* @param clientId - The client ID making the refresh request.
|
|
90
|
-
* @returns The refresh token record if valid, or undefined.
|
|
91
|
-
*/
|
|
92
|
-
export declare function consumeRefreshToken(token: string, clientId: string): RefreshTokenRecord | undefined;
|
|
93
|
-
/** Start the periodic OAuth state cleanup timer. Call once on server startup. */
|
|
94
|
-
export declare function startOAuthCleanup(): void;
|
|
95
|
-
/** Stop the periodic OAuth state cleanup timer. */
|
|
96
|
-
export declare function stopOAuthCleanup(): void;
|
|
97
|
-
/** Clear all OAuth state. Intended for testing only. */
|
|
98
|
-
export declare function clearOAuthState(): void;
|
|
99
|
-
export {};
|
|
100
|
-
//# sourceMappingURL=oauth.d.ts.map
|
package/dist/oauth.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAuBA,UAAU,YAAY;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAKD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAwBpG;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEpE;AAID,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAKD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GACf,MAAM,CAeR;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAGxF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,cAAc,GAAG,SAAS,CA+B5B;AAID,UAAU,kBAAkB;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAKD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAW7E;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAmBnG;AAMD,iFAAiF;AACjF,wBAAgB,iBAAiB,IAAI,IAAI,CAuBxC;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,IAAI,IAAI,CAKvC;AAED,wDAAwD;AACxD,wBAAgB,eAAe,IAAI,IAAI,CAItC"}
|
package/dist/oauth.js
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { randomUUID, randomBytes, createHash } from "node:crypto";
|
|
2
|
-
import { logger } from "./logger.js";
|
|
3
|
-
/** Time-to-live for authorization codes: 30 seconds. */
|
|
4
|
-
const AUTH_CODE_TTL_MS = 30 * 1000;
|
|
5
|
-
/** Time-to-live for refresh tokens: 30 days. */
|
|
6
|
-
const REFRESH_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
7
|
-
/** Interval at which expired OAuth state is cleaned up. */
|
|
8
|
-
const CLEANUP_INTERVAL_MS = 60 * 1000;
|
|
9
|
-
/** Byte length of generated tokens (authorization codes, refresh tokens). */
|
|
10
|
-
const TOKEN_BYTE_LENGTH = 32;
|
|
11
|
-
/** Maximum number of registered OAuth clients to prevent unbounded growth. */
|
|
12
|
-
const MAX_CLIENTS = 100;
|
|
13
|
-
/** Time-to-live for client registrations: 7 days. */
|
|
14
|
-
const CLIENT_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
15
|
-
/** Registered OAuth clients keyed by client ID. */
|
|
16
|
-
const clients = new Map();
|
|
17
|
-
/**
|
|
18
|
-
* Register a new OAuth client with dynamic client registration.
|
|
19
|
-
*
|
|
20
|
-
* @param redirectUris - List of allowed redirect URIs for this client.
|
|
21
|
-
* @param clientName - Human-readable name for the client (optional).
|
|
22
|
-
* @returns The newly registered client record.
|
|
23
|
-
*/
|
|
24
|
-
export function registerClient(redirectUris, clientName) {
|
|
25
|
-
// Evict expired clients before checking capacity
|
|
26
|
-
const now = Date.now();
|
|
27
|
-
for (const [id, rec] of clients) {
|
|
28
|
-
if (now - rec.createdAt > CLIENT_TTL_MS) {
|
|
29
|
-
clients.delete(id);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (clients.size >= MAX_CLIENTS) {
|
|
33
|
-
logger.warn("Maximum registered OAuth clients reached (%d)", MAX_CLIENTS);
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
const clientId = randomUUID();
|
|
37
|
-
const record = {
|
|
38
|
-
clientId,
|
|
39
|
-
redirectUris,
|
|
40
|
-
clientName: clientName || "Unknown Client",
|
|
41
|
-
createdAt: now,
|
|
42
|
-
};
|
|
43
|
-
clients.set(clientId, record);
|
|
44
|
-
logger.info({ clientId, clientName: record.clientName }, "OAuth client registered");
|
|
45
|
-
return record;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Look up a registered client by ID.
|
|
49
|
-
*
|
|
50
|
-
* @param clientId - The client ID to look up.
|
|
51
|
-
* @returns The client record, or undefined if not found.
|
|
52
|
-
*/
|
|
53
|
-
export function getClient(clientId) {
|
|
54
|
-
return clients.get(clientId);
|
|
55
|
-
}
|
|
56
|
-
/** Active authorization codes keyed by code string. */
|
|
57
|
-
const authCodes = new Map();
|
|
58
|
-
/**
|
|
59
|
-
* Create a single-use authorization code bound to the given parameters.
|
|
60
|
-
*
|
|
61
|
-
* @param clientId - The client that requested authorization.
|
|
62
|
-
* @param redirectUri - The redirect URI for this authorization.
|
|
63
|
-
* @param codeChallenge - PKCE S256 code challenge.
|
|
64
|
-
* @param resource - The resource URL the client wants to access.
|
|
65
|
-
* @returns The generated authorization code string.
|
|
66
|
-
*/
|
|
67
|
-
export function createAuthorizationCode(clientId, redirectUri, codeChallenge, resource) {
|
|
68
|
-
const code = randomBytes(TOKEN_BYTE_LENGTH).toString("base64url");
|
|
69
|
-
const now = Date.now();
|
|
70
|
-
const record = {
|
|
71
|
-
code,
|
|
72
|
-
clientId,
|
|
73
|
-
redirectUri,
|
|
74
|
-
codeChallenge,
|
|
75
|
-
resource,
|
|
76
|
-
createdAt: now,
|
|
77
|
-
expiresAt: now + AUTH_CODE_TTL_MS,
|
|
78
|
-
};
|
|
79
|
-
authCodes.set(code, record);
|
|
80
|
-
logger.info({ clientId }, "Authorization code created");
|
|
81
|
-
return code;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Compute the S256 code challenge from a code verifier.
|
|
85
|
-
*
|
|
86
|
-
* @param codeVerifier - The PKCE code verifier string.
|
|
87
|
-
* @returns The base64url-encoded SHA-256 hash.
|
|
88
|
-
*/
|
|
89
|
-
export function computeCodeChallenge(codeVerifier) {
|
|
90
|
-
return createHash("sha256").update(codeVerifier).digest("base64url");
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Verify that a code verifier matches a code challenge using S256.
|
|
94
|
-
*
|
|
95
|
-
* @param codeVerifier - The PKCE code verifier from the token request.
|
|
96
|
-
* @param codeChallenge - The code challenge stored at authorization time.
|
|
97
|
-
* @returns True if the verifier matches the challenge.
|
|
98
|
-
*/
|
|
99
|
-
export function verifyCodeChallenge(codeVerifier, codeChallenge) {
|
|
100
|
-
const computed = computeCodeChallenge(codeVerifier);
|
|
101
|
-
return computed === codeChallenge;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Consume an authorization code, validating all parameters.
|
|
105
|
-
*
|
|
106
|
-
* The code is deleted regardless of whether validation succeeds (single-use).
|
|
107
|
-
*
|
|
108
|
-
* @param code - The authorization code to consume.
|
|
109
|
-
* @param clientId - The client ID making the token request.
|
|
110
|
-
* @param redirectUri - The redirect URI from the token request.
|
|
111
|
-
* @param codeVerifier - The PKCE code verifier.
|
|
112
|
-
* @param resource - The resource URL from the token request.
|
|
113
|
-
* @returns The auth code record if valid, or undefined.
|
|
114
|
-
*/
|
|
115
|
-
export function consumeAuthorizationCode(code, clientId, redirectUri, codeVerifier, resource) {
|
|
116
|
-
const record = authCodes.get(code);
|
|
117
|
-
// Always delete to prevent replay — even if validation fails
|
|
118
|
-
authCodes.delete(code);
|
|
119
|
-
if (!record) {
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
const now = Date.now();
|
|
123
|
-
if (now > record.expiresAt) {
|
|
124
|
-
return undefined;
|
|
125
|
-
}
|
|
126
|
-
if (record.clientId !== clientId) {
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
if (record.redirectUri !== redirectUri) {
|
|
130
|
-
return undefined;
|
|
131
|
-
}
|
|
132
|
-
if (record.resource !== resource) {
|
|
133
|
-
return undefined;
|
|
134
|
-
}
|
|
135
|
-
if (!verifyCodeChallenge(codeVerifier, record.codeChallenge)) {
|
|
136
|
-
return undefined;
|
|
137
|
-
}
|
|
138
|
-
return record;
|
|
139
|
-
}
|
|
140
|
-
/** Active refresh tokens keyed by token string. */
|
|
141
|
-
const refreshTokens = new Map();
|
|
142
|
-
/**
|
|
143
|
-
* Create a new refresh token for the given client and resource.
|
|
144
|
-
*
|
|
145
|
-
* @param clientId - The client this refresh token is issued to.
|
|
146
|
-
* @param resource - The resource URL this refresh token is scoped to.
|
|
147
|
-
* @returns The generated refresh token string.
|
|
148
|
-
*/
|
|
149
|
-
export function createRefreshToken(clientId, resource) {
|
|
150
|
-
const token = randomBytes(TOKEN_BYTE_LENGTH).toString("base64url");
|
|
151
|
-
const now = Date.now();
|
|
152
|
-
refreshTokens.set(token, {
|
|
153
|
-
token,
|
|
154
|
-
clientId,
|
|
155
|
-
resource,
|
|
156
|
-
createdAt: now,
|
|
157
|
-
expiresAt: now + REFRESH_TOKEN_TTL_MS,
|
|
158
|
-
});
|
|
159
|
-
return token;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Consume a refresh token with rotation — the old token is invalidated and
|
|
163
|
-
* a new one must be issued in its place.
|
|
164
|
-
*
|
|
165
|
-
* @param token - The refresh token to consume.
|
|
166
|
-
* @param clientId - The client ID making the refresh request.
|
|
167
|
-
* @returns The refresh token record if valid, or undefined.
|
|
168
|
-
*/
|
|
169
|
-
export function consumeRefreshToken(token, clientId) {
|
|
170
|
-
const record = refreshTokens.get(token);
|
|
171
|
-
// Always delete to enforce rotation
|
|
172
|
-
refreshTokens.delete(token);
|
|
173
|
-
if (!record) {
|
|
174
|
-
return undefined;
|
|
175
|
-
}
|
|
176
|
-
const now = Date.now();
|
|
177
|
-
if (now > record.expiresAt) {
|
|
178
|
-
return undefined;
|
|
179
|
-
}
|
|
180
|
-
if (record.clientId !== clientId) {
|
|
181
|
-
return undefined;
|
|
182
|
-
}
|
|
183
|
-
return record;
|
|
184
|
-
}
|
|
185
|
-
// ─── Cleanup ──────────────────────────────────────────────────────────
|
|
186
|
-
let cleanupTimer;
|
|
187
|
-
/** Start the periodic OAuth state cleanup timer. Call once on server startup. */
|
|
188
|
-
export function startOAuthCleanup() {
|
|
189
|
-
if (cleanupTimer) {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
cleanupTimer = setInterval(() => {
|
|
193
|
-
const now = Date.now();
|
|
194
|
-
for (const [id, record] of clients) {
|
|
195
|
-
if (now - record.createdAt > CLIENT_TTL_MS) {
|
|
196
|
-
clients.delete(id);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
for (const [code, record] of authCodes) {
|
|
200
|
-
if (now > record.expiresAt) {
|
|
201
|
-
authCodes.delete(code);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
for (const [token, record] of refreshTokens) {
|
|
205
|
-
if (now > record.expiresAt) {
|
|
206
|
-
refreshTokens.delete(token);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}, CLEANUP_INTERVAL_MS);
|
|
210
|
-
cleanupTimer.unref();
|
|
211
|
-
}
|
|
212
|
-
/** Stop the periodic OAuth state cleanup timer. */
|
|
213
|
-
export function stopOAuthCleanup() {
|
|
214
|
-
if (cleanupTimer) {
|
|
215
|
-
clearInterval(cleanupTimer);
|
|
216
|
-
cleanupTimer = undefined;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
/** Clear all OAuth state. Intended for testing only. */
|
|
220
|
-
export function clearOAuthState() {
|
|
221
|
-
clients.clear();
|
|
222
|
-
authCodes.clear();
|
|
223
|
-
refreshTokens.clear();
|
|
224
|
-
}
|
|
225
|
-
//# sourceMappingURL=oauth.js.map
|
package/dist/oauth.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"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"}
|