Self-hosted, federated location sharing app and server that prioritizes user privacy and security
end-to-end-encryption location-sharing privacy self-hosted federated

Added a toggleable button for location sharing.

+57 -9
+8
app/src/settings-page/settings.html
··· 11 11 <div class="actions" x-data="settingsPageState"> 12 12 <h3>Settings</h3> 13 13 14 + <div> 15 + Location Sharing 16 + <button 17 + x-text="location_on" 18 + @click="toggleLocation()" 19 + ></button> 20 + </div> 21 + 14 22 <button class="btn-primary" @click="goto('home')"> 15 23 Back to Home 16 24 </button>
+10
app/src/settings-page/settings.ts
··· 3 3 import { goto } from "../utils/tools.ts"; 4 4 5 5 Alpine.data("settingsPageState", () => ({ 6 + location_on: Store.get("is_sharing"), 7 + 6 8 async debugLogout() { 7 9 await Store.reset(); 8 10 goto("signup"); 9 11 }, 12 + 10 13 goto(newLocation: string) { 11 14 goto(newLocation); 15 + }, 16 + 17 + async toggleLocation() { 18 + // once i finish this here, i'll move it over to api maybe? i'll have to check with azom 19 + 20 + await Store.set("is_sharing", !(await Store.get("is_sharing"))); 21 + this.location_on = Store.get("is_sharing"); 12 22 }, 13 23 })); 14 24
+34 -8
app/src/utils/api.ts
··· 7 7 /** 8 8 * This function can throw an error 9 9 */ 10 - export async function createAccount(server_url: string, signup_key: string): Promise<{ user_id: string; is_admin: boolean }> { 11 - const keyPair = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]); 10 + export async function createAccount( 11 + server_url: string, 12 + signup_key: string, 13 + ): Promise<{ user_id: string; is_admin: boolean }> { 14 + const keyPair = await crypto.subtle.generateKey("Ed25519", true, [ 15 + "sign", 16 + "verify", 17 + ]); 12 18 const pubKeyRaw = await crypto.subtle.exportKey("raw", keyPair.publicKey); 13 - const privKeyRaw = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey); 19 + const privKeyRaw = await crypto.subtle.exportKey( 20 + "pkcs8", 21 + keyPair.privateKey, 22 + ); 14 23 const pub_key_b64 = bufToBase64(pubKeyRaw); 15 24 16 25 const response = await fetch(server_url + "/create-account", { ··· 19 28 body: JSON.stringify({ signup_key, pub_key_b64 }), 20 29 }); 21 30 22 - if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); 31 + if (!response.ok) 32 + throw new Error(`HTTP ${response.status}: ${await response.text()}`); 23 33 const json = await response.json(); 24 34 25 35 // TODO validate data? ··· 28 38 await Store.set("user_id", json.user_id); 29 39 await Store.set("is_admin", json.is_admin); 30 40 await Store.set("priv_key", bufToBase64(privKeyRaw)); 41 + 42 + await Store.set("is_sharing", true); // i'm adding this so it works in settings, i think it's more user friendly to have the location to be on by default, but... less privacy friendly? wait no.. nvm. it won't share location with the server since they don't have any added friends yet :) 31 43 32 44 return json; 33 45 } ··· 41 53 await post("request-friend-request", friend_id); 42 54 } 43 55 44 - export async function isFriendRequestAccepted(friend_id: string): Promise<boolean> { 56 + export async function isFriendRequestAccepted( 57 + friend_id: string, 58 + ): Promise<boolean> { 45 59 const res = await post("is-friend-request-accepted", friend_id); 46 60 return res === "true"; 47 61 } 48 62 49 - export async function sendPings(friend_id: string, ping: string): Promise<void> { 63 + export async function sendPings( 64 + friend_id: string, 65 + ping: string, 66 + ): Promise<void> { 50 67 // later, accept a list of friend ids, but anyways this specific api won't stay in typescript for long since it needs to be run in the background 51 - await post("send-pings", JSON.stringify([{ receiver_id: friend_id, encrypted_ping: ping }])); 68 + await post( 69 + "send-pings", 70 + JSON.stringify([{ receiver_id: friend_id, encrypted_ping: ping }]), 71 + ); 52 72 } 53 73 54 74 export async function getPings(friend_id: string): Promise<string[]> { ··· 68 88 69 89 const privKeyBytes = Uint8Array.fromBase64(privKey_b64); 70 90 71 - const privKey = await crypto.subtle.importKey("pkcs8", privKeyBytes.buffer, "Ed25519", false, ["sign"]); 91 + const privKey = await crypto.subtle.importKey( 92 + "pkcs8", 93 + privKeyBytes.buffer, 94 + "Ed25519", 95 + false, 96 + ["sign"], 97 + ); 72 98 73 99 const signature = await crypto.subtle.sign("Ed25519", privKey, bodyBytes); 74 100 const signature_b64 = bufToBase64(signature);
+5 -1
app/src/utils/store.ts
··· 6 6 friends: { name: string; id: string }[]; 7 7 is_admin: boolean; 8 8 priv_key: string; 9 + is_sharing: boolean; 9 10 }; 10 11 11 12 export const Store = { ··· 19 20 return value; 20 21 }, 21 22 22 - async set<T extends keyof Settings>(key: T, value: Settings[T]): Promise<void> { 23 + async set<T extends keyof Settings>( 24 + key: T, 25 + value: Settings[T], 26 + ): Promise<void> { 23 27 const store = await TauriStore.load("settings.json"); 24 28 await store.set(key, value); 25 29 await store.save();