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!("Signup key was not there");
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] = pub_key_bytes
26 .as_slice()
27 .try_into()
28 .map_err(|_| SrvErr!("Invalid pubkey length"))?;
29 let pub_key = VerifyingKey::from_bytes(&pub_key_arr)
30 .map_err(|e| SrvErr!("Invalid public key bytes", e))?;
31
32 let user_id = nanoid!(5);
33 let mut is_admin = false;
34 let mut users = state.users.lock().await;
35 if users.is_empty() {
36 is_admin = true;
37 let mut admin_id_guard = state.admin_id.lock().await;
38 admin_id_guard.replace(user_id.clone());
39 }
40
41 users.push(User {
42 id: user_id.clone(),
43 pub_key,
44 });
45
46 return Ok(Json(CreateAccountResponse { user_id, is_admin }));
47}
48
49pub async fn generate_signup_key(State(state): State<AppState>) -> Result<String, SrvErr> {
50 let new_signup_key = nanoid!(5);
51 let mut signup_keys = state.signup_keys.lock().await;
52
53 // Assume new_signup_key will not collide
54 signup_keys.insert(new_signup_key.clone());
55
56 return Ok(new_signup_key);
57}
58
59pub async fn request_friend_request(
60 State(state): State<AppState>,
61 Extension(user_id): Extension<String>,
62 friend_id: String,
63) -> Result<(), SrvErr> {
64 if friend_id == user_id {
65 ReqBail!("Cannot friend yourself");
66 }
67
68 let mut friend_requests = state.friend_requests.lock().await;
69 let link = Link::new(friend_id, user_id);
70
71 // if we remove sucessfully the link, it means a request already existed
72 // so we are making the friendship official
73 let friend_request_accepted = friend_requests.remove(&link);
74 if friend_request_accepted {
75 drop(friend_requests);
76
77 let mut pings_state = state.positions.lock().await;
78 pings_state.insert(link.clone(), RingBuffer::new(state.ring_buffer_cap));
79 drop(pings_state);
80
81 let mut links = state.links.lock().await;
82 links.insert(link);
83 drop(links);
84 } else {
85 friend_requests.insert(link);
86 drop(friend_requests);
87 }
88
89 return Ok(());
90}
91
92pub async fn is_friend_request_accepted(
93 State(state): State<AppState>,
94 Extension(user_id): Extension<String>,
95 friend_id: String,
96) -> Result<PlainBool, SrvErr> {
97 let link = Link::new(friend_id, user_id);
98 let links = state.links.lock().await;
99 let accepted = links.contains(&link);
100 return Ok(PlainBool(accepted));
101}
102
103pub async fn send_pings(
104 State(state): State<AppState>,
105 Extension(user_id): Extension<String>,
106 Json(pings): Json<Vec<PingPayload>>,
107) -> Result<(), SrvErr> {
108 let links = state.links.lock().await;
109 for ping in &pings {
110 let link = Link::new(user_id.clone(), ping.receiver_id.clone());
111 if !links.contains(&link) {
112 ReqBail!("Ping receiver is not linked to sender");
113 }
114 }
115 drop(links);
116
117 let mut pings_state = state.positions.lock().await;
118
119 for ping in pings {
120 let link = Link::new(user_id.clone(), ping.receiver_id.clone());
121 pings_state
122 .get_mut(&link)
123 .unwrap()
124 .add(EncryptedPing(ping.encrypted_ping)); // We assured that a ringbuffer exists because it was created when the link was created, hence the .unwrap()
125 }
126
127 return Ok(());
128}
129
130pub async fn get_pings(
131 State(state): State<AppState>,
132 Extension(user_id): Extension<String>,
133 sender_id: String,
134) -> Result<EncryptedPingVec, SrvErr> {
135 let link = Link::new(user_id, sender_id);
136 let links = state.links.lock().await;
137
138 if !links.contains(&link) {
139 ReqBail!("No link exists between these users");
140 }
141 drop(links);
142
143 let pings = state.positions.lock().await;
144
145 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()
146}
147
148// TODO: random idea, but use numbers instead of strings for user ids because no need to clone (but longer to read if needed)
149// ANSWER: use number, but when showing or intputing on frontend, use base64 with frontend translation