import assert from 'node:assert/strict'; import { once } from 'node:events'; import test from 'node:test'; import { adminSignature, uploadSignature } from '../src/auth.mjs'; import { createApp } from '../src/server.mjs'; const noUploadStore = { async initialize() {}, async store() { throw new Error('must not upload'); } }; test('sync endpoint authenticates a scoped caller and passes only an optional commit', async (t) => { const calls = []; const service = { async initialize() {}, async deploySync(value) { calls.push(value); return { changed: false }; }, async recordError() {}, }; const secret = 's'.repeat(32); const { server } = await createApp({ maxBodyBytes: 4096, syncClientSecrets: { core: secret }, }, { service, uploadStore: noUploadStore }); server.listen(0, '127.0.0.1'); await once(server, 'listening'); t.after(() => server.close()); const address = server.address(); const body = Buffer.from(JSON.stringify({ commit: 'a'.repeat(40) })); const timestamp = String(Date.now()); const requestId = 'sync-request-1234'; const response = await fetch(`http://127.0.0.1:${address.port}/v1/sync`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-image-client': 'core', 'x-image-timestamp': timestamp, 'x-image-request-id': requestId, 'x-image-signature': adminSignature(secret, timestamp, requestId, body), }, body, }); assert.equal(response.status, 200); assert.deepEqual(calls, [{ requestKey: `core:${requestId}`, expectedCommit: 'a'.repeat(40) }]); }); test('upload endpoint accepts a short-lived body-bound grant and rejects replay or tampering', async (t) => { const service = { async initialize() {}, async recordError() {}, }; const calls = []; const uploadStore = { async initialize() {}, async store(value) { calls.push(value); return { duplicate: false, path: `icons/users/${value.client}/${value.filename}` }; }, }; const secret = 'u'.repeat(32); const config = { maxBodyBytes: 4096, maxUploadBytes: 51200, maxContentUploadBytes: 1048576, syncClientSecrets: { core: 's'.repeat(32) }, uploadClientSecrets: { core2026: secret }, publicBases: ['https://sam-image.hided.net', 'https://sam.hided.net/image'], }; const { server } = await createApp(config, { service, uploadStore }); server.listen(0, '127.0.0.1'); await once(server, 'listening'); t.after(() => server.close()); const address = server.address(); const pathname = `/v1/uploads/user-icons/core2026/${'a'.repeat(32)}.png`; const body = Buffer.from('89504e470d0a1a0a00000000', 'hex'); const expires = String(Math.floor(Date.now() / 1000) + 60); const requestId = 'upload-request-1234'; const signature = uploadSignature(secret, { expires, requestId, pathname, contentType: 'image/png', body }); const headers = { 'content-type': 'image/png', 'x-image-client': 'core2026', 'x-image-expires': expires, 'x-image-request-id': requestId, 'x-image-signature': signature, }; const accepted = await fetch(`http://127.0.0.1:${address.port}${pathname}`, { method: 'PUT', headers, body }); assert.equal(accepted.status, 201); assert.deepEqual(calls[0], { requestKey: `core2026:${requestId}`, category: 'user-icons', client: 'core2026', filename: `${'a'.repeat(32)}.png`, body, }); assert.deepEqual((await accepted.json()).urls, [ `https://sam-image.hided.net/icons/users/core2026/${'a'.repeat(32)}.png`, `https://sam.hided.net/image/icons/users/core2026/${'a'.repeat(32)}.png`, ]); const contentPath = `/v1/uploads/content/core2026/${'b'.repeat(32)}.webp`; const contentBody = Buffer.concat([Buffer.from('RIFF'), Buffer.alloc(4), Buffer.from('WEBP'), Buffer.alloc(4)]); const contentRequestId = 'content-request-1234'; const contentSignature = uploadSignature(secret, { expires, requestId: contentRequestId, pathname: contentPath, contentType: 'image/webp', body: contentBody, }); const contentResponse = await fetch(`http://127.0.0.1:${address.port}${contentPath}`, { method: 'PUT', headers: { 'content-type': 'image/webp', 'x-image-client': 'core2026', 'x-image-expires': expires, 'x-image-request-id': contentRequestId, 'x-image-signature': contentSignature, }, body: contentBody, }); assert.equal(contentResponse.status, 201); assert.equal(calls[1].category, 'content'); assert.equal(calls[1].filename, `${'b'.repeat(32)}.webp`); const tampered = await fetch(`http://127.0.0.1:${address.port}${pathname}`, { method: 'PUT', headers, body: Buffer.from('89504e470d0a1a0affffffff', 'hex'), }); assert.equal(tampered.status, 401); const expired = '1000000000'; const expiredHeaders = { ...headers, 'x-image-expires': expired, 'x-image-signature': uploadSignature(secret, { expires: expired, requestId, pathname, contentType: 'image/png', body }), }; const expiredResponse = await fetch(`http://127.0.0.1:${address.port}${pathname}`, { method: 'PUT', headers: expiredHeaders, body, }); assert.equal(expiredResponse.status, 401); }); test('sync endpoint rejects unknown callers and body fields outside the sync contract', async (t) => { const service = { async initialize() {}, async deploySync() { throw new Error('must not deploy'); }, async recordError() {}, }; const secret = 's'.repeat(32); const { server } = await createApp( { maxBodyBytes: 4096, syncClientSecrets: { core: secret } }, { service, uploadStore: noUploadStore }, ); server.listen(0, '127.0.0.1'); await once(server, 'listening'); t.after(() => server.close()); const address = server.address(); const body = Buffer.from(JSON.stringify({ branch: 'preview' })); const timestamp = String(Date.now()); const requestId = 'sync-request-5678'; const signedHeaders = { 'content-type': 'application/json', 'x-image-timestamp': timestamp, 'x-image-request-id': requestId, 'x-image-signature': adminSignature(secret, timestamp, requestId, body), }; const unknown = await fetch(`http://127.0.0.1:${address.port}/v1/sync`, { method: 'POST', headers: { ...signedHeaders, 'x-image-client': 'unknown' }, body, }); assert.equal(unknown.status, 401); const prototypeName = await fetch(`http://127.0.0.1:${address.port}/v1/sync`, { method: 'POST', headers: { ...signedHeaders, 'x-image-client': 'toString' }, body, }); assert.equal(prototypeName.status, 401); const extraField = await fetch(`http://127.0.0.1:${address.port}/v1/sync`, { method: 'POST', headers: { ...signedHeaders, 'x-image-client': 'core' }, body, }); assert.equal(extraField.status, 400); }); test('internal content access endpoint batches only valid uploaded content paths', async (t) => { const service = { async initialize() {}, async recordError() {} }; const touched = []; const assetStore = { async initialize() {}, touch(path) { touched.push(path); return path.startsWith('uploads/'); }, close() {}, }; const { server } = await createApp({ maxBodyBytes: 4096 }, { service, uploadStore: noUploadStore, assetStore, }); server.listen(0, '127.0.0.1'); await once(server, 'listening'); t.after(() => server.close()); const address = server.address(); const validPath = `/uploads/core2026/${'e'.repeat(32)}.png?cache=1`; const valid = await fetch(`http://127.0.0.1:${address.port}/v1/internal/content-access`, { headers: { 'x-image-path': validPath }, }); assert.equal(valid.status, 204); assert.deepEqual(touched, [`uploads/core2026/${'e'.repeat(32)}.png`]); const legacyBase = await fetch(`http://127.0.0.1:${address.port}/v1/internal/content-access`, { method: 'HEAD', headers: { 'x-image-path': `/image/uploads/core/${'f'.repeat(32)}.webp` }, }); assert.equal(legacyBase.status, 204); assert.deepEqual(touched, [ `uploads/core2026/${'e'.repeat(32)}.png`, `uploads/core/${'f'.repeat(32)}.webp`, ]); }); test('admin panel uses a separate login session and CSRF-protected asset actions', async (t) => { const service = { async initialize() {}, async recordError() {} }; const actions = []; const asset = { path: `uploads/core/${'f'.repeat(32)}.webp`, category: 'content', client: 'core', filename: `${'f'.repeat(32)}.webp`, sizeBytes: 10, digest: null, createdAt: new Date(0).toISOString(), lastSeenAt: new Date(0).toISOString(), state: 'candidate', candidateAt: new Date(0).toISOString(), quarantinedAt: null, deletedAt: null, eligibleAt: new Date(0).toISOString(), deleteAvailableAt: null, deleteAvailable: false, }; const assetStore = { async initialize() {}, touch() { return false; }, close() {}, list() { return { total: 1, limit: 100, offset: 0, assets: [asset] }; }, summary() { return { totalCount: 1, totalBytes: 10, groups: { 'content:candidate': { count: 1, bytes: 10 } } }; }, async quarantine(path) { actions.push(['quarantine', path]); return { ...asset, state: 'quarantined' }; }, }; const config = { maxBodyBytes: 4096, adminPanelPassword: 'panel-password-value', adminPanelSessionSecret: 'p'.repeat(32), adminPanelSessionTtlMs: 8 * 3_600_000, }; const { server } = await createApp(config, { service, uploadStore: noUploadStore, assetStore }); server.listen(0, '127.0.0.1'); await once(server, 'listening'); t.after(() => server.close()); const base = `http://127.0.0.1:${server.address().port}`; const anonymousApi = await fetch(`${base}/admin/api/assets`); assert.equal(anonymousApi.status, 401); const loginBody = new URLSearchParams({ password: config.adminPanelPassword }); const login = await fetch(`${base}/admin/login`, { method: 'POST', redirect: 'manual', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: loginBody, }); assert.equal(login.status, 303); const cookie = login.headers.get('set-cookie').split(';', 1)[0]; assert.match(login.headers.get('set-cookie'), /HttpOnly; Secure; SameSite=Strict/); const dashboard = await fetch(`${base}/admin/`, { headers: { cookie } }); assert.equal(dashboard.status, 200); assert.match(dashboard.headers.get('content-security-policy'), /frame-ancestors 'none'/); const html = await dashboard.text(); const csrf = html.match(/name="csrf-token" content="([^"]+)"/)[1]; const list = await fetch(`${base}/admin/api/assets`, { headers: { cookie } }); assert.equal(list.status, 200); assert.equal((await list.json()).assets[0].state, 'candidate'); const rejected = await fetch(`${base}/admin/api/assets/action`, { method: 'POST', headers: { cookie, 'content-type': 'application/json' }, body: JSON.stringify({ path: asset.path, action: 'quarantine' }), }); assert.equal(rejected.status, 403); const accepted = await fetch(`${base}/admin/api/assets/action`, { method: 'POST', headers: { cookie, 'content-type': 'application/json', 'x-csrf-token': csrf }, body: JSON.stringify({ path: asset.path, action: 'quarantine' }), }); assert.equal(accepted.status, 200); assert.deepEqual(actions, [['quarantine', asset.path]]); });