mirror of
https://github.com/ancsemi/Haven.git
synced 2026-10-27 00:54:56 +00:00
102 lines
4.6 KiB
JavaScript
102 lines
4.6 KiB
JavaScript
/**
|
|
* Uploads follow the same permission rules as the rest of Haven: a role, the
|
|
* level thresholds, or a permission set on the person, with a person's own
|
|
* deny winning over a role.
|
|
*
|
|
* node --test test/uploadPermission.integration.test.js
|
|
*/
|
|
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const http = require('node:http');
|
|
const path = require('node:path');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const { spawn } = require('node:child_process');
|
|
const Database = require('better-sqlite3');
|
|
|
|
const PORT = 3441;
|
|
const BASE = `http://127.0.0.1:${PORT}`;
|
|
const DATA = path.join(os.tmpdir(), `haven-upload-perm-${Date.now()}`);
|
|
|
|
let server;
|
|
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
const post = (p, body) => fetch(BASE + p, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
}).then(async (r) => ({ status: r.status, body: await r.json().catch(() => ({})) }));
|
|
|
|
async function uploadAs(token) {
|
|
const fd = new FormData();
|
|
fd.append('file', new Blob([Buffer.from('hello')], { type: 'text/plain' }), 'note.txt');
|
|
const r = await fetch(`${BASE}/api/upload-file`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: fd });
|
|
return r.status;
|
|
}
|
|
|
|
function withDb(fn) {
|
|
const db = new Database(path.join(DATA, 'haven.db'));
|
|
try { return fn(db); } finally { db.close(); }
|
|
}
|
|
|
|
test.before(async () => {
|
|
fs.mkdirSync(DATA, { recursive: true });
|
|
server = spawn(process.execPath, ['server.js'], {
|
|
cwd: path.join(__dirname, '..'),
|
|
env: { ...process.env, PORT: String(PORT), HOST: '127.0.0.1', FORCE_HTTP: 'true', HAVEN_DATA_DIR: DATA, ADMIN_USERNAME: 'admin' },
|
|
stdio: 'ignore',
|
|
});
|
|
for (let i = 0; i < 60; i++) {
|
|
try {
|
|
await new Promise((res, rej) => http.get(`${BASE}/api/health`, (r) => (r.statusCode === 200 ? res() : rej())).on('error', rej));
|
|
return;
|
|
} catch { await wait(500); }
|
|
}
|
|
throw new Error('server did not start');
|
|
});
|
|
|
|
test.after(() => {
|
|
server?.kill();
|
|
try { fs.rmSync(DATA, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
});
|
|
|
|
test('uploads honour level thresholds and permissions set on the person', async () => {
|
|
const admin = await post('/api/auth/register', { username: 'admin', password: 'uploadcheck1', eulaVersion: '2.0', ageVerified: true });
|
|
assert.ok(admin.body.token, 'admin registered');
|
|
const bob = await post('/api/auth/register', { username: 'bob', password: 'uploadcheck2', eulaVersion: '2.0', ageVerified: true });
|
|
assert.ok(bob.body.token, 'bob registered');
|
|
const bobId = bob.body.user.id;
|
|
|
|
// Take uploads off every role, so nothing a role gives covers bob.
|
|
const memberRole = withDb((db) => {
|
|
db.prepare("DELETE FROM role_permissions WHERE permission = 'upload_files'").run();
|
|
return db.prepare("SELECT ur.role_id FROM user_roles ur WHERE ur.user_id = ? AND ur.channel_id IS NULL LIMIT 1").get(bobId);
|
|
});
|
|
assert.equal(await uploadAs(bob.body.token), 403, 'no permission anywhere: refused');
|
|
|
|
// Level thresholds are retired (src/permissionThresholds.js): a leftover
|
|
// one hands out nothing.
|
|
withDb((db) => db.prepare("INSERT OR REPLACE INTO server_settings (key, value) VALUES ('permission_thresholds', ?)").run(JSON.stringify({ upload_files: 1 })));
|
|
assert.equal(await uploadAs(bob.body.token), 403, 'a level threshold grants nothing');
|
|
withDb((db) => db.prepare("DELETE FROM server_settings WHERE key = 'permission_thresholds'").run());
|
|
|
|
// Granted to bob on his own.
|
|
assert.ok(memberRole, 'bob holds a server role to hang the grant on');
|
|
withDb((db) => db.prepare('INSERT INTO user_role_perms (user_id, role_id, channel_id, permission, allowed) VALUES (?, ?, NULL, ?, 1)')
|
|
.run(bobId, memberRole.role_id, 'upload_files'));
|
|
assert.equal(await uploadAs(bob.body.token), 200, 'allowed by a permission set on him');
|
|
|
|
// Denied to bob on his own, while his role allows it: the deny wins.
|
|
withDb((db) => {
|
|
db.prepare("UPDATE user_role_perms SET allowed = 0 WHERE user_id = ? AND permission = 'upload_files'").run(bobId);
|
|
db.prepare('INSERT OR IGNORE INTO role_permissions (role_id, permission, allowed) VALUES (?, ?, 1)').run(memberRole.role_id, 'upload_files');
|
|
});
|
|
assert.equal(await uploadAs(bob.body.token), 403, 'his own deny beats the role');
|
|
|
|
// Without the deny, the role alone allows it, as before.
|
|
withDb((db) => db.prepare("DELETE FROM user_role_perms WHERE user_id = ? AND permission = 'upload_files'").run(bobId));
|
|
assert.equal(await uploadAs(bob.body.token), 200, 'the role allows it');
|
|
|
|
assert.equal(await uploadAs(admin.body.token), 200, 'the admin always can');
|
|
});
|