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 main 191 lines 4.5 kB view raw
1use axum::{ 2 http::StatusCode, 3 response::{IntoResponse, Response}, 4}; 5use ed25519_dalek::VerifyingKey; 6use serde::{Deserialize, Serialize}; 7use std::{ 8 collections::{HashMap, HashSet}, 9 sync::Arc, 10}; 11use tokio::sync::Mutex; 12 13// doing everything in memory for now 14#[derive(Clone)] 15pub struct AppState { 16 pub users: Arc<Mutex<Vec<User>>>, 17 pub signup_keys: Arc<Mutex<HashSet<String>>>, 18 pub friend_requests: Arc<Mutex<HashSet<DirectedFriendRequestLink>>>, 19 pub links: Arc<Mutex<HashSet<UndirectedLink>>>, 20 pub positions: Arc<Mutex<HashMap<UndirectedLink, RingBuffer>>>, 21 pub admin_id: Arc<Mutex<Option<String>>>, 22 pub ring_buffer_cap: usize, 23} 24 25#[derive(Debug, Deserialize, Clone)] 26pub struct AuthData { 27 pub user_id: String, 28 pub signature: String, 29 pub nonce: String, 30} 31 32pub struct RingBuffer { 33 pub ring: Box<[Option<EncryptedPing>]>, 34 pub idx: usize, 35} 36 37#[derive(Clone, Serialize)] 38pub struct EncryptedPing(pub String); 39 40// represents a ring buffer for a directed friend connection (ex.: user1 sending to user2, which in that case it's only user1's positions) 41impl RingBuffer { 42 pub fn new(capacity: usize) -> Self { 43 return Self { ring: vec![None; capacity].into_boxed_slice(), idx: 0 }; 44 } 45 46 pub fn add(&mut self, p: EncryptedPing) { 47 self.idx = (self.idx + 1) % self.ring.len(); 48 self.ring[self.idx] = Some(p); 49 } 50 51 /// Returns a `Vec<String>` of all the `encrypted_ping` values in the ring buffer, 52 /// starting from `self.idx` and iterating **backwards**, wrapping around to the end, 53 /// skipping `None` entries. 54 /// 55 /// # Notes 56 /// - The first element in the returned vector corresponds to the current index (`self.idx`). 57 /// - `None` entries are ignored. 58 pub fn flatten(&self) -> Vec<EncryptedPing> { 59 let len = self.ring.len(); 60 61 let mut result = Vec::with_capacity(len); 62 63 for i in 0..len { 64 let temp = (self.idx + len - i) % len; 65 let position = &self.ring[temp]; 66 match position { 67 Some(p) => result.push(p.clone()), 68 None => continue, 69 } 70 } 71 72 return result; 73 } 74} 75 76#[derive(serde::Deserialize)] 77pub struct CreateUserRequest { 78 pub signup_key: String, 79 pub pub_key_b64: String, // base64-encoded public key 80} 81 82#[derive(Debug, PartialEq, Eq)] 83pub struct User { 84 pub id: String, 85 pub pub_key: VerifyingKey, 86} 87 88#[derive(Debug, Clone, PartialEq, Eq, Hash)] 89pub struct UndirectedLink(String, String); 90 91impl UndirectedLink { 92 pub fn new(a: String, b: String) -> Self { 93 if a < b { UndirectedLink(a, b) } else { UndirectedLink(b, a) } // normalize order 94 } 95 96 pub fn from(dfrl: DirectedFriendRequestLink) -> Self { 97 return UndirectedLink::new(dfrl.accepter_id, dfrl.sender_id); 98 } 99} 100 101#[derive(Debug, PartialEq, Eq, Hash)] 102pub struct DirectedFriendRequestLink { 103 pub sender_id: String, 104 pub accepter_id: String, 105} 106 107impl DirectedFriendRequestLink { 108 pub fn swap_direction(&mut self) { 109 std::mem::swap(&mut self.sender_id, &mut self.accepter_id); 110 } 111} 112 113#[derive(Serialize)] 114pub struct CreateAccountResponse { 115 pub user_id: String, 116 pub is_admin: bool, 117} 118 119#[derive(Deserialize)] 120pub struct PingPayload { 121 pub receiver_id: String, 122 pub encrypted_ping: String, 123} 124 125pub struct PlainBool(pub bool); 126 127impl IntoResponse for PlainBool { 128 fn into_response(self) -> Response { 129 self.0.to_string().into_response() 130 } 131} 132 133#[derive(Serialize)] 134pub struct EncryptedPingVec(pub Vec<EncryptedPing>); 135 136impl IntoResponse for EncryptedPingVec { 137 fn into_response(self) -> Response { 138 axum::Json(self.0).into_response() 139 } 140} 141 142#[derive(Debug)] 143pub struct SrvErr { 144 pub msg: String, 145 pub cause: Option<String>, 146} 147 148impl IntoResponse for SrvErr { 149 fn into_response(self) -> Response { 150 match &self.cause { 151 Some(c) => eprintln!("[ERR] {} | cause: {}", self.msg, c), 152 None => eprintln!("[ERR] {}", self.msg), 153 } 154 155 let body = if cfg!(debug_assertions) { 156 match &self.cause { 157 Some(c) => format!("{} | cause: {}", self.msg, c), 158 None => self.msg.clone(), 159 } 160 } else { 161 self.msg.clone() 162 }; 163 164 (StatusCode::INTERNAL_SERVER_ERROR, body).into_response() 165 } 166} 167 168/// Central policy: what gets logged, what gets returned. 169pub fn mk_srv_err(msg: impl Into<String>, cause: Option<String>) -> SrvErr { 170 SrvErr { msg: msg.into(), cause } 171} 172 173#[macro_export] 174macro_rules! SrvErr { 175 ($msg:expr) => { 176 $crate::mk_srv_err($msg, None) 177 }; 178 ($msg:expr, $err:expr) => { 179 $crate::mk_srv_err($msg, Some(format!("{:?}", $err))) 180 }; 181} 182 183#[macro_export] 184macro_rules! ReqBail { 185 ($msg:expr) => {{ 186 return Err($crate::SrvErr!($msg)); 187 }}; 188 ($msg:expr, $err:expr) => {{ 189 return Err($crate::SrvErr!($msg, $err)); 190 }}; 191}