From a3302d2efff31a0215ca23bb2137eb14ce8f4ad6 Mon Sep 17 00:00:00 2001 From: Andy Burke Date: Wed, 8 Oct 2025 14:42:01 -0700 Subject: [PATCH] feature: require invites --- deno.json | 6 +- deno.lock | 71 ++++++++-------- models/event.ts | 7 +- public/api/users/:user_id/index.ts | 2 + public/api/users/index.ts | 66 +++++++++++---- public/signup_login_wall.html | 27 ++++-- public/tabs/tabs.html | 7 ++ tests/01_create_user.test.ts | 53 ++++++++++++ tests/02_update_user.test.ts | 56 +++++++++++++ .../users/login.test.ts => 03_login.test.ts} | 79 ++++++----------- ..._topic.test.ts => 04_create_topic.test.ts} | 26 +++--- ..._topic.test.ts => 05_update_topic.test.ts} | 29 ++++--- ..._topic.test.ts => 06_delete_topic.test.ts} | 22 ++--- ...test.ts => 07_create_topic_events.test.ts} | 13 +-- ...ts.test.ts => 08_get_topic_events.test.ts} | 13 +-- ...test.ts => 09_update_topic_events.test.ts} | 13 +-- ...ate_topic_events_when_append_only.test.ts} | 13 +-- ...ploads.test.ts => 11_file_uploads.test.ts} | 55 ++---------- tests/api/users/create_user.test.ts | 77 ----------------- tests/api/users/delete_user.test.ts | 84 ------------------- tests/api/users/update_user.test.ts | 82 ------------------ tests/helpers.ts | 66 +++++++++++---- 22 files changed, 385 insertions(+), 482 deletions(-) create mode 100644 tests/01_create_user.test.ts create mode 100644 tests/02_update_user.test.ts rename tests/{api/users/login.test.ts => 03_login.test.ts} (54%) rename tests/{api/topics/create_topic.test.ts => 04_create_topic.test.ts} (65%) rename tests/{api/topics/update_topic.test.ts => 05_update_topic.test.ts} (72%) rename tests/{api/topics/delete_topic.test.ts => 06_delete_topic.test.ts} (57%) rename tests/{api/topics/events/create_events.test.ts => 07_create_topic_events.test.ts} (86%) rename tests/{api/topics/events/get_events.test.ts => 08_get_topic_events.test.ts} (87%) rename tests/{api/topics/events/update_events.test.ts => 09_update_topic_events.test.ts} (94%) rename tests/{api/topics/events/update_events_when_append_only.test.ts => 10_update_topic_events_when_append_only.test.ts} (90%) rename tests/{01_file_uploads.test.ts => 11_file_uploads.test.ts} (84%) delete mode 100644 tests/api/users/create_user.test.ts delete mode 100644 tests/api/users/delete_user.test.ts delete mode 100644 tests/api/users/update_user.test.ts diff --git a/deno.json b/deno.json index 4bef967..9f102a7 100644 --- a/deno.json +++ b/deno.json @@ -31,14 +31,14 @@ } }, "imports": { - "@andyburke/fsdb": "jsr:@andyburke/fsdb@^1.0.3", + "@andyburke/fsdb": "jsr:@andyburke/fsdb@^1.0.4", "@andyburke/lurid": "jsr:@andyburke/lurid@^0.2.0", "@andyburke/serverus": "jsr:@andyburke/serverus@^0.13.0", "@da/bcrypt": "jsr:@da/bcrypt@^1.0.1", - "@std/assert": "jsr:@std/assert@^1.0.14", + "@std/assert": "jsr:@std/assert@^1.0.15", "@std/encoding": "jsr:@std/encoding@^1.0.10", "@std/fs": "jsr:@std/fs@^1.0.19", - "@std/http": "jsr:@std/http@^1.0.20", + "@std/http": "jsr:@std/http@^1.0.21", "@std/path": "jsr:@std/path@^1.1.2" } } diff --git a/deno.lock b/deno.lock index 8ac8a98..6db7fa8 100644 --- a/deno.lock +++ b/deno.lock @@ -1,35 +1,38 @@ { "version": "5", "specifiers": { - "jsr:@andyburke/fsdb@^1.0.3": "1.0.3", + "jsr:@andyburke/fsdb@^1.0.4": "1.0.4", "jsr:@andyburke/lurid@0.2": "0.2.0", "jsr:@andyburke/serverus@0.13": "0.13.0", "jsr:@da/bcrypt@*": "1.0.1", "jsr:@da/bcrypt@^1.0.1": "1.0.1", - "jsr:@std/assert@^1.0.14": "1.0.14", - "jsr:@std/cli@^1.0.19": "1.0.21", - "jsr:@std/cli@^1.0.20": "1.0.21", - "jsr:@std/cli@^1.0.21": "1.0.21", + "jsr:@std/assert@^1.0.15": "1.0.15", + "jsr:@std/cli@^1.0.19": "1.0.23", + "jsr:@std/cli@^1.0.20": "1.0.23", + "jsr:@std/cli@^1.0.21": "1.0.23", + "jsr:@std/cli@^1.0.23": "1.0.23", "jsr:@std/encoding@^1.0.10": "1.0.10", "jsr:@std/fmt@^1.0.6": "1.0.8", "jsr:@std/fmt@^1.0.8": "1.0.8", "jsr:@std/fs@^1.0.18": "1.0.19", "jsr:@std/fs@^1.0.19": "1.0.19", - "jsr:@std/html@^1.0.4": "1.0.4", - "jsr:@std/http@^1.0.20": "1.0.20", - "jsr:@std/internal@^1.0.10": "1.0.10", - "jsr:@std/internal@^1.0.9": "1.0.10", + "jsr:@std/html@^1.0.5": "1.0.5", + "jsr:@std/http@^1.0.20": "1.0.21", + "jsr:@std/http@^1.0.21": "1.0.21", + "jsr:@std/internal@^1.0.10": "1.0.12", + "jsr:@std/internal@^1.0.12": "1.0.12", + "jsr:@std/internal@^1.0.9": "1.0.12", "jsr:@std/media-types@^1.1.0": "1.1.0", - "jsr:@std/net@^1.0.4": "1.0.4", + "jsr:@std/net@^1.0.6": "1.0.6", "jsr:@std/path@^1.1.0": "1.1.2", "jsr:@std/path@^1.1.1": "1.1.2", "jsr:@std/path@^1.1.2": "1.1.2", - "jsr:@std/streams@^1.0.10": "1.0.10", + "jsr:@std/streams@^1.0.13": "1.0.13", "npm:@types/node@*": "22.15.15" }, "jsr": { - "@andyburke/fsdb@1.0.3": { - "integrity": "e80d831bf173948cb98f6687d17d098f5f44d016cf04a2d851dc1b58ee327263", + "@andyburke/fsdb@1.0.4": { + "integrity": "ce4bf858e6af25bf257726d08b2901c7409f82aa409f435795d5381caffffad4", "dependencies": [ "jsr:@std/cli@^1.0.20", "jsr:@std/fs@^1.0.18", @@ -48,7 +51,7 @@ "jsr:@std/cli@^1.0.21", "jsr:@std/fmt@^1.0.6", "jsr:@std/fs@^1.0.19", - "jsr:@std/http", + "jsr:@std/http@^1.0.20", "jsr:@std/media-types", "jsr:@std/path@^1.1.1" ] @@ -56,14 +59,14 @@ "@da/bcrypt@1.0.1": { "integrity": "d2172d3acbcff52e0465557a1a48b1ff1c92df08c90712dae5372255a8c45eb3" }, - "@std/assert@1.0.14": { - "integrity": "68d0d4a43b365abc927f45a9b85c639ea18a9fab96ad92281e493e4ed84abaa4", + "@std/assert@1.0.15": { + "integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b", "dependencies": [ - "jsr:@std/internal@^1.0.10" + "jsr:@std/internal@^1.0.12" ] }, - "@std/cli@1.0.21": { - "integrity": "cd25b050bdf6282e321854e3822bee624f07aca7636a3a76d95f77a3a919ca2a" + "@std/cli@1.0.23": { + "integrity": "bf95b7a9425ba2af1ae5a6359daf58c508f2decf711a76ed2993cd352498ccca" }, "@std/encoding@1.0.10": { "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" @@ -78,31 +81,31 @@ "jsr:@std/path@^1.1.1" ] }, - "@std/html@1.0.4": { - "integrity": "eff3497c08164e6ada49b7f81a28b5108087033823153d065e3f89467dd3d50e" + "@std/html@1.0.5": { + "integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e" }, - "@std/http@1.0.20": { - "integrity": "b5cc33fc001bccce65ed4c51815668c9891c69ccd908295997e983d8f56070a1", + "@std/http@1.0.21": { + "integrity": "abb5c747651ee6e3ea6139858fd9b1810d2c97f53a5e6722f3b6d27a6d263edc", "dependencies": [ - "jsr:@std/cli@^1.0.21", + "jsr:@std/cli@^1.0.23", "jsr:@std/encoding", "jsr:@std/fmt@^1.0.8", "jsr:@std/fs@^1.0.19", "jsr:@std/html", "jsr:@std/media-types", "jsr:@std/net", - "jsr:@std/path@^1.1.1", + "jsr:@std/path@^1.1.2", "jsr:@std/streams" ] }, - "@std/internal@1.0.10": { - "integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7" + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" }, "@std/media-types@1.1.0": { "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" }, - "@std/net@1.0.4": { - "integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852" + "@std/net@1.0.6": { + "integrity": "110735f93e95bb9feb95790a8b1d1bf69ec0dc74f3f97a00a76ea5efea25500c" }, "@std/path@1.1.2": { "integrity": "c0b13b97dfe06546d5e16bf3966b1cadf92e1cc83e56ba5476ad8b498d9e3038", @@ -110,8 +113,8 @@ "jsr:@std/internal@^1.0.10" ] }, - "@std/streams@1.0.10": { - "integrity": "75c0b1431873cd0d8b3d679015220204d36d3c7420d93b60acfc379eb0dc30af" + "@std/streams@1.0.13": { + "integrity": "772d208cd0d3e5dac7c1d9e6cdb25842846d136eea4a41a62e44ed4ab0c8dd9e" } }, "npm": { @@ -130,14 +133,14 @@ }, "workspace": { "dependencies": [ - "jsr:@andyburke/fsdb@^1.0.3", + "jsr:@andyburke/fsdb@^1.0.4", "jsr:@andyburke/lurid@0.2", "jsr:@andyburke/serverus@0.13", "jsr:@da/bcrypt@^1.0.1", - "jsr:@std/assert@^1.0.14", + "jsr:@std/assert@^1.0.15", "jsr:@std/encoding@^1.0.10", "jsr:@std/fs@^1.0.19", - "jsr:@std/http@^1.0.20", + "jsr:@std/http@^1.0.21", "jsr:@std/path@^1.1.2" ] } diff --git a/models/event.ts b/models/event.ts index be904aa..90bb390 100644 --- a/models/event.ts +++ b/models/event.ts @@ -41,12 +41,7 @@ type TOPIC_EVENT_CACHE_ENTRY = { export function VALIDATE_EVENT(event: EVENT) { const errors: any[] = []; - const { - groups: { - type, - id - } - } = /^(?\w+)\:(?[A-Za-z-]+)$/.exec(event.id ?? '') ?? { groups: {} }; + const [type, id] = (event.id ?? '').split(':', 2); if (typeof type !== 'string' || type.length === 0) { errors.push({ diff --git a/public/api/users/:user_id/index.ts b/public/api/users/:user_id/index.ts index 5afdcbb..2d2a5bd 100644 --- a/public/api/users/:user_id/index.ts +++ b/public/api/users/:user_id/index.ts @@ -143,6 +143,8 @@ export async function DELETE(_req: Request, meta: Record): Promise< await USERS.delete(user); + // TODO: delete any uploads? + return Response.json({ deleted: true }, { diff --git a/public/api/users/index.ts b/public/api/users/index.ts index 9878d42..28d1c1d 100644 --- a/public/api/users/index.ts +++ b/public/api/users/index.ts @@ -8,12 +8,13 @@ import { create_new_session, SESSION_RESULT } from '../auth/index.ts'; import { get_session, get_user, PRECHECK_TABLE, require_user } from '../../../utils/prechecks.ts'; import * as CANNED_RESPONSES from '../../../utils/canned_responses.ts'; import * as bcrypt from '@da/bcrypt'; -import { WALK_ENTRY } from '@andyburke/fsdb'; import { INVITE_CODE, INVITE_CODES } from '../../../models/invites.ts'; // TODO: figure out a better solution for doling out permissions const DEFAULT_USER_PERMISSIONS: string[] = [ 'files.write.own', + 'invites.create', + 'invites.read.own', 'self.read', 'self.write', 'topics.read', @@ -112,22 +113,52 @@ export async function POST(req: Request, meta: Record): Promise= invite_code.limit - : false; + status: 400 + }); + } - if (!invite_code || is_expired || is_limited) { + const invite_code: INVITE_CODE | undefined = (await INVITE_CODES.find({ + code: secret_code + })).shift()?.load(); + + const is_expired = invite_code?.timestamps.expires ? now <= invite_code.timestamps.expires : false; + const is_used = (await SIGNUPS.find({ + referring_invite_code_id: invite_code?.id + }, { + limit: 1 + })).length > 0; + const is_cancelled = !!invite_code?.timestamps?.cancelled; + + if (!invite_code || is_expired || is_used || is_cancelled) { return Response.json({ error: { cause: 'invalid_signup_code', @@ -135,7 +166,8 @@ export async function POST(req: Request, meta: Record): Promise): Promise #signup-login-wall { display: flex; + flex-direction: column; align-items: center; justify-content: center; position: absolute; @@ -16,6 +17,14 @@ transition: all 0.33s; } + #login-tab .tab-content { + min-height: 17rem; + } + + #signup-tab .tab-content { + min-height: 21rem; + } + body[data-user] #signup-login-wall { visibility: hidden; opacity: 0; @@ -23,24 +32,21 @@ #signup-login-wall .limiter { width: 95%; + min-height: 24rem; position: relative; background: hsl(from var(--bg) h s 15); max-width: 40em; - min-height: 22rem; margin: 0 auto; } - #login-tab, - #signup-tab { - min-height: 22rem; - } - #signup-login-wall form { width: 100%; padding: 1.5rem 1.5rem 0 1.5rem; } + +
@@ -118,6 +124,15 @@
+
+ + +
diff --git a/public/tabs/tabs.html b/public/tabs/tabs.html index 3ed580d..f39c881 100644 --- a/public/tabs/tabs.html +++ b/public/tabs/tabs.html @@ -35,6 +35,7 @@ justify-content: space-between; align-items: flex-start; min-height: inherit; + background: inherit; } .tabs::before, @@ -48,6 +49,7 @@ } .tab { + background: inherit; } .tab-switch { @@ -83,6 +85,9 @@ border-top: 1px solid var(--border-subtle); margin-top: 1px; overflow-y: scroll; + visibility: hidden; + display: none; + background: inherit; } .tab-switch, @@ -99,6 +104,8 @@ .tab-switch:checked + label + .tab-content { z-index: 2; opacity: 1; + visibility: visible; + display: block; } @media screen and (max-width: 800px) { diff --git a/tests/01_create_user.test.ts b/tests/01_create_user.test.ts new file mode 100644 index 0000000..9309566 --- /dev/null +++ b/tests/01_create_user.test.ts @@ -0,0 +1,53 @@ +import { api, API_CLIENT } from '../utils/api.ts'; +import * as asserts from '@std/assert'; +import { USER } from '../models/user.ts'; +import { delete_user, EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, random_username } from './helpers.ts'; +import { encodeBase64 } from '@std/encoding'; +import { generateTotp } from '../utils/totp.ts'; + +Deno.test({ + name: 'API - USERS - Create', + permissions: { + env: true, + read: true, + write: true, + net: true + }, + fn: async () => { + let test_server_info: EPHEMERAL_SERVER | null = null; + try { + test_server_info = await get_ephemeral_listen_server(); + const client: API_CLIENT = api({ + prefix: '/api', + hostname: test_server_info.hostname, + port: test_server_info.port + }); + + const username = random_username(); + const password = 'password'; + const password_hash = encodeBase64( + await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password)) + ); + + const info = await get_new_user(client, { + username, + password_hash + }); + + asserts.assert(info); + asserts.assert(info.user); + asserts.assert(info.session); + asserts.assert(info.headers); + + const user = info.user; + + asserts.assertEquals(user.username, username); + + await delete_user(client, info); + } finally { + if (test_server_info) { + await test_server_info?.server?.stop(); + } + } + } +}); diff --git a/tests/02_update_user.test.ts b/tests/02_update_user.test.ts new file mode 100644 index 0000000..240d4fc --- /dev/null +++ b/tests/02_update_user.test.ts @@ -0,0 +1,56 @@ +import { api, API_CLIENT } from '../utils/api.ts'; +import * as asserts from '@std/assert'; +import { USER } from '../models/user.ts'; +import { delete_user, EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, random_username } from './helpers.ts'; +import { Cookie, getSetCookies } from '@std/http/cookie'; +import { encodeBase64 } from '@std/encoding'; +import { generateTotp } from '../utils/totp.ts'; + +Deno.test({ + name: 'API - USERS - Update', + permissions: { + env: true, + read: true, + write: true, + net: true + }, + fn: async () => { + let test_server_info: EPHEMERAL_SERVER | null = null; + try { + test_server_info = await get_ephemeral_listen_server(); + const client: API_CLIENT = api({ + prefix: '/api', + hostname: test_server_info.hostname, + port: test_server_info.port + }); + + const info = await get_new_user(client); + asserts.assert(info); + + const user = info.user; + asserts.assert(user); + + const original_username = user.username; + asserts.assertEquals(original_username, user.username); + + const updated_user: USER = await client.fetch(`/users/${user?.id}`, { + method: 'PUT', + json: { + username: random_username() + }, + headers: info.headers + }) as USER; + + asserts.assert(updated_user); + + asserts.assertNotEquals(user.username, updated_user.username); + asserts.assertNotEquals(user.timestamps.updated, updated_user.timestamps.updated); + + await delete_user(client, info); + } finally { + if (test_server_info) { + await test_server_info?.server?.stop(); + } + } + } +}); diff --git a/tests/api/users/login.test.ts b/tests/03_login.test.ts similarity index 54% rename from tests/api/users/login.test.ts rename to tests/03_login.test.ts index 8bd92bb..25cc4d4 100644 --- a/tests/api/users/login.test.ts +++ b/tests/03_login.test.ts @@ -1,10 +1,10 @@ -import { api, API_CLIENT } from '../../../utils/api.ts'; +import { api, API_CLIENT } from '../utils/api.ts'; import * as asserts from '@std/assert'; -import { USER } from '../../../models/user.ts'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, random_username } from '../../helpers.ts'; +import { USER } from '../models/user.ts'; +import { delete_user, EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, random_username } from './helpers.ts'; import { Cookie, getSetCookies } from '@std/http/cookie'; import { encodeBase64 } from '@std/encoding'; -import { generateTotp } from '../../../utils/totp.ts'; +import { generateTotp } from '../utils/totp.ts'; Deno.test({ name: 'API - USERS - Login (password)', @@ -24,25 +24,19 @@ Deno.test({ port: test_server_info.port }); - const username = random_username(); - const password = 'password'; - - const user_creation_response: Record = await client.fetch('/users', { - method: 'POST', - json: { - username, - password - } + const info = await get_new_user(client, { + password: 'password' }); + asserts.assert(info); - asserts.assert(user_creation_response?.user); - asserts.assert(user_creation_response?.session); + const user = info.user; + asserts.assert(user); let cookies: Cookie[] = []; const auth_response: any = await client.fetch('/auth', { method: 'POST', json: { - username, + username: user.username, password: 'password' }, done: (response) => { @@ -50,12 +44,12 @@ Deno.test({ } }); - const user: USER | undefined = auth_response.user; - const session: Record | undefined = auth_response.session; + const authed_user: USER | undefined = auth_response.user; + const authed_session: Record | undefined = auth_response.session; cookies.push({ name: 'totp', - value: await generateTotp(session?.secret ?? ''), + value: await generateTotp(authed_session?.secret ?? ''), maxAge: 30, expires: Date.now() + 30_000, path: '/' @@ -74,7 +68,9 @@ Deno.test({ headers: headers_for_get }) as USER; - asserts.assertObjectMatch(retrieved_user, user ?? {}); + asserts.assertObjectMatch(retrieved_user, user); + + await delete_user(client, info); } finally { if (test_server_info) { await test_server_info?.server?.stop(); @@ -101,53 +97,26 @@ Deno.test({ port: test_server_info.port }); - const username = random_username(); const password = 'hashed password!!!'; const password_hash = encodeBase64( await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password)) ); - let cookies: Cookie[] = []; - - const user_creation_response: Record = await client.fetch('/users', { - method: 'POST', - json: { - username, - password_hash - }, - done: (response) => { - cookies = getSetCookies(response.headers); - } + const info = await get_new_user(client, { + password_hash }); + asserts.assert(info); - asserts.assert(user_creation_response?.user); - asserts.assert(user_creation_response?.session); - - const user: USER | undefined = user_creation_response.user; - const session: Record | undefined = user_creation_response.session; - - cookies.push({ - name: 'totp', - value: await generateTotp(session?.secret), - maxAge: 30, - expires: Date.now() + 30_000, - path: '/' - }); - - const headers_for_get = new Headers(); - for (const cookie of cookies) { - headers_for_get.append(`x-${cookie.name}`, cookie.value); - } - headers_for_get.append( - 'cookie', - cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ') - ); + const user = info.user; + asserts.assert(user); const retrieved_user: USER = await client.fetch(`/users/${user?.id}`, { - headers: headers_for_get + headers: info.headers }) as USER; asserts.assertObjectMatch(retrieved_user, user ?? {}); + + await delete_user(client, info); } finally { if (test_server_info) { await test_server_info?.server?.stop(); diff --git a/tests/api/topics/create_topic.test.ts b/tests/04_create_topic.test.ts similarity index 65% rename from tests/api/topics/create_topic.test.ts rename to tests/04_create_topic.test.ts index e13918c..6fee81e 100644 --- a/tests/api/topics/create_topic.test.ts +++ b/tests/04_create_topic.test.ts @@ -1,8 +1,8 @@ -import { api, API_CLIENT } from '../../../utils/api.ts'; +import { api, API_CLIENT } from '../utils/api.ts'; import * as asserts from '@std/assert'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../helpers.ts'; -import { generateTotp } from '../../../utils/totp.ts'; -import { clear_topic_events_cache } from '../../../models/event.ts'; +import { delete_user, EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from './helpers.ts'; +import { generateTotp } from '../utils/totp.ts'; +import { clear_topic_events_cache } from '../models/event.ts'; Deno.test({ name: 'API - TOPICS - Create', @@ -22,14 +22,14 @@ Deno.test({ port: test_server_info.port }); - const user_info = await get_new_user(client); + const info = await get_new_user(client); try { const _permission_denied_topic = await client.fetch('/topics', { method: 'POST', headers: { - 'x-session_id': user_info.session.id, - 'x-totp': await generateTotp(user_info.session.secret) + 'x-session_id': info.session.id, + 'x-totp': await generateTotp(info.session.secret) }, json: { name: 'this should not be allowed' @@ -41,14 +41,14 @@ Deno.test({ asserts.assertEquals((error as Error).cause, 'permission_denied'); } - await set_user_permissions(client, user_info.user, user_info.session, [...user_info.user.permissions, 'topics.create']); + await set_user_permissions(client, info.user, info.session, [...info.user.permissions, 'topics.create']); try { const _too_long_name_topic = await client.fetch('/topics', { method: 'POST', headers: { - 'x-session_id': user_info.session.id, - 'x-totp': await generateTotp(user_info.session.secret) + 'x-session_id': info.session.id, + 'x-totp': await generateTotp(info.session.secret) }, json: { name: 'X'.repeat(1024) @@ -63,8 +63,8 @@ Deno.test({ const new_topic = await client.fetch('/topics', { method: 'POST', headers: { - 'x-session_id': user_info.session.id, - 'x-totp': await generateTotp(user_info.session.secret) + 'x-session_id': info.session.id, + 'x-totp': await generateTotp(info.session.secret) }, json: { name: 'test topic' @@ -72,6 +72,8 @@ Deno.test({ }); asserts.assert(new_topic); + + await delete_user(client, info); } finally { clear_topic_events_cache(); if (test_server_info) { diff --git a/tests/api/topics/update_topic.test.ts b/tests/05_update_topic.test.ts similarity index 72% rename from tests/api/topics/update_topic.test.ts rename to tests/05_update_topic.test.ts index 2140bf2..e258aed 100644 --- a/tests/api/topics/update_topic.test.ts +++ b/tests/05_update_topic.test.ts @@ -1,8 +1,8 @@ -import { api, API_CLIENT } from '../../../utils/api.ts'; +import { api, API_CLIENT } from '../utils/api.ts'; import * as asserts from '@std/assert'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../helpers.ts'; -import { generateTotp } from '../../../utils/totp.ts'; -import { clear_topic_events_cache } from '../../../models/event.ts'; +import { delete_user, EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from './helpers.ts'; +import { generateTotp } from '../utils/totp.ts'; +import { clear_topic_events_cache } from '../models/event.ts'; Deno.test({ name: 'API - TOPICS - Update', @@ -22,15 +22,15 @@ Deno.test({ port: test_server_info.port }); - const user_info = await get_new_user(client); + const info = await get_new_user(client); - await set_user_permissions(client, user_info.user, user_info.session, [...user_info.user.permissions, 'topics.create']); + await set_user_permissions(client, info.user, info.session, [...info.user.permissions, 'topics.create']); const new_topic = await client.fetch('/topics', { method: 'POST', headers: { - 'x-session_id': user_info.session.id, - 'x-totp': await generateTotp(user_info.session.secret) + 'x-session_id': info.session.id, + 'x-totp': await generateTotp(info.session.secret) }, json: { name: 'test update topic' @@ -39,7 +39,7 @@ Deno.test({ asserts.assert(new_topic); - const other_user_info = await get_new_user(client); + const other_user_info = await get_new_user(client, {}, info); try { const _permission_denied_topic = await client.fetch(`/topics/${new_topic.id}`, { @@ -61,8 +61,8 @@ Deno.test({ const updated_by_owner_topic = await client.fetch(`/topics/${new_topic.id}`, { method: 'PUT', headers: { - 'x-session_id': user_info.session.id, - 'x-totp': await generateTotp(user_info.session.secret) + 'x-session_id': info.session.id, + 'x-totp': await generateTotp(info.session.secret) }, json: { topic: 'this is a new topic', @@ -75,7 +75,7 @@ Deno.test({ asserts.assert(updated_by_owner_topic); asserts.assertEquals(updated_by_owner_topic.topic, 'this is a new topic'); - asserts.assertEquals(updated_by_owner_topic.permissions.write, [user_info.user.id, other_user_info.user.id]); + asserts.assertEquals(updated_by_owner_topic.permissions.write, [info.user.id, other_user_info.user.id]); const updated_by_other_user_topic = await client.fetch(`/topics/${new_topic.id}`, { method: 'PUT', @@ -90,7 +90,10 @@ Deno.test({ asserts.assert(updated_by_other_user_topic); asserts.assertEquals(updated_by_other_user_topic.topic, 'this is a newer topic'); - asserts.assertEquals(updated_by_other_user_topic.permissions.write, [user_info.user.id, other_user_info.user.id]); + asserts.assertEquals(updated_by_other_user_topic.permissions.write, [info.user.id, other_user_info.user.id]); + + await delete_user(client, other_user_info); + await delete_user(client, info); } finally { clear_topic_events_cache(); if (test_server_info) { diff --git a/tests/api/topics/delete_topic.test.ts b/tests/06_delete_topic.test.ts similarity index 57% rename from tests/api/topics/delete_topic.test.ts rename to tests/06_delete_topic.test.ts index a518eca..46e6014 100644 --- a/tests/api/topics/delete_topic.test.ts +++ b/tests/06_delete_topic.test.ts @@ -1,8 +1,8 @@ -import { api, API_CLIENT } from '../../../utils/api.ts'; +import { api, API_CLIENT } from '../utils/api.ts'; import * as asserts from '@std/assert'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../helpers.ts'; -import { generateTotp } from '../../../utils/totp.ts'; -import { clear_topic_events_cache } from '../../../models/event.ts'; +import { delete_user, EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from './helpers.ts'; +import { generateTotp } from '../utils/totp.ts'; +import { clear_topic_events_cache } from '../models/event.ts'; Deno.test({ name: 'API - TOPICS - Delete', @@ -22,15 +22,15 @@ Deno.test({ port: test_server_info.port }); - const user_info = await get_new_user(client); + const info = await get_new_user(client); - await set_user_permissions(client, user_info.user, user_info.session, [...user_info.user.permissions, 'topics.create']); + await set_user_permissions(client, info.user, info.session, [...info.user.permissions, 'topics.create']); const new_topic = await client.fetch('/topics', { method: 'POST', headers: { - 'x-session_id': user_info.session.id, - 'x-totp': await generateTotp(user_info.session.secret) + 'x-session_id': info.session.id, + 'x-totp': await generateTotp(info.session.secret) }, json: { name: 'test delete topic' @@ -42,12 +42,14 @@ Deno.test({ const deleted_topic = await client.fetch(`/topics/${new_topic.id}`, { method: 'DELETE', headers: { - 'x-session_id': user_info.session.id, - 'x-totp': await generateTotp(user_info.session.secret) + 'x-session_id': info.session.id, + 'x-totp': await generateTotp(info.session.secret) } }); asserts.assert(deleted_topic); + + await delete_user(client, info); } finally { clear_topic_events_cache(); if (test_server_info) { diff --git a/tests/api/topics/events/create_events.test.ts b/tests/07_create_topic_events.test.ts similarity index 86% rename from tests/api/topics/events/create_events.test.ts rename to tests/07_create_topic_events.test.ts index a352e51..84eaeda 100644 --- a/tests/api/topics/events/create_events.test.ts +++ b/tests/07_create_topic_events.test.ts @@ -1,8 +1,8 @@ import * as asserts from '@std/assert'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../../helpers.ts'; -import { api, API_CLIENT } from '../../../../utils/api.ts'; -import { generateTotp } from '../../../../utils/totp.ts'; -import { clear_topic_events_cache } from '../../../../models/event.ts'; +import { delete_user, EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from './helpers.ts'; +import { api, API_CLIENT } from '../utils/api.ts'; +import { generateTotp } from '../utils/totp.ts'; +import { clear_topic_events_cache } from '../models/event.ts'; Deno.test({ name: 'API - TOPICS - EVENTS - Create', @@ -58,7 +58,7 @@ Deno.test({ asserts.assert(event_from_owner); - const other_user_info = await get_new_user(client); + const other_user_info = await get_new_user(client, {}, owner_info); try { const _permission_denied_topic = await client.fetch(`/topics/${topic.id}/events`, { @@ -113,6 +113,9 @@ Deno.test({ }); asserts.assert(event_from_other_user); + + await delete_user(client, other_user_info); + await delete_user(client, owner_info); } finally { clear_topic_events_cache(); if (test_server_info) { diff --git a/tests/api/topics/events/get_events.test.ts b/tests/08_get_topic_events.test.ts similarity index 87% rename from tests/api/topics/events/get_events.test.ts rename to tests/08_get_topic_events.test.ts index 708e9cd..d9ce1ff 100644 --- a/tests/api/topics/events/get_events.test.ts +++ b/tests/08_get_topic_events.test.ts @@ -1,8 +1,8 @@ import * as asserts from '@std/assert'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../../helpers.ts'; -import { api, API_CLIENT } from '../../../../utils/api.ts'; -import { generateTotp } from '../../../../utils/totp.ts'; -import { clear_topic_events_cache } from '../../../../models/event.ts'; +import { delete_user, EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from './helpers.ts'; +import { api, API_CLIENT } from '../utils/api.ts'; +import { generateTotp } from '../utils/totp.ts'; +import { clear_topic_events_cache } from '../models/event.ts'; Deno.test({ name: 'API - TOPICS - EVENTS - Get', @@ -67,7 +67,7 @@ Deno.test({ asserts.assertEquals(events_initial_batch.length, NUM_INITIAL_EVENTS); - const other_user_info = await get_new_user(client); + const other_user_info = await get_new_user(client, {}, owner_info); const events_from_server = await client.fetch(`/topics/${topic.id}/events`, { method: 'GET', @@ -116,6 +116,9 @@ Deno.test({ asserts.assertEquals(long_polled_events.length, 1); asserts.assertEquals(long_polled_events[0].data?.i, 12345); }); + + await delete_user(client, other_user_info); + await delete_user(client, owner_info); } finally { clear_topic_events_cache(); if (test_server_info) { diff --git a/tests/api/topics/events/update_events.test.ts b/tests/09_update_topic_events.test.ts similarity index 94% rename from tests/api/topics/events/update_events.test.ts rename to tests/09_update_topic_events.test.ts index a7441bc..9c26058 100644 --- a/tests/api/topics/events/update_events.test.ts +++ b/tests/09_update_topic_events.test.ts @@ -1,8 +1,8 @@ import * as asserts from '@std/assert'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../../helpers.ts'; -import { api, API_CLIENT } from '../../../../utils/api.ts'; -import { generateTotp } from '../../../../utils/totp.ts'; -import { clear_topic_events_cache } from '../../../../models/event.ts'; +import { delete_user, EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from './helpers.ts'; +import { api, API_CLIENT } from '../utils/api.ts'; +import { generateTotp } from '../utils/totp.ts'; +import { clear_topic_events_cache } from '../models/event.ts'; Deno.test({ name: 'API - TOPICS - EVENTS - Update', @@ -95,7 +95,7 @@ Deno.test({ asserts.assertNotEquals(fetched_updated_event_from_owner, fetched_event_from_owner); asserts.assertEquals(fetched_updated_event_from_owner, updated_event_from_owner); - const other_user_info = await get_new_user(client); + const other_user_info = await get_new_user(client, {}, owner_info); const event_from_other_user = await client.fetch(`/topics/${topic.id}/events`, { method: 'POST', @@ -235,6 +235,9 @@ Deno.test({ }); asserts.assertEquals(delete_owner_event_response.deleted, true); + + await delete_user(client, other_user_info); + await delete_user(client, owner_info); } finally { clear_topic_events_cache(); if (test_server_info) { diff --git a/tests/api/topics/events/update_events_when_append_only.test.ts b/tests/10_update_topic_events_when_append_only.test.ts similarity index 90% rename from tests/api/topics/events/update_events_when_append_only.test.ts rename to tests/10_update_topic_events_when_append_only.test.ts index 505c681..3ce4024 100644 --- a/tests/api/topics/events/update_events_when_append_only.test.ts +++ b/tests/10_update_topic_events_when_append_only.test.ts @@ -1,8 +1,8 @@ import * as asserts from '@std/assert'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from '../../../helpers.ts'; -import { api, API_CLIENT } from '../../../../utils/api.ts'; -import { generateTotp } from '../../../../utils/totp.ts'; -import { clear_topic_events_cache } from '../../../../models/event.ts'; +import { delete_user, EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, set_user_permissions } from './helpers.ts'; +import { api, API_CLIENT } from '../utils/api.ts'; +import { generateTotp } from '../utils/totp.ts'; +import { clear_topic_events_cache } from '../models/event.ts'; Deno.test({ name: 'API - TOPICS - EVENTS - Update (APPEND_ONLY_EVENTS)', @@ -99,7 +99,7 @@ Deno.test({ asserts.assertEquals((error as Error).cause, 'append_only_events'); } - const other_user_info = await get_new_user(client); + const other_user_info = await get_new_user(client, {}, owner_info); const event_from_other_user = await client.fetch(`/topics/${topic.id}/events`, { method: 'POST', @@ -157,6 +157,9 @@ Deno.test({ } catch (error) { asserts.assertEquals((error as Error).cause, 'append_only_events'); } + + await delete_user(client, other_user_info); + await delete_user(client, owner_info); } finally { Deno.env.delete('APPEND_ONLY_EVENTS'); diff --git a/tests/01_file_uploads.test.ts b/tests/11_file_uploads.test.ts similarity index 84% rename from tests/01_file_uploads.test.ts rename to tests/11_file_uploads.test.ts index 39eb3c5..413cd8d 100644 --- a/tests/01_file_uploads.test.ts +++ b/tests/11_file_uploads.test.ts @@ -1,7 +1,7 @@ import { api, API_CLIENT } from '../utils/api.ts'; import * as asserts from '@std/assert'; import { USER } from '../models/user.ts'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, random_username, set_user_permissions } from './helpers.ts'; +import { delete_user, EPHEMERAL_SERVER, get_ephemeral_listen_server, get_new_user, random_username, set_user_permissions } from './helpers.ts'; import { Cookie, getSetCookies } from '@std/http/cookie'; import { generateTotp } from '../utils/totp.ts'; import * as fs from '@std/fs'; @@ -34,56 +34,13 @@ Deno.test({ port: test_server_info.port }); - const username = random_username(); - const password = 'password'; + const owner_info = await get_new_user(client); + asserts.assert(owner_info); - const user_creation_response: Record = await client.fetch('/users', { - method: 'POST', - json: { - username, - password - } - }); - - asserts.assert(user_creation_response?.user); - asserts.assert(user_creation_response?.session); - - let cookies: Cookie[] = []; - const auth_response: any = await client.fetch('/auth', { - method: 'POST', - json: { - username, - password: 'password' - }, - done: (response) => { - cookies = getSetCookies(response.headers); - } - }); - - const user: USER | undefined = auth_response.user; + const user = owner_info.user; asserts.assert(user); asserts.assert(user.id); - const session: Record | undefined = auth_response.session; - asserts.assert(session); - - cookies.push({ - name: 'totp', - value: await generateTotp(session?.secret ?? ''), - maxAge: 30, - expires: Date.now() + 30_000, - path: '/' - }); - - const headers_for_upload_request = new Headers(); - for (const cookie of cookies) { - headers_for_upload_request.append(`x-${cookie.name}`, cookie.value); - } - headers_for_upload_request.append( - 'cookie', - cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ') - ); - const upload_body = new FormData(); upload_body.append( 'file', @@ -93,7 +50,7 @@ Deno.test({ `http://${test_server_info.hostname}:${test_server_info.port}/files/users/${user.id}/test_uploading_to_home_dir.txt`, { method: 'PUT', - headers: headers_for_upload_request, + headers: owner_info.headers, body: upload_body } ); @@ -147,6 +104,8 @@ Deno.test({ await Deno.remove(dir); dir = path.dirname(dir); } while (dir.length); + + await delete_user(client, owner_info); } finally { if (test_server_info) { await test_server_info?.server?.stop(); diff --git a/tests/api/users/create_user.test.ts b/tests/api/users/create_user.test.ts deleted file mode 100644 index 6eb3585..0000000 --- a/tests/api/users/create_user.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { api, API_CLIENT } from '../../../utils/api.ts'; -import * as asserts from '@std/assert'; -import { USER } from '../../../models/user.ts'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, random_username } from '../../helpers.ts'; -import { Cookie, getSetCookies } from '@std/http/cookie'; -import { encodeBase64 } from '@std/encoding'; -import { generateTotp } from '../../../utils/totp.ts'; - -Deno.test({ - name: 'API - USERS - Create', - permissions: { - env: true, - read: true, - write: true, - net: true - }, - fn: async () => { - let test_server_info: EPHEMERAL_SERVER | null = null; - try { - test_server_info = await get_ephemeral_listen_server(); - const client: API_CLIENT = api({ - prefix: '/api', - hostname: test_server_info.hostname, - port: test_server_info.port - }); - - const username = random_username(); - const password = 'password'; - const password_hash = encodeBase64( - await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password)) - ); - - let cookies: Cookie[] = []; - - const user_creation_response: Record = await client.fetch('/users', { - method: 'POST', - json: { - username, - password_hash - }, - done: (response) => { - cookies = getSetCookies(response.headers); - } - }); - - asserts.assert(user_creation_response?.user); - asserts.assert(user_creation_response?.session); - - const user: USER | undefined = user_creation_response.user; - const session: Record | undefined = user_creation_response.session; - - cookies.push({ - name: 'totp', - value: await generateTotp(session?.secret), - maxAge: 30, - expires: Date.now() + 30_000, - path: '/' - }); - - const headers_for_get = new Headers(); - for (const cookie of cookies) { - headers_for_get.append(`x-${cookie.name}`, cookie.value); - } - headers_for_get.append('cookie', cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ')); - - const retrieved_user: USER = await client.fetch(`/users/${user?.id}`, { - headers: headers_for_get - }) as USER; - - asserts.assertObjectMatch(retrieved_user, user ?? {}); - } finally { - if (test_server_info) { - await test_server_info?.server?.stop(); - } - } - } -}); diff --git a/tests/api/users/delete_user.test.ts b/tests/api/users/delete_user.test.ts deleted file mode 100644 index 7e60446..0000000 --- a/tests/api/users/delete_user.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { api, API_CLIENT } from '../../../utils/api.ts'; -import * as asserts from '@std/assert'; -import { USER } from '../../../models/user.ts'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, random_username } from '../../helpers.ts'; -import { Cookie, getSetCookies } from '@std/http/cookie'; -import { encodeBase64 } from '@std/encoding'; -import { generateTotp } from '../../../utils/totp.ts'; - -Deno.test({ - name: 'API - USERS - Delete', - permissions: { - env: true, - read: true, - write: true, - net: true - }, - fn: async () => { - let test_server_info: EPHEMERAL_SERVER | null = null; - try { - test_server_info = await get_ephemeral_listen_server(); - const client: API_CLIENT = api({ - prefix: '/api', - hostname: test_server_info.hostname, - port: test_server_info.port - }); - - const username = random_username(); - const password = 'password'; - const password_hash = encodeBase64( - await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password)) - ); - - let cookies: Cookie[] = []; - - const user_creation_response: Record = await client.fetch('/users', { - method: 'POST', - json: { - username, - password_hash - }, - done: (response) => { - cookies = getSetCookies(response.headers); - } - }); - - asserts.assert(user_creation_response?.user); - asserts.assert(user_creation_response?.session); - - const user: USER | undefined = user_creation_response.user; - const session: Record | undefined = user_creation_response.session; - - cookies.push({ - name: 'totp', - value: await generateTotp(session?.secret), - maxAge: 30, - expires: Date.now() + 30_000, - path: '/' - }); - - const headers_for_get = new Headers(); - for (const cookie of cookies) { - headers_for_get.append(`x-${cookie.name}`, cookie.value); - } - headers_for_get.append('cookie', cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ')); - - const retrieved_user: USER = await client.fetch(`/users/${user?.id}`, { - headers: headers_for_get - }) as USER; - - asserts.assertObjectMatch(retrieved_user, user ?? {}); - - const deleted_user_response: Record = await client.fetch(`/users/${user?.id}`, { - method: 'DELETE', - headers: headers_for_get - }) as USER; - - asserts.assert(deleted_user_response?.deleted); - } finally { - if (test_server_info) { - await test_server_info?.server?.stop(); - } - } - } -}); diff --git a/tests/api/users/update_user.test.ts b/tests/api/users/update_user.test.ts deleted file mode 100644 index 478bfed..0000000 --- a/tests/api/users/update_user.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { api, API_CLIENT } from '../../../utils/api.ts'; -import * as asserts from '@std/assert'; -import { USER } from '../../../models/user.ts'; -import { EPHEMERAL_SERVER, get_ephemeral_listen_server, random_username } from '../../helpers.ts'; -import { Cookie, getSetCookies } from '@std/http/cookie'; -import { encodeBase64 } from '@std/encoding'; -import { generateTotp } from '../../../utils/totp.ts'; - -Deno.test({ - name: 'API - USERS - Update', - permissions: { - env: true, - read: true, - write: true, - net: true - }, - fn: async () => { - let test_server_info: EPHEMERAL_SERVER | null = null; - try { - test_server_info = await get_ephemeral_listen_server(); - const client: API_CLIENT = api({ - prefix: '/api', - hostname: test_server_info.hostname, - port: test_server_info.port - }); - - const username = random_username(); - const password = 'password'; - const password_hash = encodeBase64( - await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password)) - ); - - let cookies: Cookie[] = []; - - const user_creation_response: Record = await client.fetch('/users', { - method: 'POST', - json: { - username, - password_hash - }, - done: (response) => { - cookies = getSetCookies(response.headers); - } - }); - - asserts.assert(user_creation_response?.user); - asserts.assert(user_creation_response?.session); - - const user: USER | undefined = user_creation_response.user; - const session: Record | undefined = user_creation_response.session; - - cookies.push({ - name: 'totp', - value: await generateTotp(session?.secret), - maxAge: 30, - expires: Date.now() + 30_000, - path: '/' - }); - - const headers_for_put = new Headers(); - for (const cookie of cookies) { - headers_for_put.append(`x-${cookie.name}`, cookie.value); - } - headers_for_put.append('cookie', cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ')); - - const updated_user: USER = await client.fetch(`/users/${user?.id}`, { - method: 'PUT', - json: { - username: random_username() - }, - headers: headers_for_put - }) as USER; - - asserts.assertNotEquals(user?.username ?? '', updated_user?.username ?? ''); - asserts.assertNotEquals(user?.timestamps.updated ?? '', updated_user?.timestamps.updated ?? ''); - } finally { - if (test_server_info) { - await test_server_info?.server?.stop(); - } - } - } -}); diff --git a/tests/helpers.ts b/tests/helpers.ts index d7e7cdb..2e53f6e 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -3,6 +3,8 @@ import { convert_to_words } from '@andyburke/lurid/word_bytes'; import { API_CLIENT } from '../utils/api.ts'; import { Cookie, getSetCookies } from '@std/http/cookie'; import { generateTotp } from '../utils/totp.ts'; +import { USER } from '../models/user.ts'; +import { SESSION } from '../models/session.ts'; const TLDs: string[] = [ 'com', @@ -98,50 +100,74 @@ export async function get_ephemeral_listen_server(options?: SERVER_OPTIONS): Pro return ephemeral_server; } -type NEW_USER_INFO = { - username: string; - password: string; +export type TEST_USER_INFO = { + user: USER; + session: SESSION; + headers: Headers; }; -export async function get_new_user(client: API_CLIENT, user_info?: Record): Promise> { - const info: Record = { +export async function get_new_user(client: API_CLIENT, user_info?: Record, inviting_user_info?: TEST_USER_INFO): Promise { + const new_user_request_json: Record = { username: random_username(), - password: `${random_username()} ! ${random_username()}`, ...user_info }; + if ( + !((typeof new_user_request_json.password === 'string' && new_user_request_json.password.length) || + (typeof new_user_request_json.password_hash === 'string' && new_user_request_json.password_hash.length)) + ) { + new_user_request_json.password = `${Math.round(Math.random() * 10)} - ${random_username()} ! ${random_username()}`; + } + + if (inviting_user_info) { + const invite_code = await client.fetch(`/users/${inviting_user_info.user?.id}/invites`, { + method: 'POST', + headers: { + 'x-session_id': inviting_user_info.session.id, + 'x-totp': await generateTotp(inviting_user_info.session.secret) + }, + json: { + code: random_username() + } + }); + + new_user_request_json.invite_code = invite_code.code; + } + await client.fetch('/users', { method: 'POST', - json: info + json: new_user_request_json }); const cookies: Cookie[] = []; const auth_response: any = await client.fetch('/auth', { method: 'POST', - json: info, + json: new_user_request_json, done: (response) => { cookies.push(...getSetCookies(response.headers)); } }); - info.user = auth_response.user; - info.session = auth_response.session; + const test_user_info: TEST_USER_INFO = { + user: auth_response.user, + session: auth_response.session, + headers: new Headers() + }; cookies.push({ name: 'totp', - value: await generateTotp(info.session?.secret), + value: await generateTotp(test_user_info.session?.secret), maxAge: 30, expires: Date.now() + 30_000, path: '/' }); - info.headers_for_get = new Headers(); for (const cookie of cookies) { - info.headers_for_get.append(`x-${cookie.name}`, cookie.value); + test_user_info.headers.append(`x-${cookie.name}`, cookie.value); } - info.headers_for_get.append('cookie', cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ')); + test_user_info.headers.append('cookie', cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ')); - return info; + return test_user_info; } export async function set_user_permissions(client: API_CLIENT, user: any, session: any, permissions: string[]): Promise { @@ -157,3 +183,13 @@ export async function set_user_permissions(client: API_CLIENT, user: any, sessio } }); } + +export async function delete_user(client: API_CLIENT, user_info: any): Promise { + await client.fetch(`/users/${user_info.user.id}`, { + method: 'DELETE', + headers: { + 'x-session_id': user_info.session.id, + 'x-totp': await generateTotp(user_info.session.secret) + } + }); +}