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 145 lines 4.7 kB view raw
1use axum::{Extension, Json, extract::State, response::Result}; 2use base64::{Engine, prelude::BASE64_STANDARD}; 3use ed25519_dalek::VerifyingKey; 4use nanoid::nanoid; 5 6use crate::{ReqBail, SrvErr, types::*}; 7 8pub async fn create_user( 9 State(state): State<AppState>, // TODO: some time ago, I change this (and all other handlers) to State(state): State<Arc<AppState>> no idea if I actually need it 10 Json(payload): Json<CreateUserRequest>, 11) -> Result<Json<CreateAccountResponse>, SrvErr> { 12 let key_used = { state.signup_keys.lock().await.remove(&payload.signup_key) }; 13 14 if !key_used { 15 ReqBail!("Invalid signup key"); 16 } 17 18 // todo check 19 let pub_key_bytes = match BASE64_STANDARD.decode(&payload.pub_key_b64) { 20 Ok(b) => b, 21 Err(_) => ReqBail!("Invalid base64 public key"), 22 }; 23 24 // todo check 25 let pub_key_arr: [u8; 32] = 26 pub_key_bytes.as_slice().try_into().map_err(|_| SrvErr!("Invalid pubkey length"))?; 27 let pub_key = VerifyingKey::from_bytes(&pub_key_arr) 28 .map_err(|e| SrvErr!("Invalid public key bytes", e))?; 29 30 let user_id = nanoid!(5); 31 let mut is_admin = false; 32 let mut users = state.users.lock().await; 33 if users.is_empty() { 34 is_admin = true; 35 let mut admin_id_guard = state.admin_id.lock().await; 36 admin_id_guard.replace(user_id.clone()); 37 } 38 39 users.push(User { id: user_id.clone(), pub_key }); 40 41 return Ok(Json(CreateAccountResponse { user_id, is_admin })); 42} 43 44pub async fn generate_signup_key(State(state): State<AppState>) -> Result<String, SrvErr> { 45 let new_signup_key = nanoid!(5); 46 let mut signup_keys = state.signup_keys.lock().await; 47 48 // Assume new_signup_key will not collide 49 signup_keys.insert(new_signup_key.clone()); 50 51 return Ok(new_signup_key); 52} 53 54pub async fn request_friend_request( 55 State(state): State<AppState>, 56 Extension(user_id): Extension<String>, 57 friend_id: String, 58) -> Result<(), SrvErr> { 59 if friend_id == user_id { 60 ReqBail!("Cannot friend yourself"); 61 } 62 63 let mut friend_requests = state.friend_requests.lock().await; 64 let mut friend_request_link = 65 DirectedFriendRequestLink { sender_id: friend_id, accepter_id: user_id }; 66 67 // if we remove sucessfully the link, it means a request already existed 68 // so we are making the friendship official 69 let friend_request_accepted = friend_requests.remove(&friend_request_link); 70 71 if friend_request_accepted { 72 drop(friend_requests); 73 74 let mut pings_state = state.positions.lock().await; 75 let link = UndirectedLink::from(friend_request_link); 76 pings_state.insert(link.clone(), RingBuffer::new(state.ring_buffer_cap)); 77 drop(pings_state); 78 79 let mut links = state.links.lock().await; 80 links.insert(link); 81 drop(links); 82 } else { 83 friend_request_link.swap_direction(); 84 friend_requests.insert(friend_request_link); 85 drop(friend_requests); 86 } 87 88 return Ok(()); 89} 90 91pub async fn is_friend_request_accepted( 92 State(state): State<AppState>, 93 Extension(user_id): Extension<String>, 94 friend_id: String, 95) -> Result<PlainBool, SrvErr> { 96 let link = UndirectedLink::new(friend_id, user_id); 97 let links = state.links.lock().await; 98 let accepted = links.contains(&link); 99 return Ok(PlainBool(accepted)); 100} 101 102pub async fn send_pings( 103 State(state): State<AppState>, 104 Extension(user_id): Extension<String>, 105 Json(pings): Json<Vec<PingPayload>>, 106) -> Result<(), SrvErr> { 107 let links = state.links.lock().await; 108 for ping in &pings { 109 let link = UndirectedLink::new(user_id.clone(), ping.receiver_id.clone()); 110 if !links.contains(&link) { 111 ReqBail!("Ping receiver is not linked to sender"); 112 } 113 } 114 drop(links); 115 116 let mut pings_state = state.positions.lock().await; 117 118 for ping in pings { 119 let link = UndirectedLink::new(user_id.clone(), ping.receiver_id.clone()); 120 pings_state.get_mut(&link).unwrap().add(EncryptedPing(ping.encrypted_ping)); // We assured that a ringbuffer exists because it was created when the link was created, hence the .unwrap() 121 } 122 123 return Ok(()); 124} 125 126pub async fn get_pings( 127 State(state): State<AppState>, 128 Extension(user_id): Extension<String>, 129 sender_id: String, 130) -> Result<EncryptedPingVec, SrvErr> { 131 let link = UndirectedLink::new(user_id, sender_id); 132 let links = state.links.lock().await; 133 134 if !links.contains(&link) { 135 ReqBail!("No link exists between these users"); 136 } 137 drop(links); 138 139 let pings = state.positions.lock().await; 140 141 return Ok(EncryptedPingVec(pings.get(&link).unwrap().flatten())); // We assured that a ringbuffer exists because it was created when the link was created, hence the .unwrap() 142} 143 144// TODO: random idea, but use numbers instead of strings for user ids because no need to clone (but longer to read if needed) 145// ANSWER: use number, but when showing or intputing on frontend, use base64 with frontend translation