use axum::{body::Body, extract::State, http::Request, middleware::Next}; use base64::{Engine, prelude::BASE64_STANDARD}; use ed25519_dalek::Signature; use crate::{ ReqBail, SrvErr, types::{AppState, AuthData}, }; pub async fn auth_test( State(state): State, req: Request, next: Next, ) -> Result { let endpoint = req.uri().path().to_owned(); if endpoint != "/create-account" { // CURSED STUFF BEGIN let (parts, body) = req.into_parts(); let body_bytes = axum::body::to_bytes(body, usize::MAX).await.unwrap(); let new_body = Body::from(body_bytes.clone()); let mut req = Request::from_parts(parts, new_body); // CURSED STUFF END let auth_header = req .headers() .get("x-auth") .and_then(|v| v.to_str().ok()) .ok_or(SrvErr!("missing x-auth header"))?; let auth_data: AuthData = serde_json::from_str(auth_header) .map_err(|e| SrvErr!("failed to parse x-auth JSON", e))?; let users = state.users.lock().await; let user_id = auth_data.user_id; let user = users .iter() .find(|u| u.id == user_id) .ok_or(SrvErr!("User not found"))?; let verifying_key = user.pub_key.clone(); // NOTE (key chaining): // Do NOT drop the `users` lock until after both steps are complete: // 1) verify the request using the current stored key // 2) update the stored key to the next key from this request // // If we unlock in between, a replay/duplicate of the same request can race: // // - Request A reads pk_i and starts verifying // - Attacker replays A (same signature) while A is still verifying // - Replay gets verified against pk_i and proceeds to update pk -> pk_attacker // - Request A finishes and updates pk again, but the replay has already // been accepted and may have advanced the key to an attacker-chosen value // // Keeping the lock across "verify + update" makes the transition atomic. drop(users); //////////////////////////////////// //////////////////////////////////// unsure //////////////////////////////////// let sig_vec = BASE64_STANDARD .decode(&auth_data.signature) .map_err(|e| SrvErr!("base64 decode fail", e))?; let sig_bytes: [u8; 64] = sig_vec .try_into() .map_err(|e| SrvErr!("invalid signature length", e))?; let signature = Signature::from_bytes(&sig_bytes); if let Err(err) = verifying_key.verify_strict(&body_bytes, &signature) { panic!("Signature verification failed: {err}"); } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// // TODO: Make the endpoints as enums at some point if endpoint == "/generate-signup-key" { let admin_id = state.admin_id.lock().await; if admin_id.as_ref() != Some(&user_id) { ReqBail!("not allowed: admin only"); } } req.extensions_mut().insert(user_id); // pass user_id to the actual request handler, whatever it is, in handlers.rs *req.body_mut() = Body::from(body_bytes); return Ok(next.run(req).await); } return Ok(next.run(req).await); }