@lifeaitools/clauth 1.5.30 → 1.5.32

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.
Files changed (2) hide show
  1. package/cli/commands/serve.js +16 -113
  2. package/package.json +1 -1
@@ -2938,121 +2938,17 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
2938
2938
  // claude.ai ignores metadata endpoints and constructs /register, /authorize,
2939
2939
  // /token from the domain root (issue #82). Keep well-known as 404 so claude.ai
2940
2940
  // uses the fallback paths. OAuth endpoints below are live.
2941
+ // well-known OAuth discovery — HTML 404, no CORS, no JSON (match Express/regen-media)
2941
2942
  if (reqPath.startsWith("/.well-known/oauth-protected-resource") ||
2942
2943
  reqPath === "/.well-known/oauth-authorization-server") {
2943
- res.writeHead(404, { "Content-Type": "application/json", ...CORS });
2944
- return res.end(JSON.stringify({ error: "not_found" }));
2945
- }
2946
-
2947
- // ── Dynamic Client Registration (RFC 7591) ──────────────
2948
- if (method === "POST" && reqPath === "/register") {
2949
- let body;
2950
- try { body = await readBody(req); } catch {
2951
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
2952
- return res.end(JSON.stringify({ error: "invalid_request" }));
2953
- }
2954
- const clientId = crypto.randomBytes(16).toString("hex");
2955
- const clientSecret = crypto.randomBytes(32).toString("hex");
2956
- const client = {
2957
- client_id: clientId, client_secret: clientSecret,
2958
- client_name: body.client_name || "unknown",
2959
- redirect_uris: body.redirect_uris || [],
2960
- grant_types: body.grant_types || ["authorization_code"],
2961
- response_types: body.response_types || ["code"],
2962
- token_endpoint_auth_method: body.token_endpoint_auth_method || "client_secret_post",
2963
- };
2964
- oauthClients.set(clientId, client);
2965
- const logMsg = `[${new Date().toISOString()}] OAuth: registered client ${clientId} (${client.client_name})\n`;
2966
- try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
2967
- res.writeHead(201, { "Content-Type": "application/json", ...CORS });
2968
- return res.end(JSON.stringify(client));
2969
- }
2970
-
2971
- // ── Authorization endpoint — auto-approve ──────────────
2972
- if (method === "GET" && reqPath === "/authorize") {
2973
- const clientId = url.searchParams.get("client_id");
2974
- const redirectUri = url.searchParams.get("redirect_uri");
2975
- const state = url.searchParams.get("state");
2976
- const codeChallenge = url.searchParams.get("code_challenge");
2977
-
2978
- if (!clientId || !redirectUri) {
2979
- res.writeHead(400, { "Content-Type": "text/plain", ...CORS });
2980
- return res.end("Missing client_id or redirect_uri");
2981
- }
2982
-
2983
- const code = crypto.randomBytes(32).toString("hex");
2984
- oauthCodes.set(code, {
2985
- client_id: clientId, redirect_uri: redirectUri,
2986
- code_challenge: codeChallenge,
2987
- expires: Date.now() + 300_000,
2988
- });
2989
-
2990
- const redirect = new URL(redirectUri);
2991
- redirect.searchParams.set("code", code);
2992
- if (state) redirect.searchParams.set("state", state);
2993
-
2994
- const logMsg = `[${new Date().toISOString()}] OAuth: authorize → code for ${clientId}, redirect to ${redirect.origin}\n`;
2995
- try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
2996
- res.writeHead(302, { Location: redirect.toString(), ...CORS });
2997
- return res.end();
2944
+ res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
2945
+ return res.end("<!DOCTYPE html><html><head><title>404</title></head><body><h1>Not Found</h1></body></html>");
2998
2946
  }
2999
2947
 
