fix: extend OAuth state cookie lifetime to match the 10-minute state TTL (#544)
This commit is contained in:
parent
b32c0bd841
commit
61b32ec647
@ -15,6 +15,16 @@ export function createBaseAuthConfig() {
|
||||
// /api/auth endpoint. Header lookup is case-insensitive.
|
||||
ipAddressHeaders: ["cf-connecting-ip"],
|
||||
},
|
||||
// Better Auth writes the OAuth state verification row with a 10-minute
|
||||
// expiry but sets the matching signed cookie with maxAge 300, and
|
||||
// parseGenericState checks the cookie before the row's expiresAt — so the
|
||||
// intended 10-minute window is unreachable. The GSC and GA4 providers
|
||||
// below force `select_account consent`, a two-screen Google flow, so a
|
||||
// user who takes more than 5 minutes returns with a live verification row
|
||||
// and a dead cookie and fails with "State mismatch: State not persisted
|
||||
// correctly". The row's expiresAt still enforces the real 10-minute
|
||||
// window and the state stays single-use, so this is not a weakening.
|
||||
cookies: { state: { attributes: { maxAge: 600 } } },
|
||||
},
|
||||
account: {
|
||||
// Encrypt OAuth access/refresh tokens at rest in D1. Also covers the
|
||||
|
||||
@ -68,14 +68,27 @@ function createAuth() {
|
||||
const auth = betterAuth({
|
||||
baseURL: baseUrl,
|
||||
secret: getHostedSecret(),
|
||||
// The api-key plugin logs every verification failure at error level — a
|
||||
// stale key or a throttled caller included. The /mcp handler already logs
|
||||
// the response it returns at the right level (debug for 401, warn for 429),
|
||||
// so drop the duplicate.
|
||||
logger: {
|
||||
log: (level, message, ...args) => {
|
||||
log: (level, message, ...args: unknown[]) => {
|
||||
// The api-key plugin logs every verification failure at error level — a
|
||||
// stale key or a throttled caller included. The /mcp handler already logs
|
||||
// the response it returns at the right level (debug for 401, warn for 429),
|
||||
// so drop the duplicate.
|
||||
if (message.startsWith("Failed to validate API key")) return;
|
||||
console[level](`[better-auth] ${message}`, ...args);
|
||||
// "Failed to parse state" is user/browser behavior: a replayed OAuth
|
||||
// callback URL (back button, restored tab), or a consent screen left
|
||||
// open past the state's lifetime. The request already redirects the
|
||||
// user to an error page; nothing here is on-call actionable.
|
||||
const effectiveLevel =
|
||||
level === "error" && message === "Failed to parse state"
|
||||
? "warn"
|
||||
: level;
|
||||
// Also drops Better Auth's ISO-timestamp prefix, which makes every log
|
||||
// line fingerprint as its own error group in observability.
|
||||
console[effectiveLevel === "debug" ? "log" : effectiveLevel](
|
||||
`[better-auth] ${message}`,
|
||||
...args,
|
||||
);
|
||||
},
|
||||
},
|
||||
...baseAuthConfig,
|
||||
|
||||
@ -103,6 +103,7 @@ export async function handleMcpApiKeyRequest(
|
||||
// clients (see lib/auth-api-key.ts). Cloudflare's counter is per-colo
|
||||
// best-effort, which is all this needs to be: credits bound spend, this
|
||||
// bounds runaway request volume.
|
||||
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- the binding is declared in alchemy.run.ts; env stays unknown through the MCP handler chain
|
||||
const rateLimit = (env as { MCP_RATE_LIMIT?: RateLimit }).MCP_RATE_LIMIT;
|
||||
if (rateLimit) {
|
||||
const { success } = await rateLimit.limit({ key: userId });
|
||||
|
||||
@ -106,7 +106,7 @@ async function checkBatchLive(
|
||||
results.push(outcome.value);
|
||||
return;
|
||||
}
|
||||
const reason = outcome.reason;
|
||||
const reason: unknown = outcome.reason;
|
||||
const code = reason instanceof AppError ? reason.code : "UNKNOWN";
|
||||
const message = reason instanceof Error ? reason.message : String(reason);
|
||||
// DataForSEO erring on its own side is a provider flake, not our bug: the
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user