Self-hosted, federated location sharing app and server that prioritizes user privacy and security
end-to-end-encryption
location-sharing
privacy
self-hosted
federated
1import { Store } from "./store.ts";
2
3function bufToBase64(buf: ArrayBuffer): string {
4 return btoa(String.fromCharCode(...new Uint8Array(buf)));
5}
6
7function strToBytes(str: string): Uint8Array {
8 return new TextEncoder().encode(str);
9}
10
11export async function createAccount(server_url: string, signup_key: string): Promise<{ user_id: string; is_admin: boolean }> {
12 try {
13 await Store.set("server_url", server_url);
14 const keyPair = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
15 const pubKeyRaw = await crypto.subtle.exportKey("raw", keyPair.publicKey);
16 const privKeyRaw = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
17 const pub_key_b64 = bufToBase64(pubKeyRaw);
18
19 const response = await fetch(server_url + "/create-account", {
20 method: "POST",
21 headers: { "Content-Type": "application/json" },
22 body: JSON.stringify({ signup_key, pub_key_b64 }),
23 });
24
25 if (!response.ok) throw new Error(await response.text());
26 const json = await response.json();
27
28 await Store.set("user_id", json.user_id);
29 await Store.set("priv_key", bufToBase64(privKeyRaw));
30
31 return json;
32 } catch (err) {
33 alert(`${err}`);
34 throw err;
35 }
36}
37
38export async function post(endpoint: string, data: object | string | undefined): Promise<any> {
39 try {
40 const user_id = await Store.get("user_id");
41 const server_url = await Store.get("server_url");
42 console.log(`Exhibit B: ${server_url}`);
43 const privKey_b64 = await Store.get("priv_key");
44
45 if (!user_id || !privKey_b64) throw new Error("Missing user credentials");
46
47 // Prepare request body bytes
48 let bodyStr = "";
49 if (typeof data === "object") bodyStr = JSON.stringify(data);
50 else if (typeof data === "string") bodyStr = data;
51 const bodyBytes = strToBytes(bodyStr);
52
53 // Import private key and sign
54 const privKeyBytes = Uint8Array.from(atob(privKey_b64), (c) => c.charCodeAt(0));
55
56 const privKey = await crypto.subtle.importKey("pkcs8", privKeyBytes.buffer, { name: "Ed25519" }, false, ["sign"]);
57
58 const signature = await crypto.subtle.sign("Ed25519", privKey, bodyBytes);
59 const signature_b64 = bufToBase64(signature);
60
61 // Encode header JSON to base64
62 const authJson = JSON.stringify({ user_id, signature: signature_b64 });
63 const authHeader = btoa(authJson);
64
65 const headers: Record<string, string> = {
66 "x-auth": authHeader,
67 };
68 if (typeof data === "object") headers["Content-Type"] = "application/json";
69
70 const res = await fetch(`${server_url}/${endpoint}`, {
71 method: "POST",
72 headers,
73 body: bodyStr.length > 0 ? bodyStr : undefined,
74 });
75
76 if (!res.ok) throw new Error(await res.text());
77 return await res.text();
78 } catch (err) {
79 alert(`${err}`);
80 throw err;
81 }
82}