import { SERVER, SERVER_OPTIONS } from 'jsr:@andyburke/serverus/server'; import { convert_to_words } from 'jsr:@andyburke/lurid/word_bytes'; import { API_CLIENT } from '../utils/api.ts'; import { Cookie, getSetCookies } from '@std/http/cookie'; import { generateTotp } from '@stdext/crypto/totp'; const TLDs: string[] = [ 'com', 'org', 'net', 'edu', 'gov', 'nexus', 'shop', 'unreasonablylongtldname' ]; const random_byte_buffer: Uint8Array = new Uint8Array(3); export function random_email_address(): string { crypto.getRandomValues(random_byte_buffer); const name = convert_to_words(random_byte_buffer).join('-'); crypto.getRandomValues(random_byte_buffer); const domain = convert_to_words(random_byte_buffer).join('-'); const tld = TLDs[Math.floor(Math.random() * TLDs.length)]; return `${name}@${domain}.${tld}`; } export function random_username(): string { crypto.getRandomValues(random_byte_buffer); return convert_to_words(random_byte_buffer).join('-'); } const BASE_PORT: number = 50_000; const MAX_PORT_OFFSET: number = 10_000; let current_port_offset = 0; function get_next_free_port(): number { let free_port: number | undefined = undefined; let attempts = 0; do { const port_to_try: number = BASE_PORT + (current_port_offset++ % MAX_PORT_OFFSET); try { Deno.listen({ port: port_to_try }).close(); free_port = port_to_try; } catch (error) { if (!(error instanceof Deno.errors.AddrInUse)) { throw error; } } ++attempts; if (attempts % MAX_PORT_OFFSET === 0) { console.warn(`Tried all ports at least once while trying to locate a free one, something wrong?`); } } while (!free_port); return free_port; } /** * Interface defining the configuration for an ephemeral server * @property {string} hostname - hostname bound to, default: 'localhost' * @property {number} port - port bound to, default: next free port * @property {SERVER} server - server instance */ export interface EPHEMERAL_SERVER { hostname: string; port: number; server: SERVER; } /** * Gets an ephemeral Serverus SERVER on an unused port. * * @param options Optional SERVER_OPTIONS * @returns A LISTEN_SERVER_SETUP object with information and a reference to the server */ export async function get_ephemeral_listen_server(options?: SERVER_OPTIONS): Promise { const server_options = { ...{ hostname: 'localhost', port: get_next_free_port() }, ...(options ?? {}) }; const server = new SERVER(server_options); const ephemeral_server: EPHEMERAL_SERVER = { hostname: server_options.hostname, port: server_options.port, server: await server.start() }; return ephemeral_server; } type NEW_USER_INFO = { username: string; password: string; }; export async function get_new_user(client: API_CLIENT, user_info?: Record): Promise> { const info: Record = { username: random_username(), password: `${random_username()} ! ${random_username()}`, ...user_info }; await client.fetch('/users', { method: 'POST', json: info }); const cookies: Cookie[] = []; const auth_response: any = await client.fetch('/auth', { method: 'POST', json: info, done: (response) => { cookies.push(...getSetCookies(response.headers)); } }); info.user = auth_response.user; info.session = auth_response.session; cookies.push({ name: 'totp', value: await generateTotp(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); } info.headers_for_get.append('cookie', cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ')); return info; } export async function set_user_permissions(client: API_CLIENT, user: any, session: any, permissions: string[]): Promise { return await client.fetch(`/users/${user.id}`, { method: 'PUT', headers: { 'x-session_id': session.id, 'x-totp': await generateTotp(session.secret), 'x-test-override': 'true' }, json: { permissions } }); }