import { test } from 'node:test'; import assert from 'node:assert/strict'; import { randomUUID } from 'node:crypto'; import { readFileSync, writeFileSync, existsSync } from 'node:fs'; import { eq } from 'drizzle-orm'; import { fixture } from './helpers.js'; import { visitorRegion } from '../src/ownerInfo.js'; import { loadConfig } from '../src/config.js'; import * as s from '../src/db/schema.js'; test('basic country and city use only trusted Cloudflare headers and ignore precise coordinates', () => { const cfg = { ...loadConfig(), clientIpSource: 'cloudflare', trustedProxies: ['127.0.0.1'] }; const req = { raw: { socket: { remoteAddress: '127.0.0.1' } }, headers: { 'cf-connecting-ip': '8.8.8.8', 'cf-ipcountry': 'KR', 'cf-region': 'Daejeon', 'cf-ipcity': Buffer.from('대전', 'utf8').toString('latin1'), 'cf-iplatitude': '37.123456', 'cf-iplongitude': '127.123456', }, }; assert.deepEqual(visitorRegion(req, cfg), { ip: '8.8.8.8', countryCode: 'KR', region: 'Daejeon', city: '대전', source: 'cloudflare', }); for (const change of [{ trustedProxies: [] }, { clientIpSource: 'forwarded' }]) assert.deepEqual(visitorRegion(req, { ...cfg, ...change }), { ip: null, countryCode: null, region: null, city: null, source: 'unavailable', }); assert.equal( visitorRegion({ ...req, headers: { ...req.headers, 'cf-ipcountry': 'XX' } }, cfg).countryCode, null, ); }); test('region records are NAS-only, deduplicated by request, bounded and cleaned with owner files', async () => { const f = await fixture(); try { const a = await f.create(), b = await f.create(); await f.space(a, b); f.cfg.clientIpSource = 'cloudflare'; const key = randomUUID(), input = { targetDeviceId: b.deviceId, kind: 'file', byteSize: 4, count: 1, files: [{ name: 'test.mp4', size: 4, type: 'video/mp4' }], }, headers = { 'cf-connecting-ip': '8.8.8.8', 'cf-ipcountry': 'KR', 'cf-region': 'Daejeon', 'cf-ipcity': 'Daejeon', 'cf-iplatitude': '37.123456', }; const result = await f.call(a, '/transfer-requests', 'POST', input, key, headers); assert.equal(result.statusCode, 200); const id = result.json().requestId, row = f.service.storage.fileRows(id)[0], owner = f.service.storage.layout.ownerInfo.details(row.storageKey); const json = () => JSON.parse(readFileSync(owner.path, 'utf8')); assert.equal(json().uploader.id, row.ownerGuestId); assert.equal(json().recentLocations[0].city, 'Daejeon'); const serialized = readFileSync(owner.path, 'utf8'); assert.equal(json().recentLocations[0].ip, '8.8.8.8'); for (const secret of ['37.123456', a.csrf, a.cookie]) assert.equal(serialized.includes(secret), false); await f.call(a, '/transfer-requests', 'POST', input, key, headers); assert.equal(json().recentLocations.length, 1); for (let i = 0; i < 30; i++) f.service.storage.layout.ownerInfo.record(row, randomUUID(), { countryCode: 'CN', region: null, city: null, source: 'cloudflare', }); assert.equal(json().recentLocations.length, 20); const info = f.service.storage.layout.ownerInfo; assert.equal( info.fileLocation(row).city, 'Daejeon', 'newer uploads cannot replace this file location', ); info.record(row, id, { ip: '1.1.1.1', countryCode: 'JP', city: 'Tokyo' }); assert.equal( info.fileLocation(row).city, 'Daejeon', 'idempotent retries cannot overwrite the original snapshot', ); assert.equal(info.fileLocation(row).ip, '8.8.8.8'); const publicRequest = await f.call(b, '/transfer-requests'); assert.equal(publicRequest.body.includes('Daejeon'), false); assert.equal(publicRequest.body.includes('8.8.8.8'), false); const response = await f.app.inject({ url: '/uploads/originals/' + row.storageKey.split('/')[0] + '/owner-info.json', }); assert.equal( response.body.includes('recentLocations'), false, 'metadata is not publicly served', ); const expired = json(); for (const event of expired.recentLocations) event.recordedAt = new Date(Date.now() - 8 * 86400000).toISOString(); writeFileSync(owner.path, JSON.stringify(expired)); await f.service.storage.layout.ownerInfo.cleanup(); assert.equal(json().recentLocations.length, 0); f.service.db.update(s.files).set({ status: 'failed' }).where(eq(s.files.id, row.id)).run(); await f.service.storage.layout.ownerInfo.cleanup(); assert.equal(existsSync(owner.path), false); assert.equal(f.service.db.select().from(s.fileUploadContexts).all().length, 0); } finally { await f.close(); } }); test('each file keeps its own request location; legacy lookup matches its request and expired snapshots are removed', async () => { const f = await fixture(); try { const a = await f.create(), b = await f.create(); await f.space(a, b); f.cfg.clientIpSource = 'cloudflare'; async function upload(country, city, ip) { const request = await f.call( a, '/transfer-requests', 'POST', { targetDeviceId: b.deviceId, kind: 'file', count: 1, byteSize: 4, files: [{ name: 'test.txt', size: 4, type: 'text/plain' }], }, randomUUID(), { 'cf-connecting-ip': ip, 'cf-ipcountry': country, 'cf-ipcity': city }, ); assert.equal(request.statusCode, 200, request.body); const id = request.json().requestId; await f.call(b, `/transfer-requests/${id}/accept`, 'POST', {}, randomUUID()); const result = await f.call( a, `/uploads?requestId=${id}&fileIndex=0`, 'POST', Buffer.from( '--loc\r\nContent-Disposition: form-data; name="file"; filename="test.txt"\r\nContent-Type: text/plain\r\n\r\ntest\r\n--loc--\r\n', ), randomUUID(), { 'content-type': 'multipart/form-data; boundary=loc' }, ); assert.equal(result.statusCode, 200, result.body); return f.service.storage.fileRows(id)[0]; } const first = await upload('KR', 'Daejeon', '8.8.8.8'), second = await upload('JP', 'Tokyo', '1.1.1.1'), info = f.service.storage.layout.ownerInfo; const locations = new Map( f.service.admin.files({}).items.map((file) => [file.id, file.lastLocation]), ); assert.equal(locations.get(first.id).city, 'Daejeon'); assert.equal(locations.get(second.id).city, 'Tokyo'); f.service.db .delete(s.fileUploadContexts) .where(eq(s.fileUploadContexts.fileId, first.id)) .run(); assert.equal( info.fileLocation(first).city, 'Daejeon', 'recover only the matching legacy request', ); f.service.db .delete(s.fileUploadContexts) .where(eq(s.fileUploadContexts.fileId, first.id)) .run(); const owner = info.details(first.storageKey); info.write( owner, info.read(owner).events.filter((event) => event.requestId !== first.requestId), ); assert.equal( info.fileLocation(first), null, 'a missing old record must not use the newest location', ); f.service.db .update(s.fileUploadContexts) .set({ expiresAt: Date.now() - 1 }) .run(); const old = info.read(owner).events.map((event) => ({ ...event, recordedAt: new Date(Date.now() - 8 * 86400000).toISOString(), })); info.write(owner, old); await info.cleanup(); assert.equal(f.service.db.select().from(s.fileUploadContexts).all().length, 0); assert.equal(info.fileLocation(second), null); } finally { await f.close(); } });