import { test } from 'node:test'; import assert from 'node:assert/strict'; import { randomUUID } from 'node:crypto'; import { EventEmitter } from 'node:events'; import { existsSync } from 'node:fs'; import { io } from 'socket.io-client'; import { fixture } from './helpers.js'; import * as s from '../src/db/schema.js'; import { eq } from 'drizzle-orm'; import { Administration, adminPatch, matchesIp } from '../src/administration.js'; async function login(f, email = 'hello@noname.company', verified = true) { const c = await f.create(), id = randomUUID(); f.service.db .insert(s.users) .values({ id, email, displayName: email, passwordHash: await f.service.auth.password('12345678'), role: 'user', status: verified ? 'active' : 'pending', verifiedAt: verified ? Date.now() : null, createdAt: Date.now(), }) .run(); const response = await f.call(c, '/auth/login', 'POST', { email, password: '12345678' }); assert.equal(response.statusCode, 200); c.cookie += '; ' + String(response.headers['set-cookie']).split(';')[0]; return c; } async function patch(f, c, changes, headers = {}) { return f.call( c, '/admin/settings', 'PATCH', { revision: f.service.admin.state.revision, ...changes }, undefined, headers, ); } async function fileRequest(f, a, b, size = 4) { const r = await f.call( a, '/transfer-requests', 'POST', { targetDeviceId: b.deviceId, kind: 'file', byteSize: size, count: 1, files: [{ name: 'private.txt', size, type: 'text/plain' }], }, randomUUID(), ); assert.equal(r.statusCode, 200, r.body); return r.json().requestId; } test('only the verified active hello account can view and mutate admin APIs; guest CSRF still applies', async () => { const f = await fixture(); try { const guest = await f.create(), other = await login(f, 'other@hop.test'), admin = await login(f), caseAlias = await login(f, 'Hello@noname.company'), pending = await login(f, 'pending@hop.test', false); for (const path of ['/admin/summary', '/admin/files', '/admin/audit']) { assert.equal((await f.call(null, path)).statusCode, 401); assert.equal((await f.call(guest, path)).statusCode, 401); assert.equal((await f.call(other, path)).statusCode, 403); assert.equal((await f.call(caseAlias, path)).statusCode, 403); assert.equal((await f.call(pending, path)).statusCode, 403); assert.equal((await f.call(admin, path)).statusCode, 200); } assert.equal((await f.call(admin, '/auth/me')).json().account.administrator, true); assert.equal((await f.call(other, '/auth/me')).json().account.administrator, false); assert.deepEqual((await f.call(admin, '/admin/summary')).json().limits, { perFileBytes: f.cfg.maxFileBytes, accountBytes: f.cfg.userQuota, deviceBytes: f.cfg.guestQuota, devicesTotalBytes: f.cfg.totalGuestQuota, minDiskFreeBytes: f.cfg.minFreeBytes, }); assert.equal((await f.call(caseAlias, '/auth/me')).json().account.administrator, false); assert.equal((await patch(f, other, { uploadsPaused: true })).statusCode, 403); assert.equal( (await patch(f, admin, { uploadsPaused: true }, { 'x-csrf-token': 'bad' })).statusCode, 403, ); assert.equal((await f.call(other, `/admin/files/${randomUUID()}`, 'DELETE')).statusCode, 403); const state = f.service.admin.state; assert.equal(state.uploadsPaused, false); f.service.db .update(s.users) .set({ status: 'suspended' }) .where(eq(s.users.email, 'hello@noname.company')) .run(); assert.equal((await f.call(admin, '/admin/summary')).statusCode, 401); } finally { await f.close(); } }); test('unverified hello is not an administrator even with an admin role', async () => { const f = await fixture(); try { const c = await login(f, 'hello@noname.company', false); f.service.db .update(s.users) .set({ role: 'admin' }) .where(eq(s.users.email, 'hello@noname.company')) .run(); assert.equal((await f.call(c, '/auth/me')).json().account.administrator, false); assert.equal((await f.call(c, '/admin/summary')).statusCode, 403); } finally { await f.close(); } }); test('pause blocks new guest and personal file requests but keeps links and accepted uploads; settings and audit persist', async () => { const f = await fixture(); try { const admin = await login(f), a = await f.create(), b = await f.create(); await f.space(a, b); const id = await fileRequest(f, a, b); await f.call(b, `/transfer-requests/${id}/accept`, 'POST', {}, randomUUID()); const r = await patch(f, admin, { uploadsPaused: true, maxConcurrentUploads: 3 }); assert.equal(r.statusCode, 200); const body = Buffer.from( '--boundary\r\nContent-Disposition: form-data; name="file"; filename="private.txt"\r\nContent-Type: text/plain\r\n\r\ntest\r\n--boundary--\r\n', ); const uploaded = await f.call( a, `/uploads?requestId=${id}&fileIndex=0`, 'POST', body, randomUUID(), { 'content-type': 'multipart/form-data; boundary=boundary' }, ); assert.equal(uploaded.statusCode, 200, uploaded.body); const denied = await f.call( a, '/transfer-requests', 'POST', { targetDeviceId: b.deviceId, kind: 'file', byteSize: 4, count: 1, files: [{ name: 'new.txt', size: 4, type: 'text/plain' }], }, randomUUID(), ); assert.equal(denied.json().code, 'UPLOADS_PAUSED'); const own = await f.call( admin, '/vault', 'POST', { kind: 'file', files: [{ name: 'own.txt', size: 4, type: 'text/plain' }] }, randomUUID(), ); assert.equal(own.json().code, 'UPLOADS_PAUSED'); assert.equal((await f.request(a, b)).statusCode, 200); assert.equal(new Administration(f.service).state.uploadsPaused, true); assert.equal(new Administration(f.service).state.maxConcurrentUploads, 3); assert.equal((await f.call(admin, '/admin/audit')).json().events[0].action, 'settings-change'); const stale = await f.call(admin, '/admin/settings', 'PATCH', { revision: 0, uploadsPaused: false, }); assert.equal(stale.statusCode, 409); } finally { await f.close(); } }); test('country and CIDR rules cover HTTP and WebSocket; untrusted location headers are ignored and self-block is rejected', async () => { const f = await fixture(); let socket; try { const admin = await login(f), guest = await f.create(); f.cfg.clientIpSource = 'cloudflare'; const actor = { 'cf-connecting-ip': '8.8.4.4', 'cf-ipcountry': 'KR' }; assert.equal( ( await patch( f, admin, { blockedCountries: ['CN'], blockedIps: ['1.1.1.0/24', '2001:db8::/32'] }, actor, ) ).statusCode, 200, ); assert.equal(matchesIp('2001:db8::123', f.service.admin.ipRules), true); assert.equal(matchesIp('2001:db9::123', f.service.admin.ipRules), false); assert.equal(matchesIp('::ffff:1.1.1.12', f.service.admin.ipRules), true); assert.equal( ( await f.call(guest, '/device/me', 'GET', undefined, undefined, { 'cf-connecting-ip': '8.8.8.8', 'cf-ipcountry': 'CN', }) ).json().code, 'ACCESS_BLOCKED', ); assert.equal( ( await f.call(guest, '/device/me', 'GET', undefined, undefined, { 'cf-connecting-ip': '1.1.1.99', 'cf-ipcountry': 'US', }) ).statusCode, 403, ); assert.equal( ( await f.call(guest, '/device/me', 'GET', undefined, undefined, { 'cf-connecting-ip': '8.8.8.8', }) ).statusCode, 200, 'unknown country is not guessed', ); assert.equal( (await patch(f, admin, { blockedCountries: ['KR'] }, actor)).json().code, 'ADMIN_SELF_BLOCK', ); assert.equal( (await patch(f, admin, { blockedIps: ['8.8.0.0/16'] }, actor)).json().code, 'ADMIN_SELF_BLOCK', ); assert.equal((await patch(f, admin, { blockedIps: ['bad'] }, actor)).statusCode, 400); assert.equal((await patch(f, admin, { maxConcurrentUploads: 100 }, actor)).statusCode, 400); socket = io(f.url, { transports: ['websocket'], reconnection: false, auth: { csrfToken: guest.csrf }, extraHeaders: { Origin: f.cfg.appUrl, Cookie: guest.cookie, 'CF-Connecting-IP': '8.8.8.8', 'CF-IPCountry': 'CN', }, }); await new Promise((resolve, reject) => { const timer = setTimeout(() => reject(Error('blocked socket unexpectedly connected')), 2000); socket.once('connect_error', () => { clearTimeout(timer); resolve(); }); socket.once('connect', () => reject(Error('blocked socket connected'))); }); f.cfg.trustedProxies = []; assert.equal( ( await f.call(guest, '/device/me', 'GET', undefined, undefined, { 'cf-connecting-ip': '1.1.1.99', 'cf-ipcountry': 'CN', }) ).statusCode, 200, ); } finally { socket?.disconnect(); await f.close(); } }); test('a newly blocked IP aborts its admitted request while keeping its raw HTTP objects private', async () => { const f = await fixture(); try { const admin = await login(f); const raw = new EventEmitter(), response = new EventEmitter(); raw.socket = { remoteAddress: '127.0.0.1' }; let destroyed = 0; raw.destroy = () => destroyed++; response.destroy = () => destroyed++; f.service.admin.admit( { raw, headers: { 'x-forwarded-for': '8.8.8.8' } }, { raw: response }, 'attacker', ); const summary = f.service.admin.summary(); assert.equal(summary.requests[0].ip, '8.8.8.8'); assert.equal('raw' in summary.requests[0], false); assert.equal((await patch(f, admin, { blockedIps: ['8.8.8.8'] })).statusCode, 200); assert.equal(destroyed, 2); assert.equal(f.service.admin.inflight.size, 0); } finally { await f.close(); } }); test('a failed admin settings commit does not change the effective policy or audit ledger', async () => { const f = await fixture(); try { const admin = await login(f); assert.equal((await patch(f, admin, { maxConcurrentUploads: 3 })).statusCode, 200); f.sqlite.exec( "CREATE TRIGGER reject_admin BEFORE UPDATE ON admin_settings BEGIN SELECT RAISE(ABORT, 'test settings failure'); END", ); assert.equal((await patch(f, admin, { uploadsPaused: true })).statusCode, 500); assert.equal(f.service.admin.state.uploadsPaused, false); assert.equal(new Administration(f.service).state.uploadsPaused, false); assert.equal(f.service.admin.events().length, 1); } finally { await f.close(); } }); test('global quota includes existing reservations across different upload owners', async () => { const f = await fixture(); try { const admin = await login(f), a = await f.create(), b = await f.create(), c = await f.create(); await f.space(a, b); await f.space(a, c); assert.equal((await patch(f, admin, { totalStorageLimitMb: 1024 })).statusCode, 200); const one = await fileRequest(f, a, b, 700 * 1048576), two = await fileRequest(f, c, b, 700 * 1048576); assert.equal( (await f.call(b, `/transfer-requests/${one}/accept`, 'POST', {}, randomUUID())).statusCode, 200, ); const denied = await f.call(b, `/transfer-requests/${two}/accept`, 'POST', {}, randomUUID()); assert.equal(denied.statusCode, 507); assert.equal(denied.json().code, 'STORAGE_QUOTA'); assert.equal((await f.call(admin, '/admin/summary')).json().reservedBytes, 700 * 1048576); } finally { await f.close(); } }); test('body admission rejects extra uploads before parsing, releases slots on abort/response and enforces per-owner concurrency', async () => { const f = await fixture(); try { const c = await f.create(); f.service.admin.state.maxConcurrentUploads = 1; const req = { raw: new EventEmitter(), headers: {} }; req.raw.socket = { remoteAddress: '127.0.0.1' }; const reply = { raw: new EventEmitter() }; const release = f.service.admin.admit(req, reply, 'owner'); const busy = await f.call( c, `/uploads/resumable/${randomUUID()}`, 'PUT', Buffer.alloc(10), undefined, { 'content-type': 'application/octet-stream', 'upload-offset': '0' }, ); assert.equal(busy.statusCode, 429); assert.equal(busy.json().code, 'UPLOAD_BUSY'); assert.equal(Number(busy.headers['retry-after']), 1); req.raw.emit('aborted'); release(); assert.equal(f.service.admin.inflight.size, 0); const next = await f.call( c, `/uploads/resumable/${randomUUID()}`, 'PUT', Buffer.alloc(10), undefined, { 'content-type': 'application/octet-stream', 'upload-offset': '0' }, ); assert.equal(next.statusCode, 404); assert.equal(f.service.admin.inflight.size, 0); f.service.admin.state.maxConcurrentUploads = 4; const releases = [ f.service.admin.admit(req, reply, 'owner'), f.service.admin.admit(req, reply, 'owner'), ]; assert.throws( () => f.service.admin.admit(req, reply, 'owner'), (error) => error.code === 'UPLOAD_BUSY', ); releases.forEach((stop) => stop()); const oversized = await f.call(null, '/guest/session', 'POST', { value: 'a'.repeat(2000) }); assert.equal(oversized.statusCode, 413); } finally { await f.close(); } }); test('admin deletion removes physical bytes and all shared references; listing is paginated and audit entries expire', async () => { const f = await fixture(); try { const admin = await login(f), a = await f.create(), b = await f.create(); await f.space(a, b); const id = await fileRequest(f, a, b); await f.call(b, `/transfer-requests/${id}/accept`, 'POST', {}, randomUUID()); const payload = Buffer.from( '--delete\r\nContent-Disposition: form-data; name="file"; filename="private.txt"\r\nContent-Type: text/plain\r\n\r\ntest\r\n--delete--\r\n', ); await f.call(a, `/uploads?requestId=${id}&fileIndex=0`, 'POST', payload, randomUUID(), { 'content-type': 'multipart/form-data; boundary=delete', }); const row = f.service.storage.fileRows(id)[0], path = f.service.storage.layout.path(row), access = (await f.call(b, '/device/items')).json().items[0]; const secondId = await fileRequest(f, a, b); await f.call(b, `/transfer-requests/${secondId}/accept`, 'POST', {}, randomUUID()); const secondUpload = await f.call( a, `/uploads?requestId=${secondId}&fileIndex=0`, 'POST', payload, randomUUID(), { 'content-type': 'multipart/form-data; boundary=delete' }, ); assert.equal(secondUpload.statusCode, 200, secondUpload.body); const secondRow = f.service.storage.fileRows(secondId)[0]; const list = (await f.call(admin, '/admin/files?limit=1')).json(); assert.equal(list.items[0].name, 'private.txt'); assert.equal(list.items[0].uploader.id, row.ownerGuestId); assert.ok(list.nextCursor); const nextPage = (await f.call(admin, '/admin/files?limit=1&cursor=' + list.nextCursor)).json(); assert.equal(nextPage.items.length, 1); assert.notEqual(list.items[0].id, nextPage.items[0].id); assert.deepEqual( new Set([list.items[0].id, nextPage.items[0].id]), new Set([row.id, secondRow.id]), ); assert.equal(nextPage.nextCursor, null); assert.equal((await f.call(admin, '/admin/files?cursor=bad')).statusCode, 400); assert.equal((await f.call(admin, `/admin/files/${row.id}`, 'DELETE')).statusCode, 200); assert.equal(existsSync(path), false); assert.equal((await f.call(b, `/access/${access.accessId}/download`)).statusCode, 404); assert.equal((await f.call(admin, '/admin/summary')).json().storedBytes, 4); assert.equal((await f.call(admin, `/admin/files/${secondRow.id}`, 'DELETE')).statusCode, 200); assert.equal((await f.call(admin, '/admin/summary')).json().storedBytes, 0); assert.equal((await f.call(admin, '/admin/audit')).json().events[0].action, 'file-delete'); f.service.db .update(s.adminAudit) .set({ createdAt: Date.now() - 31 * 86400000 }) .run(); f.service.admin.cleanup(); assert.equal(f.service.admin.events().length, 0); } finally { await f.close(); } });