Self-hosted, federated location sharing app and server that prioritizes user privacy and security
end-to-end-encryption location-sharing privacy self-hosted federated
at auth 3.1 kB view raw
1use axum::{body::Body, extract::State, http::Request, middleware::Next}; 2use base64::{Engine, prelude::BASE64_STANDARD}; 3use ed25519_dalek::Signature; 4 5use crate::{ 6 ReqBail, SrvErr, 7 types::{AppState, AuthData}, 8}; 9 10pub async fn auth_test( 11 State(state): State<AppState>, 12 req: Request<Body>, 13 next: Next, 14) -> Result<axum::response::Response, SrvErr> { 15 let endpoint = req.uri().path().to_owned(); 16 17 if endpoint != "/create-account" { 18 // CURSED STUFF BEGIN 19 let (parts, body) = req.into_parts(); 20 let body_bytes = axum::body::to_bytes(body, usize::MAX).await.unwrap(); 21 let new_body = Body::from(body_bytes.clone()); 22 let mut req = Request::from_parts(parts, new_body); 23 // CURSED STUFF END 24 25 let auth_header = req 26 .headers() 27 .get("x-auth") 28 .and_then(|v| v.to_str().ok()) 29 .ok_or(SrvErr!("missing x-auth header"))?; 30 31 let auth_data: AuthData = serde_json::from_str(auth_header) 32 .map_err(|e| SrvErr!("failed to parse x-auth JSON", e))?; 33 34 let users = state.users.lock().await; 35 let user_id = auth_data.user_id; 36 let user = users 37 .iter() 38 .find(|u| u.id == user_id) 39 .ok_or(SrvErr!("User not found"))?; 40 let verifying_key = user.pub_key.clone(); 41 42 // NOTE (key chaining): 43 // Do NOT drop the `users` lock until after both steps are complete: 44 // 1) verify the request using the current stored key 45 // 2) update the stored key to the next key from this request 46 // 47 // If we unlock in between, a replay/duplicate of the same request can race: 48 // 49 // - Request A reads pk_i and starts verifying 50 // - Attacker replays A (same signature) while A is still verifying 51 // - Replay gets verified against pk_i and proceeds to update pk -> pk_attacker 52 // - Request A finishes and updates pk again, but the replay has already 53 // been accepted and may have advanced the key to an attacker-chosen value 54 // 55 // Keeping the lock across "verify + update" makes the transition atomic. 56 drop(users); 57 58 //////////////////////////////////// 59 //////////////////////////////////// unsure 60 //////////////////////////////////// 61 62 let sig_vec = BASE64_STANDARD 63 .decode(&auth_data.signature) 64 .map_err(|e| SrvErr!("base64 decode fail", e))?; 65 let sig_bytes: [u8; 64] = sig_vec 66 .try_into() 67 .map_err(|e| SrvErr!("invalid signature length", e))?; 68 let signature = Signature::from_bytes(&sig_bytes); 69 70 if let Err(err) = verifying_key.verify_strict(&body_bytes, &signature) { 71 panic!("Signature verification failed: {err}"); 72 } 73 74 //////////////////////////////////// 75 //////////////////////////////////// 76 //////////////////////////////////// 77 78 // TODO: Make the endpoints as enums at some point 79 if endpoint == "/generate-signup-key" { 80 let admin_id = state.admin_id.lock().await; 81 if admin_id.as_ref() != Some(&user_id) { 82 ReqBail!("not allowed: admin only"); 83 } 84 } 85 86 req.extensions_mut().insert(user_id); // pass user_id to the actual request handler, whatever it is, in handlers.rs 87 *req.body_mut() = Body::from(body_bytes); 88 89 return Ok(next.run(req).await); 90 } 91 92 return Ok(next.run(req).await); 93}