Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f7c9f3dc94 | |||
| b71833ef79 | |||
| 9c7bc2881c |
8
check-oauth.sh
Executable file
8
check-oauth.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
docker exec gearbox bun -e "
|
||||
const{Database}=require('bun:sqlite');
|
||||
const db=new Database('./data/gearbox.db');
|
||||
console.log('CLIENTS:', JSON.stringify(db.query('SELECT * FROM oauth_clients ORDER BY id DESC LIMIT 5').all(), null, 2));
|
||||
console.log('CODES:', JSON.stringify(db.query('SELECT id,client_id,used,expires_at FROM oauth_codes ORDER BY id DESC LIMIT 5').all(), null, 2));
|
||||
console.log('TOKENS:', JSON.stringify(db.query('SELECT id,client_id,expires_at FROM oauth_tokens ORDER BY id DESC LIMIT 5').all(), null, 2));
|
||||
"
|
||||
BIN
e2e/test.db-shm
Normal file
BIN
e2e/test.db-shm
Normal file
Binary file not shown.
BIN
e2e/test.db-wal
Normal file
BIN
e2e/test.db-wal
Normal file
Binary file not shown.
@@ -1,5 +1,6 @@
|
||||
import { Hono } from "hono";
|
||||
import { serveStatic } from "hono/bun";
|
||||
import { cors } from "hono/cors";
|
||||
import { db as prodDb } from "../db/index.ts";
|
||||
import { seedDefaults } from "../db/seed.ts";
|
||||
import { mcpRoutes } from "./mcp/index.ts";
|
||||
@@ -34,6 +35,11 @@ app.get("/api/health", (c) => {
|
||||
return c.json({ status: "ok" });
|
||||
});
|
||||
|
||||
// CORS for OAuth and MCP endpoints (required for claude.ai browser-based flows)
|
||||
app.use("/.well-known/*", cors());
|
||||
app.use("/oauth/*", cors());
|
||||
app.use("/mcp/*", cors());
|
||||
|
||||
// OAuth routes (must be before /api/* middleware)
|
||||
app.use("/oauth/*", async (c, next) => {
|
||||
c.set("db", prodDb);
|
||||
|
||||
@@ -99,7 +99,7 @@ mcpRoutes.use("/*", async (c, next) => {
|
||||
const authHeader = c.req.header("Authorization");
|
||||
if (authHeader?.startsWith("Bearer ")) {
|
||||
const token = authHeader.slice(7);
|
||||
if (verifyAccessToken(db, token)) {
|
||||
if (await verifyAccessToken(db, token)) {
|
||||
return next();
|
||||
}
|
||||
return c.json({ error: "invalid_token" }, 401);
|
||||
@@ -115,10 +115,12 @@ mcpRoutes.use("/*", async (c, next) => {
|
||||
return c.json({ error: "Invalid API key" }, 401);
|
||||
}
|
||||
|
||||
// No auth provided — return 401 with WWW-Authenticate to trigger OAuth flow
|
||||
// No auth provided — return 401 with WWW-Authenticate to trigger OAuth flow (RFC 9728)
|
||||
const baseUrl = (
|
||||
process.env.GEARBOX_URL || new URL(c.req.url).origin
|
||||
).replace(/\/$/, "");
|
||||
return c.text("Unauthorized", 401, {
|
||||
"WWW-Authenticate":
|
||||
'Bearer resource_metadata="/.well-known/oauth-authorization-server"',
|
||||
"WWW-Authenticate": `Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource"`,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -84,6 +84,16 @@ function renderLoginForm(params: {
|
||||
|
||||
export const wellKnownRoute = new Hono<Env>();
|
||||
|
||||
// Protected Resource Metadata (RFC 9728) — Claude fetches this first after 401
|
||||
wellKnownRoute.get("/oauth-protected-resource", (c) => {
|
||||
const baseUrl = getBaseUrl(c);
|
||||
return c.json({
|
||||
resource: `${baseUrl}/mcp`,
|
||||
authorization_servers: [baseUrl],
|
||||
});
|
||||
});
|
||||
|
||||
// OAuth Authorization Server Metadata (RFC 8414) — Claude fetches this second
|
||||
wellKnownRoute.get("/oauth-authorization-server", (c) => {
|
||||
const baseUrl = getBaseUrl(c);
|
||||
return c.json({
|
||||
|
||||
Reference in New Issue
Block a user