@mkterswingman/yt-mcp 0.1.1 → 0.1.2

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.
@@ -9,9 +9,10 @@ function base64url(buf) {
9
9
  .replace(/=+$/, "");
10
10
  }
11
11
  export async function runOAuthFlow(authUrl) {
12
- // 1. Generate PKCE
12
+ // 1. Generate PKCE + state
13
13
  const codeVerifier = base64url(randomBytes(32));
14
14
  const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());
15
+ const state = base64url(randomBytes(32));
15
16
  // 2. Start temp HTTP server to get the actual port for redirect_uri
16
17
  const { server: httpServer, port } = await startCallbackServer();
17
18
  const redirectUri = `http://127.0.0.1:${port}`;
@@ -55,6 +56,7 @@ export async function runOAuthFlow(authUrl) {
55
56
  const url = new URL(req.url ?? "/", `http://127.0.0.1`);
56
57
  const code = url.searchParams.get("code");
57
58
  const error = url.searchParams.get("error");
59
+ const returnedState = url.searchParams.get("state");
58
60
  if (error) {
59
61
  const safeError = error.replace(/[<>&"']/g, (c) => `&#${c.charCodeAt(0)};`);
60
62
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
@@ -68,6 +70,14 @@ export async function runOAuthFlow(authUrl) {
68
70
  res.end("<h1>Waiting for authorization...</h1>");
69
71
  return;
70
72
  }
73
+ // Verify state to prevent CSRF
74
+ if (returnedState !== state) {
75
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
76
+ res.end("<h1>Authorization failed</h1><p>State mismatch — possible CSRF attack.</p>");
77
+ cleanup();
78
+ reject(new Error("OAuth state mismatch"));
79
+ return;
80
+ }
71
81
  // Exchange code for tokens
72
82
  const tokenBody = {
73
83
  grant_type: "authorization_code",
@@ -111,7 +121,7 @@ export async function runOAuthFlow(authUrl) {
111
121
  }
112
122
  });
113
123
  // Open browser
114
- const authorizeUrl = `${authUrl}/oauth/authorize?response_type=code&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&code_challenge=${encodeURIComponent(codeChallenge)}&code_challenge_method=S256`;
124
+ const authorizeUrl = `${authUrl}/oauth/authorize?response_type=code&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&code_challenge=${encodeURIComponent(codeChallenge)}&code_challenge_method=S256&state=${encodeURIComponent(state)}`;
115
125
  console.log("\n\x1b[1mOpen this URL in your browser to authorize:\x1b[0m");
116
126
  console.log(`\n ${authorizeUrl}\n`);
117
127
  import("node:child_process").then(({ exec }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mkterswingman/yt-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "YouTube MCP client — local subtitles + remote API proxy",
5
5
  "type": "module",
6
6
  "bin": {