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

@@ -14,16 +14,25 @@ import { lucideUserX, lucideBan } from '@ng-icons/lucide';
import {
Room,
RoomMember,
UserRole
RoomRole
} from '../../../../shared-kernel';
import { RealtimeSessionFacade } from '../../../../core/realtime';
import { RoomsActions } from '../../../../store/rooms/rooms.actions';
import { UsersActions } from '../../../../store/users/users.actions';
import { selectCurrentUser, selectUsersEntities } from '../../../../store/users/users.selectors';
import { selectCurrentRoom } from '../../../../store/rooms/rooms.selectors';
import { UserAvatarComponent } from '../../../../shared';
import {
canManageMember,
findAssignableRoles,
getDisplayRoleName,
getRoleIdsForMember,
normalizeRoomAccessControl,
setRoleAssignmentsForMember
} from '../../../../domains/access-control';
interface ServerMemberView extends RoomMember {
assignedRoleIds: string[];
displayRoleName: string;
isOnline: boolean;
}
@@ -46,20 +55,25 @@ interface ServerMemberView extends RoomMember {
})
export class MembersSettingsComponent {
private store = inject(Store);
private webrtcService = inject(RealtimeSessionFacade);
/** The currently selected server, passed from the parent. */
server = input<Room | null>(null);
/** Whether the current user is admin of this server. */
isAdmin = input(false);
accessRole = input<UserRole | null>(null);
accessRole = input<string | null>(null);
currentUser = this.store.selectSignal(selectCurrentUser);
currentRoom = this.store.selectSignal(selectCurrentRoom);
usersEntities = this.store.selectSignal(selectUsersEntities);
normalizedServer = computed(() => {
const room = this.server();
return room ? normalizeRoomAccessControl(room) : null;
});
assignableRoles = computed<RoomRole[]>(() => findAssignableRoles(this.normalizedServer()?.roles ?? []));
members = computed<ServerMemberView[]>(() => {
const room = this.server();
const room = this.normalizedServer();
const me = this.currentUser();
const currentRoom = this.currentRoom();
const usersEntities = this.usersEntities();
@@ -78,6 +92,8 @@ export class MembersSettingsComponent {
return {
...member,
assignedRoleIds: getRoleIdsForMember(room, member),
displayRoleName: getDisplayRoleName(room, member),
avatarUrl: liveUser?.avatarUrl || member.avatarUrl,
displayName: liveUser?.displayName || member.displayName,
isOnline: !!liveUser && (liveUser.isOnline === true || liveUser.status !== 'offline')
@@ -85,55 +101,47 @@ export class MembersSettingsComponent {
});
});
canChangeRoles(): boolean {
const role = this.accessRole();
canChangeRoles(member: ServerMemberView): boolean {
const room = this.normalizedServer();
const currentUser = this.currentUser();
return role === 'host' || role === 'admin';
return !!room && !!currentUser && canManageMember(room, currentUser, member, 'manageRoles');
}
canKickMembers(): boolean {
const role = this.accessRole();
canKickMembers(member: ServerMemberView): boolean {
const room = this.normalizedServer();
const currentUser = this.currentUser();
return role === 'host' || role === 'admin' || role === 'moderator';
return !!room && !!currentUser && canManageMember(room, currentUser, member, 'kickMembers');
}
canBanMembers(): boolean {
const role = this.accessRole();
canBanMembers(member: ServerMemberView): boolean {
const room = this.normalizedServer();
const currentUser = this.currentUser();
return role === 'host' || role === 'admin';
return !!room && !!currentUser && canManageMember(room, currentUser, member, 'banMembers');
}
changeRole(member: ServerMemberView, role: 'admin' | 'moderator' | 'member'): void {
const room = this.server();
toggleRole(member: ServerMemberView, roleId: string, event: Event): void {
const room = this.normalizedServer();
if (!room)
return;
const members = (room.members ?? []).map((existingMember) =>
existingMember.id === member.id || existingMember.oderId === member.oderId
? { ...existingMember,
role }
: existingMember
);
const checkbox = event.target as HTMLInputElement;
const nextRoleIds = checkbox.checked
? [...member.assignedRoleIds, roleId]
: member.assignedRoleIds.filter((candidateRoleId) => candidateRoleId !== roleId);
const roleAssignments = setRoleAssignmentsForMember(room.roleAssignments, member, nextRoleIds);
this.store.dispatch(RoomsActions.updateRoom({ roomId: room.id,
changes: { members } }));
if (this.currentRoom()?.id === room.id) {
this.store.dispatch(UsersActions.updateUserRole({ userId: member.id,
role }));
}
this.webrtcService.broadcastMessage({
type: 'role-change',
this.store.dispatch(RoomsActions.updateRoomAccessControl({
roomId: room.id,
targetUserId: member.id,
role
});
changes: { roleAssignments }
}));
}
kickMember(member: ServerMemberView): void {
const room = this.server();
const room = this.normalizedServer();
if (!room)
return;
@@ -143,7 +151,7 @@ export class MembersSettingsComponent {
}
banMember(member: ServerMemberView): void {
const room = this.server();
const room = this.normalizedServer();
if (!room)
return;