Add access control rework

This commit is contained in:
2026-04-02 03:18:37 +02:00
parent 314a26325f
commit 37cac95b38
111 changed files with 5355 additions and 1892 deletions

View File

@@ -6,6 +6,7 @@ import {
updateJoinRequestStatus
} from '../cqrs';
import { notifyUser } from '../websocket/broadcast';
import { resolveServerPermission } from '../services/server-permissions.service';
const router = Router();
@@ -19,7 +20,7 @@ router.put('/:id', async (req, res) => {
const server = await getServerById(request.serverId);
if (!server || server.ownerId !== ownerId)
if (!server || !ownerId || !resolveServerPermission(server, String(ownerId), 'manageServer'))
return res.status(403).json({ error: 'Not authorized' });
await updateJoinRequestStatus(id, status as JoinRequestPayload['status']);

View File

@@ -1,9 +1,6 @@
import { Response, Router } from 'express';
import { v4 as uuidv4 } from 'uuid';
import {
ServerChannelPayload,
ServerPayload
} from '../cqrs/types';
import { ServerChannelPayload, ServerPayload } from '../cqrs/types';
import {
getAllPublicServers,
getServerById,
@@ -30,21 +27,18 @@ import {
buildInviteUrl,
getRequestOrigin
} from './invite-utils';
import {
canManageServerUpdate,
canModerateServerMember,
resolveServerPermission
} from '../services/server-permissions.service';
const router = Router();
function normalizeRole(role: unknown): string | null {
return typeof role === 'string' ? role.trim().toLowerCase() : null;
}
function channelNameKey(type: ServerChannelPayload['type'], name: string): string {
return `${type}:${name.toLocaleLowerCase()}`;
}
function isAllowedRole(role: string | null, allowedRoles: string[]): boolean {
return !!role && allowedRoles.includes(role);
}
function normalizeServerChannels(value: unknown): ServerChannelPayload[] {
if (!Array.isArray(value)) {
return [];
@@ -212,15 +206,20 @@ router.put('/:id', async (req, res) => {
} = req.body;
const existing = await getServerById(id);
const authenticatedOwnerId = currentOwnerId ?? req.body.ownerId;
const normalizedRole = normalizeRole(actingRole);
if (!existing)
return res.status(404).json({ error: 'Server not found' });
if (
existing.ownerId !== authenticatedOwnerId &&
!isAllowedRole(normalizedRole, ['host', 'admin'])
) {
if (!authenticatedOwnerId) {
return res.status(400).json({ error: 'Missing currentOwnerId' });
}
if (!canManageServerUpdate(existing, authenticatedOwnerId, {
...updates,
channels,
password,
actingRole
})) {
return res.status(403).json({ error: 'Not authorized' });
}
@@ -298,7 +297,7 @@ router.post('/:id/invites', async (req, res) => {
router.post('/:id/moderation/kick', async (req, res) => {
const { id: serverId } = req.params;
const { actorUserId, actorRole, targetUserId } = req.body;
const { actorUserId, targetUserId } = req.body;
const server = await getServerById(serverId);
if (!server) {
@@ -309,14 +308,7 @@ router.post('/:id/moderation/kick', async (req, res) => {
return res.status(400).json({ error: 'Missing targetUserId', errorCode: 'MISSING_TARGET' });
}
if (
server.ownerId !== actorUserId &&
!isAllowedRole(normalizeRole(actorRole), [
'host',
'admin',
'moderator'
])
) {
if (!canModerateServerMember(server, String(actorUserId || ''), String(targetUserId), 'kickMembers')) {
return res.status(403).json({ error: 'Not authorized', errorCode: 'NOT_AUTHORIZED' });
}
@@ -327,7 +319,7 @@ router.post('/:id/moderation/kick', async (req, res) => {
router.post('/:id/moderation/ban', async (req, res) => {
const { id: serverId } = req.params;
const { actorUserId, actorRole, targetUserId, banId, displayName, reason, expiresAt } = req.body;
const { actorUserId, targetUserId, banId, displayName, reason, expiresAt } = req.body;
const server = await getServerById(serverId);
if (!server) {
@@ -338,14 +330,7 @@ router.post('/:id/moderation/ban', async (req, res) => {
return res.status(400).json({ error: 'Missing targetUserId', errorCode: 'MISSING_TARGET' });
}
if (
server.ownerId !== actorUserId &&
!isAllowedRole(normalizeRole(actorRole), [
'host',
'admin',
'moderator'
])
) {
if (!canModerateServerMember(server, String(actorUserId || ''), String(targetUserId), 'banMembers')) {
return res.status(403).json({ error: 'Not authorized', errorCode: 'NOT_AUTHORIZED' });
}
@@ -364,21 +349,14 @@ router.post('/:id/moderation/ban', async (req, res) => {
router.post('/:id/moderation/unban', async (req, res) => {
const { id: serverId } = req.params;
const { actorUserId, actorRole, banId, targetUserId } = req.body;
const { actorUserId, banId, targetUserId } = req.body;
const server = await getServerById(serverId);
if (!server) {
return res.status(404).json({ error: 'Server not found', errorCode: 'SERVER_NOT_FOUND' });
}
if (
server.ownerId !== actorUserId &&
!isAllowedRole(normalizeRole(actorRole), [
'host',
'admin',
'moderator'
])
) {
if (!resolveServerPermission(server, String(actorUserId || ''), 'manageBans')) {
return res.status(403).json({ error: 'Not authorized', errorCode: 'NOT_AUTHORIZED' });
}