import type { AccessRolePayload, PermissionStatePayload, RoleAssignmentPayload, ServerPayload, ServerPermissionKeyPayload } from '../cqrs/types'; import { normalizeServerRoleAssignments, normalizeServerRoles } from '../cqrs/relations'; const SYSTEM_ROLE_IDS = { everyone: 'system-everyone' } as const; interface ServerIdentity { userId: string; oderId?: string; } function getServerRoles(server: Pick): AccessRolePayload[] { return normalizeServerRoles(server.roles); } function getServerAssignments(server: Pick): RoleAssignmentPayload[] { return normalizeServerRoleAssignments(server.roleAssignments, getServerRoles(server)); } function matchesIdentity(identity: ServerIdentity, assignment: RoleAssignmentPayload): boolean { return assignment.userId === identity.userId || assignment.oderId === identity.userId || (!!identity.oderId && (assignment.userId === identity.oderId || assignment.oderId === identity.oderId)); } function resolveAssignedRoleIds(server: Pick, identity: ServerIdentity): string[] { const assignment = getServerAssignments(server).find((candidateAssignment) => matchesIdentity(identity, candidateAssignment)); return assignment?.roleIds ?? []; } function compareRolePosition(firstRole: AccessRolePayload, secondRole: AccessRolePayload): number { if (firstRole.position !== secondRole.position) { return firstRole.position - secondRole.position; } return firstRole.name.localeCompare(secondRole.name, undefined, { sensitivity: 'base' }); } function resolveRolePermissionState( roles: readonly AccessRolePayload[], assignedRoleIds: readonly string[], permission: ServerPermissionKeyPayload ): PermissionStatePayload { const roleLookup = new Map(roles.map((role) => [role.id, role])); const effectiveRoles = [roleLookup.get(SYSTEM_ROLE_IDS.everyone), ...assignedRoleIds.map((roleId) => roleLookup.get(roleId))] .filter((role): role is AccessRolePayload => !!role) .sort(compareRolePosition); let state: PermissionStatePayload = 'inherit'; for (const role of effectiveRoles) { const nextState = role.permissions?.[permission] ?? 'inherit'; if (nextState !== 'inherit') { state = nextState; } } return state; } function resolveHighestRole( server: Pick, identity: ServerIdentity ): AccessRolePayload | null { const roles = getServerRoles(server); const assignedRoleIds = resolveAssignedRoleIds(server, identity); const roleLookup = new Map(roles.map((role) => [role.id, role])); const assignedRoles = assignedRoleIds .map((roleId) => roleLookup.get(roleId)) .filter((role): role is AccessRolePayload => !!role) .sort((firstRole, secondRole) => compareRolePosition(secondRole, firstRole)); return assignedRoles[0] ?? roleLookup.get(SYSTEM_ROLE_IDS.everyone) ?? null; } export function isServerOwner(server: Pick, actorUserId: string): boolean { return server.ownerId === actorUserId; } export function resolveServerPermission( server: Pick, actorUserId: string, permission: ServerPermissionKeyPayload, actorOderId?: string ): boolean { if (isServerOwner(server, actorUserId)) { return true; } const roles = getServerRoles(server); const assignedRoleIds = resolveAssignedRoleIds(server, { userId: actorUserId, oderId: actorOderId }); return resolveRolePermissionState(roles, assignedRoleIds, permission) === 'allow'; } export function canManageServerUpdate( server: Pick, actorUserId: string, updates: Record, actorOderId?: string ): boolean { if (isServerOwner(server, actorUserId)) { return true; } if (typeof updates['ownerId'] === 'string' || typeof updates['ownerPublicKey'] === 'string') { return false; } const requiredPermissions = new Set(); if ( Array.isArray(updates['roles']) || Array.isArray(updates['roleAssignments']) || Array.isArray(updates['channelPermissions']) ) { requiredPermissions.add('manageRoles'); } if (Array.isArray(updates['channels'])) { requiredPermissions.add('manageChannels'); } if (typeof updates['icon'] === 'string') { requiredPermissions.add('manageIcon'); } if ( typeof updates['name'] === 'string' || typeof updates['description'] === 'string' || typeof updates['isPrivate'] === 'boolean' || typeof updates['maxUsers'] === 'number' || typeof updates['password'] === 'string' || typeof updates['passwordHash'] === 'string' || typeof updates['slowModeInterval'] === 'number' ) { requiredPermissions.add('manageServer'); } return Array.from(requiredPermissions).every((permission) => resolveServerPermission(server, actorUserId, permission, actorOderId) ); } export function canModerateServerMember( server: Pick, actorUserId: string, targetUserId: string, permission: 'kickMembers' | 'banMembers' | 'manageBans', actorOderId?: string, targetOderId?: string ): boolean { if (!actorUserId || !targetUserId || actorUserId === targetUserId) { return false; } if (isServerOwner(server, targetUserId) && !isServerOwner(server, actorUserId)) { return false; } if (isServerOwner(server, actorUserId)) { return true; } if (!resolveServerPermission(server, actorUserId, permission, actorOderId)) { return false; } const actorRole = resolveHighestRole(server, { userId: actorUserId, oderId: actorOderId }); const targetRole = resolveHighestRole(server, { userId: targetUserId, oderId: targetOderId }); return (actorRole?.position ?? 0) > (targetRole?.position ?? 0); }