3000
- // ── Token endpoint ──────────────────────────────────────
3001
- if (method === "POST" && reqPath === "/token") {
3002
- let body;
3003
- const ct = req.headers["content-type"] || "";
3004
- try {
3005
- if (ct.includes("application/json")) {
3006
- body = await readBody(req);
3007
- } else {
3008
- const raw = await readRawBody(req);
3009
- body = Object.fromEntries(new URLSearchParams(raw));
3010
- }
3011
- } catch {
3012
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3013
- return res.end(JSON.stringify({ error: "invalid_request" }));
3014
- }
3015
-
3016
- const tokenLog = (msg) => { try { fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] OAuth /token: ${msg}\n`); } catch {} };
3017
- tokenLog(`grant_type=${body.grant_type} code=${(body.code||"").slice(0,8)}… verifier=${body.code_verifier ? "present" : "missing"}`);
3018
-
3019
- if (body.grant_type !== "authorization_code") {
3020
- tokenLog(`REJECT: unsupported_grant_type (${body.grant_type})`);
3021
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3022
- return res.end(JSON.stringify({ error: "unsupported_grant_type" }));
3023
- }
3024
-
3025
- const stored = oauthCodes.get(body.code);
3026
- if (!stored || stored.expires < Date.now()) {
3027
- tokenLog(`REJECT: invalid_grant (stored=${!!stored}, expired=${stored ? stored.expires < Date.now() : "n/a"}, codes_size=${oauthCodes.size})`);
3028
- oauthCodes.delete(body.code);
3029
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3030
- return res.end(JSON.stringify({ error: "invalid_grant" }));
3031
- }
3032
-
3033
- // PKCE verification
3034
- if (stored.code_challenge && body.code_verifier) {
3035
- const computed = sha256base64url(body.code_verifier);
3036
- tokenLog(`PKCE: challenge=${stored.code_challenge.slice(0,12)}… computed=${computed.slice(0,12)}… match=${computed === stored.code_challenge}`);
3037
- if (computed !== stored.code_challenge) {
3038
- oauthCodes.delete(body.code);
3039
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3040
- return res.end(JSON.stringify({ error: "invalid_grant", error_description: "PKCE failed" }));
3041
- }
3042
- } else {
3043
- tokenLog(`PKCE: skipped (challenge=${!!stored.code_challenge}, verifier=${!!body.code_verifier})`);
3044
- }
3045
-
3046
- oauthCodes.delete(body.code);
3047
- const accessToken = crypto.randomBytes(32).toString("hex");
3048
- oauthTokens.add(accessToken);
3049
- saveTokens(oauthTokens);
3050
-
3051
- const logMsg = `[${new Date().toISOString()}] OAuth: token issued for ${stored.client_id} (token=${accessToken.slice(0,8)}…)\n`;
3052
- try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
3053
- res.writeHead(200, { "Content-Type": "application/json", ...CORS });
3054
- return res.end(JSON.stringify({ access_token: accessToken, token_type: "Bearer", scope: "mcp:tools", expires_in: 86400 }));
3055
- }
2948
+ // ── OAuth endpoints REMOVED ──────────────
2949
+ // claude.ai's OAuth is bugged (token issued, never used anthropics/claude-code#46140).
2950
+ // regen-media works because it has NO OAuth handlers at all.
2951
+ // These paths fall through to the catch-all 404 at the bottom.
3056
2952
 
3057
2953
  // ── MCP path helpers ──
3058
2954
  const MCP_PATHS = ["/mcp", "/gws", "/clauth"];
@@ -4406,11 +4302,18 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
4406
4302
  }
4407
4303
  }
4408
4304
 
4305
+ // OAuth paths — return plain HTML 404 (not JSON) to match Express/regen-media pattern.
4306
+ // claude.ai treats JSON 404 as "OAuth endpoint exists but errored" vs HTML 404 = "path doesn't exist"
4307
+ if (["/register", "/authorize", "/token"].includes(reqPath) ||
4308
+ reqPath.startsWith("/.well-known/oauth")) {
4309
+ res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
4310
+ return res.end("<!DOCTYPE html><html><head><title>404</title></head><body><h1>Not Found</h1></body></html>");
4311
+ }
4312
+
4409
4313
  // Unknown route — don't count browser/MCP noise as auth failures
4410
- // Don't count browser noise, MCP discovery probes, or OAuth probes as auth failures
4411
4314
  const isBenign = reqPath.startsWith("/.well-known/") || [
4412
4315
  "/favicon.ico", "/robots.txt", "/apple-touch-icon.png", "/apple-touch-icon-precomposed.png",
4413
- "/sse", "/mcp", "/gws", "/clauth", "/message", "/register", "/authorize", "/token", "/shutdown", "/restart",
4316
+ "/sse", "/mcp", "/gws", "/clauth", "/message", "/shutdown", "/restart",
4414
4317
  ].includes(reqPath);
4415
4318
  if (isBenign) {
4416
4319
  res.writeHead(404, { "Content-Type": "application/json", ...CORS });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.5.30",
3
+ "version": "1.5.32",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {