Self-hosted, federated location sharing app and server that prioritizes user privacy and security
end-to-end-encryption
location-sharing
privacy
self-hosted
federated
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}