patchwork-os 1.2.0-beta.3.canary.887 → 1.2.0-beta.3.canary.889
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/claudeAuthHttp.d.ts +23 -31
- package/dist/claudeAuthHttp.js +239 -202
- package/dist/claudeAuthHttp.js.map +1 -1
- package/dist/claudeOrchestrator.js +35 -1
- package/dist/claudeOrchestrator.js.map +1 -1
- package/dist/drivers/claude/envSanitizer.js +4 -0
- package/dist/drivers/claude/envSanitizer.js.map +1 -1
- package/dist/governance/doctorReport.js +1 -1
- package/dist/governance/doctorReport.js.map +1 -1
- package/dist/server.js +15 -7
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/claudeAuthHttp.d.ts
CHANGED
|
@@ -1,41 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude
|
|
3
|
-
*
|
|
4
|
-
* Implements the three endpoints the dashboard settings "Connect Claude" UX
|
|
5
|
-
* expects on the bridge:
|
|
2
|
+
* Claude sign-in relay.
|
|
6
3
|
*
|
|
7
4
|
* POST /auth/claude/start → { sessionId, url }
|
|
8
|
-
* POST /auth/claude/complete → {
|
|
9
|
-
* POST /auth/claude/cancel → { ok: true }
|
|
5
|
+
* POST /auth/claude/complete → { ok, loggedIn, authMethod } body { sessionId, code }
|
|
6
|
+
* POST /auth/claude/cancel → { ok: true } body { sessionId? }
|
|
7
|
+
* GET /auth/claude/status → { loggedIn, authMethod }
|
|
8
|
+
* POST /auth/claude/logout → { ok: true }
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* 3. complete — exchange the code + verifier for an access token at
|
|
17
|
-
* https://api.anthropic.com/v1/oauth/token. Return the token.
|
|
18
|
-
* 4. Dashboard POSTs the token to /api/auth/anthropic-key → control-plane
|
|
19
|
-
* stores it encrypted and re-provisions the container with
|
|
20
|
-
* CLAUDE_CODE_OAUTH_TOKEN=<token> so the subprocess driver works.
|
|
21
|
-
*/
|
|
22
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
23
|
-
/**
|
|
24
|
-
* Turn whatever the user pasted into `{ code, state }`.
|
|
10
|
+
* Sign-in is performed by the UNMODIFIED Claude Code CLI (`claude auth login`),
|
|
11
|
+
* which talks to Anthropic and stores its own login where Claude Code always
|
|
12
|
+
* keeps it. This module only relays two things: the sign-in link the CLI
|
|
13
|
+
* prints (out to the person) and the code the person pastes (into the CLI's
|
|
14
|
+
* stdin). It never sees, stores, logs or returns a Claude credential.
|
|
25
15
|
*
|
|
26
|
-
* Anthropic
|
|
27
|
-
*
|
|
28
|
-
*
|
|
16
|
+
* Why: Anthropic does not permit applications to run their own Claude.ai
|
|
17
|
+
* login, or to collect, store or intermediate Claude.ai credentials; sign-in
|
|
18
|
+
* must complete through Anthropic's own flow. The previous implementation
|
|
19
|
+
* ran its own OAuth exchange and returned the access token (and dropped the
|
|
20
|
+
* refresh token, so the login also expired within hours).
|
|
29
21
|
*
|
|
30
|
-
* `
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* is why the flow never worked regardless of what was pasted.
|
|
22
|
+
* The binary is `PATCHWORK_CLAUDE_BINARY` or `claude` on PATH, run with the
|
|
23
|
+
* bridge's HOME / CLAUDE_CONFIG_DIR so the login lands where every later
|
|
24
|
+
* `claude` invocation by this bridge will find it.
|
|
34
25
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
26
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
27
|
+
/** Test seam: drop any in-flight session. */
|
|
28
|
+
export declare function _resetClaudeLoginForTesting(): void;
|
|
39
29
|
export declare function handleClaudeAuthStart(_req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
40
30
|
export declare function handleClaudeAuthComplete(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
41
31
|
export declare function handleClaudeAuthCancel(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
32
|
+
export declare function handleClaudeAuthStatus(_req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
33
|
+
export declare function handleClaudeAuthLogout(_req: IncomingMessage, res: ServerResponse): Promise<void>;
|
package/dist/claudeAuthHttp.js
CHANGED
|
@@ -1,108 +1,131 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude
|
|
3
|
-
*
|
|
4
|
-
* Implements the three endpoints the dashboard settings "Connect Claude" UX
|
|
5
|
-
* expects on the bridge:
|
|
2
|
+
* Claude sign-in relay.
|
|
6
3
|
*
|
|
7
4
|
* POST /auth/claude/start → { sessionId, url }
|
|
8
|
-
* POST /auth/claude/complete → {
|
|
9
|
-
* POST /auth/claude/cancel → { ok: true }
|
|
5
|
+
* POST /auth/claude/complete → { ok, loggedIn, authMethod } body { sessionId, code }
|
|
6
|
+
* POST /auth/claude/cancel → { ok: true } body { sessionId? }
|
|
7
|
+
* GET /auth/claude/status → { loggedIn, authMethod }
|
|
8
|
+
* POST /auth/claude/logout → { ok: true }
|
|
9
|
+
*
|
|
10
|
+
* Sign-in is performed by the UNMODIFIED Claude Code CLI (`claude auth login`),
|
|
11
|
+
* which talks to Anthropic and stores its own login where Claude Code always
|
|
12
|
+
* keeps it. This module only relays two things: the sign-in link the CLI
|
|
13
|
+
* prints (out to the person) and the code the person pastes (into the CLI's
|
|
14
|
+
* stdin). It never sees, stores, logs or returns a Claude credential.
|
|
10
15
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* CLAUDE_CODE_OAUTH_TOKEN=<token> so the subprocess driver works.
|
|
16
|
+
* Why: Anthropic does not permit applications to run their own Claude.ai
|
|
17
|
+
* login, or to collect, store or intermediate Claude.ai credentials; sign-in
|
|
18
|
+
* must complete through Anthropic's own flow. The previous implementation
|
|
19
|
+
* ran its own OAuth exchange and returned the access token (and dropped the
|
|
20
|
+
* refresh token, so the login also expired within hours).
|
|
21
|
+
*
|
|
22
|
+
* The binary is `PATCHWORK_CLAUDE_BINARY` or `claude` on PATH, run with the
|
|
23
|
+
* bridge's HOME / CLAUDE_CONFIG_DIR so the login lands where every later
|
|
24
|
+
* `claude` invocation by this bridge will find it.
|
|
21
25
|
*/
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// UUID") and renders an "OAuth Request Failed" page, so the client-metadata
|
|
29
|
-
// URL that used to live here made the flow unreachable before the user could
|
|
30
|
-
// even approve. Public by design — an OAuth public client id is an
|
|
31
|
-
// identifier, not a secret; PKCE is what protects the exchange.
|
|
32
|
-
const CLAUDE_OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
33
|
-
const CLAUDE_OAUTH_AUTH_URL = "https://claude.com/cai/oauth/authorize";
|
|
34
|
-
const CLAUDE_OAUTH_TOKEN_URL = "https://api.anthropic.com/v1/oauth/token";
|
|
35
|
-
const CLAUDE_OAUTH_REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
|
|
36
|
-
const CLAUDE_OAUTH_SCOPES = "user:inference user:profile org:create_api_key";
|
|
37
|
-
// Sessions expire after 10 minutes — long enough for a user to complete the
|
|
38
|
-
// browser flow, short enough not to accumulate stale entries.
|
|
26
|
+
import { spawn } from "node:child_process";
|
|
27
|
+
import { randomBytes } from "node:crypto";
|
|
28
|
+
import { ensureCmdShim } from "./winShim.js";
|
|
29
|
+
const URL_TIMEOUT_MS = 20_000;
|
|
30
|
+
const COMPLETE_TIMEOUT_MS = 30_000;
|
|
31
|
+
const STATUS_TIMEOUT_MS = 15_000;
|
|
39
32
|
const SESSION_TTL_MS = 10 * 60 * 1000;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
33
|
+
/** Anthropic's sign-in pages; any other URL the CLI prints is not relayed. */
|
|
34
|
+
const SIGN_IN_URL_RE = /https:\/\/(?:claude\.com|claude\.ai)\/[^\s"'<>]+/;
|
|
35
|
+
/** One sign-in at a time per bridge: a new start replaces any pending one. */
|
|
36
|
+
let current = null;
|
|
37
|
+
function claudeBinary() {
|
|
38
|
+
// npm installs `claude` as a `.cmd` shim on Windows, which spawn() cannot
|
|
39
|
+
// resolve from a bare name; same helper as the Claude subprocess driver.
|
|
40
|
+
return ensureCmdShim(process.env.PATCHWORK_CLAUDE_BINARY || "claude");
|
|
41
|
+
}
|
|
42
|
+
/** Only what Claude Code needs to find its own config and reach the network. */
|
|
43
|
+
function childEnv() {
|
|
44
|
+
const keep = [
|
|
45
|
+
"HOME",
|
|
46
|
+
"PATH",
|
|
47
|
+
"USER",
|
|
48
|
+
"LANG",
|
|
49
|
+
"TMPDIR",
|
|
50
|
+
"CLAUDE_CONFIG_DIR",
|
|
51
|
+
"HTTPS_PROXY",
|
|
52
|
+
"HTTP_PROXY",
|
|
53
|
+
"NO_PROXY",
|
|
54
|
+
"SSL_CERT_FILE",
|
|
55
|
+
"NODE_EXTRA_CA_CERTS",
|
|
56
|
+
];
|
|
57
|
+
const env = {};
|
|
58
|
+
for (const k of keep)
|
|
59
|
+
if (process.env[k] !== undefined)
|
|
60
|
+
env[k] = process.env[k];
|
|
61
|
+
// Never let a stray credential in the bridge's env decide which account
|
|
62
|
+
// Claude Code signs in as.
|
|
63
|
+
return env;
|
|
64
|
+
}
|
|
65
|
+
function endSession(s) {
|
|
66
|
+
if (!s)
|
|
67
|
+
return;
|
|
68
|
+
try {
|
|
69
|
+
s.child.stdin?.end();
|
|
53
70
|
}
|
|
71
|
+
catch {
|
|
72
|
+
/* already closed */
|
|
73
|
+
}
|
|
74
|
+
if (s.child.exitCode === null)
|
|
75
|
+
s.child.kill("SIGTERM");
|
|
54
76
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return randomBytes(32).toString("base64url");
|
|
77
|
+
/** Test seam: drop any in-flight session. */
|
|
78
|
+
export function _resetClaudeLoginForTesting() {
|
|
79
|
+
endSession(current);
|
|
80
|
+
current = null;
|
|
60
81
|
}
|
|
61
|
-
function
|
|
62
|
-
return
|
|
82
|
+
function run(args, timeoutMs) {
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
let stdout = "";
|
|
85
|
+
let settled = false;
|
|
86
|
+
const child = spawn(claudeBinary(), args, {
|
|
87
|
+
env: childEnv(),
|
|
88
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
89
|
+
});
|
|
90
|
+
const timer = setTimeout(() => {
|
|
91
|
+
child.kill("SIGTERM");
|
|
92
|
+
}, timeoutMs);
|
|
93
|
+
child.stdout?.on("data", (d) => {
|
|
94
|
+
stdout += d.toString("utf8");
|
|
95
|
+
});
|
|
96
|
+
child.on("error", (err) => {
|
|
97
|
+
if (settled)
|
|
98
|
+
return;
|
|
99
|
+
settled = true;
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
resolve({ code: null, stdout, spawnError: err.code ?? "spawn_failed" });
|
|
102
|
+
});
|
|
103
|
+
child.on("close", (code) => {
|
|
104
|
+
if (settled)
|
|
105
|
+
return;
|
|
106
|
+
settled = true;
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
resolve({ code, stdout });
|
|
109
|
+
});
|
|
110
|
+
});
|
|
63
111
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
* live endpoint 2026-08-12. Sending only `code` failed for every user, which
|
|
77
|
-
* is why the flow never worked regardless of what was pasted.
|
|
78
|
-
*/
|
|
79
|
-
export function parsePastedAuthCode(raw) {
|
|
80
|
-
const trimmed = raw.trim();
|
|
81
|
-
// Full callback URL — pull code/state out of the query string.
|
|
82
|
-
if (/^https?:\/\//i.test(trimmed)) {
|
|
83
|
-
try {
|
|
84
|
-
const url = new URL(trimmed);
|
|
85
|
-
const code = url.searchParams.get("code")?.trim();
|
|
86
|
-
const state = url.searchParams.get("state")?.trim();
|
|
87
|
-
if (code)
|
|
88
|
-
return { code, state: state || undefined };
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
// Not a parseable URL — fall through to the `#` handling below.
|
|
92
|
-
}
|
|
112
|
+
async function readStatus() {
|
|
113
|
+
const r = await run(["auth", "status"], STATUS_TIMEOUT_MS);
|
|
114
|
+
if (r.spawnError)
|
|
115
|
+
return { error: "claude_not_installed" };
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(r.stdout);
|
|
118
|
+
// Return only these two fields: the rest (e.g. email, organisation) is
|
|
119
|
+
// not this endpoint's business.
|
|
120
|
+
return {
|
|
121
|
+
loggedIn: parsed.loggedIn === true,
|
|
122
|
+
authMethod: typeof parsed.authMethod === "string" ? parsed.authMethod : "unknown",
|
|
123
|
+
};
|
|
93
124
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (hash !== -1) {
|
|
97
|
-
const code = trimmed.slice(0, hash).trim();
|
|
98
|
-
const state = trimmed.slice(hash + 1).trim();
|
|
99
|
-
return { code, state: state || undefined };
|
|
125
|
+
catch {
|
|
126
|
+
return { error: "status_unreadable" };
|
|
100
127
|
}
|
|
101
|
-
return { code: trimmed };
|
|
102
128
|
}
|
|
103
|
-
// ---------------------------------------------------------------------------
|
|
104
|
-
// Body reader (reuse the same pattern as the bridge's other POST handlers)
|
|
105
|
-
// ---------------------------------------------------------------------------
|
|
106
129
|
async function readBody(req) {
|
|
107
130
|
return new Promise((resolve, reject) => {
|
|
108
131
|
const chunks = [];
|
|
@@ -127,48 +150,83 @@ function jsonReply(res, status, body) {
|
|
|
127
150
|
});
|
|
128
151
|
res.end(payload);
|
|
129
152
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
153
|
+
async function parseJson(req) {
|
|
154
|
+
try {
|
|
155
|
+
const raw = await readBody(req);
|
|
156
|
+
if (!raw.trim())
|
|
157
|
+
return {};
|
|
158
|
+
const v = JSON.parse(raw);
|
|
159
|
+
return v && typeof v === "object" && !Array.isArray(v)
|
|
160
|
+
? v
|
|
161
|
+
: null;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// --- POST /auth/claude/start ----------------------------------------------
|
|
133
168
|
export async function handleClaudeAuthStart(_req, res) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
169
|
+
endSession(current);
|
|
170
|
+
current = null;
|
|
171
|
+
let child;
|
|
172
|
+
try {
|
|
173
|
+
child = spawn(claudeBinary(), ["auth", "login", "--claudeai"], {
|
|
174
|
+
env: childEnv(),
|
|
175
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
141
176
|
});
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
jsonReply(res, 503, { error: "claude_not_installed" });
|
|
142
180
|
return;
|
|
143
181
|
}
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
182
|
+
const session = {
|
|
183
|
+
sessionId: randomBytes(16).toString("hex"),
|
|
184
|
+
child,
|
|
185
|
+
createdAt: Date.now(),
|
|
186
|
+
output: "",
|
|
187
|
+
exited: new Promise((resolve) => {
|
|
188
|
+
child.on("close", (code) => resolve(code));
|
|
189
|
+
child.on("error", () => resolve(null));
|
|
190
|
+
}),
|
|
191
|
+
};
|
|
192
|
+
const outcome = await new Promise((resolve) => {
|
|
193
|
+
const timer = setTimeout(() => {
|
|
194
|
+
resolve({ error: "sign_in_link_timeout", status: 504 });
|
|
195
|
+
}, URL_TIMEOUT_MS);
|
|
196
|
+
const onData = (d) => {
|
|
197
|
+
session.output += d.toString("utf8");
|
|
198
|
+
const m = SIGN_IN_URL_RE.exec(session.output);
|
|
199
|
+
if (m) {
|
|
200
|
+
clearTimeout(timer);
|
|
201
|
+
resolve({ url: m[0] });
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
child.stdout?.on("data", onData);
|
|
205
|
+
child.stderr?.on("data", onData);
|
|
206
|
+
child.on("error", (err) => {
|
|
207
|
+
clearTimeout(timer);
|
|
208
|
+
resolve({
|
|
209
|
+
error: err.code === "ENOENT" ? "claude_not_installed" : "spawn_failed",
|
|
210
|
+
status: 503,
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
child.on("close", () => {
|
|
214
|
+
clearTimeout(timer);
|
|
215
|
+
resolve({ error: "sign_in_exited_early", status: 502 });
|
|
216
|
+
});
|
|
156
217
|
});
|
|
157
|
-
|
|
158
|
-
|
|
218
|
+
if ("error" in outcome) {
|
|
219
|
+
endSession(session);
|
|
220
|
+
jsonReply(res, outcome.status, { error: outcome.error });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
current = session;
|
|
224
|
+
jsonReply(res, 200, { sessionId: session.sessionId, url: outcome.url });
|
|
159
225
|
}
|
|
160
|
-
//
|
|
161
|
-
// POST /auth/claude/complete { sessionId, code }
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
226
|
+
// --- POST /auth/claude/complete -------------------------------------------
|
|
163
227
|
export async function handleClaudeAuthComplete(req, res) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
pruneExpiredSessions();
|
|
167
|
-
let body;
|
|
168
|
-
try {
|
|
169
|
-
body = JSON.parse(await readBody(req));
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
228
|
+
const body = await parseJson(req);
|
|
229
|
+
if (!body) {
|
|
172
230
|
jsonReply(res, 400, { error: "invalid_json" });
|
|
173
231
|
return;
|
|
174
232
|
}
|
|
@@ -182,97 +240,76 @@ export async function handleClaudeAuthComplete(req, res) {
|
|
|
182
240
|
});
|
|
183
241
|
return;
|
|
184
242
|
}
|
|
185
|
-
const
|
|
186
|
-
if (!
|
|
243
|
+
const s = current;
|
|
244
|
+
if (!s ||
|
|
245
|
+
s.sessionId !== sessionId ||
|
|
246
|
+
Date.now() - s.createdAt > SESSION_TTL_MS ||
|
|
247
|
+
s.child.exitCode !== null) {
|
|
187
248
|
jsonReply(res, 404, {
|
|
188
249
|
error: "session_not_found",
|
|
189
|
-
detail: "
|
|
250
|
+
detail: "Sign-in expired or was replaced. Start again.",
|
|
190
251
|
});
|
|
191
252
|
return;
|
|
192
253
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
254
|
+
current = null;
|
|
255
|
+
// The code goes to Claude Code's stdin and nowhere else: not logged, not
|
|
256
|
+
// stored, not echoed back.
|
|
257
|
+
const outputBefore = s.output.length;
|
|
258
|
+
s.child.stdin?.write(`${code.trim()}\n`);
|
|
259
|
+
const exit = await Promise.race([
|
|
260
|
+
s.exited,
|
|
261
|
+
new Promise((r) => setTimeout(() => r("timeout"), COMPLETE_TIMEOUT_MS)),
|
|
262
|
+
]);
|
|
263
|
+
endSession(s);
|
|
264
|
+
if (exit === "timeout") {
|
|
265
|
+
jsonReply(res, 504, { error: "sign_in_timeout" });
|
|
199
266
|
return;
|
|
200
267
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// indefinitely. Map an abort to a 504 (distinct from a 502 network error).
|
|
205
|
-
// The pasted value may be `<code>#<state>`, a bare code, or the whole
|
|
206
|
-
// callback URL. Fall back to the session id for `state` — that is what we
|
|
207
|
-
// put in the authorize URL, so it round-trips correctly when the user pasted
|
|
208
|
-
// only the code half.
|
|
209
|
-
const { code: authCode, state: pastedState } = parsePastedAuthCode(code);
|
|
210
|
-
if (!authCode) {
|
|
211
|
-
jsonReply(res, 400, {
|
|
212
|
-
error: "missing_fields",
|
|
213
|
-
detail: "sessionId and code required",
|
|
214
|
-
});
|
|
268
|
+
const status = await readStatus();
|
|
269
|
+
if ("error" in status) {
|
|
270
|
+
jsonReply(res, 502, { error: status.error });
|
|
215
271
|
return;
|
|
216
272
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
method: "POST",
|
|
224
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
225
|
-
body: new URLSearchParams({
|
|
226
|
-
grant_type: "authorization_code",
|
|
227
|
-
client_id: CLAUDE_OAUTH_CLIENT_ID,
|
|
228
|
-
code: authCode,
|
|
229
|
-
state: authState,
|
|
230
|
-
redirect_uri: CLAUDE_OAUTH_REDIRECT_URI,
|
|
231
|
-
code_verifier: session.codeVerifier,
|
|
232
|
-
}).toString(),
|
|
233
|
-
signal: controller.signal,
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
catch (err) {
|
|
237
|
-
const aborted = err instanceof Error &&
|
|
238
|
-
(err.name === "AbortError" || controller.signal.aborted);
|
|
239
|
-
jsonReply(res, aborted ? 504 : 502, {
|
|
240
|
-
error: aborted ? "token_exchange_timeout" : "token_exchange_failed",
|
|
241
|
-
detail: aborted
|
|
242
|
-
? "Timed out contacting Anthropic. Please try again."
|
|
243
|
-
: `Network error contacting Anthropic: ${err instanceof Error ? err.message : String(err)}`,
|
|
244
|
-
});
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
finally {
|
|
248
|
-
clearTimeout(timeout);
|
|
249
|
-
}
|
|
250
|
-
const tokenData = (await tokenRes.json().catch(() => ({})));
|
|
251
|
-
if (!tokenRes.ok || !tokenData.access_token) {
|
|
273
|
+
// The CLI prints "Invalid code" yet still exits 0, and an earlier login
|
|
274
|
+
// would make `status` look signed in, so neither the exit code nor status
|
|
275
|
+
// alone proves THIS code worked: also read what it printed after the code.
|
|
276
|
+
const reply = s.output.slice(outputBefore);
|
|
277
|
+
const rejected = /invalid|error|fail/i.test(reply);
|
|
278
|
+
if (exit !== 0 || rejected || !status.loggedIn) {
|
|
252
279
|
jsonReply(res, 400, {
|
|
253
|
-
error: "
|
|
254
|
-
detail:
|
|
255
|
-
tokenData.error ??
|
|
256
|
-
`Anthropic returned ${tokenRes.status}. Make sure you pasted the correct authorization code.`,
|
|
280
|
+
error: "invalid_code",
|
|
281
|
+
detail: "Claude Code did not accept that code. Copy the whole code and try again.",
|
|
257
282
|
});
|
|
258
283
|
return;
|
|
259
284
|
}
|
|
260
|
-
|
|
261
|
-
jsonReply(res, 200, { token: tokenData.access_token });
|
|
285
|
+
jsonReply(res, 200, { ok: true, ...status });
|
|
262
286
|
}
|
|
263
|
-
//
|
|
264
|
-
// POST /auth/claude/cancel { sessionId? }
|
|
265
|
-
// ---------------------------------------------------------------------------
|
|
287
|
+
// --- POST /auth/claude/cancel ---------------------------------------------
|
|
266
288
|
export async function handleClaudeAuthCancel(req, res) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
catch {
|
|
273
|
-
// Body parse failure is fine — we just won't delete a specific session.
|
|
289
|
+
const body = (await parseJson(req)) ?? {};
|
|
290
|
+
if (current &&
|
|
291
|
+
(body.sessionId === undefined || body.sessionId === current.sessionId)) {
|
|
292
|
+
endSession(current);
|
|
293
|
+
current = null;
|
|
274
294
|
}
|
|
275
|
-
pruneExpiredSessions();
|
|
276
295
|
jsonReply(res, 200, { ok: true });
|
|
277
296
|
}
|
|
297
|
+
// --- GET /auth/claude/status ----------------------------------------------
|
|
298
|
+
export async function handleClaudeAuthStatus(_req, res) {
|
|
299
|
+
const status = await readStatus();
|
|
300
|
+
if ("error" in status) {
|
|
301
|
+
jsonReply(res, status.error === "claude_not_installed" ? 503 : 502, status);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
jsonReply(res, 200, status);
|
|
305
|
+
}
|
|
306
|
+
// --- POST /auth/claude/logout ---------------------------------------------
|
|
307
|
+
export async function handleClaudeAuthLogout(_req, res) {
|
|
308
|
+
const r = await run(["auth", "logout"], STATUS_TIMEOUT_MS);
|
|
309
|
+
if (r.spawnError) {
|
|
310
|
+
jsonReply(res, 503, { error: "claude_not_installed" });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
jsonReply(res, r.code === 0 ? 200 : 502, { ok: r.code === 0 });
|
|
314
|
+
}
|
|
278
315
|
//# sourceMappingURL=claudeAuthHttp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claudeAuthHttp.js","sourceRoot":"","sources":["../src/claudeAuthHttp.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"claudeAuthHttp.js","sourceRoot":"","sources":["../src/claudeAuthHttp.ts"],"names":[],"mappings":"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"}
|