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 { expect } from "bun:test";
2
3export const URL = "http://127.0.0.1:3000";
4
5// Generate an Ed25519 keypair and register it with the server.
6export async function generateUser(signup_key: string | undefined, should_be_admin: boolean = false): Promise<{ user_id: string; pubKey: Uint8Array; privKey: Uint8Array }> {
7 if (!signup_key) {
8 throw new Error("signup_key was not provided or captured from server output");
9 }
10
11 // Create Ed25519 keypair
12 const keyPair = await crypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]);
13
14 // Export raw public key
15 const pubKey = new Uint8Array(await crypto.subtle.exportKey("raw", keyPair.publicKey));
16 const privKey = new Uint8Array(await crypto.subtle.exportKey("pkcs8", keyPair.privateKey));
17
18 // Base64 encode the public key
19 const pub_key_b64 = btoa(String.fromCharCode(...pubKey));
20
21 // Send signup_key and pubkey to server
22 const res = await fetch(`${URL}/create-account`, {
23 method: "POST",
24 headers: { "Content-Type": "application/json" },
25 body: JSON.stringify({ signup_key, pub_key_b64 }),
26 });
27
28 expect(res.status).toBe(200);
29 const json = await res.json();
30 expect(json).toEqual({
31 user_id: expect.any(String),
32 is_admin: should_be_admin,
33 });
34
35 return { user_id: json.user_id, pubKey, privKey };
36}
37
38export async function post(endpoint: string, user: { user_id: string; privKey: Uint8Array }, data: Object | string | undefined): Promise<any> {
39 let bodyBytes: Uint8Array;
40
41 if (typeof data === "object") {
42 const json = JSON.stringify(data);
43 bodyBytes = new TextEncoder().encode(json);
44 } else if (typeof data === "string") {
45 bodyBytes = new TextEncoder().encode(data);
46 } else {
47 bodyBytes = new Uint8Array();
48 }
49
50 // Sign body using private key
51 const privateKey = await crypto.subtle.importKey("pkcs8", user.privKey.buffer, { name: "Ed25519" }, false, ["sign"]);
52
53 const signature = new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, bodyBytes));
54 const signature_b64 = btoa(String.fromCharCode(...signature));
55
56 const authJson = JSON.stringify({
57 user_id: user.user_id,
58 signature: signature_b64, // changed key
59 });
60 const authHeader = btoa(authJson);
61
62 const headers: Record<string, string> = {
63 "x-auth": authHeader,
64 "Content-Type": "application/json",
65 };
66
67 const res = await fetch(`${URL}/${endpoint}`, {
68 method: "POST",
69 headers,
70 body: bodyBytes.length > 0 ? new TextDecoder().decode(bodyBytes) : undefined,
71 });
72
73 expect(res.status).toBe(200);
74 return await res.text();
75}