From 2c3d97528e67c8f29ca28adad3bc03062fa39f07 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 26 May 2026 15:03:13 -0700 Subject: [PATCH] fix(hub): drop re-auth gate on coolify webhook secret rotate PR #67 cleared the csrf_failed 403 on POST /api/account/coolify-webhook-secret/rotate, but the request then hit the requireRecentAuth() gate at hub/src/index.ts:198 and 401'd. Two failure modes: - Legacy Bearer-JWT clients have no cookie session at all -> the gate returns {error: 're_auth_required', reason: 'no_cookie_session'} with no client- side recovery short of logout+login. - Cookie-auth users whose session is older than 5 min get re_auth_required even though their session is otherwise valid. Threat model for rotate: an attacker who already controls the user's session (or bearer JWT) can rotate the webhook secret -- but they already control the account. Re-auth on rotate alone buys nothing. The per-user mutation rate limit (10/min/user) at hub/src/index.ts:223 still applies. Sister re-auth gates on api-keys POST/DELETE and error-projects DELETE remain untouched -- those grant credential issuance / data destruction and the elevated friction is warranted. Verified: csrf + reauth test suites both green (33 pass). --- hub/src/index.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/hub/src/index.ts b/hub/src/index.ts index ffbb4cc..0b293ee 100644 --- a/hub/src/index.ts +++ b/hub/src/index.ts @@ -195,7 +195,15 @@ app.use('/api/api-keys', async (c, next) => { if (m === 'POST' || m === 'DELETE') return requireRecentAuth()(c, next) return next() }) -app.use('/api/account/coolify-webhook-secret/rotate', requireRecentAuth()) +// NOTE: rotate intentionally does NOT require recent-auth. Legacy Bearer-JWT +// clients carry no session creation timestamp, so requireRecentAuth() would +// hard-fail them with `no_cookie_session` 401 with no client-side recovery. +// Cookie-auth users with a session >5 min old would also 401. Threat model +// for rotate: an attacker who already has the user's valid session/bearer +// can rotate the webhook secret — but they already control the account, so +// re-auth on rotate alone buys nothing. The userMutationLimit (10/min/user) +// below still applies. Sister gates on api-keys + error-projects DELETE +// remain — those grant credential issuance / data destruction. app.use('/api/error-projects/:id', async (c, next) => { if (c.req.method.toUpperCase() === 'DELETE') return requireRecentAuth()(c, next) return next()