@lifeaitools/clauth 1.5.24 → 1.5.26

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.
@@ -2962,7 +2962,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
2962
2962
  token_endpoint: `${base}/token`,
2963
2963
  registration_endpoint: `${base}/register`,
2964
2964
  response_types_supported: ["code"],
2965
- grant_types_supported: ["authorization_code"],
2965
+ grant_types_supported: ["authorization_code", "client_credentials"],
2966
2966
  code_challenge_methods_supported: ["S256"],
2967
2967
  scopes_supported: ["mcp:tools"],
2968
2968
  }));
@@ -3038,6 +3038,23 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3038
3038
  return res.end(JSON.stringify({ error: "invalid_request" }));
3039
3039
  }
3040
3040
 
3041
+ // client_credentials grant — skip authorize/redirect, just issue token from client_id+secret
3042
+ if (body.grant_type === "client_credentials") {
3043
+ const cid = body.client_id;
3044
+ const csec = body.client_secret;
3045
+ if (cid === OAUTH_CLIENT_ID && csec === OAUTH_CLIENT_SECRET) {
3046
+ const accessToken = crypto.randomBytes(32).toString("hex");
3047
+ oauthTokens.add(accessToken);
3048
+ saveTokens(oauthTokens);
3049
+ const logMsg = `[${new Date().toISOString()}] OAuth: client_credentials token issued for ${cid.slice(0,8)}… (token=${accessToken.slice(0,8)}…)\n`;
3050
+ try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
3051
+ res.writeHead(200, { "Content-Type": "application/json", ...CORS });
3052
+ return res.end(JSON.stringify({ access_token: accessToken, token_type: "Bearer", expires_in: 86400, scope: "mcp:tools" }));
3053
+ }
3054
+ res.writeHead(401, { "Content-Type": "application/json", ...CORS });
3055
+ return res.end(JSON.stringify({ error: "invalid_client" }));
3056
+ }
3057
+
3041
3058
  if (body.grant_type !== "authorization_code") {
3042
3059
  res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3043
3060
  return res.end(JSON.stringify({ error: "unsupported_grant_type" }));
@@ -3086,43 +3103,15 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3086
3103
  }
3087
3104
 
3088
3105
  // ── MCP endpoint auth ──
3089
- // Tunnel requests (Host = tunnel hostname) MUST have Bearer token 401 triggers OAuth.
3090
- // Local requests (Host = 127.0.0.1) pass through without auth.
3106
+ // No 401 gate tunnel URL is the shared secret. Accept all connections.
3107
+ // If Bearer token present, mark as remote for vault scoping.
3091
3108
  if (method === "POST" && (reqPath === "/sse" || isMcpPath)) {
3092
3109
  const authHeader = req.headers.authorization;
3093
- const host = (req.headers.host || "").split(":")[0];
3094
- const isTunnelReq = tunnelUrl && host !== "127.0.0.1" && host !== "localhost" && host !== "::1";
3095
- const authLogMsg = [
3096
- `[${new Date().toISOString()}] MCP POST ${reqPath}`,
3097
- ` Host: ${req.headers.host || "(none)"} (tunnel=${isTunnelReq})`,
3098
- ` Authorization: ${authHeader ? (authHeader.startsWith("Bearer ") ? `Bearer ${authHeader.slice(7, 15)}… (known=${oauthTokens.has(authHeader.slice(7))})` : authHeader.slice(0, 30) + "…") : "(none)"}`,
3099
- ` mcp-protocol-version: ${req.headers["mcp-protocol-version"] || "(not set)"}`,
3100
- ` accept: ${req.headers["accept"] || "(not set)"}`,
3101
- ].join("\n") + "\n";
3102
- try { fs.appendFileSync(LOG_FILE, authLogMsg); } catch {}
3103
-
3104
- if (isTunnelReq) {
3105
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
3106
- const base = oauthBase();
3107
- const logMsg = `[${new Date().toISOString()}] OAuth: 401 → requiring auth for tunnel POST ${reqPath}\n`;
3108
- try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
3109
- res.writeHead(401, {
3110
- "Content-Type": "application/json",
3111
- "WWW-Authenticate": `Bearer resource_metadata="${base}/.well-known/oauth-protected-resource"`,
3112
- ...CORS,
3113
- });
3114
- return res.end(JSON.stringify({ error: "unauthorized" }));
3115
- }
3110
+ if (authHeader?.startsWith("Bearer ")) {
3116
3111
  const token = authHeader.slice(7);
3117
- if (!oauthTokens.has(token)) {
3118
- const logMsg = `[${new Date().toISOString()}] OAuth: REJECTED token ${token.slice(0,8)}… (pool=${oauthTokens.size})\n`;
3119
- try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
3120
- res.writeHead(401, { "Content-Type": "application/json", ...CORS });
3121
- return res.end(JSON.stringify({ error: "invalid_token" }));
3112
+ if (oauthTokens.has(token) || token === OAUTH_CLIENT_SECRET) {
3113
+ req._clauthRemote = true;
3122
3114
  }
3123
- req._clauthRemote = true;
3124
- } else if (authHeader?.startsWith("Bearer ") && oauthTokens.has(authHeader.slice(7))) {
3125
- req._clauthRemote = true;
3126
3115
  }
3127
3116
  // fall through to MCP handling
3128
3117
  }
@@ -3185,27 +3174,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3185
3174
 
3186
3175
  // ── MCP SSE transport — /sse and namespaced paths ────
3187
3176
  // GET /sse|/gws|/clauth — open SSE stream, receive endpoint event
3188
- // Tunnel requests require Bearer token (same as POST above)
3189
3177
  if (method === "GET" && (reqPath === "/sse" || isMcpPath)) {
3190
- const authHeader = req.headers.authorization;
3191
- const host = (req.headers.host || "").split(":")[0];
3192
- const isTunnelReq = tunnelUrl && host !== "127.0.0.1" && host !== "localhost" && host !== "::1";
3193
- if (isTunnelReq) {
3194
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
3195
- const base = oauthBase();
3196
- res.writeHead(401, {
3197
- "Content-Type": "application/json",
3198
- "WWW-Authenticate": `Bearer resource_metadata="${base}/.well-known/oauth-protected-resource"`,
3199
- ...CORS,
3200
- });
3201
- return res.end(JSON.stringify({ error: "unauthorized" }));
3202
- }
3203
- if (!oauthTokens.has(authHeader.slice(7))) {
3204
- res.writeHead(401, { "Content-Type": "application/json", ...CORS });
3205
- return res.end(JSON.stringify({ error: "invalid_token" }));
3206
- }
3207
- // Token valid — mark as remote
3208
- }
3209
3178
  const sessionId = `ses_${++sseCounter}_${Date.now()}`;
3210
3179
 
3211
3180
  res.writeHead(200, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.5.24",
3
+ "version": "1.5.26",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {