-
-
Notifications
You must be signed in to change notification settings - Fork 169
Description
When a user's OIDC session expires mid-session and they submit a POST request with a large body (e.g. a fileC middleware in oidc.rs immediately returns an HttpResponse::SeeOther() redirect without consuming the request payload.
When SQLPage sits behind a reverse proxy (e.g. nginx) with proxy_request_buffering on, the proxy has already accepted the full upload from the client and is forwarding it to the SQLPage Unix socket via sendfile(). The premature socket closure by SQLPage produces EPIPE (Broken pipe) and ECONNRESET (Connection reset by peer) errors on the proxy side, which are reported to the client as 5xx errors instead of the intended 303 redirect.
To Reproduce
1. Minimal SQLPage app (upload.sql):
SELECT 'form' AS component, 'Upload' AS title;
SELECT 'file' AS type, 'photo' AS name;2. Nginx config (fronting SQLPage on a Unix socket):
location / {
proxy_pass http://unix:/run/sqlpage/sqlpage.sock;
proxy_request_buffering on; # default, buffers body before forwarding
client_max_body_size 50M;
}3. Enable OIDC authentication via SQLPAGE_OIDC_* environment variables.
4. Steps:
- Log in via OIDC, confirm the session works
- Wait for the OIDC session/JWT to expire (or manually delete the
sqlpage_authcookie) - Upload a file (even a small one — nginx buffering +
sendfileis enough to trigger the race)
5. Observe: nginx logs sendfile() failed (32: Broken pipe) and/or `readv() failed (104 the client. The 303 redirect is never delivered.
Actual behavior
The OIDC middleware at src/webserver/oidc.rs:444-459 returns immediately without reading the request body:
fn handle_unauthenticated_request(
oidc_state: & ...
let response = build_auth_provider_redirect_response(oidc_state, &initial_url, redirect_count);
MiddlewareResponse::Respond(request.into_response(response))
// ← request payload is dropped here without being consumed
}When the ServiceRequest is converted into a ServiceResponse, the unconsumed Payload stream is dropped. Actix-web then closes the underlying connection. If the peer (nginx a connection reset.
Expected behavior
The middleware should drain the request payload before sending the 303 response, so the reverse proxy can complete its sendfile() transfer cleanly and deliver the redirect to the client.
Something like:
async fn handle_unauthenticated_request(
oidc_state: &OidcState,
request: ServiceRequest,
) -> MiddlewareResponse {
if !oidc_state.config.is_public_path(request.path()) {
// Drain the request body to prevent broken pipes with buffering proxies
let mut payload = request.take_payload();
while payload.next().await.is_some() {}
}
// ... build and return the 303 redirect
}This would require making handle_unauthenticated_request async, and updating handle_request accordingly. An alternative is limiting the drain to POST/PUT/PATCH methods only to avoid overhead on GETs.
Version information
- Affected code:
src/webserver/oidc.rs,handle_unauthenticated_request(line 444) - Actix-web: 4.x (as per
Cargo.toml) - Database: Any (not database-specific)
- Proxy: Any reverse proxy with request body buffering (nginx, HAProxy, etc.)
Additional context
- Only affects OIDC-protected endpoints receiving requests with a body (POST file uploads, form submissions)
- GET requests are unaffected since they have no body to drain
- Without a reverse proxy (direct client → SQLPage), the client's browser follows the 303 transparently and no error occurs — the bug is specific to the proxy ↔ backend interaction
- In production, this upload, hit the same expired session, and generated dozens of 5xx errors from a single auth expiry event