@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.
- package/cli/commands/serve.js +23 -54
- package/package.json +1 -1
package/cli/commands/serve.js
CHANGED
|
@@ -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
|
-
//
|
|
3090
|
-
//
|
|
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
|
-
|
|
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 (
|
|
3118
|
-
|
|
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, {
|