Skip to content

Commit 20ea012

Browse files
committed
feat(mcp): update validation
1 parent 649e855 commit 20ea012

File tree

2 files changed

+103
-50
lines changed

2 files changed

+103
-50
lines changed

doit-mcp-server/src/app.ts

Lines changed: 64 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "./utils";
1212
import type { OAuthHelpers } from "@cloudflare/workers-oauth-provider";
1313
import { handleValidateUserRequest } from "../../src/tools/validateUser";
14+
import { decodeJWT } from "../../src/utils/util";
1415

1516
export type Bindings = Env & {
1617
OAUTH_PROVIDER: OAuthHelpers;
@@ -100,65 +101,78 @@ async function handleApprove(c: any) {
100101
);
101102
}
102103

104+
// Helper function to render authorization rejection response
105+
async function renderAuthorizationRejection(c: any, redirectUri: string) {
106+
return c.html(
107+
layout(
108+
await renderAuthorizationRejectedContent(redirectUri),
109+
"DoiT MCP Remote - Authorization Status"
110+
)
111+
);
112+
}
113+
103114
app.post("/customer-context", async (c) => {
104115
const { action, oauthReqInfo, apiKey } = await parseApproveFormBody(
105116
await c.req.parseBody()
106117
);
107118

108-
let isDoitUser = false;
109-
const validatePromises = [
110-
handleValidateUserRequest({}, apiKey),
111-
handleValidateUserRequest(
112-
{ customerContext: "EE8CtpzYiKp0dVAESVrB" }, // Validate doers
113-
apiKey
114-
),
115-
];
119+
try {
120+
const jwtInfo = decodeJWT(apiKey);
121+
const payload = jwtInfo?.payload;
116122

117-
return Promise.allSettled(validatePromises)
118-
.then(async (results) => {
119-
let allFailed = true;
120-
for (const res of results) {
121-
if (res.status === "fulfilled") {
122-
const result = res.value;
123-
if (result.content[0].text.includes("Domain: doit.com")) {
124-
isDoitUser = true;
125-
}
126-
if (!result.content[0].text.includes("Failed")) {
127-
allFailed = false;
128-
}
129-
}
130-
}
131-
if (allFailed) {
132-
return c.html(
133-
layout(
134-
await renderAuthorizationRejectedContent(
135-
oauthReqInfo?.redirectUri || "/"
136-
),
137-
"MCP Remote Auth Demo - Authorization Status"
138-
)
123+
if (!jwtInfo || !payload) {
124+
// If the JWT is invalid, reject the authorization request
125+
return await renderAuthorizationRejection(
126+
c,
127+
oauthReqInfo?.redirectUri || "/"
128+
);
129+
}
130+
131+
if (!payload.DoitEmployee) {
132+
// request validation for non-doit employees
133+
const validatePromise = await handleValidateUserRequest({}, apiKey);
134+
const result = validatePromise.content[0].text;
135+
136+
if (!result.toLowerCase().includes(payload.sub)) {
137+
return await renderAuthorizationRejection(
138+
c,
139+
oauthReqInfo?.redirectUri || "/"
139140
);
140141
}
141-
if (!isDoitUser) {
142-
// Forward to approve logic
143-
return await handleApprove(c);
144-
}
145-
const content = await renderCustomerContextScreen(
146-
action,
147-
oauthReqInfo,
148-
apiKey
149-
);
150-
return c.html(layout(content, "DoiT MCP Remote - Customer Context"));
151-
})
152-
.catch(async (error) => {
153-
return c.html(
154-
layout(
155-
await renderAuthorizationRejectedContent(
156-
oauthReqInfo?.redirectUri || "/"
157-
),
158-
"MCP Remote Auth Demo - Authorization Status"
159-
)
142+
143+
return await handleApprove(c);
144+
}
145+
146+
// request validation for doit employees
147+
const validatePromise = await handleValidateUserRequest(
148+
{
149+
customerContext: "EE8CtpzYiKp0dVAESVrB",
150+
},
151+
apiKey
152+
);
153+
154+
const result = validatePromise.content[0].text;
155+
156+
if (!result.toLowerCase().includes(payload.sub)) {
157+
return await renderAuthorizationRejection(
158+
c,
159+
oauthReqInfo?.redirectUri || "/"
160160
);
161-
});
161+
}
162+
163+
const content = await renderCustomerContextScreen(
164+
action,
165+
oauthReqInfo,
166+
apiKey
167+
);
168+
return c.html(layout(content, "DoiT MCP Remote - Customer Context"));
169+
} catch (error) {
170+
console.error("Error decoding JWT:", error);
171+
return await renderAuthorizationRejection(
172+
c,
173+
oauthReqInfo?.redirectUri || "/"
174+
);
175+
}
162176
});
163177

164178
// The /authorize page has a form that will POST to /approve

src/utils/util.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,42 @@ export function formatDate(timestamp: number): string {
176176
if (!timestamp) return "";
177177
return new Date(timestamp).toISOString().split("T")[0];
178178
}
179+
180+
/**
181+
* Decodes a JWT token without validation
182+
* @param token The JWT token string
183+
* @returns The decoded JWT object containing header, payload, and signature
184+
*/
185+
export function decodeJWT(token: string): {
186+
header: any;
187+
payload: any;
188+
signature: string;
189+
} | null {
190+
try {
191+
// Split the token into its three parts
192+
const parts = token.split(".");
193+
194+
if (parts.length !== 3) {
195+
console.error("Invalid JWT format: token must have 3 parts");
196+
return null;
197+
}
198+
199+
// Decode header (first part)
200+
const header = JSON.parse(atob(parts[0]));
201+
202+
// Decode payload (second part)
203+
const payload = JSON.parse(atob(parts[1]));
204+
205+
// Keep signature as is (third part)
206+
const signature = parts[2];
207+
208+
return {
209+
header,
210+
payload,
211+
signature,
212+
};
213+
} catch (error) {
214+
console.error("Error decoding JWT:", error);
215+
return null;
216+
}
217+
}

0 commit comments

Comments
 (0)