diff --git a/.gitignore b/.gitignore index 639a628..b74bc5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ data/ -.fsdb* +.fsdb public/files/* -.vscode/* +.vscode/* \ No newline at end of file diff --git a/public/api/topics/:topic_id/events/index.ts b/public/api/topics/:topic_id/events/index.ts index fe99165..95f947d 100644 --- a/public/api/topics/:topic_id/events/index.ts +++ b/public/api/topics/:topic_id/events/index.ts @@ -204,6 +204,10 @@ export async function POST(req: Request, meta: Record): Promise[A-Za-z\-]+)\/?(?\w+)?/gm; const UPDATE_TOPICS_FREQUENCY = 60_000; const APP = { - user: undefined, user_servers: [], user_watches: [], @@ -57,19 +56,16 @@ const APP = { groups: {}, }; - /* console.dir({ url: window.location.href, hash: window.location.hash, topic_id, view, }); - */ if (!document.body.dataset.topic || document.body.dataset.topic !== topic_id) { const previous = document.body.dataset.topic; - /* console.dir({ topic_changed: { detail: { @@ -78,7 +74,6 @@ const APP = { }, }, }); - */ document.body.dataset.topic = topic_id; @@ -99,7 +94,6 @@ const APP = { const previous = document.body.dataset.view; document.body.dataset.view = view; - /* console.dir({ view_changed: { detail: { @@ -108,7 +102,6 @@ const APP = { }, }, }); - */ this._emit( 'view_changed', { previous, @@ -156,9 +149,8 @@ const APP = { this._emit( 'load', this ); }, - update_user: async function( user ) { - this.user = user; - + update_user: async function( updated_user ) { + const user = this.user = updated_user; document.body.dataset.user = JSON.stringify(user); document.body.dataset.perms = user.permissions.join(":"); diff --git a/public/sidebar/sidebar.html b/public/sidebar/sidebar.html index d118b03..556e4fe 100644 --- a/public/sidebar/sidebar.html +++ b/public/sidebar/sidebar.html @@ -809,10 +809,9 @@ form.on_reply = (new_topic) => { const topic_list = document.getElementById("topic-list"); - topic_list.querySelectorAll( 'li' ).forEach( (li) => li.classList.remove( 'active' ) ); topic_list.insertAdjacentHTML( "beforeend", - `
  • ${new_topic.name}
  • `, + `
  • ${new_topic.name}
  • `, ); new_topic_name_input.value = ""; diff --git a/public/signup_login_wall.html b/public/signup_login_wall.html index f4b2834..e252cdc 100644 --- a/public/signup_login_wall.html +++ b/public/signup_login_wall.html @@ -105,7 +105,12 @@ const form = document.currentScript.closest("form"); form.on_reply = (response) => { const user = response.user; - APP.login( user ); + document.body.dataset.user = JSON.stringify(user); + document.body.dataset.perms = user.permissions.join(":"); + + document.dispatchEvent( + new CustomEvent("user_logged_in", { detail: { user } }), + ); }; } diff --git a/tests/03_login.test.ts b/tests/03_login.test.ts index 3ed1c88..25cc4d4 100644 --- a/tests/03_login.test.ts +++ b/tests/03_login.test.ts @@ -44,7 +44,7 @@ Deno.test({ } }); - const _authed_user: USER | undefined = auth_response.user; + const authed_user: USER | undefined = auth_response.user; const authed_session: Record | undefined = auth_response.session; cookies.push({ diff --git a/tests/04_create_topic.test.ts b/tests/04_create_topic.test.ts index 5b248e9..6fee81e 100644 --- a/tests/04_create_topic.test.ts +++ b/tests/04_create_topic.test.ts @@ -22,34 +22,14 @@ Deno.test({ port: test_server_info.port }); - const root_user_info = await get_new_user(client); - - try { - const root_user_topic = await client.fetch('/topics', { - method: 'POST', - headers: { - 'x-session_id': root_user_info.session.id, - 'x-totp': await generateTotp(root_user_info.session.secret) - }, - json: { - name: 'this is the root user topic' - } - }); - - asserts.assert(root_user_topic); - } catch (error) { - const reason: string = (error as Error).cause as string ?? (error as Error).toString(); - asserts.fail(reason); - } - - const regular_user_info = await get_new_user(client, {}, root_user_info); + const info = await get_new_user(client); try { const _permission_denied_topic = await client.fetch('/topics', { method: 'POST', headers: { - 'x-session_id': regular_user_info.session.id, - 'x-totp': await generateTotp(regular_user_info.session.secret) + 'x-session_id': info.session.id, + 'x-totp': await generateTotp(info.session.secret) }, json: { name: 'this should not be allowed' @@ -61,14 +41,14 @@ Deno.test({ asserts.assertEquals((error as Error).cause, 'permission_denied'); } - await set_user_permissions(client, regular_user_info.user, regular_user_info.session, [...regular_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': regular_user_info.session.id, - 'x-totp': await generateTotp(regular_user_info.session.secret) + 'x-session_id': info.session.id, + 'x-totp': await generateTotp(info.session.secret) }, json: { name: 'X'.repeat(1024) @@ -83,8 +63,8 @@ Deno.test({ const new_topic = await client.fetch('/topics', { method: 'POST', headers: { - 'x-session_id': regular_user_info.session.id, - 'x-totp': await generateTotp(regular_user_info.session.secret) + 'x-session_id': info.session.id, + 'x-totp': await generateTotp(info.session.secret) }, json: { name: 'test topic' @@ -93,8 +73,7 @@ Deno.test({ asserts.assert(new_topic); - await delete_user(client, regular_user_info); - await delete_user(client, root_user_info); + await delete_user(client, info); } finally { clear_topic_events_cache(); if (test_server_info) { diff --git a/tests/11_file_uploads.test.ts b/tests/11_file_uploads.test.ts index e2d1c5e..413cd8d 100644 --- a/tests/11_file_uploads.test.ts +++ b/tests/11_file_uploads.test.ts @@ -1,7 +1,8 @@ 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, set_user_permissions } from './helpers.ts'; -import { Cookie } from '@std/http/cookie'; +import { Cookie, getSetCookies } from '@std/http/cookie'; import { generateTotp } from '../utils/totp.ts'; import * as fs from '@std/fs'; import * as path from '@std/path'; @@ -135,11 +136,55 @@ Deno.test({ port: test_server_info.port }); - const root_user_info = await get_new_user(client); - asserts.assert(root_user_info); + const username = random_username(); + const password = 'password'; - const regular_user_info = await get_new_user(client, {}, root_user_info); - asserts.assert(regular_user_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; + 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( @@ -151,10 +196,7 @@ Deno.test({ `http://${test_server_info.hostname}:${test_server_info.port}/files/test_uploading_to_root_dir.txt`, { method: 'PUT', - headers: { - 'x-session_id': regular_user_info.session.id, - 'x-totp': await generateTotp(regular_user_info.session.secret) - }, + headers: headers_for_upload_request, body: upload_body } ); @@ -162,16 +204,13 @@ Deno.test({ asserts.assert(!disallowed_upload_response.ok); await disallowed_upload_response.text(); - await set_user_permissions(client, regular_user_info.user, regular_user_info.session, [...regular_user_info.user.permissions, 'files.write.all']); + await set_user_permissions(client, user, session, [...user.permissions, 'files.write.all']); const allowed_upload_response = await fetch( `http://${test_server_info.hostname}:${test_server_info.port}/files/test_uploading_to_root_dir.txt`, { method: 'PUT', - headers: { - 'x-session_id': regular_user_info.session.id, - 'x-totp': await generateTotp(regular_user_info.session.secret) - }, + headers: headers_for_upload_request, body: upload_body } ); diff --git a/utils/totp.ts b/utils/totp.ts index 43460e2..8c913be 100644 --- a/utils/totp.ts +++ b/utils/totp.ts @@ -7,10 +7,9 @@ import { decodeBase32 } from '@std/encoding'; * * @ignore */ -export function counterToBuffer(counter: number): ArrayBuffer { - const buffer = new ArrayBuffer(8); - const view = new DataView(buffer); - view.setBigUint64(0, BigInt(counter), false); +export function counterToBuffer(counter: number): DataView { + const buffer = new DataView(new ArrayBuffer(8)); + buffer.setBigUint64(0, BigInt(counter), false); return buffer; } @@ -24,7 +23,7 @@ export async function generateHmacSha1( ): Promise { const importedKey = await crypto.subtle.importKey( 'raw', - new Uint8Array(key), + key, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']