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::{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