Add moderation permissions and durable player records
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { UserRole } from '../../auth.js';
|
||||
import { canModerate, canReadModeration, type UserRole } from '../../auth.js';
|
||||
|
||||
export type RoutePermission =
|
||||
| 'public'
|
||||
@@ -7,6 +7,8 @@ export type RoutePermission =
|
||||
| 'viewer'
|
||||
| 'operator-read'
|
||||
| 'admin-read'
|
||||
| 'moderation-read'
|
||||
| 'moderation'
|
||||
| 'operator'
|
||||
| 'admin';
|
||||
|
||||
@@ -53,6 +55,9 @@ export function routePermission(routeId: string | null, method: string): RoutePe
|
||||
if (routeId === '/api/settings/retention/preview' && method === 'POST') return 'admin-read';
|
||||
if (within(routeId, '/api/auth')) return 'admin';
|
||||
|
||||
if (within(routeId, '/moderation') || within(routeId, '/api/moderation'))
|
||||
return reading ? 'moderation-read' : 'moderation';
|
||||
|
||||
if (
|
||||
within(routeId, '/api/users') ||
|
||||
within(routeId, '/settings') ||
|
||||
@@ -94,11 +99,13 @@ export function routePermission(routeId: string | null, method: string): RoutePe
|
||||
|
||||
/** A browser role never substitutes for a machine credential. */
|
||||
export function roleAllows(role: UserRole, permission: RoutePermission): boolean {
|
||||
if (!['viewer', 'super_viewer', 'operator', 'admin'].includes(role)) return false;
|
||||
if (!['viewer', 'super_viewer', 'operator', 'moderator', 'admin'].includes(role)) return false;
|
||||
if (permission === 'machine') return false;
|
||||
if (permission === 'public' || permission === 'authenticated' || permission === 'viewer')
|
||||
return true;
|
||||
if (permission === 'operator') return role === 'operator' || role === 'admin';
|
||||
if (permission === 'moderation') return canModerate(role);
|
||||
if (permission === 'moderation-read') return canReadModeration(role);
|
||||
if (permission === 'operator-read')
|
||||
return role === 'super_viewer' || role === 'operator' || role === 'admin';
|
||||
if (permission === 'admin-read') return role === 'super_viewer' || role === 'admin';
|
||||
|
||||
@@ -167,9 +167,13 @@ export const authenticationHandle: Handle = async ({ event, resolve }) => {
|
||||
? 'Administrator access is required.'
|
||||
: permission === 'admin-read'
|
||||
? 'Administrator or Super Viewer access is required.'
|
||||
: permission === 'operator-read'
|
||||
? 'Operator, Administrator or Super Viewer access is required.'
|
||||
: 'Operator access is required for this action.';
|
||||
: permission === 'moderation'
|
||||
? 'Moderator or Administrator access is required.'
|
||||
: permission === 'moderation-read'
|
||||
? 'Moderator, Administrator or Super Viewer access is required.'
|
||||
: permission === 'operator-read'
|
||||
? 'Operator, Administrator or Super Viewer access is required.'
|
||||
: 'Operator access is required for this action.';
|
||||
if (api)
|
||||
return json(
|
||||
{ error: message },
|
||||
|
||||
@@ -82,8 +82,8 @@ function displayName(value: unknown, fallback: string): string {
|
||||
return name.trim();
|
||||
}
|
||||
function role(value: unknown): UserRole {
|
||||
if (!['admin', 'super_viewer', 'operator', 'viewer'].includes(value as string))
|
||||
throw new AuthError('Choose administrator, super viewer, operator or viewer.');
|
||||
if (!['admin', 'super_viewer', 'operator', 'moderator', 'viewer'].includes(value as string))
|
||||
throw new AuthError('Choose administrator, super viewer, operator, moderator or viewer.');
|
||||
return value as UserRole;
|
||||
}
|
||||
export function recordAudit(
|
||||
|
||||
@@ -61,3 +61,32 @@ export function migrateSuperViewerRole(handle: Database.Database): void {
|
||||
ALTER TABLE auth_users_super_viewer RENAME TO auth_users;
|
||||
`);
|
||||
}
|
||||
|
||||
/** Run with foreign keys disabled by the migration runner before its transaction. */
|
||||
export function migrateModeratorRole(handle: Database.Database): void {
|
||||
const schema = handle
|
||||
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='auth_users'")
|
||||
.get() as { sql: string };
|
||||
if (schema.sql.includes("'moderator'")) return;
|
||||
if (handle.pragma('foreign_keys', { simple: true }))
|
||||
throw new Error('The auth role migration requires foreign keys to be disabled');
|
||||
|
||||
// Rebuild without renaming the parent: existing session references must stay
|
||||
// attached to auth_users, including remembered sessions added after v17.
|
||||
handle.exec(`
|
||||
CREATE TABLE auth_users_moderator (
|
||||
id TEXT PRIMARY KEY, username TEXT NOT NULL COLLATE NOCASE UNIQUE,
|
||||
display_name TEXT NOT NULL, password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL CHECK(role IN ('admin','super_viewer','operator','moderator','viewer')),
|
||||
disabled INTEGER NOT NULL DEFAULT 0, must_change_password INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, last_login_at INTEGER
|
||||
);
|
||||
INSERT INTO auth_users_moderator (
|
||||
id,username,display_name,password_hash,role,disabled,must_change_password,
|
||||
created_at,updated_at,last_login_at
|
||||
) SELECT id,username,display_name,password_hash,role,disabled,must_change_password,
|
||||
created_at,updated_at,last_login_at FROM auth_users;
|
||||
DROP TABLE auth_users;
|
||||
ALTER TABLE auth_users_moderator RENAME TO auth_users;
|
||||
`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user