@lifeaitools/clauth 1.5.27 → 1.5.29
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/cli/commands/serve.js +111 -7
- package/package.json +1 -1
package/cli/commands/serve.js
CHANGED
|
@@ -2934,23 +2934,127 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
2934
2934
|
return res.end();
|
|
2935
2935
|
}
|
|
2936
2936
|
|
|
2937
|
-
// ── OAuth Discovery — 404
|
|
2938
|
-
// claude.ai
|
|
2939
|
-
//
|
|
2940
|
-
//
|
|
2937
|
+
// ── OAuth Discovery — 404 well-known only ──────────────
|
|
2938
|
+
// claude.ai ignores metadata endpoints and constructs /register, /authorize,
|
|
2939
|
+
// /token from the domain root (issue #82). Keep well-known as 404 so claude.ai
|
|
2940
|
+
// uses the fallback paths. OAuth endpoints below are live.
|
|
2941
2941
|
if (reqPath.startsWith("/.well-known/oauth-protected-resource") ||
|
|
2942
2942
|
reqPath === "/.well-known/oauth-authorization-server") {
|
|
2943
2943
|
res.writeHead(404, { "Content-Type": "application/json", ...CORS });
|
|
2944
2944
|
return res.end(JSON.stringify({ error: "not_found" }));
|
|
2945
2945
|
}
|
|
2946
2946
|
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2947
|
+
// ── Dynamic Client Registration — DISABLED ──────────────
|
|
2948
|
+
// claude.ai's OAuth authorization_code flow is bugged (token issued, never used).
|
|
2949
|
+
// If /register returns 201, claude.ai starts OAuth and fails.
|
|
2950
|
+
// If /register returns 404, claude.ai falls back to authless (which works).
|
|
2951
|
+
if (method === "POST" && reqPath === "/register") {
|
|
2950
2952
|
res.writeHead(404, { "Content-Type": "application/json", ...CORS });
|
|
2951
2953
|
return res.end(JSON.stringify({ error: "not_found" }));
|
|
2952
2954
|
}
|
|
2953
2955
|
|
|
2956
|
+
// ── Dynamic Client Registration (kept for future use) ──────────────
|
|
2957
|
+
if (false && method === "POST" && reqPath === "/register") {
|
|
2958
|
+
let body;
|
|
2959
|
+
try { body = await readBody(req); } catch {
|
|
2960
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
2961
|
+
return res.end(JSON.stringify({ error: "invalid_request" }));
|
|
2962
|
+
}
|
|
2963
|
+
const clientId = crypto.randomBytes(16).toString("hex");
|
|
2964
|
+
const clientSecret = crypto.randomBytes(32).toString("hex");
|
|
2965
|
+
const client = {
|
|
2966
|
+
client_id: clientId, client_secret: clientSecret,
|
|
2967
|
+
client_name: body.client_name || "unknown",
|
|
2968
|
+
redirect_uris: body.redirect_uris || [],
|
|
2969
|
+
grant_types: body.grant_types || ["authorization_code"],
|
|
2970
|
+
response_types: body.response_types || ["code"],
|
|
2971
|
+
token_endpoint_auth_method: body.token_endpoint_auth_method || "client_secret_post",
|
|
2972
|
+
};
|
|
2973
|
+
oauthClients.set(clientId, client);
|
|
2974
|
+
const logMsg = `[${new Date().toISOString()}] OAuth: registered client ${clientId} (${client.client_name})\n`;
|
|
2975
|
+
try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
|
|
2976
|
+
res.writeHead(201, { "Content-Type": "application/json", ...CORS });
|
|
2977
|
+
return res.end(JSON.stringify(client));
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
// ── Authorization endpoint — auto-approve ──────────────
|
|
2981
|
+
if (method === "GET" && reqPath === "/authorize") {
|
|
2982
|
+
const clientId = url.searchParams.get("client_id");
|
|
2983
|
+
const redirectUri = url.searchParams.get("redirect_uri");
|
|
2984
|
+
const state = url.searchParams.get("state");
|
|
2985
|
+
const codeChallenge = url.searchParams.get("code_challenge");
|
|
2986
|
+
|
|
2987
|
+
if (!clientId || !redirectUri) {
|
|
2988
|
+
res.writeHead(400, { "Content-Type": "text/plain", ...CORS });
|
|
2989
|
+
return res.end("Missing client_id or redirect_uri");
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
const code = crypto.randomBytes(32).toString("hex");
|
|
2993
|
+
oauthCodes.set(code, {
|
|
2994
|
+
client_id: clientId, redirect_uri: redirectUri,
|
|
2995
|
+
code_challenge: codeChallenge,
|
|
2996
|
+
expires: Date.now() + 300_000,
|
|
2997
|
+
});
|
|
2998
|
+
|
|
2999
|
+
const redirect = new URL(redirectUri);
|
|
3000
|
+
redirect.searchParams.set("code", code);
|
|
3001
|
+
if (state) redirect.searchParams.set("state", state);
|
|
3002
|
+
|
|
3003
|
+
const logMsg = `[${new Date().toISOString()}] OAuth: authorize → code for ${clientId}, redirect to ${redirect.origin}\n`;
|
|
3004
|
+
try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
|
|
3005
|
+
res.writeHead(302, { Location: redirect.toString(), ...CORS });
|
|
3006
|
+
return res.end();
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
// ── Token endpoint ──────────────────────────────────────
|
|
3010
|
+
if (method === "POST" && reqPath === "/token") {
|
|
3011
|
+
let body;
|
|
3012
|
+
const ct = req.headers["content-type"] || "";
|
|
3013
|
+
try {
|
|
3014
|
+
if (ct.includes("application/json")) {
|
|
3015
|
+
body = await readBody(req);
|
|
3016
|
+
} else {
|
|
3017
|
+
const raw = await readRawBody(req);
|
|
3018
|
+
body = Object.fromEntries(new URLSearchParams(raw));
|
|
3019
|
+
}
|
|
3020
|
+
} catch {
|
|
3021
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
3022
|
+
return res.end(JSON.stringify({ error: "invalid_request" }));
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
if (body.grant_type !== "authorization_code") {
|
|
3026
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
3027
|
+
return res.end(JSON.stringify({ error: "unsupported_grant_type" }));
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
const stored = oauthCodes.get(body.code);
|
|
3031
|
+
if (!stored || stored.expires < Date.now()) {
|
|
3032
|
+
oauthCodes.delete(body.code);
|
|
3033
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
3034
|
+
return res.end(JSON.stringify({ error: "invalid_grant" }));
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
// PKCE verification
|
|
3038
|
+
if (stored.code_challenge && body.code_verifier) {
|
|
3039
|
+
const computed = sha256base64url(body.code_verifier);
|
|
3040
|
+
if (computed !== stored.code_challenge) {
|
|
3041
|
+
oauthCodes.delete(body.code);
|
|
3042
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
3043
|
+
return res.end(JSON.stringify({ error: "invalid_grant", error_description: "PKCE failed" }));
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
oauthCodes.delete(body.code);
|
|
3048
|
+
const accessToken = crypto.randomBytes(32).toString("hex");
|
|
3049
|
+
oauthTokens.add(accessToken);
|
|
3050
|
+
saveTokens(oauthTokens);
|
|
3051
|
+
|
|
3052
|
+
const logMsg = `[${new Date().toISOString()}] OAuth: token issued for ${stored.client_id} (token=${accessToken.slice(0,8)}…)\n`;
|
|
3053
|
+
try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
|
|
3054
|
+
res.writeHead(200, { "Content-Type": "application/json", ...CORS });
|
|
3055
|
+
return res.end(JSON.stringify({ access_token: accessToken, token_type: "Bearer", scope: "mcp:tools", expires_in: 86400 }));
|
|
3056
|
+
}
|
|
3057
|
+
|
|
2954
3058
|
// ── MCP path helpers ──
|
|
2955
3059
|
const MCP_PATHS = ["/mcp", "/gws", "/clauth"];
|
|
2956
3060
|
const isMcpPath = MCP_PATHS.includes(reqPath);
|