supermemory/apps/web/lib/verify-session.ts
MaheshtheDev 3b0fc9c959 fix(web): authenticate and bound metered /api routes (#1589)
Cherry-picks #1579 and #1580 from @Sravanjangam (security audit #1578), plus improvements on top.

- `/api/og`, `/api/onboarding/extract-content` and `/api/onboarding/research` now verify the session against the auth backend; the middleware only checked that a cookie was present, so a forged cookie reached handlers that spend metered Exa/xAI quota.
- Bounds those routes: 2MB cap on fetched HTML, max 10 http(s) URLs per request, name/email length limits and a 60s timeout on the LLM call.
- De-duplicates URLs before calling Exa, and collapses whitespace in `name`/`email` so a newline can't forge extra prompt lines. Both adapted from @SEPURI-SAI-KRISHNA's #1528 and #1530.
- Deletes the unused, unauthenticated `account-status` route.

Verified locally: pre-fix `/api/og` returned 200 for a forged cookie, post-fix it returns 401. Five duplicate URLs collapse to two before reaching Exa, and a newline-laden `name` arrives as a single prompt line.

Supersedes #1528 and #1530.
2026-08-23 21:48:46 +00:00

47 lines
1.1 KiB
TypeScript

import { getBackendUrl } from "./url-helpers"
const LOCAL_DEV_HOSTS = new Set(["localhost", "127.0.0.1", "::1"])
// `bun run dev:local` serves localhost while auth lives on api.supermemory.ai, so its cookie never arrives.
function isLocalDevRequest(request: Request): boolean {
if (process.env.NODE_ENV !== "development") {
return false
}
try {
return LOCAL_DEV_HOSTS.has(new URL(request.url).hostname)
} catch {
return false
}
}
// middleware.ts only checks the cookie is present; metered/proxy routes must verify it server-side.
export async function hasVerifiedSession(request: Request): Promise<boolean> {
if (isLocalDevRequest(request)) {
return true
}
const cookie = request.headers.get("cookie")
if (!cookie) {
return false
}
try {
const response = await fetch(`${getBackendUrl()}/api/auth/get-session`, {
headers: { cookie },
redirect: "error",
cache: "no-store",
})
if (!response.ok) {
return false
}
const session: unknown = await response.json()
return Boolean(
session &&
typeof session === "object" &&
"user" in session &&
session.user,
)
} catch {
return false
}
}