Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@cosmonexus:registry=http://localhost:4873
16 changes: 14 additions & 2 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@
"rules": {
"recommended": true
},
"includes": ["**", "!**/dist", "!**/*.bs.js", "!**/lib", "!**/__generated__"]
"includes": [
"**",
"!**/dist",
"!**/*.bs.js",
"!**/lib",
"!**/__generated__"
]
},
"formatter": {
"enabled": true,
"includes": ["**", "!**/dist", "!**/*.bs.js", "!**/lib", "!**/__generated__"]
"includes": [
"**",
"!**/dist",
"!**/*.bs.js",
"!**/lib",
"!**/__generated__"
]
}
}
Binary file modified bun.lockb
Binary file not shown.
88 changes: 86 additions & 2 deletions mock-server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const users = [
},
];

let posts = [
const posts = [
{
id: "post-1",
title: "Getting Started with ReScript",
Expand Down Expand Up @@ -87,8 +87,92 @@ const yoga = createYoga({
},
});

const server = createServer(yoga);
// Store pending auth codes for the mock OAuth flow
const pendingCodes = new Map();

const server = createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);

// Mock OAuth authorize endpoint
if (url.pathname === "/oauth/authorize") {
const redirectUri = url.searchParams.get("redirect_uri");
const state = url.searchParams.get("state");
const code = `mock-code-${Date.now()}`;

// Store the code verifier expectation
pendingCodes.set(code, {
codeChallenge: url.searchParams.get("code_challenge"),
clientId: url.searchParams.get("client_id"),
});

// Redirect back with code and state (simulates user approving)
const callbackUrl = `${redirectUri}?code=${code}&state=${state}`;
res.writeHead(302, { Location: callbackUrl });
res.end();
return;
Comment on lines +97 to +112

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing null check for redirect_uri could cause malformed redirect.

If redirect_uri is missing from the request, the callback URL will contain null, resulting in an invalid redirect. Consider adding validation.

🛡️ Proposed fix
 	// Mock OAuth authorize endpoint
 	if (url.pathname === "/oauth/authorize") {
 		const redirectUri = url.searchParams.get("redirect_uri");
 		const state = url.searchParams.get("state");
+
+		if (!redirectUri) {
+			res.writeHead(400, { "Content-Type": "application/json" });
+			res.end(JSON.stringify({ error: "invalid_request", error_description: "redirect_uri is required" }));
+			return;
+		}
+
 		const code = `mock-code-${Date.now()}`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (url.pathname === "/oauth/authorize") {
const redirectUri = url.searchParams.get("redirect_uri");
const state = url.searchParams.get("state");
const code = `mock-code-${Date.now()}`;
// Store the code verifier expectation
pendingCodes.set(code, {
codeChallenge: url.searchParams.get("code_challenge"),
clientId: url.searchParams.get("client_id"),
});
// Redirect back with code and state (simulates user approving)
const callbackUrl = `${redirectUri}?code=${code}&state=${state}`;
res.writeHead(302, { Location: callbackUrl });
res.end();
return;
if (url.pathname === "/oauth/authorize") {
const redirectUri = url.searchParams.get("redirect_uri");
const state = url.searchParams.get("state");
if (!redirectUri) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "invalid_request", error_description: "redirect_uri is required" }));
return;
}
const code = `mock-code-${Date.now()}`;
// Store the code verifier expectation
pendingCodes.set(code, {
codeChallenge: url.searchParams.get("code_challenge"),
clientId: url.searchParams.get("client_id"),
});
// Redirect back with code and state (simulates user approving)
const callbackUrl = `${redirectUri}?code=${code}&state=${state}`;
res.writeHead(302, { Location: callbackUrl });
res.end();
return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mock-server/index.js` around lines 97 - 112, The redirect handler builds a
callback URL using redirect_uri without validating it, which can produce an
invalid redirect; update the /oauth/authorize branch to validate that
redirect_uri is present and a non-empty string (and optionally URL-decodable)
before using it (the code that reads redirectUri from url.searchParams and
constructs callbackUrl); if validation fails, respond with a 400 error
(res.writeHead(400) / res.end with a clear message) instead of performing the
302 redirect, and when valid use encodeURIComponent or URL constructor to safely
build the callback URL and continue storing the pendingCodes entry.

}

// Mock OAuth token endpoint
if (url.pathname === "/oauth/token" && req.method === "POST") {
let body = "";
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
const params = new URLSearchParams(body);
const code = params.get("code");

if (code && pendingCodes.has(code)) {
pendingCodes.delete(code);
res.writeHead(200, {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "http://localhost:8080",
});
res.end(
JSON.stringify({
access_token: `mock-token-${Date.now()}`,
token_type: "Bearer",
expires_in: 3600,
}),
);
} else {
res.writeHead(400, {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "http://localhost:8080",
});
res.end(JSON.stringify({ error: "invalid_grant" }));
}
});
return;
}

// Mock OAuth logout endpoint
if (url.pathname === "/oauth/logout") {
const postLogoutUri =
url.searchParams.get("post_logout_redirect_uri") || "http://localhost:8080";
res.writeHead(302, { Location: postLogoutUri });
res.end();
return;
}

// CORS preflight for token endpoint
if (url.pathname === "/oauth/token" && req.method === "OPTIONS") {
res.writeHead(204, {
"Access-Control-Allow-Origin": "http://localhost:8080",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
});
res.end();
return;
}

// Pass everything else to GraphQL yoga
yoga(req, res);
});

server.listen(4000, () => {
console.log("GraphQL server running at http://localhost:4000/graphql");
console.log(
"OAuth endpoints at http://localhost:4000/oauth/authorize and /oauth/token",
);
});
28 changes: 14 additions & 14 deletions mock-server/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
type Query {
posts: [Post!]!
post(id: ID!): Post
me: User
posts: [Post!]!
post(id: ID!): Post
me: User
}

type Mutation {
createPost(title: String!, body: String!): Post!
createPost(title: String!, body: String!): Post!
}

type User implements Node {
id: ID!
name: String!
email: String!
avatarUrl: String
id: ID!
name: String!
email: String!
avatarUrl: String
}

type Post implements Node {
id: ID!
title: String!
body: String!
createdAt: String!
author: User!
id: ID!
title: String!
body: String!
createdAt: String!
author: User!
}

interface Node {
id: ID!
id: ID!
}
Loading