Skip to content

Commit ba92905

Browse files
committed
fix: separate error handling in OAuth approval for accurate diagnostics
Split the monolithic try/catch in /oauth/approve into three isolated steps: JWT verification, user resolution, and pending request approval. Each returns specific error messages instead of the generic 'Invalid or expired approval'. User resolution failures are now non-fatal (approval proceeds without per-user scoping). Health endpoint now reports OAuth configuration status for debugging. Made-with: Cursor
1 parent eb923b7 commit ba92905

1 file changed

Lines changed: 48 additions & 13 deletions

File tree

src/mcp-server.ts

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -230,35 +230,59 @@ export function startMcpServer(config: McpServerConfig, port: number): void {
230230
}
231231

232232
if (!dashboardJwtSecret) {
233-
res.status(500).json({ error: "OAuth consent not configured" });
233+
console.error("[oauth] RM_DASHBOARD_JWT_SECRET not configured");
234+
res.status(500).json({ error: "OAuth consent not configured on server" });
234235
return;
235236
}
236237

238+
// Step 1: Verify the JWT from the dashboard
239+
let payload: Record<string, unknown>;
237240
try {
238241
const key = new TextEncoder().encode(dashboardJwtSecret);
239-
const { payload } = await jwtVerify(token, key, {
242+
const result = await jwtVerify(token, key, {
240243
audience: "reflect-memory",
241244
issuer: "reflect-dashboard",
242245
});
246+
payload = result.payload as Record<string, unknown>;
247+
} catch (err) {
248+
const msg = (err as Error).message || String(err);
249+
console.error(`[oauth] JWT verification failed: ${msg}`);
250+
res.status(403).json({ error: "Token verification failed. Check that AUTH_SECRET and RM_DASHBOARD_JWT_SECRET match." });
251+
return;
252+
}
243253

244-
const pendingId = payload.pending_id as string;
245-
if (!pendingId) {
246-
res.status(400).json({ error: "Invalid approval token" });
247-
return;
248-
}
254+
const pendingId = payload.pending_id as string;
255+
if (!pendingId) {
256+
res.status(400).json({ error: "Missing pending_id in approval token" });
257+
return;
258+
}
249259

250-
let approvedUserId: string | undefined;
251-
const email = payload.email as string | undefined;
252-
if (email) {
260+
// Step 2: Resolve the user from the email in the JWT (non-fatal if it fails)
261+
let approvedUserId: string | undefined;
262+
const email = payload.email as string | undefined;
263+
if (email) {
264+
try {
253265
approvedUserId = findOrCreateUserByEmail(db, email);
254266
console.log(`[oauth] Resolved user ${approvedUserId} from email ${email}`);
267+
} catch (err) {
268+
console.error(`[oauth] User resolution failed for ${email}: ${(err as Error).message}`);
255269
}
270+
}
256271

272+
// Step 3: Approve the pending request and redirect
273+
try {
257274
const redirectUrl = oauthProvider.approvePendingRequest(pendingId, approvedUserId);
258275
res.redirect(302, redirectUrl);
259276
} catch (err) {
260-
console.error("[oauth] Approval failed:", (err as Error).message);
261-
res.status(403).json({ error: "Invalid or expired approval" });
277+
const msg = (err as Error).message || String(err);
278+
console.error(`[oauth] Pending request approval failed: ${msg}`);
279+
if (msg.includes("expired")) {
280+
res.status(410).json({ error: "Authorization request expired. Go back to your AI tool and try connecting again." });
281+
} else if (msg.includes("not found")) {
282+
res.status(404).json({ error: "Authorization request not found. It may have been used already. Try connecting again." });
283+
} else {
284+
res.status(500).json({ error: `Approval failed: ${msg}` });
285+
}
262286
}
263287
});
264288

@@ -490,7 +514,18 @@ export function startMcpServer(config: McpServerConfig, port: number): void {
490514
});
491515

492516
app.get("/health", (_req, res) => {
493-
res.json({ service: "reflect-memory-mcp", status: "ok" });
517+
const secretLen = dashboardJwtSecret?.length ?? 0;
518+
res.json({
519+
service: "reflect-memory-mcp",
520+
status: "ok",
521+
oauth: {
522+
jwt_secret_configured: secretLen > 0,
523+
jwt_secret_length: secretLen,
524+
dashboard_url: dashboardUrl || "(not set)",
525+
public_url: publicUrl || "(not set)",
526+
},
527+
sessions: Object.keys(transports).length,
528+
});
494529
});
495530

496531
const vendors = Object.keys(agentKeys);

0 commit comments

Comments
 (0)