feat(http): allow config.apiKey fallback on server for single-tenant deployments
This commit is contained in:
+10
-2
@@ -37,7 +37,11 @@ export interface HttpServerInstance {
|
|||||||
* 2. X-OpenProject-Api-Key: <key>
|
* 2. X-OpenProject-Api-Key: <key>
|
||||||
* 3. ?apiKey=<key> query parameter
|
* 3. ?apiKey=<key> query parameter
|
||||||
*/
|
*/
|
||||||
export function extractApiKey(req: Request, url: URL): string | undefined {
|
export function extractApiKey(
|
||||||
|
req: Request,
|
||||||
|
url: URL,
|
||||||
|
defaultApiKey?: string
|
||||||
|
): string | undefined {
|
||||||
const authHeader = req.headers.get("authorization");
|
const authHeader = req.headers.get("authorization");
|
||||||
if (authHeader) {
|
if (authHeader) {
|
||||||
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
||||||
@@ -56,6 +60,10 @@ export function extractApiKey(req: Request, url: URL): string | undefined {
|
|||||||
return queryKey.trim();
|
return queryKey.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (defaultApiKey && defaultApiKey.trim().length > 0) {
|
||||||
|
return defaultApiKey.trim();
|
||||||
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +200,7 @@ export async function startHttpServer(config: AppConfig): Promise<HttpServerInst
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiKey = extractApiKey(req, url);
|
const apiKey = extractApiKey(req, url, config.apiKey);
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -243,6 +243,27 @@ describe("Hosted Remote MCP Server (HTTP/SSE)", () => {
|
|||||||
expect(res.headers.get("access-control-allow-methods")).toContain("GET");
|
expect(res.headers.get("access-control-allow-methods")).toContain("GET");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("GET /sse falls back to config.apiKey when configured on server", async () => {
|
||||||
|
const singleTenantServer = await startHttpServer({
|
||||||
|
baseUrl: "https://mock.openproject.example.com",
|
||||||
|
readOnly: true,
|
||||||
|
port: 0,
|
||||||
|
host: "127.0.0.1",
|
||||||
|
apiKey: "server-configured-key",
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetch(`http://127.0.0.1:${singleTenantServer.port}/sse`, {
|
||||||
|
headers: { Accept: "text/event-stream" },
|
||||||
|
});
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
const reader = res.body?.getReader();
|
||||||
|
const { value } = await reader!.read();
|
||||||
|
const text = new TextDecoder().decode(value);
|
||||||
|
expect(text).toContain("event: endpoint");
|
||||||
|
await reader!.cancel();
|
||||||
|
await singleTenantServer.stop();
|
||||||
|
});
|
||||||
|
|
||||||
test("concurrent clients with different API keys maintain isolated sessions", async () => {
|
test("concurrent clients with different API keys maintain isolated sessions", async () => {
|
||||||
// Client A
|
// Client A
|
||||||
const resA = await fetch(`${serverUrl}/sse?apiKey=user-a-secret`, {
|
const resA = await fetch(`${serverUrl}/sse?apiKey=user-a-secret`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user