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::types::*;
7
8macro_rules! my_err {
9 ($msg:expr) => {
10 Err(MyErr($msg))
11 };
12}
13
14pub async fn create_user(
15 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
16 Json(payload): Json<CreateUserRequest>,
17) -> Result<Json<CreateAccountResponse>, MyErr> {
18 let key_used = { state.signup_keys.lock().await.remove(&payload.signup_key) };
19
20 if !key_used {
21 return my_err!("Signup key was not there");
22 }
23
24 // todo check
25 let pub_key_bytes = match BASE64_STANDARD.decode(&payload.pub_key_b64) {
26 Ok(b) => b,
27 Err(_) => return my_err!("Invalid base64 public key"),
28 };
29
30 // todo check
31 let pub_key = match VerifyingKey::from_bytes(
32 &pub_key_bytes
33 .try_into()
34 .map_err(|_| MyErr("Invalid pubkey length".into()))?,
35 ) {
36 Ok(pk) => pk,
37 Err(_) => return my_err!("Invalid public key bytes"),
38 };
39
40 let user_id = nanoid!(5);
41 let mut is_admin = false;
42 let mut users = state.users.lock().await;
43 if users.is_empty() {
44 is_admin = true;
45 let mut admin_id_guard = state.admin_id.lock().await;
46 admin_id_guard.replace(user_id.clone());
47 }
48
49 users.push(User {
50 id: user_id.clone(),
51 pub_key,
52 });
53
54 return Ok(Json(CreateAccountResponse { user_id, is_admin }));
55}
56
57pub async fn generate_signup_key(State(state): State<AppState>) -> Result<String, MyErr> {
58 let new_signup_key = nanoid!(5);
59 let mut signup_keys = state.signup_keys.lock().await;
60
61 // Assume new_signup_key will not collide
62 signup_keys.insert(new_signup_key.clone());
63
64 return Ok(new_signup_key);
65}
66
67pub async fn create_friend_request(
68 State(state): State<AppState>,
69 Extension(user_id): Extension<String>,
70 accepter_id: String,
71) -> Result<(), MyErr> {
72 if accepter_id == user_id {
73 return my_err!("Cannot friend yourself");
74 }
75
76 let mut friend_requests = state.friend_requests.lock().await;
77 let link = Link::new(accepter_id, user_id);
78 if friend_requests.contains(&link) {
79 return my_err!("Friend request already exists");
80 }
81 friend_requests.insert(link);
82 return Ok(());
83}
84
85pub async fn accept_friend_request(
86 State(state): State<AppState>,
87 Extension(user_id): Extension<String>,
88 sender_id: String,
89) -> Result<(), MyErr> {
90 let link = Link::new(user_id, sender_id);
91
92 let friend_request_accepted = { state.friend_requests.lock().await.remove(&link) };
93
94 if !friend_request_accepted {
95 return my_err!("Friend request not found");
96 }
97
98 let mut pings_state = state.positions.lock().await;
99 pings_state.insert(link.clone(), RingBuffer::new(state.ring_buffer_cap));
100 drop(pings_state);
101
102 let mut links = state.links.lock().await;
103 links.insert(link);
104
105 return Ok(());
106}
107
108pub async fn is_friend_request_accepted(
109 State(state): State<AppState>,
110 Extension(user_id): Extension<String>,
111 friend_id: String,
112) -> Result<PlainBool, MyErr> {
113 let link = Link::new(friend_id, user_id);
114 let links = state.links.lock().await;
115 let accepted = links.contains(&link);
116 return Ok(PlainBool(accepted));
117}
118
119pub async fn send_pings(
120 State(state): State<AppState>,
121 Extension(user_id): Extension<String>,
122 Json(pings): Json<Vec<PingPayload>>,
123) -> Result<(), MyErr> {
124 let links = state.links.lock().await;
125 for ping in &pings {
126 let link = Link::new(user_id.clone(), ping.receiver_id.clone());
127 if !links.contains(&link) {
128 return my_err!("Ping receiver is not linked to sender");
129 }
130 }
131 drop(links);
132
133 let mut pings_state = state.positions.lock().await;
134
135 for ping in pings {
136 let link = Link::new(user_id.clone(), ping.receiver_id.clone());
137 pings_state
138 .get_mut(&link)
139 .unwrap()
140 .add(EncryptedPing(ping.encrypted_ping)); // We assured that a ringbuffer exists because it was created when the link was created, hence the .unwrap()
141 }
142
143 return Ok(());
144}
145
146pub async fn get_pings(
147 State(state): State<AppState>,
148 Extension(user_id): Extension<String>,
149 sender_id: String,
150) -> Result<EncryptedPingVec, MyErr> {
151 let link = Link::new(user_id, sender_id);
152 let links = state.links.lock().await;
153
154 if !links.contains(&link) {
155 return my_err!("No link exists between these users");
156 }
157 drop(links);
158
159 let pings = state.positions.lock().await;
160
161 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()
162}
163
164// TODO: random idea, but use numbers instead of strings for user ids because no need to clone (but longer to read if needed)
165// ANSWER: use number, but when showing or intputing on frontend, use base64 with frontend translation