This commit is contained in:
@@ -47,6 +47,11 @@ cannot save keys indirectly through a launch request. An operator's content
|
|||||||
permissions include executable scripts and plugins; assign that role only to
|
permissions include executable scripts and plugins; assign that role only to
|
||||||
people trusted to operate the game fleet.
|
people trusted to operate the game fleet.
|
||||||
|
|
||||||
|
Drift previews (`/api/drift`) and GitHub release lookups
|
||||||
|
(`/api/plugins/github/release`) require operator or administrator access, including
|
||||||
|
`GET` and `HEAD` requests. These endpoints initiate fleet file scans or use GitHub
|
||||||
|
API quota; viewers cannot trigger them.
|
||||||
|
|
||||||
RCON failure replies are available only to operators and administrators in the
|
RCON failure replies are available only to operators and administrators in the
|
||||||
server's Diagnostics tab. Player addresses are redacted before diagnostics are
|
server's Diagnostics tab. Player addresses are redacted before diagnostics are
|
||||||
saved; successful checks clear the saved reply. Fleet snapshots and live status
|
saved; successful checks clear the saved reply. Fleet snapshots and live status
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ export function routePermission(routeId: string | null, method: string): RoutePe
|
|||||||
if (!reading && ['/api/maintenance', '/api/plutonium', '/api/watchdog'].includes(routeId))
|
if (!reading && ['/api/maintenance', '/api/plutonium', '/api/watchdog'].includes(routeId))
|
||||||
return 'admin';
|
return 'admin';
|
||||||
|
|
||||||
|
// These reads initiate fleet file scans or spend external API quota.
|
||||||
|
if (routeId === '/api/drift' || routeId === '/api/plugins/github/release') return 'operator';
|
||||||
|
|
||||||
// Summaries remain visible to viewers; raw configuration, historical file
|
// Summaries remain visible to viewers; raw configuration, historical file
|
||||||
// bytes, and unfiltered diagnostics may contain runtime credentials.
|
// bytes, and unfiltered diagnostics may contain runtime credentials.
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -272,6 +272,24 @@ describe('browser authentication guard', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
it.each([
|
||||||
|
['GET', '/api/drift'],
|
||||||
|
['HEAD', '/api/drift'],
|
||||||
|
['GET', '/api/plugins/github/release'],
|
||||||
|
['HEAD', '/api/plugins/github/release']
|
||||||
|
])('restricts expensive %s %s requests before handler execution', async (method, route) => {
|
||||||
|
const anonymous = requestFor(route, { method, cookie: null });
|
||||||
|
expect((await anonymous.invoke()).status).toBe(401);
|
||||||
|
expect(anonymous.resolve).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
for (const role of ['viewer', 'operator', 'admin'] as const) {
|
||||||
|
signIn(role);
|
||||||
|
const call = requestFor(route, { method });
|
||||||
|
expect((await call.invoke()).status).toBe(role === 'viewer' ? 403 : 200);
|
||||||
|
if (role === 'viewer') expect(call.resolve).not.toHaveBeenCalled();
|
||||||
|
else expect(call.resolve).toHaveBeenCalledOnce();
|
||||||
|
}
|
||||||
|
});
|
||||||
it('requires temporary-password users to change it even if their role is administrator', async () => {
|
it('requires temporary-password users to change it even if their role is administrator', async () => {
|
||||||
signIn('admin', true);
|
signIn('admin', true);
|
||||||
await expect(requestFor('/settings').invoke()).rejects.toMatchObject({
|
await expect(requestFor('/settings').invoke()).rejects.toMatchObject({
|
||||||
|
|||||||
Reference in New Issue
Block a user