refactor: stricter domain: access-control
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
import {
|
||||
Room,
|
||||
RoomMember,
|
||||
RoomRole,
|
||||
RoomRoleAssignment
|
||||
} from '../../../../shared-kernel';
|
||||
import { SYSTEM_ROLE_IDS } from '../constants/access-control.constants';
|
||||
import type { MemberIdentity } from '../models/access-control.model';
|
||||
import {
|
||||
buildRoleLookup,
|
||||
compareText,
|
||||
memberIdentityKey,
|
||||
matchesIdentity,
|
||||
roleSortDescending,
|
||||
uniqueStrings
|
||||
} from '../util/access-control.util';
|
||||
import { getRoomRoleById, normalizeRoomRoles } from './role.rules';
|
||||
|
||||
function sortAssignments(assignments: readonly RoomRoleAssignment[]): RoomRoleAssignment[] {
|
||||
return [...assignments].sort((firstAssignment, secondAssignment) =>
|
||||
compareText(firstAssignment.oderId || firstAssignment.userId, secondAssignment.oderId || secondAssignment.userId)
|
||||
);
|
||||
}
|
||||
|
||||
function resolveLegacyMemberRoleIds(member: RoomMember, validRoleIds: Set<string>): string[] {
|
||||
if (Array.isArray(member.roleIds) && member.roleIds.length > 0) {
|
||||
return uniqueStrings(member.roleIds).filter((roleId) => validRoleIds.has(roleId));
|
||||
}
|
||||
|
||||
if (member.role === 'admin') {
|
||||
return [SYSTEM_ROLE_IDS.admin];
|
||||
}
|
||||
|
||||
if (member.role === 'moderator') {
|
||||
return [SYSTEM_ROLE_IDS.moderator];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export function normalizeRoomRoleAssignments(
|
||||
assignments: readonly RoomRoleAssignment[] | undefined,
|
||||
members: readonly RoomMember[] | undefined,
|
||||
roles: readonly RoomRole[]
|
||||
): RoomRoleAssignment[] {
|
||||
const validRoleIds = new Set(roles.map((role) => role.id).filter((roleId) => roleId !== SYSTEM_ROLE_IDS.everyone));
|
||||
const normalizedByUserKey = new Map<string, RoomRoleAssignment>();
|
||||
|
||||
for (const assignment of assignments ?? []) {
|
||||
if (!assignment || typeof assignment !== 'object') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const userId = typeof assignment.userId === 'string' ? assignment.userId.trim() : '';
|
||||
const oderId = typeof assignment.oderId === 'string' ? assignment.oderId.trim() : undefined;
|
||||
const key = oderId || userId;
|
||||
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const roleIds = uniqueStrings(assignment.roleIds ?? []).filter((roleId) => validRoleIds.has(roleId));
|
||||
|
||||
if (roleIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
normalizedByUserKey.set(key, {
|
||||
userId: userId || key,
|
||||
oderId,
|
||||
roleIds
|
||||
});
|
||||
}
|
||||
|
||||
if (normalizedByUserKey.size > 0) {
|
||||
return sortAssignments(Array.from(normalizedByUserKey.values()));
|
||||
}
|
||||
|
||||
for (const member of members ?? []) {
|
||||
const key = memberIdentityKey(member);
|
||||
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const roleIds = resolveLegacyMemberRoleIds(member, validRoleIds);
|
||||
|
||||
if (roleIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
normalizedByUserKey.set(key, {
|
||||
userId: member.id || key,
|
||||
oderId: member.oderId || undefined,
|
||||
roleIds
|
||||
});
|
||||
}
|
||||
|
||||
return sortAssignments(Array.from(normalizedByUserKey.values()));
|
||||
}
|
||||
|
||||
export function getAssignedRoleIds(assignments: readonly RoomRoleAssignment[] | undefined, identity: MemberIdentity | null | undefined): string[] {
|
||||
const assignment = (assignments ?? []).find((candidateAssignment) => matchesIdentity(identity, candidateAssignment));
|
||||
|
||||
return uniqueStrings(assignment?.roleIds ?? []);
|
||||
}
|
||||
|
||||
export function getDisplayRoleName(room: Room, member: MemberIdentity | null | undefined): string {
|
||||
if (!member) {
|
||||
return 'Member';
|
||||
}
|
||||
|
||||
if (room.hostId === member.id || room.hostId === member.oderId) {
|
||||
return 'Owner';
|
||||
}
|
||||
|
||||
const roles = normalizeRoomRoles(room.roles, room.permissions);
|
||||
const roleLookup = buildRoleLookup(roles);
|
||||
const assignedRoles = getAssignedRoleIds(room.roleAssignments, member)
|
||||
.map((roleId) => roleLookup.get(roleId))
|
||||
.filter((role): role is RoomRole => !!role)
|
||||
.sort(roleSortDescending);
|
||||
|
||||
return assignedRoles[0]?.name || '@everyone';
|
||||
}
|
||||
|
||||
export function getAssignedRoles(room: Room, identity: MemberIdentity | null | undefined): RoomRole[] {
|
||||
const roles = normalizeRoomRoles(room.roles, room.permissions);
|
||||
const roleLookup = buildRoleLookup(roles);
|
||||
|
||||
return getAssignedRoleIds(room.roleAssignments, identity)
|
||||
.map((roleId) => roleLookup.get(roleId))
|
||||
.filter((role): role is RoomRole => !!role)
|
||||
.sort(roleSortDescending);
|
||||
}
|
||||
|
||||
export function getHighestAssignedRole(room: Room, identity: MemberIdentity | null | undefined): RoomRole | null {
|
||||
if (!identity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (room.hostId === identity.id || room.hostId === identity.oderId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getAssignedRoles(room, identity)[0] ?? getRoomRoleById(normalizeRoomRoles(room.roles, room.permissions), SYSTEM_ROLE_IDS.everyone) ?? null;
|
||||
}
|
||||
|
||||
export function setRoleAssignmentsForMember(
|
||||
assignments: readonly RoomRoleAssignment[] | undefined,
|
||||
member: MemberIdentity,
|
||||
roleIds: readonly string[]
|
||||
): RoomRoleAssignment[] {
|
||||
const nextAssignments = new Map<string, RoomRoleAssignment>();
|
||||
const memberKey = memberIdentityKey(member);
|
||||
|
||||
for (const assignment of assignments ?? []) {
|
||||
const key = memberIdentityKey({
|
||||
id: assignment.userId,
|
||||
oderId: assignment.oderId
|
||||
});
|
||||
|
||||
if (!key || key === memberKey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nextAssignments.set(key, {
|
||||
userId: assignment.userId,
|
||||
oderId: assignment.oderId,
|
||||
roleIds: uniqueStrings(assignment.roleIds)
|
||||
});
|
||||
}
|
||||
|
||||
const normalizedRoleIds = uniqueStrings(roleIds);
|
||||
|
||||
if (memberKey && normalizedRoleIds.length > 0) {
|
||||
nextAssignments.set(memberKey, {
|
||||
userId: member.id || member.oderId || memberKey,
|
||||
oderId: member.oderId || undefined,
|
||||
roleIds: normalizedRoleIds
|
||||
});
|
||||
}
|
||||
|
||||
return sortAssignments(Array.from(nextAssignments.values()));
|
||||
}
|
||||
|
||||
export function removeRoleFromAssignments(assignments: readonly RoomRoleAssignment[] | undefined, roleId: string): RoomRoleAssignment[] {
|
||||
return (assignments ?? [])
|
||||
.map((assignment) => ({
|
||||
...assignment,
|
||||
roleIds: assignment.roleIds.filter((candidateRoleId) => candidateRoleId !== roleId)
|
||||
}))
|
||||
.filter((assignment) => assignment.roleIds.length > 0);
|
||||
}
|
||||
|
||||
export function getRoleIdsForMember(room: Room, member: MemberIdentity | null | undefined): string[] {
|
||||
return getAssignedRoleIds(
|
||||
normalizeRoomRoleAssignments(room.roleAssignments, room.members, normalizeRoomRoles(room.roles, room.permissions)),
|
||||
member
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user