Add moderation permissions and durable player records
This commit is contained in:
@@ -1,18 +1,61 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { relative, resolve } from 'node:path';
|
||||
import type { UserRole } from '../../src/lib/auth.js';
|
||||
import {
|
||||
canModerate,
|
||||
canReadModeration,
|
||||
moderatorMaximumRestrictionMs,
|
||||
type UserRole
|
||||
} from '../../src/lib/auth.js';
|
||||
import {
|
||||
roleAllows,
|
||||
routePermission,
|
||||
type RoutePermission
|
||||
} from '../../src/lib/server/auth/access.js';
|
||||
|
||||
const roles: UserRole[] = ['viewer', 'super_viewer', 'operator', 'admin'];
|
||||
const roles: UserRole[] = ['viewer', 'super_viewer', 'operator', 'moderator', 'admin'];
|
||||
const allows = (role: UserRole, route: string | null, method = 'GET') =>
|
||||
roleAllows(role, routePermission(route, method));
|
||||
|
||||
describe('management route permissions', () => {
|
||||
it.each([
|
||||
'/moderation',
|
||||
'/moderation/players/[guid]',
|
||||
'/api/moderation',
|
||||
'/api/moderation/players/[guid]',
|
||||
'/api/moderation/sanctions/[id]/revoke'
|
||||
])('limits private moderation access at %s without changing fleet privileges', (route) => {
|
||||
for (const method of ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']) {
|
||||
const reading = ['GET', 'HEAD'].includes(method);
|
||||
expect(routePermission(route, method)).toBe(reading ? 'moderation-read' : 'moderation');
|
||||
for (const role of roles) {
|
||||
expect(allows(role, route, method)).toBe(
|
||||
role === 'admin' || role === 'moderator' || (reading && role === 'super_viewer')
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps moderators within safe summary reads and explicit moderation operations', () => {
|
||||
expect(allows('moderator', '/api/fleet')).toBe(true);
|
||||
expect(allows('moderator', '/api/servers/[name]/chat')).toBe(true);
|
||||
expect(allows('moderator', '/api/servers', 'POST')).toBe(false);
|
||||
expect(allows('moderator', '/api/users')).toBe(false);
|
||||
expect(allows('moderator', '/api/configs/[id]')).toBe(false);
|
||||
expect(allows('moderator', '/api/drift')).toBe(false);
|
||||
expect(allows('moderator', '/api/moderation-extra', 'POST')).toBe(false);
|
||||
expect(allows('moderator', '/api/auth/password', 'POST')).toBe(true);
|
||||
for (const role of roles) {
|
||||
expect(canModerate(role)).toBe(role === 'moderator' || role === 'admin');
|
||||
expect(canReadModeration(role)).toBe(
|
||||
role === 'moderator' || role === 'admin' || role === 'super_viewer'
|
||||
);
|
||||
}
|
||||
expect(canModerate(null)).toBe(false);
|
||||
expect(canReadModeration(undefined)).toBe(false);
|
||||
expect(moderatorMaximumRestrictionMs).toBe(604_800_000);
|
||||
});
|
||||
|
||||
it('denies Super Viewer writes across every registered management route', () => {
|
||||
const routes = resolve('src/routes');
|
||||
const entries = readdirSync(routes, { recursive: true, withFileTypes: true });
|
||||
@@ -285,6 +328,8 @@ describe('authentication and machine boundaries', () => {
|
||||
'operator',
|
||||
'operator-read',
|
||||
'admin-read',
|
||||
'moderation',
|
||||
'moderation-read',
|
||||
'admin',
|
||||
'machine'
|
||||
] as RoutePermission[]) {
|
||||
|
||||
Reference in New Issue
Block a user