mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-02 02:11:20 +00:00
fix(mcp): classify auth-validation failures before logging
Split the validateApiKey and validateOAuthToken catch blocks so transient auth-backend failures log at ERROR under a distinct 'Auth backend transient failure:' template while expected rejections log at debug. Return values and 401/503 status contract are unchanged. Co-authored-by: Dhravya Shah <dhravya@supermemory.com>
This commit is contained in:
parent
2415a5c796
commit
59e3fb40ba
2 changed files with 30 additions and 10 deletions
|
|
@ -83,16 +83,19 @@ describe("MCP authentication", () => {
|
|||
})
|
||||
|
||||
it("rejects a token issued for a different audience", async () => {
|
||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
vi.spyOn(console, "debug").mockImplementation(() => {})
|
||||
const token = await signToken({ audience: "https://api.example.com" })
|
||||
|
||||
await expect(
|
||||
validateOAuthToken(token, API_URL, MCP_RESOURCE, keySet),
|
||||
).resolves.toBeNull()
|
||||
expect(errorSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("rejects expired tokens and tokens without a subject", async () => {
|
||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
vi.spyOn(console, "debug").mockImplementation(() => {})
|
||||
const expired = await signToken({ expiresIn: "-1s" })
|
||||
const noSubject = await signToken({ subject: "" })
|
||||
|
||||
|
|
@ -102,10 +105,11 @@ describe("MCP authentication", () => {
|
|||
await expect(
|
||||
validateOAuthToken(noSubject, API_URL, MCP_RESOURCE, keySet),
|
||||
).resolves.toBeNull()
|
||||
expect(errorSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("rejects opaque API keys without an API request", async () => {
|
||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
vi.spyOn(console, "debug").mockImplementation(() => {})
|
||||
const fetchSpy = vi.fn()
|
||||
vi.stubGlobal("fetch", fetchSpy)
|
||||
|
||||
|
|
@ -166,7 +170,8 @@ describe("MCP authentication", () => {
|
|||
})
|
||||
|
||||
it("rejects an API key the session endpoint refuses", async () => {
|
||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
vi.spyOn(console, "debug").mockImplementation(() => {})
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockResolvedValue(new Response(null, { status: 401 })),
|
||||
|
|
@ -175,6 +180,7 @@ describe("MCP authentication", () => {
|
|||
await expect(
|
||||
validateApiKey("sm_revoked_key_0123456789abcdef", API_URL),
|
||||
).resolves.toBeNull()
|
||||
expect(errorSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("rejects malformed API keys without an API request", async () => {
|
||||
|
|
@ -187,7 +193,7 @@ describe("MCP authentication", () => {
|
|||
})
|
||||
|
||||
it("surfaces a 500 from the session endpoint as TransientAuthError, not invalid token", async () => {
|
||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockResolvedValue(new Response(null, { status: 500 })),
|
||||
|
|
@ -196,10 +202,14 @@ describe("MCP authentication", () => {
|
|||
await expect(
|
||||
validateApiKey("sm_outage_key_0123456789abcdef", API_URL),
|
||||
).rejects.toThrow(TransientAuthError)
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
"Auth backend transient failure:",
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
|
||||
it("surfaces a session-endpoint timeout as TransientAuthError", async () => {
|
||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockRejectedValue(
|
||||
|
|
@ -212,5 +222,9 @@ describe("MCP authentication", () => {
|
|||
await expect(
|
||||
validateApiKey("sm_timeout_key_0123456789abcd", API_URL),
|
||||
).rejects.toThrow(TransientAuthError)
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
"Auth backend transient failure:",
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -132,9 +132,12 @@ export async function validateApiKey(
|
|||
})
|
||||
return user
|
||||
} catch (error) {
|
||||
console.error("API key validation error:", error)
|
||||
const transient = transientAuthErrorFor(error)
|
||||
if (transient) throw transient
|
||||
if (transient) {
|
||||
console.error("Auth backend transient failure:", error)
|
||||
throw transient
|
||||
}
|
||||
console.debug("API key validation rejected:", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -183,9 +186,12 @@ export async function validateOAuthToken(
|
|||
expiresAt: payload.exp,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("OAuth token validation error:", error)
|
||||
const transient = transientAuthErrorFor(error)
|
||||
if (transient) throw transient
|
||||
if (transient) {
|
||||
console.error("Auth backend transient failure:", error)
|
||||
throw transient
|
||||
}
|
||||
console.debug("OAuth token validation rejected:", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue