+18
-17
app/bun.lock
+18
-17
app/bun.lock
···
1
1
{
2
2
"lockfileVersion": 1,
3
+
"configVersion": 0,
3
4
"workspaces": {
4
5
"": {
5
6
"name": "privacypin",
6
7
"dependencies": {
7
-
"@tauri-apps/api": "^2",
8
-
"@tauri-apps/plugin-opener": "^2",
8
+
"@tauri-apps/api": "^2.9.0",
9
+
"@tauri-apps/plugin-opener": "^2.5.2",
9
10
"@tauri-apps/plugin-store": "^2.4.1",
10
11
"alpinejs": "^3.15.1",
11
12
},
12
13
"devDependencies": {
13
-
"@tauri-apps/cli": "^2",
14
+
"@tauri-apps/cli": "^2.9.4",
14
15
"@types/alpinejs": "^3.13.11",
15
-
"typescript": "~5.6.2",
16
-
"vite": "^6.0.3",
16
+
"typescript": "~5.6.3",
17
+
"vite": "^6.4.1",
17
18
},
18
19
},
19
20
},
···
116
117
117
118
"@tauri-apps/api": ["@tauri-apps/api@2.9.0", "", {}, "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw=="],
118
119
119
-
"@tauri-apps/cli": ["@tauri-apps/cli@2.9.3", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.9.3", "@tauri-apps/cli-darwin-x64": "2.9.3", "@tauri-apps/cli-linux-arm-gnueabihf": "2.9.3", "@tauri-apps/cli-linux-arm64-gnu": "2.9.3", "@tauri-apps/cli-linux-arm64-musl": "2.9.3", "@tauri-apps/cli-linux-riscv64-gnu": "2.9.3", "@tauri-apps/cli-linux-x64-gnu": "2.9.3", "@tauri-apps/cli-linux-x64-musl": "2.9.3", "@tauri-apps/cli-win32-arm64-msvc": "2.9.3", "@tauri-apps/cli-win32-ia32-msvc": "2.9.3", "@tauri-apps/cli-win32-x64-msvc": "2.9.3" }, "bin": { "tauri": "tauri.js" } }, "sha512-BQ7iLUXTQcyG1PpzLWeVSmBCedYDpnA/6Cm/kRFGtqjTf/eVUlyYO5S2ee07tLum3nWwDBWTGFZeruO8yEukfA=="],
120
+
"@tauri-apps/cli": ["@tauri-apps/cli@2.9.4", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.9.4", "@tauri-apps/cli-darwin-x64": "2.9.4", "@tauri-apps/cli-linux-arm-gnueabihf": "2.9.4", "@tauri-apps/cli-linux-arm64-gnu": "2.9.4", "@tauri-apps/cli-linux-arm64-musl": "2.9.4", "@tauri-apps/cli-linux-riscv64-gnu": "2.9.4", "@tauri-apps/cli-linux-x64-gnu": "2.9.4", "@tauri-apps/cli-linux-x64-musl": "2.9.4", "@tauri-apps/cli-win32-arm64-msvc": "2.9.4", "@tauri-apps/cli-win32-ia32-msvc": "2.9.4", "@tauri-apps/cli-win32-x64-msvc": "2.9.4" }, "bin": { "tauri": "tauri.js" } }, "sha512-pvylWC9QckrOS9ATWXIXcgu7g2hKK5xTL5ZQyZU/U0n9l88SEFGcWgLQNa8WZmd+wWIOWhkxOFcOl3i6ubDNNw=="],
120
121
121
-
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.9.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W8FQXZXQmQ0Fmj9UJXNrm2mLdIaLLriKVY7o/FzmizyIKTPIvHjfZALTNybbpTQRbJvKoGHLrW1DNzAWVDWJYg=="],
122
+
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9rHkMVtbMhe0AliVbrGpzMahOBg3rwV46JYRELxR9SN6iu1dvPOaMaiC4cP6M/aD1424ziXnnMdYU06RAH8oIw=="],
122
123
123
-
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.9.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-zDwu40rlshijt3TU6aRvzPUyVpapsx1sNfOlreDMTaMelQLHl6YoQzSRpLHYwrHrhimxyX2uDqnKIiuGel0Lhg=="],
124
+
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-VT9ymNuT06f5TLjCZW2hfSxbVtZDhORk7CDUDYiq5TiSYQdxkl8MVBy0CCFFcOk4QAkUmqmVUA9r3YZ/N/vPRQ=="],
124
125
125
-
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.9.3", "", { "os": "linux", "cpu": "arm" }, "sha512-+Oc2OfcTRwYtW93VJqd/HOk77buORwC9IToj/qsEvM7bTMq6Kda4alpZprzwrCHYANSw+zD8PgjJdljTpe4p+g=="],
126
+
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.9.4", "", { "os": "linux", "cpu": "arm" }, "sha512-tTWkEPig+2z3Rk0zqZYfjUYcgD+aSm72wdrIhdYobxbQZOBw0zfn50YtWv+av7bm0SHvv75f0l7JuwgZM1HFow=="],
126
127
127
-
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.9.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-59GqU/J1n9wFyAtleoQOaU0oVIo+kwQynEw4meFDoKRXszKGor6lTsbsS3r0QKLSPbc0o/yYGJhqqCtkYjb/eg=="],
128
+
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-ql6vJ611qoqRYHxkKPnb2vHa27U+YRKRmIpLMMBeZnfFtZ938eao7402AQCH1mO2+/8ioUhbpy9R/ZcLTXVmkg=="],
128
129
129
-
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.9.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-fzvG+jEn5/iYGNH6Z2IRMheYFC4pJdXa19BR9fFm6Bdn2cuajRLDKdUcEME/DCtwqclphXtFZTrT4oezY5vI/A=="],
130
+
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-vg7yNn7ICTi6hRrcA/6ff2UpZQP7un3xe3SEld5QM0prgridbKAiXGaCKr3BnUBx/rGXegQlD/wiLcWdiiraSw=="],
130
131
131
-
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.9.3", "", { "os": "linux", "cpu": "none" }, "sha512-qV8DZXI/fZwawk6T3Th1g6smiNC2KeQTk7XFgKvqZ6btC01z3UTsQmNGvI602zwm3Ld1TBZb4+rEWu2QmQimmw=="],
132
+
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.9.4", "", { "os": "linux", "cpu": "none" }, "sha512-l8L+3VxNk6yv5T/Z/gv5ysngmIpsai40B9p6NQQyqYqxImqYX37pqREoEBl1YwG7szGnDibpWhidPrWKR59OJA=="],
132
133
133
-
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.9.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tquyEONCNRfqEBWEe4eAHnxFN5yY5lFkCuD4w79XLIovUxVftQ684+xLp7zkhntkt4y20SMj2AgJa/+MOlx4Kg=="],
134
+
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-PepPhCXc/xVvE3foykNho46OmCyx47E/aG676vKTVp+mqin5d+IBqDL6wDKiGNT5OTTxKEyNlCQ81Xs2BQhhqA=="],
134
135
135
-
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.9.3", "", { "os": "linux", "cpu": "x64" }, "sha512-v2cBIB/6ji8DL+aiL5QUykU3ZO8OoJGyx50/qv2HQVzkf85KdaYSis3D/oVRemN/pcDz+vyCnnL3XnzFnDl4JQ=="],
136
+
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-zcd1QVffh5tZs1u1SCKUV/V7RRynebgYUNWHuV0FsIF1MjnULUChEXhAhug7usCDq4GZReMJOoXa6rukEozWIw=="],
136
137
137
-
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.9.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-ZGvBy7nvrHPbE0HeKp/ioaiw8bNgAHxWnb7JRZ4/G0A+oFj0SeSFxl9k5uU6FKnM7bHM23Gd1oeaDex9g5Fceg=="],
138
+
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-/7ZhnP6PY04bEob23q8MH/EoDISdmR1wuNm0k9d5HV7TDMd2GGCDa8dPXA4vJuglJKXIfXqxFmZ4L+J+MO42+w=="],
138
139
139
-
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.9.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-UsgIwOnpCoY9NK9/65QiwgmWVIE80LE7SwRYVblGtmlY9RYfsYvpbItwsovA/AcHMTiO+OCvS/q9yLeqS3m6Sg=="],
140
+
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.9.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-1LmAfaC4Cq+3O1Ir1ksdhczhdtFSTIV51tbAGtbV/mr348O+M52A/xwCCXQank0OcdBxy5BctqkMtuZnQvA8uQ=="],
140
141
141
-
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.9.3", "", { "os": "win32", "cpu": "x64" }, "sha512-fmw7NrrHE5m49idCvJAx9T9bsupjdJ0a3p3DPCNCZRGANU6R1tA1L+KTlVuUtdAldX2NqU/9UPo2SCslYKgJHQ=="],
142
+
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-EdYd4c9wGvtPB95kqtEyY+bUR+k4kRw3IA30mAQ1jPH6z57AftT8q84qwv0RDp6kkEqOBKxeInKfqi4BESYuqg=="],
142
143
143
144
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew=="],
144
145
+2
-1
app/index.html
+2
-1
app/index.html
···
2
2
import { Store } from "/src/utils/store.ts";
3
3
4
4
// For testing (to easily go to home or signup page). You only need to uncomment the right line, run it once, and comment it out again right after
5
-
// await Store.reset();
5
+
await Store.reset();
6
6
// await Store.set("user_id", "adummyuserid");
7
7
8
+
// await alert(appLocalDataDirPath); // ~/.local/share/dev.azom.privacypin
8
9
if (await Store.isLoggedIn()) {
9
10
window.location.href = "/src/home-page/home.html";
10
11
} else {
+5
-5
app/package.json
+5
-5
app/package.json
···
10
10
"tauri": "WEBKIT_DISABLE_DMABUF_RENDERER=1 tauri"
11
11
},
12
12
"dependencies": {
13
-
"@tauri-apps/api": "^2",
14
-
"@tauri-apps/plugin-opener": "^2",
13
+
"@tauri-apps/api": "^2.9.0",
14
+
"@tauri-apps/plugin-opener": "^2.5.2",
15
15
"@tauri-apps/plugin-store": "^2.4.1",
16
16
"alpinejs": "^3.15.1"
17
17
},
18
18
"devDependencies": {
19
-
"@tauri-apps/cli": "^2",
19
+
"@tauri-apps/cli": "^2.9.4",
20
20
"@types/alpinejs": "^3.13.11",
21
-
"typescript": "~5.6.2",
22
-
"vite": "^6.0.3"
21
+
"typescript": "~5.6.3",
22
+
"vite": "^6.4.1"
23
23
}
24
24
}
app/src/add-friend-page/add-friend.css
app/src/add-friend-page/add-friend.css
This is a binary file and will not be displayed.
app/src/add-friend-page/add-friend.html
app/src/add-friend-page/add-friend.html
This is a binary file and will not be displayed.
app/src/add-friend-page/add-friend.ts
app/src/add-friend-page/add-friend.ts
This is a binary file and will not be displayed.
+4
-5
app/src/signup-page/signup.ts
+4
-5
app/src/signup-page/signup.ts
···
1
1
import Alpine from "alpinejs";
2
2
import { createAccount } from "../utils/api.ts";
3
-
import { Store } from "../utils/store.ts";
4
3
5
4
Alpine.data("signupPageState", () => ({
6
5
serverAddress: "",
···
9
8
10
9
async signup() {
11
10
this.isDoingStuff = true;
12
-
await new Promise((resolve) => setTimeout(resolve, 2000)); // temp
11
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // temp
13
12
try {
14
-
const res = await createAccount(this.serverAddress, this.signupKey);
15
-
Store.set("is_admin", res.is_admin);
16
-
Store.set("user_id", res.user_id);
13
+
await createAccount(this.serverAddress, this.signupKey);
17
14
window.location.href = "/src/home-page/home.html";
18
15
} catch (e) {
16
+
const err = e instanceof Error ? e.message : e;
17
+
alert(`Sign-up failed: ${err}`);
19
18
this.isDoingStuff = false;
20
19
}
21
20
},
+53
app/src/types.d.ts
+53
app/src/types.d.ts
···
1
+
// THIS FILE IS TEMPORARY UNTIL WE CAN HAVE A TYPESCRIPT VERSION THAT INCLUDE THE BASE64 <-> UINT8ARRAY CONVERSION STUFF
2
+
3
+
declare global {
4
+
interface Uint8Array {
5
+
/**
6
+
* Converts this `Uint8Array` to a Base64 or Base64URL encoded string.
7
+
*
8
+
* @param options Optional configuration:
9
+
* - `alphabet`: Selects between `"base64"` (default) and `"base64url"` alphabets.
10
+
* - `omitPadding`: If true, omits the trailing `=` padding characters.
11
+
*
12
+
* @returns The Base64-encoded representation of the byte array.
13
+
*
14
+
* @example
15
+
* ```ts
16
+
* const bytes = new Uint8Array([72, 101, 108, 108, 111]);
17
+
* console.log(bytes.toBase64()); // "SGVsbG8="
18
+
* ```
19
+
*/
20
+
toBase64(options?: { alphabet?: "base64" | "base64url"; omitPadding?: boolean }): string;
21
+
}
22
+
23
+
interface Uint8ArrayConstructor {
24
+
/**
25
+
* Creates a `Uint8Array` from a Base64 or Base64URL encoded string.
26
+
*
27
+
* @param base64 The input string to decode.
28
+
* @param options Optional configuration:
29
+
* - `alphabet`: Selects between `"base64"` (default) and `"base64url"` alphabets.
30
+
* - `lastChunkHandling`: Controls how to handle incomplete input:
31
+
* - `"strict"` (default): Throws an error if input is not valid Base64.
32
+
* - `"loose"`: Tolerates missing padding or invalid trailing characters.
33
+
* - `"stop-before-partial"`: Ignores an incomplete trailing chunk.
34
+
*
35
+
* @returns A new `Uint8Array` containing the decoded bytes.
36
+
*
37
+
* @example
38
+
* ```ts
39
+
* const bytes = Uint8Array.fromBase64("SGVsbG8=");
40
+
* console.log(new TextDecoder().decode(bytes)); // "Hello"
41
+
* ```
42
+
*/
43
+
fromBase64(
44
+
base64: string,
45
+
options?: {
46
+
alphabet?: "base64" | "base64url";
47
+
lastChunkHandling?: "loose" | "strict" | "stop-before-partial";
48
+
},
49
+
): Uint8Array;
50
+
}
51
+
}
52
+
53
+
export {};
+69
-60
app/src/utils/api.ts
+69
-60
app/src/utils/api.ts
···
1
1
import { Store } from "./store.ts";
2
2
3
3
function bufToBase64(buf: ArrayBuffer): string {
4
-
return btoa(String.fromCharCode(...new Uint8Array(buf)));
4
+
return new Uint8Array(buf).toBase64();
5
5
}
6
6
7
-
function strToBytes(str: string): Uint8Array {
8
-
return new TextEncoder().encode(str);
7
+
/**
8
+
* This function can throw an error
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"]);
12
+
const pubKeyRaw = await crypto.subtle.exportKey("raw", keyPair.publicKey);
13
+
const privKeyRaw = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
14
+
const pub_key_b64 = bufToBase64(pubKeyRaw);
15
+
16
+
const response = await fetch(server_url + "/create-account", {
17
+
method: "POST",
18
+
headers: { "Content-Type": "application/json" },
19
+
body: JSON.stringify({ signup_key, pub_key_b64 }),
20
+
});
21
+
22
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
23
+
const json = await response.json();
24
+
25
+
// TODO validate data?
26
+
27
+
await Store.set("server_url", server_url);
28
+
await Store.set("user_id", json.user_id);
29
+
await Store.set("is_admin", json.is_admin);
30
+
await Store.set("priv_key", bufToBase64(privKeyRaw));
31
+
32
+
return json;
9
33
}
10
34
11
-
export 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);
35
+
export async function generateSignupKey(): Promise<string> {
36
+
return await post("generate-signup-key");
37
+
}
18
38
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
-
});
39
+
export async function createFriendRequest(friend_id: string): Promise<void> {
40
+
await post("create-friend-request", friend_id);
41
+
}
24
42
25
-
if (!response.ok) throw new Error(await response.text());
26
-
const json = await response.json();
43
+
export async function acceptFriendRequest(friend_id: string): Promise<void> {
44
+
await post("accept-friend-request", friend_id);
45
+
}
27
46
28
-
await Store.set("user_id", json.user_id);
29
-
await Store.set("priv_key", bufToBase64(privKeyRaw));
47
+
export async function isFriendRequestAccepted(friend_id: string): Promise<boolean> {
48
+
const res = await post("is-friend-request-accepted", friend_id);
49
+
return res === "true";
50
+
}
30
51
31
-
return json;
32
-
} catch (err) {
33
-
alert(`${err}`);
34
-
throw err;
35
-
}
52
+
export async function sendPings(friend_id: string, ping: string): Promise<void> {
53
+
await post("send-pings", JSON.stringify({ receiver_id: friend_id, encrypted_ping: ping }));
36
54
}
37
55
38
-
export 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");
56
+
export async function getPings(friend_id: string): Promise<string[]> {
57
+
const res = await post("get-pings", friend_id);
58
+
return JSON.parse(res);
59
+
}
46
60
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);
61
+
/**
62
+
* This function can throw an error
63
+
*/
64
+
async function post(endpoint: string, body: string | undefined = undefined) {
65
+
const user_id = await Store.get("user_id");
66
+
const server_url = await Store.get("server_url");
67
+
const privKey_b64 = await Store.get("priv_key");
52
68
53
-
// Import private key and sign
54
-
const privKeyBytes = Uint8Array.from(atob(privKey_b64), (c) => c.charCodeAt(0));
69
+
const bodyBytes = new TextEncoder().encode(body === undefined ? "" : body);
55
70
56
-
const privKey = await crypto.subtle.importKey("pkcs8", privKeyBytes.buffer, { name: "Ed25519" }, false, ["sign"]);
71
+
const privKeyBytes = Uint8Array.fromBase64(privKey_b64);
57
72
58
-
const signature = await crypto.subtle.sign("Ed25519", privKey, bodyBytes);
59
-
const signature_b64 = bufToBase64(signature);
73
+
const privKey = await crypto.subtle.importKey("pkcs8", privKeyBytes.buffer, "Ed25519", false, ["sign"]);
60
74
61
-
// Encode header JSON to base64
62
-
const authJson = JSON.stringify({ user_id, signature: signature_b64 });
63
-
const authHeader = btoa(authJson);
75
+
const signature = await crypto.subtle.sign("Ed25519", privKey, bodyBytes);
76
+
const signature_b64 = bufToBase64(signature);
64
77
65
-
const headers: Record<string, string> = {
66
-
"x-auth": authHeader,
67
-
};
68
-
if (typeof data === "object") headers["Content-Type"] = "application/json";
78
+
const headers = {
79
+
"x-auth": JSON.stringify({ user_id, signature: signature_b64 }),
80
+
"Content-Type": "application/json", // TODO: not always json tho, but does it matter?
81
+
};
69
82
70
-
const res = await fetch(`${server_url}/${endpoint}`, {
71
-
method: "POST",
72
-
headers,
73
-
body: bodyStr.length > 0 ? bodyStr : undefined,
74
-
});
83
+
const res = await fetch(`${server_url}/${endpoint}`, {
84
+
method: "POST",
85
+
headers,
86
+
body: bodyBytes, // TODO: do we need to send bodyBytes instead to match server side auth?
87
+
});
75
88
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
-
}
89
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
90
+
return await res.text();
82
91
}
+190
-60
server/Cargo.lock
+190
-60
server/Cargo.lock
···
3
3
version = 4
4
4
5
5
[[package]]
6
+
name = "aho-corasick"
7
+
version = "1.1.4"
8
+
source = "registry+https://github.com/rust-lang/crates.io-index"
9
+
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
10
+
dependencies = [
11
+
"memchr",
12
+
]
13
+
14
+
[[package]]
6
15
name = "atomic-waker"
7
16
version = "1.1.2"
8
17
source = "registry+https://github.com/rust-lang/crates.io-index"
···
10
19
11
20
[[package]]
12
21
name = "axum"
13
-
version = "0.8.6"
22
+
version = "0.8.8"
14
23
source = "registry+https://github.com/rust-lang/crates.io-index"
15
-
checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871"
24
+
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
16
25
dependencies = [
17
26
"axum-core",
18
27
"bytes",
···
43
52
44
53
[[package]]
45
54
name = "axum-core"
46
-
version = "0.5.5"
55
+
version = "0.5.6"
47
56
source = "registry+https://github.com/rust-lang/crates.io-index"
48
-
checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22"
57
+
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
49
58
dependencies = [
50
59
"bytes",
51
60
"futures-core",
···
83
92
84
93
[[package]]
85
94
name = "bytes"
86
-
version = "1.10.1"
95
+
version = "1.11.0"
87
96
source = "registry+https://github.com/rust-lang/crates.io-index"
88
-
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
97
+
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
89
98
90
99
[[package]]
91
100
name = "cfg-if"
···
110
119
111
120
[[package]]
112
121
name = "crypto-common"
113
-
version = "0.2.0-rc.5"
122
+
version = "0.2.0-rc.8"
114
123
source = "registry+https://github.com/rust-lang/crates.io-index"
115
-
checksum = "919bd05924682a5480aec713596b9e2aabed3a0a6022fab6847f85a99e5f190a"
124
+
checksum = "e6165b8029cdc3e765b74d3548f85999ee799d5124877ce45c2c85ca78e4d4aa"
116
125
dependencies = [
117
126
"hybrid-array",
118
127
]
119
128
120
129
[[package]]
121
130
name = "curve25519-dalek"
122
-
version = "5.0.0-pre.1"
131
+
version = "5.0.0-pre.3"
123
132
source = "registry+https://github.com/rust-lang/crates.io-index"
124
-
checksum = "6f9200d1d13637f15a6acb71e758f64624048d85b31a5fdbfd8eca1e2687d0b7"
133
+
checksum = "92419e1cdc506051ffd30713ad09d0ec6a24bba9197e12989de389e35b19c77a"
125
134
dependencies = [
126
135
"cfg-if",
127
136
"cpufeatures",
···
146
155
147
156
[[package]]
148
157
name = "der"
149
-
version = "0.8.0-rc.9"
158
+
version = "0.8.0-rc.10"
150
159
source = "registry+https://github.com/rust-lang/crates.io-index"
151
-
checksum = "e9d8dd2f26c86b27a2a8ea2767ec7f9df7a89516e4794e54ac01ee618dda3aa4"
160
+
checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653"
152
161
dependencies = [
153
162
"const-oid",
154
163
]
155
164
156
165
[[package]]
157
166
name = "digest"
158
-
version = "0.11.0-rc.4"
167
+
version = "0.11.0-rc.5"
159
168
source = "registry+https://github.com/rust-lang/crates.io-index"
160
-
checksum = "ea390c940e465846d64775e55e3115d5dc934acb953de6f6e6360bc232fe2bf7"
169
+
checksum = "ebf9423bafb058e4142194330c52273c343f8a5beb7176d052f0e73b17dd35b9"
161
170
dependencies = [
162
171
"block-buffer",
163
172
"crypto-common",
···
175
184
176
185
[[package]]
177
186
name = "ed25519-dalek"
178
-
version = "3.0.0-pre.1"
187
+
version = "3.0.0-pre.3"
179
188
source = "registry+https://github.com/rust-lang/crates.io-index"
180
-
checksum = "ad207ed88a133091f83224265eac21109930db09bedcad05d5252f2af2de20a1"
189
+
checksum = "5d6d275a4ffdfc16e98fbcb5f5417214a06957c7cdc6eb2815c2dc50dce1c1dd"
181
190
dependencies = [
182
191
"curve25519-dalek",
183
192
"ed25519",
···
187
196
]
188
197
189
198
[[package]]
199
+
name = "errno"
200
+
version = "0.3.14"
201
+
source = "registry+https://github.com/rust-lang/crates.io-index"
202
+
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
203
+
dependencies = [
204
+
"libc",
205
+
"windows-sys 0.61.2",
206
+
]
207
+
208
+
[[package]]
190
209
name = "fiat-crypto"
191
210
version = "0.3.0"
192
211
source = "registry+https://github.com/rust-lang/crates.io-index"
193
212
checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
194
213
195
214
[[package]]
196
-
name = "fnv"
197
-
version = "1.0.7"
198
-
source = "registry+https://github.com/rust-lang/crates.io-index"
199
-
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
200
-
201
-
[[package]]
202
215
name = "form_urlencoded"
203
216
version = "1.2.2"
204
217
source = "registry+https://github.com/rust-lang/crates.io-index"
···
253
266
254
267
[[package]]
255
268
name = "http"
256
-
version = "1.3.1"
269
+
version = "1.4.0"
257
270
source = "registry+https://github.com/rust-lang/crates.io-index"
258
-
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
271
+
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
259
272
dependencies = [
260
273
"bytes",
261
-
"fnv",
262
274
"itoa",
263
275
]
264
276
···
308
320
309
321
[[package]]
310
322
name = "hyper"
311
-
version = "1.7.0"
323
+
version = "1.8.1"
312
324
source = "registry+https://github.com/rust-lang/crates.io-index"
313
-
checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
325
+
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
314
326
dependencies = [
315
327
"atomic-waker",
316
328
"bytes",
···
329
341
330
342
[[package]]
331
343
name = "hyper-util"
332
-
version = "0.1.17"
344
+
version = "0.1.19"
333
345
source = "registry+https://github.com/rust-lang/crates.io-index"
334
-
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
346
+
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
335
347
dependencies = [
336
348
"bytes",
337
349
"futures-core",
···
345
357
346
358
[[package]]
347
359
name = "itoa"
348
-
version = "1.0.15"
360
+
version = "1.0.17"
349
361
source = "registry+https://github.com/rust-lang/crates.io-index"
350
-
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
362
+
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
363
+
364
+
[[package]]
365
+
name = "lazy_static"
366
+
version = "1.5.0"
367
+
source = "registry+https://github.com/rust-lang/crates.io-index"
368
+
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
351
369
352
370
[[package]]
353
371
name = "libc"
354
-
version = "0.2.177"
372
+
version = "0.2.178"
355
373
source = "registry+https://github.com/rust-lang/crates.io-index"
356
-
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
374
+
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
357
375
358
376
[[package]]
359
377
name = "lock_api"
···
366
384
367
385
[[package]]
368
386
name = "log"
369
-
version = "0.4.28"
387
+
version = "0.4.29"
370
388
source = "registry+https://github.com/rust-lang/crates.io-index"
371
-
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
389
+
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
390
+
391
+
[[package]]
392
+
name = "matchers"
393
+
version = "0.2.0"
394
+
source = "registry+https://github.com/rust-lang/crates.io-index"
395
+
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
396
+
dependencies = [
397
+
"regex-automata",
398
+
]
372
399
373
400
[[package]]
374
401
name = "matchit"
···
390
417
391
418
[[package]]
392
419
name = "mio"
393
-
version = "1.1.0"
420
+
version = "1.1.1"
394
421
source = "registry+https://github.com/rust-lang/crates.io-index"
395
-
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
422
+
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
396
423
dependencies = [
397
424
"libc",
398
425
"wasi",
···
406
433
checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8"
407
434
dependencies = [
408
435
"rand",
436
+
]
437
+
438
+
[[package]]
439
+
name = "nu-ansi-term"
440
+
version = "0.50.3"
441
+
source = "registry+https://github.com/rust-lang/crates.io-index"
442
+
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
443
+
dependencies = [
444
+
"windows-sys 0.61.2",
409
445
]
410
446
411
447
[[package]]
···
476
512
477
513
[[package]]
478
514
name = "proc-macro2"
479
-
version = "1.0.103"
515
+
version = "1.0.104"
480
516
source = "registry+https://github.com/rust-lang/crates.io-index"
481
-
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
517
+
checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
482
518
dependencies = [
483
519
"unicode-ident",
484
520
]
···
532
568
]
533
569
534
570
[[package]]
571
+
name = "regex-automata"
572
+
version = "0.4.13"
573
+
source = "registry+https://github.com/rust-lang/crates.io-index"
574
+
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
575
+
dependencies = [
576
+
"aho-corasick",
577
+
"memchr",
578
+
"regex-syntax",
579
+
]
580
+
581
+
[[package]]
582
+
name = "regex-syntax"
583
+
version = "0.8.8"
584
+
source = "registry+https://github.com/rust-lang/crates.io-index"
585
+
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
586
+
587
+
[[package]]
535
588
name = "rust-server"
536
589
version = "0.1.0"
537
590
dependencies = [
···
543
596
"serde_json",
544
597
"tokio",
545
598
"tower-http",
599
+
"tracing",
600
+
"tracing-subscriber",
546
601
]
547
602
548
603
[[package]]
···
556
611
557
612
[[package]]
558
613
name = "ryu"
559
-
version = "1.0.20"
614
+
version = "1.0.22"
560
615
source = "registry+https://github.com/rust-lang/crates.io-index"
561
-
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
616
+
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
562
617
563
618
[[package]]
564
619
name = "scopeguard"
···
604
659
605
660
[[package]]
606
661
name = "serde_json"
607
-
version = "1.0.145"
662
+
version = "1.0.148"
608
663
source = "registry+https://github.com/rust-lang/crates.io-index"
609
-
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
664
+
checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
610
665
dependencies = [
611
666
"itoa",
612
667
"memchr",
613
-
"ryu",
614
668
"serde",
615
669
"serde_core",
670
+
"zmij",
616
671
]
617
672
618
673
[[package]]
···
650
705
]
651
706
652
707
[[package]]
708
+
name = "sharded-slab"
709
+
version = "0.1.7"
710
+
source = "registry+https://github.com/rust-lang/crates.io-index"
711
+
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
712
+
dependencies = [
713
+
"lazy_static",
714
+
]
715
+
716
+
[[package]]
653
717
name = "signal-hook-registry"
654
-
version = "1.4.6"
718
+
version = "1.4.8"
655
719
source = "registry+https://github.com/rust-lang/crates.io-index"
656
-
checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
720
+
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
657
721
dependencies = [
722
+
"errno",
658
723
"libc",
659
724
]
660
725
661
726
[[package]]
662
727
name = "signature"
663
-
version = "3.0.0-rc.5"
728
+
version = "3.0.0-rc.6"
664
729
source = "registry+https://github.com/rust-lang/crates.io-index"
665
-
checksum = "2a0251c9d6468f4ba853b6352b190fb7c1e405087779917c238445eb03993826"
730
+
checksum = "597a96996ccff7dfa16f052bd995b4cecc72af22c35138738dc029f0ead6608d"
666
731
667
732
[[package]]
668
733
name = "smallvec"
···
697
762
698
763
[[package]]
699
764
name = "syn"
700
-
version = "2.0.109"
765
+
version = "2.0.112"
701
766
source = "registry+https://github.com/rust-lang/crates.io-index"
702
-
checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f"
767
+
checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4"
703
768
dependencies = [
704
769
"proc-macro2",
705
770
"quote",
···
713
778
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
714
779
715
780
[[package]]
781
+
name = "thread_local"
782
+
version = "1.1.9"
783
+
source = "registry+https://github.com/rust-lang/crates.io-index"
784
+
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
785
+
dependencies = [
786
+
"cfg-if",
787
+
]
788
+
789
+
[[package]]
716
790
name = "tokio"
717
791
version = "1.48.0"
718
792
source = "registry+https://github.com/rust-lang/crates.io-index"
···
758
832
759
833
[[package]]
760
834
name = "tower-http"
761
-
version = "0.6.6"
835
+
version = "0.6.8"
762
836
source = "registry+https://github.com/rust-lang/crates.io-index"
763
-
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
837
+
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
764
838
dependencies = [
765
839
"bitflags",
766
840
"bytes",
767
841
"http",
842
+
"http-body",
768
843
"pin-project-lite",
769
844
"tower-layer",
770
845
"tower-service",
846
+
"tracing",
771
847
]
772
848
773
849
[[package]]
···
784
860
785
861
[[package]]
786
862
name = "tracing"
787
-
version = "0.1.41"
863
+
version = "0.1.44"
788
864
source = "registry+https://github.com/rust-lang/crates.io-index"
789
-
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
865
+
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
790
866
dependencies = [
791
867
"log",
792
868
"pin-project-lite",
869
+
"tracing-attributes",
793
870
"tracing-core",
794
871
]
795
872
796
873
[[package]]
874
+
name = "tracing-attributes"
875
+
version = "0.1.31"
876
+
source = "registry+https://github.com/rust-lang/crates.io-index"
877
+
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
878
+
dependencies = [
879
+
"proc-macro2",
880
+
"quote",
881
+
"syn",
882
+
]
883
+
884
+
[[package]]
797
885
name = "tracing-core"
798
-
version = "0.1.34"
886
+
version = "0.1.36"
799
887
source = "registry+https://github.com/rust-lang/crates.io-index"
800
-
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
888
+
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
801
889
dependencies = [
802
890
"once_cell",
891
+
"valuable",
892
+
]
893
+
894
+
[[package]]
895
+
name = "tracing-log"
896
+
version = "0.2.0"
897
+
source = "registry+https://github.com/rust-lang/crates.io-index"
898
+
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
899
+
dependencies = [
900
+
"log",
901
+
"once_cell",
902
+
"tracing-core",
903
+
]
904
+
905
+
[[package]]
906
+
name = "tracing-subscriber"
907
+
version = "0.3.22"
908
+
source = "registry+https://github.com/rust-lang/crates.io-index"
909
+
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
910
+
dependencies = [
911
+
"matchers",
912
+
"nu-ansi-term",
913
+
"once_cell",
914
+
"regex-automata",
915
+
"sharded-slab",
916
+
"smallvec",
917
+
"thread_local",
918
+
"tracing",
919
+
"tracing-core",
920
+
"tracing-log",
803
921
]
804
922
805
923
[[package]]
···
813
931
version = "1.0.22"
814
932
source = "registry+https://github.com/rust-lang/crates.io-index"
815
933
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
934
+
935
+
[[package]]
936
+
name = "valuable"
937
+
version = "0.1.1"
938
+
source = "registry+https://github.com/rust-lang/crates.io-index"
939
+
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
816
940
817
941
[[package]]
818
942
name = "wasi"
···
911
1035
912
1036
[[package]]
913
1037
name = "zerocopy"
914
-
version = "0.8.27"
1038
+
version = "0.8.31"
915
1039
source = "registry+https://github.com/rust-lang/crates.io-index"
916
-
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
1040
+
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
917
1041
dependencies = [
918
1042
"zerocopy-derive",
919
1043
]
920
1044
921
1045
[[package]]
922
1046
name = "zerocopy-derive"
923
-
version = "0.8.27"
1047
+
version = "0.8.31"
924
1048
source = "registry+https://github.com/rust-lang/crates.io-index"
925
-
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
1049
+
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
926
1050
dependencies = [
927
1051
"proc-macro2",
928
1052
"quote",
···
934
1058
version = "1.8.2"
935
1059
source = "registry+https://github.com/rust-lang/crates.io-index"
936
1060
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
1061
+
1062
+
[[package]]
1063
+
name = "zmij"
1064
+
version = "1.0.7"
1065
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1066
+
checksum = "de9211a9f64b825911bdf0240f58b7a8dac217fe260fc61f080a07f61372fbd5"
+3
-1
server/Cargo.toml
+3
-1
server/Cargo.toml
···
11
11
serde = { version = "1.0.228", features = ["derive"] }
12
12
serde_json = "1.0.145"
13
13
tokio = { version = "1.48.0", features = ["full"] }
14
-
tower-http = {version="0.6.6", features=["cors"]}
14
+
tower-http = {version="0.6.6", features=["cors", "trace"]}
15
+
tracing = "0.1.44"
16
+
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
+99
server/src/auth.rs
+99
server/src/auth.rs
···
1
+
use axum::{body::Body, extract::State, http::Request, middleware::Next};
2
+
use base64::{Engine, prelude::BASE64_STANDARD};
3
+
use ed25519_dalek::Signature;
4
+
5
+
use crate::{
6
+
ReqBail, SrvErr,
7
+
types::{AppState, AuthData},
8
+
};
9
+
10
+
pub async fn auth_test(
11
+
State(state): State<AppState>,
12
+
req: Request<Body>,
13
+
next: Next,
14
+
) -> Result<axum::response::Response, SrvErr> {
15
+
let endpoint = req.uri().path().to_owned();
16
+
if endpoint != "/create-account" {
17
+
// CURSED STUFF BEGIN
18
+
let (parts, body) = req.into_parts();
19
+
let body_bytes = axum::body::to_bytes(body, usize::MAX).await.unwrap();
20
+
let new_body = Body::from(body_bytes.clone());
21
+
let mut req = Request::from_parts(parts, new_body);
22
+
// CURSED STUFF END
23
+
24
+
let auth_header = req
25
+
.headers()
26
+
.get("x-auth")
27
+
.and_then(|v| v.to_str().ok())
28
+
.ok_or(SrvErr!("missing x-auth header"))?;
29
+
30
+
let decoded_auth = BASE64_STANDARD
31
+
.decode(auth_header)
32
+
.map_err(|e| SrvErr!("invalid base64 in x-auth header", e))?;
33
+
34
+
let auth_str = String::from_utf8(decoded_auth)
35
+
.map_err(|e| SrvErr!("invalid utf8 in x-auth header", e))?;
36
+
37
+
let auth_data: AuthData = serde_json::from_str(&auth_str)
38
+
.map_err(|e| SrvErr!("failed to parse x-auth JSON", e))?;
39
+
40
+
let users = state.users.lock().await;
41
+
let user_id = auth_data.user_id;
42
+
let user = users
43
+
.iter()
44
+
.find(|u| u.id == user_id)
45
+
.ok_or(SrvErr!("User not found"))?;
46
+
let verifying_key = user.pub_key.clone();
47
+
48
+
// NOTE (key chaining):
49
+
// Do NOT drop the `users` lock until after both steps are complete:
50
+
// 1) verify the request using the current stored key
51
+
// 2) update the stored key to the next key from this request
52
+
//
53
+
// If we unlock in between, a replay/duplicate of the same request can race:
54
+
//
55
+
// - Request A reads pk_i and starts verifying
56
+
// - Attacker replays A (same signature) while A is still verifying
57
+
// - Replay gets verified against pk_i and proceeds to update pk -> pk_attacker
58
+
// - Request A finishes and updates pk again, but the replay has already
59
+
// been accepted and may have advanced the key to an attacker-chosen value
60
+
//
61
+
// Keeping the lock across "verify + update" makes the transition atomic.
62
+
drop(users);
63
+
64
+
////////////////////////////////////
65
+
//////////////////////////////////// unsure
66
+
////////////////////////////////////
67
+
68
+
let sig_vec = BASE64_STANDARD
69
+
.decode(&auth_data.signature)
70
+
.map_err(|e| SrvErr!("base64 decode fail", e))?;
71
+
let sig_bytes: [u8; 64] = sig_vec
72
+
.try_into()
73
+
.map_err(|e| SrvErr!("invalid signature length", e))?;
74
+
let signature = Signature::from_bytes(&sig_bytes);
75
+
76
+
if let Err(err) = verifying_key.verify_strict(&body_bytes, &signature) {
77
+
panic!("Signature verification failed: {err}");
78
+
}
79
+
80
+
////////////////////////////////////
81
+
////////////////////////////////////
82
+
////////////////////////////////////
83
+
84
+
// TODO: Make the endpoints as enums at some point
85
+
if endpoint == "/generate-signup-key" {
86
+
let admin_id = state.admin_id.lock().await;
87
+
if admin_id.as_ref() != Some(&user_id) {
88
+
ReqBail!("not allowed: admin only");
89
+
}
90
+
}
91
+
92
+
req.extensions_mut().insert(user_id); // pass user_id to the actual request handler, whatever it is, in handlers.rs
93
+
*req.body_mut() = Body::from(body_bytes);
94
+
95
+
return Ok(next.run(req).await);
96
+
}
97
+
98
+
return Ok(next.run(req).await);
99
+
}
+17
-23
server/src/handlers.rs
+17
-23
server/src/handlers.rs
···
3
3
use ed25519_dalek::VerifyingKey;
4
4
use nanoid::nanoid;
5
5
6
-
use crate::types::*;
7
-
8
-
macro_rules! my_err {
9
-
($msg:expr) => {
10
-
Err(MyErr($msg))
11
-
};
12
-
}
6
+
use crate::{ReqBail, SrvErr, types::*};
13
7
14
8
pub async fn create_user(
15
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
16
10
Json(payload): Json<CreateUserRequest>,
17
-
) -> Result<Json<CreateAccountResponse>, MyErr> {
11
+
) -> Result<Json<CreateAccountResponse>, SrvErr> {
18
12
let key_used = { state.signup_keys.lock().await.remove(&payload.signup_key) };
19
13
20
14
if !key_used {
21
-
return my_err!("Signup key was not there");
15
+
ReqBail!("Signup key was not there");
22
16
}
23
17
24
18
// todo check
25
19
let pub_key_bytes = match BASE64_STANDARD.decode(&payload.pub_key_b64) {
26
20
Ok(b) => b,
27
-
Err(_) => return my_err!("Invalid base64 public key"),
21
+
Err(_) => ReqBail!("Invalid base64 public key"),
28
22
};
29
23
30
24
// todo check
31
25
let pub_key = match VerifyingKey::from_bytes(
32
26
&pub_key_bytes
33
27
.try_into()
34
-
.map_err(|_| MyErr("Invalid pubkey length".into()))?,
28
+
.map_err(|_| SrvErr("Invalid pubkey length".into()))?,
35
29
) {
36
30
Ok(pk) => pk,
37
-
Err(_) => return my_err!("Invalid public key bytes"),
31
+
Err(_) => ReqBail!("Invalid public key bytes"),
38
32
};
39
33
40
34
let user_id = nanoid!(5);
···
54
48
return Ok(Json(CreateAccountResponse { user_id, is_admin }));
55
49
}
56
50
57
-
pub async fn generate_signup_key(State(state): State<AppState>) -> Result<String, MyErr> {
51
+
pub async fn generate_signup_key(State(state): State<AppState>) -> Result<String, SrvErr> {
58
52
let new_signup_key = nanoid!(5);
59
53
let mut signup_keys = state.signup_keys.lock().await;
60
54
···
68
62
State(state): State<AppState>,
69
63
Extension(user_id): Extension<String>,
70
64
accepter_id: String,
71
-
) -> Result<(), MyErr> {
65
+
) -> Result<(), SrvErr> {
72
66
if accepter_id == user_id {
73
-
return my_err!("Cannot friend yourself");
67
+
ReqBail!("Cannot friend yourself");
74
68
}
75
69
76
70
let mut friend_requests = state.friend_requests.lock().await;
77
71
let link = Link::new(accepter_id, user_id);
78
72
if friend_requests.contains(&link) {
79
-
return my_err!("Friend request already exists");
73
+
ReqBail!("Friend request already exists");
80
74
}
81
75
friend_requests.insert(link);
82
76
return Ok(());
···
86
80
State(state): State<AppState>,
87
81
Extension(user_id): Extension<String>,
88
82
sender_id: String,
89
-
) -> Result<(), MyErr> {
83
+
) -> Result<(), SrvErr> {
90
84
let link = Link::new(user_id, sender_id);
91
85
92
86
let friend_request_accepted = { state.friend_requests.lock().await.remove(&link) };
93
87
94
88
if !friend_request_accepted {
95
-
return my_err!("Friend request not found");
89
+
ReqBail!("Friend request not found");
96
90
}
97
91
98
92
let mut pings_state = state.positions.lock().await;
···
109
103
State(state): State<AppState>,
110
104
Extension(user_id): Extension<String>,
111
105
friend_id: String,
112
-
) -> Result<PlainBool, MyErr> {
106
+
) -> Result<PlainBool, SrvErr> {
113
107
let link = Link::new(friend_id, user_id);
114
108
let links = state.links.lock().await;
115
109
let accepted = links.contains(&link);
···
120
114
State(state): State<AppState>,
121
115
Extension(user_id): Extension<String>,
122
116
Json(pings): Json<Vec<PingPayload>>,
123
-
) -> Result<(), MyErr> {
117
+
) -> Result<(), SrvErr> {
124
118
let links = state.links.lock().await;
125
119
for ping in &pings {
126
120
let link = Link::new(user_id.clone(), ping.receiver_id.clone());
127
121
if !links.contains(&link) {
128
-
return my_err!("Ping receiver is not linked to sender");
122
+
ReqBail!("Ping receiver is not linked to sender");
129
123
}
130
124
}
131
125
drop(links);
···
147
141
State(state): State<AppState>,
148
142
Extension(user_id): Extension<String>,
149
143
sender_id: String,
150
-
) -> Result<EncryptedPingVec, MyErr> {
144
+
) -> Result<EncryptedPingVec, SrvErr> {
151
145
let link = Link::new(user_id, sender_id);
152
146
let links = state.links.lock().await;
153
147
154
148
if !links.contains(&link) {
155
-
return my_err!("No link exists between these users");
149
+
ReqBail!("No link exists between these users");
156
150
}
157
151
drop(links);
158
152
+208
server/src/log.rs
+208
server/src/log.rs
···
1
+
use std::{
2
+
sync::atomic::{AtomicU64, Ordering},
3
+
time::Instant,
4
+
};
5
+
6
+
use axum::{
7
+
body::{Body, Bytes, to_bytes},
8
+
http::{HeaderMap, Method, Request},
9
+
middleware::Next,
10
+
response::Response,
11
+
};
12
+
use base64::{Engine, prelude::BASE64_STANDARD};
13
+
use serde_json::Value;
14
+
15
+
static REQ_SEQ: AtomicU64 = AtomicU64::new(1);
16
+
17
+
fn format_x_auth(headers: &HeaderMap) -> Option<String> {
18
+
let v = headers.get("x-auth")?;
19
+
let s = v.to_str().ok()?.to_string();
20
+
21
+
const MAX: usize = 120;
22
+
if s.len() > MAX {
23
+
Some(format!("{}… (len={})", &s[..MAX], s.len()))
24
+
} else {
25
+
Some(s)
26
+
}
27
+
}
28
+
29
+
fn status_emoji(status: axum::http::StatusCode) -> &'static str {
30
+
if status.is_success() {
31
+
"✅"
32
+
} else if status.is_redirection() {
33
+
"↪"
34
+
} else if status.is_client_error() {
35
+
"⚠"
36
+
} else if status.is_server_error() {
37
+
"❌"
38
+
} else {
39
+
"ℹ"
40
+
}
41
+
}
42
+
43
+
/// Convert JSON into a "key: value" style display.
44
+
/// - Objects: `key: value` (nested objects/arrays are indented)
45
+
/// - Arrays: `- item` (nested indented)
46
+
/// If not JSON: UTF-8 text, else base64.
47
+
fn body_as_kv(bytes: &Bytes) -> String {
48
+
if bytes.is_empty() {
49
+
return "<empty>".to_string();
50
+
}
51
+
52
+
if let Ok(v) = serde_json::from_slice::<Value>(bytes) {
53
+
let mut out = String::new();
54
+
write_value(&mut out, &v, 0);
55
+
return out.trim_end().to_string();
56
+
}
57
+
58
+
match std::str::from_utf8(bytes) {
59
+
Ok(s) => s.to_string(),
60
+
Err(_) => format!("<non-utf8; base64>\n{}", BASE64_STANDARD.encode(bytes)),
61
+
}
62
+
}
63
+
64
+
fn write_value(out: &mut String, v: &Value, indent: usize) {
65
+
match v {
66
+
Value::Object(map) => {
67
+
for (k, val) in map {
68
+
write_key_value(out, k, val, indent);
69
+
}
70
+
}
71
+
Value::Array(arr) => {
72
+
for item in arr {
73
+
write_array_item(out, item, indent);
74
+
}
75
+
}
76
+
_ => {
77
+
// Root primitive
78
+
out.push_str(&indent_str(indent));
79
+
out.push_str(&format_primitive(v));
80
+
out.push('\n');
81
+
}
82
+
}
83
+
}
84
+
85
+
fn write_key_value(out: &mut String, key: &str, val: &Value, indent: usize) {
86
+
let pad = indent_str(indent);
87
+
88
+
match val {
89
+
Value::Object(_) | Value::Array(_) => {
90
+
out.push_str(&pad);
91
+
out.push_str(key);
92
+
out.push_str(":\n");
93
+
write_value(out, val, indent + 2);
94
+
}
95
+
_ => {
96
+
out.push_str(&pad);
97
+
out.push_str(key);
98
+
out.push_str(": ");
99
+
out.push_str(&format_primitive(val));
100
+
out.push('\n');
101
+
}
102
+
}
103
+
}
104
+
105
+
fn write_array_item(out: &mut String, item: &Value, indent: usize) {
106
+
let pad = indent_str(indent);
107
+
108
+
match item {
109
+
Value::Object(_) | Value::Array(_) => {
110
+
out.push_str(&pad);
111
+
out.push_str("-\n");
112
+
write_value(out, item, indent + 2);
113
+
}
114
+
_ => {
115
+
out.push_str(&pad);
116
+
out.push_str("- ");
117
+
out.push_str(&format_primitive(item));
118
+
out.push('\n');
119
+
}
120
+
}
121
+
}
122
+
123
+
fn format_primitive(v: &Value) -> String {
124
+
match v {
125
+
Value::String(s) => s.clone(),
126
+
Value::Number(n) => n.to_string(),
127
+
Value::Bool(b) => b.to_string(),
128
+
Value::Null => "null".to_string(),
129
+
// Shouldn’t happen here (we route these elsewhere), but safe fallback
130
+
Value::Object(_) | Value::Array(_) => "<complex>".to_string(),
131
+
}
132
+
}
133
+
134
+
fn indent_str(spaces: usize) -> String {
135
+
" ".repeat(spaces)
136
+
}
137
+
138
+
fn indent_block(s: &str, spaces: usize) -> String {
139
+
let pad = " ".repeat(spaces);
140
+
s.lines()
141
+
.map(|line| format!("{pad}{line}\n"))
142
+
.collect::<String>()
143
+
.trim_end_matches('\n')
144
+
.to_string()
145
+
}
146
+
147
+
pub async fn log_req_res_bodies(req: Request<Body>, next: Next) -> Response {
148
+
// Avoid noisy CORS preflight logs
149
+
if req.method() == Method::OPTIONS {
150
+
return next.run(req).await;
151
+
}
152
+
153
+
let id = REQ_SEQ.fetch_add(1, Ordering::Relaxed);
154
+
let start = Instant::now();
155
+
156
+
let method = req.method().clone();
157
+
let uri = req.uri().clone();
158
+
let req_headers = req.headers().clone();
159
+
160
+
// Read + restore request body
161
+
let (req_parts, req_body) = req.into_parts();
162
+
let req_bytes = to_bytes(req_body, usize::MAX).await.unwrap();
163
+
let req = Request::from_parts(req_parts, Body::from(req_bytes.clone()));
164
+
165
+
// Run handler
166
+
let res = next.run(req).await;
167
+
168
+
let status = res.status();
169
+
let res_headers = res.headers().clone();
170
+
171
+
// Read + restore response body
172
+
let (res_parts, res_body) = res.into_parts();
173
+
let res_bytes = to_bytes(res_body, usize::MAX).await.unwrap();
174
+
let res = Response::from_parts(res_parts, Body::from(res_bytes.clone()));
175
+
176
+
let ms = start.elapsed().as_millis();
177
+
let sep = "════════════════════════════════════════════════════════════════";
178
+
179
+
let mut out = String::new();
180
+
out.push('\n');
181
+
out.push_str(sep);
182
+
out.push('\n');
183
+
out.push_str(&format!(
184
+
"🚦 #{id} {method} {uri} {} {status} ⏱ {ms}ms\n",
185
+
status_emoji(status)
186
+
));
187
+
188
+
out.push('\n');
189
+
190
+
out.push_str("📥 Request\n");
191
+
if let Some(xauth) = format_x_auth(&req_headers) {
192
+
out.push_str(&format!(" 🔐 x-auth: {xauth}\n"));
193
+
}
194
+
out.push_str(&indent_block(&body_as_kv(&req_bytes), 2));
195
+
out.push('\n');
196
+
197
+
out.push_str("📤 Response\n");
198
+
out.push_str(&indent_block(&body_as_kv(&res_bytes), 2));
199
+
out.push('\n');
200
+
201
+
out.push_str(sep);
202
+
out.push('\n');
203
+
204
+
tracing::info!("{out}");
205
+
206
+
let _ = res_headers;
207
+
res
208
+
}
+19
-85
server/src/main.rs
+19
-85
server/src/main.rs
···
1
1
use std::{collections::HashMap, sync::Arc};
2
2
3
-
use axum::{
4
-
Router,
5
-
body::{Body, to_bytes},
6
-
extract::{Request, State},
7
-
middleware::Next,
8
-
response::IntoResponse,
9
-
routing::post,
10
-
};
11
-
use base64::{Engine, prelude::BASE64_STANDARD};
12
-
use ed25519_dalek::Signature;
3
+
use axum::{Router, routing::post};
13
4
use nanoid::nanoid;
14
-
use serde::Deserialize;
15
5
use std::collections::HashSet;
16
6
use tokio::sync::Mutex;
17
-
use tower_http::cors::{Any, CorsLayer};
7
+
use tower_http::cors::CorsLayer;
8
+
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
18
9
10
+
mod auth;
19
11
mod handlers;
12
+
mod log;
20
13
mod types;
21
14
22
15
use handlers::*;
23
16
use types::*;
17
+
18
+
use crate::auth::auth_test;
19
+
use crate::log::log_req_res_bodies;
24
20
25
21
#[tokio::main]
26
22
async fn main() {
23
+
tracing_subscriber::registry()
24
+
.with(
25
+
tracing_subscriber::EnvFilter::try_from_default_env()
26
+
.unwrap_or_else(|_| "info,tower_http=info,axum=info".into()),
27
+
)
28
+
.with(tracing_subscriber::fmt::layer())
29
+
.init();
30
+
27
31
// TODO: should this be inside an Arc?
28
32
let state = AppState {
29
33
users: Arc::new(Mutex::new(Vec::new())),
···
56
60
.route("/get-pings", post(get_pings))
57
61
.with_state(state.clone())
58
62
.layer(CorsLayer::permissive())
59
-
.layer(axum::middleware::from_fn_with_state(state, auth_test));
63
+
.layer(axum::middleware::from_fn_with_state(state, auth_test))
64
+
.layer(axum::middleware::from_fn(log_req_res_bodies));
60
65
61
66
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
62
67
axum::serve(listener, app).await.unwrap();
63
68
}
64
69
65
-
async fn auth_test(State(state): State<AppState>, req: Request, next: Next) -> impl IntoResponse {
66
-
let endpoint = req.uri().path().to_owned();
67
-
if endpoint != "/create-account" {
68
-
// CURSED STUFF BEGIN
69
-
let (parts, body) = req.into_parts();
70
-
let body_bytes = to_bytes(body, usize::MAX).await.unwrap();
71
-
let new_body = Body::from(body_bytes.clone());
72
-
let mut req = Request::from_parts(parts, new_body);
73
-
// CURSED STUFF END
74
-
75
-
let auth_header = req
76
-
.headers()
77
-
.get("x-auth")
78
-
.and_then(|v| v.to_str().ok())
79
-
.unwrap_or_else(|| panic!("missing x-auth header"));
80
-
81
-
let decoded_auth = BASE64_STANDARD
82
-
.decode(auth_header)
83
-
.unwrap_or_else(|_| panic!("invalid base64 in x-auth header"));
84
-
85
-
let auth_str = String::from_utf8(decoded_auth)
86
-
.unwrap_or_else(|_| panic!("invalid utf8 in x-auth header"));
87
-
88
-
let auth_data: Auth = serde_json::from_str(&auth_str)
89
-
.unwrap_or_else(|e| panic!("failed to parse x-auth JSON: {e}"));
90
-
91
-
let users = state.users.lock().await;
92
-
let user_id = auth_data.user_id;
93
-
let user = users
94
-
.iter()
95
-
.find(|u| u.id == user_id)
96
-
.unwrap_or_else(|| panic!("User not found"));
97
-
let verifying_key = user.pub_key.clone();
98
-
drop(users);
99
-
100
-
////////////////////////////////////
101
-
//////////////////////////////////// unsure
102
-
////////////////////////////////////
103
-
104
-
let sig_vec = BASE64_STANDARD.decode(&auth_data.signature).unwrap();
105
-
let sig_bytes: [u8; 64] = sig_vec.try_into().expect("invalid signature length");
106
-
let signature = Signature::from_bytes(&sig_bytes);
107
-
108
-
if let Err(err) = verifying_key.verify_strict(&body_bytes, &signature) {
109
-
panic!("Signature verification failed: {err}");
110
-
}
111
-
112
-
////////////////////////////////////
113
-
////////////////////////////////////
114
-
////////////////////////////////////
115
-
116
-
// TODO: Maybe make the endpoints an enum
117
-
if endpoint == "/generate-signup-key" {
118
-
let admin_id = state.admin_id.lock().await;
119
-
if admin_id.as_ref() != Some(&user_id) {
120
-
todo!("not allowed")
121
-
}
122
-
}
123
-
124
-
req.extensions_mut().insert(user_id);
125
-
126
-
return next.run(req).await;
127
-
}
128
-
129
-
return next.run(req).await;
130
-
}
131
-
132
-
#[derive(Debug, Deserialize, Clone)]
133
-
struct Auth {
134
-
user_id: String,
135
-
signature: String,
136
-
}
70
+
// TODO: potential security risk of returning error messages directly to the user. nice for debugging tho :p
+54
-6
server/src/types.rs
+54
-6
server/src/types.rs
···
22
22
pub ring_buffer_cap: usize,
23
23
}
24
24
25
+
#[derive(Debug, Deserialize, Clone)]
26
+
pub struct AuthData {
27
+
pub user_id: String,
28
+
pub signature: String,
29
+
}
30
+
25
31
pub struct RingBuffer {
26
32
pub ring: Box<[Option<EncryptedPing>]>,
27
33
pub idx: usize,
···
80
86
pub id: String,
81
87
pub pub_key: VerifyingKey,
82
88
}
83
-
// pub struct User(pub String);
84
89
85
90
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
86
91
pub struct Link(String, String);
···
116
121
117
122
impl IntoResponse for EncryptedPingVec {
118
123
fn into_response(self) -> Response {
119
-
axum::Json(self.0).into_response() // TODO: check if this is correct
124
+
axum::Json(self.0).into_response()
120
125
}
121
126
}
122
127
123
-
pub struct MyErr(pub &'static str);
128
+
#[derive(Debug)]
129
+
pub struct SrvErr(pub String);
124
130
125
-
impl IntoResponse for MyErr {
131
+
impl IntoResponse for SrvErr {
126
132
fn into_response(self) -> Response {
127
-
let body = format!(r#"{{"error":"{}"}}"#, self.0); // example: {"error":"something went wrong"}
128
-
return (StatusCode::INTERNAL_SERVER_ERROR, body).into_response();
133
+
(StatusCode::INTERNAL_SERVER_ERROR, self.0).into_response()
129
134
}
130
135
}
136
+
137
+
/// Central policy: what gets logged, what gets returned.
138
+
pub fn mk_srv_err(msg: impl Into<String>, cause: Option<String>) -> SrvErr {
139
+
let msg = msg.into();
140
+
141
+
match cause {
142
+
Some(c) => {
143
+
// Always log server-side
144
+
eprintln!("[ERR] {msg} | cause: {c}");
145
+
146
+
// Only include cause in client response in debug builds
147
+
if cfg!(debug_assertions) {
148
+
SrvErr(format!("{msg} | cause: {c}"))
149
+
} else {
150
+
SrvErr(msg)
151
+
}
152
+
}
153
+
None => {
154
+
eprintln!("[ERR] {msg}");
155
+
SrvErr(msg)
156
+
}
157
+
}
158
+
}
159
+
160
+
#[macro_export]
161
+
macro_rules! SrvErr {
162
+
($msg:expr) => {
163
+
$crate::mk_srv_err($msg, None)
164
+
};
165
+
($msg:expr, $err:expr) => {
166
+
$crate::mk_srv_err($msg, Some(format!("{:?}", $err)))
167
+
};
168
+
}
169
+
170
+
#[macro_export]
171
+
macro_rules! ReqBail {
172
+
($msg:expr) => {{
173
+
return Err($crate::SrvErr!($msg));
174
+
}};
175
+
($msg:expr, $err:expr) => {{
176
+
return Err($crate::SrvErr!($msg, $err));
177
+
}};
178
+
}
-47
server/test/autils.ts
-47
server/test/autils.ts
···
1
-
import { expect } from "bun:test";
2
-
3
-
export const URL = "http://127.0.0.1:3000";
4
-
5
-
export async function generateUser(signup_key: string | undefined, should_be_admin: boolean = false): Promise<string> {
6
-
if (signup_key === undefined) {
7
-
throw new Error("signup_key was not provided or captured from server output");
8
-
}
9
-
10
-
const res = await fetch(`${URL}/create-account`, {
11
-
method: "POST",
12
-
body: signup_key,
13
-
});
14
-
const json = await res.json();
15
-
expect(res.status).toBe(200);
16
-
expect(json).toEqual({
17
-
user_id: expect.any(String),
18
-
is_admin: should_be_admin,
19
-
});
20
-
21
-
return json.user_id;
22
-
}
23
-
24
-
export async function post(endpoint: string, user_id: string, data: Object | string | undefined): Promise<any> {
25
-
const headers: Record<string, string> = {
26
-
"x-auth": JSON.stringify({ user_id }),
27
-
};
28
-
29
-
let stringified_data: string | undefined;
30
-
31
-
if (typeof data === "object") {
32
-
stringified_data = JSON.stringify(data);
33
-
headers["Content-Type"] = "application/json";
34
-
} else {
35
-
stringified_data = data;
36
-
}
37
-
38
-
const res = await fetch(`${URL}/${endpoint}`, {
39
-
method: "POST",
40
-
headers,
41
-
body: stringified_data,
42
-
});
43
-
44
-
expect(res.status).toBe(200);
45
-
46
-
return await res.text();
47
-
}