ts6-botflow/auth.test.mjs

44 lines
3.0 KiB
JavaScript

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; owner session requires authentication', 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: '111111111111111111' }, 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 = '111111111111111111';
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 request('/api/dashboard', { headers: { Cookie: sessionCookie } })).status, 200);
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 });
}
});