import { test } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { createApp } from './server.mjs'; test('OAuth rejects strangers, invalid state and replay; both owners can write and readers cannot', async () => { const directory = mkdtempSync(join(tmpdir(), 'botflow-test-')); let profileId = '999999999999999999'; let requests = 0; const server = createApp({ env: { DATA_DIR: directory, APP_URL: 'http://localhost:3080', DISCORD_CLIENT_ID: 'client', DISCORD_CLIENT_SECRET: 'secret', OWNER_DISCORD_ID: '376768118535749633, 723613831196311603', ALLOWED_DISCORD_IDS: '222222222222222222,723613831196311603' }, discordFetch: async (url) => { requests++; return { ok: true, json: async () => url.endsWith('/token') ? { access_token: 'temporary' } : { id: profileId, username: 'bxio' } }; } }); await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); const base = `http://127.0.0.1:${server.address().port}`; const request = (path, options = {}) => fetch(base + path, { redirect: 'manual', ...options }); const begin = async () => { const response = await request('/auth/discord'); const state = new URL(response.headers.get('location')).searchParams.get('state'); return { state, cookie: `oauth_state=${state}` }; }; try { assert.equal((await request('/')).status, 200); assert.equal((await request('/api/dashboard')).status, 401); assert.equal((await request('/auth/discord/callback?state=fake&code=code', { headers: { Cookie: 'oauth_state=fake' } })).headers.get('location'), '/?error=authentication'); assert.equal(requests, 0); const outsider = await begin(); const denied = await request(`/auth/discord/callback?state=${outsider.state}&code=code`, { headers: { Cookie: outsider.cookie } }); assert.equal(denied.headers.get('location'), '/?error=denied'); profileId = '222222222222222222'; const reader = await begin(); const readerLogin = await request(`/auth/discord/callback?state=${reader.state}&code=code`, { headers: { Cookie: reader.cookie } }); assert.equal(readerLogin.headers.get('location'), '/'); const readerCookie = readerLogin.headers.getSetCookie().find(v => v.startsWith('session=')).split(';')[0]; const readerProfile = await (await request('/api/me', { headers: { Cookie: readerCookie } })).json(); assert.equal(readerProfile.user.role, 'Leitor'); assert.equal((await request('/api/dashboard', { headers: { Cookie: readerCookie } })).status, 200); assert.equal((await request('/api/bots', { method: 'POST', headers: { Cookie: readerCookie, Origin: 'http://localhost:3080', 'Content-Type': 'application/json' }, body: JSON.stringify({name:'Reader bot'}) })).status, 403); profileId = '723613831196311603'; const secondOwner = await begin(); const secondLogin = await request(`/auth/discord/callback?state=${secondOwner.state}&code=code`, { headers: { Cookie: secondOwner.cookie } }); assert.equal(secondLogin.headers.get('location'), '/'); const secondCookie = secondLogin.headers.getSetCookie().find(v => v.startsWith('session=')).split(';')[0]; assert.equal((await (await request('/api/me', { headers: { Cookie: secondCookie } })).json()).user.role, 'Dono'); assert.equal((await request('/api/bots', { method: 'POST', headers: { Cookie: secondCookie, Origin: 'http://localhost:3080', 'Content-Type': 'application/json' }, body: JSON.stringify({name:'Second owner bot'}) })).status, 201); profileId = '376768118535749633'; const owner = await begin(); const callback = `/auth/discord/callback?state=${owner.state}&code=code`; const login = await request(callback, { headers: { Cookie: owner.cookie } }); assert.equal(login.headers.get('location'), '/'); const sessionCookie = login.headers.getSetCookie().find(v => v.startsWith('session=')).split(';')[0]; assert.equal((await (await request('/api/me', { headers: { Cookie: sessionCookie } })).json()).user.role, 'Dono'); assert.equal((await request('/api/dashboard', { headers: { Cookie: sessionCookie } })).status, 200); assert.equal((await request('/api/bots', { method: 'POST', headers: { Cookie: sessionCookie, Origin: 'http://localhost:3080', 'Content-Type': 'application/json' }, body: JSON.stringify({name:'First owner bot'}) })).status, 201); assert.equal((await request(callback, { headers: { Cookie: owner.cookie } })).headers.get('location'), '/?error=authentication'); assert.equal((await request('/auth/logout', { method: 'POST', headers: { Cookie: sessionCookie, Origin: 'http://evil.example' } })).status, 403); assert.equal((await request('/auth/logout', { method: 'POST', headers: { Cookie: sessionCookie, Origin: 'http://localhost:3080' } })).status, 200); assert.equal((await request('/api/dashboard', { headers: { Cookie: sessionCookie } })).status, 401); } finally { await new Promise(resolve => server.close(resolve)); rmSync(directory, { recursive: true, force: true }); } });