refactor: stricter domain: access-control
This commit is contained in:
@@ -9,7 +9,7 @@ infrastructure adapters and UI.
|
|||||||
| Domain | Purpose | Public entry point |
|
| Domain | Purpose | Public entry point |
|
||||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- |
|
| -------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- |
|
||||||
| **attachment** | File upload/download, chunk transfer, persistence | `AttachmentFacade` |
|
| **attachment** | File upload/download, chunk transfer, persistence | `AttachmentFacade` |
|
||||||
| **access-control** | Role, permission, moderation, and room access rules | `normalizeRoomAccessControl()`, `resolveRoomPermission()` |
|
| **access-control** | Role, permission, ban matching, moderation, and room access rules | `normalizeRoomAccessControl()`, `resolveRoomPermission()`, `hasRoomBanForUser()` |
|
||||||
| **auth** | Login / register HTTP orchestration, user-bar UI | `AuthService` |
|
| **auth** | Login / register HTTP orchestration, user-bar UI | `AuthService` |
|
||||||
| **chat** | Messaging rules, sync logic, GIF/Klipy integration, chat UI | `KlipyService`, `canEditMessage()`, `ChatMessagesComponent` |
|
| **chat** | Messaging rules, sync logic, GIF/Klipy integration, chat UI | `KlipyService`, `canEditMessage()`, `ChatMessagesComponent` |
|
||||||
| **notifications** | Notification preferences, unread tracking, desktop alert orchestration | `NotificationsFacade` |
|
| **notifications** | Notification preferences, unread tracking, desktop alert orchestration | `NotificationsFacade` |
|
||||||
|
|||||||
@@ -7,13 +7,18 @@ Role and permission rules for servers, including default system roles, role assi
|
|||||||
```
|
```
|
||||||
access-control/
|
access-control/
|
||||||
├── domain/
|
├── domain/
|
||||||
│ ├── access-control.models.ts MemberIdentity and RoomPermissionDefinition domain types
|
│ ├── models/
|
||||||
│ ├── access-control.constants.ts SYSTEM_ROLE_IDS and permission metadata
|
│ │ └── access-control.model.ts MemberIdentity and RoomPermissionDefinition domain types
|
||||||
│ ├── role.rules.ts Role defaults, normalization, ordering, create/update helpers
|
│ ├── constants/
|
||||||
│ ├── role-assignment.rules.ts Assignment normalization and member-role lookups
|
│ │ └── access-control.constants.ts SYSTEM_ROLE_IDS and permission metadata
|
||||||
│ ├── permission.rules.ts Permission resolution and moderation hierarchy checks
|
│ ├── util/
|
||||||
│ ├── room.rules.ts Legacy compatibility, room hydration, room-level normalization
|
│ │ └── access-control.util.ts Internal helpers (normalization, identity matching, sorting)
|
||||||
│ └── access-control.logic.ts Public barrel for domain rules
|
│ └── rules/
|
||||||
|
│ ├── role.rules.ts Role defaults, normalization, ordering, create/update helpers
|
||||||
|
│ ├── role-assignment.rules.ts Assignment normalization and member-role lookups
|
||||||
|
│ ├── permission.rules.ts Permission resolution and moderation hierarchy checks
|
||||||
|
│ ├── room.rules.ts Legacy compatibility, room hydration, room-level normalization
|
||||||
|
│ └── ban.rules.ts Ban matching and user-ban resolution
|
||||||
│
|
│
|
||||||
└── index.ts Domain barrel used by other layers
|
└── index.ts Domain barrel used by other layers
|
||||||
```
|
```
|
||||||
@@ -29,6 +34,8 @@ access-control/
|
|||||||
| `canManageMember(...)` | Applies both permission checks and role hierarchy checks |
|
| `canManageMember(...)` | Applies both permission checks and role hierarchy checks |
|
||||||
| `canManageRole(...)` | Prevents editing roles at or above the actor's highest role |
|
| `canManageRole(...)` | Prevents editing roles at or above the actor's highest role |
|
||||||
| `normalizeRoomAccessControl(room)` | Produces a fully hydrated room with normalized roles, assignments, overrides, and legacy compatibility fields |
|
| `normalizeRoomAccessControl(room)` | Produces a fully hydrated room with normalized roles, assignments, overrides, and legacy compatibility fields |
|
||||||
|
| `hasRoomBanForUser(bans, user, persistedUserId?)` | Returns true when any active ban entry targets the provided user |
|
||||||
|
| `isRoomBanMatch(ban, user, persistedUserId?)` | Returns true when a single ban entry targets the provided user |
|
||||||
|
|
||||||
## Layering
|
## Layering
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export * from './access-control.models';
|
|
||||||
export * from './access-control.constants';
|
|
||||||
export * from './role.rules';
|
|
||||||
export * from './role-assignment.rules';
|
|
||||||
export * from './permission.rules';
|
|
||||||
export * from './room.rules';
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { RoomPermissionDefinition } from './access-control.models';
|
import type { RoomPermissionDefinition } from '../models/access-control.model';
|
||||||
|
|
||||||
export const SYSTEM_ROLE_IDS = {
|
export const SYSTEM_ROLE_IDS = {
|
||||||
everyone: 'system-everyone',
|
everyone: 'system-everyone',
|
||||||
@@ -2,7 +2,7 @@ import type {
|
|||||||
RoomMember,
|
RoomMember,
|
||||||
RoomPermissionKey,
|
RoomPermissionKey,
|
||||||
User
|
User
|
||||||
} from '../../../shared-kernel';
|
} from '../../../../shared-kernel';
|
||||||
|
|
||||||
export interface RoomPermissionDefinition {
|
export interface RoomPermissionDefinition {
|
||||||
key: RoomPermissionKey;
|
key: RoomPermissionKey;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BanEntry, User } from '../models/index';
|
import { BanEntry, User } from '../../../../shared-kernel';
|
||||||
|
|
||||||
type BanAwareUser = Pick<User, 'id' | 'oderId'> | null | undefined;
|
type BanAwareUser = Pick<User, 'id' | 'oderId'> | null | undefined;
|
||||||
|
|
||||||
@@ -4,9 +4,9 @@ import {
|
|||||||
Room,
|
Room,
|
||||||
RoomPermissionKey,
|
RoomPermissionKey,
|
||||||
RoomRole
|
RoomRole
|
||||||
} from '../../../shared-kernel';
|
} from '../../../../shared-kernel';
|
||||||
import { SYSTEM_ROLE_IDS } from './access-control.constants';
|
import { SYSTEM_ROLE_IDS } from '../constants/access-control.constants';
|
||||||
import type { MemberIdentity } from './access-control.models';
|
import type { MemberIdentity } from '../models/access-control.model';
|
||||||
import {
|
import {
|
||||||
buildRoleLookup,
|
buildRoleLookup,
|
||||||
getRolePermissionState,
|
getRolePermissionState,
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
normalizePermissionState,
|
normalizePermissionState,
|
||||||
roleSortAscending,
|
roleSortAscending,
|
||||||
compareText
|
compareText
|
||||||
} from './access-control.internal';
|
} from '../util/access-control.util';
|
||||||
import { getAssignedRoleIds, getHighestAssignedRole } from './role-assignment.rules';
|
import { getAssignedRoleIds, getHighestAssignedRole } from './role-assignment.rules';
|
||||||
import { getRoomRoleById, normalizeRoomRoles } from './role.rules';
|
import { getRoomRoleById, normalizeRoomRoles } from './role.rules';
|
||||||
|
|
||||||
@@ -3,9 +3,9 @@ import {
|
|||||||
RoomMember,
|
RoomMember,
|
||||||
RoomRole,
|
RoomRole,
|
||||||
RoomRoleAssignment
|
RoomRoleAssignment
|
||||||
} from '../../../shared-kernel';
|
} from '../../../../shared-kernel';
|
||||||
import { SYSTEM_ROLE_IDS } from './access-control.constants';
|
import { SYSTEM_ROLE_IDS } from '../constants/access-control.constants';
|
||||||
import type { MemberIdentity } from './access-control.models';
|
import type { MemberIdentity } from '../models/access-control.model';
|
||||||
import {
|
import {
|
||||||
buildRoleLookup,
|
buildRoleLookup,
|
||||||
compareText,
|
compareText,
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
matchesIdentity,
|
matchesIdentity,
|
||||||
roleSortDescending,
|
roleSortDescending,
|
||||||
uniqueStrings
|
uniqueStrings
|
||||||
} from './access-control.internal';
|
} from '../util/access-control.util';
|
||||||
import { getRoomRoleById, normalizeRoomRoles } from './role.rules';
|
import { getRoomRoleById, normalizeRoomRoles } from './role.rules';
|
||||||
|
|
||||||
function sortAssignments(assignments: readonly RoomRoleAssignment[]): RoomRoleAssignment[] {
|
function sortAssignments(assignments: readonly RoomRoleAssignment[]): RoomRoleAssignment[] {
|
||||||
@@ -2,8 +2,8 @@ import {
|
|||||||
RoomPermissionMatrix,
|
RoomPermissionMatrix,
|
||||||
RoomPermissions,
|
RoomPermissions,
|
||||||
RoomRole
|
RoomRole
|
||||||
} from '../../../shared-kernel';
|
} from '../../../../shared-kernel';
|
||||||
import { SYSTEM_ROLE_IDS } from './access-control.constants';
|
import { SYSTEM_ROLE_IDS } from '../constants/access-control.constants';
|
||||||
import {
|
import {
|
||||||
buildRoleLookup,
|
buildRoleLookup,
|
||||||
buildSystemRole,
|
buildSystemRole,
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
normalizePermissionMatrix,
|
normalizePermissionMatrix,
|
||||||
roleSortAscending,
|
roleSortAscending,
|
||||||
roleSortDescending
|
roleSortDescending
|
||||||
} from './access-control.internal';
|
} from '../util/access-control.util';
|
||||||
|
|
||||||
const ROLE_COLORS = {
|
const ROLE_COLORS = {
|
||||||
everyone: '#6b7280',
|
everyone: '#6b7280',
|
||||||
@@ -7,14 +7,14 @@ import {
|
|||||||
RoomRole,
|
RoomRole,
|
||||||
RoomRoleAssignment,
|
RoomRoleAssignment,
|
||||||
UserRole
|
UserRole
|
||||||
} from '../../../shared-kernel';
|
} from '../../../../shared-kernel';
|
||||||
import { SYSTEM_ROLE_IDS } from './access-control.constants';
|
import { SYSTEM_ROLE_IDS } from '../constants/access-control.constants';
|
||||||
import {
|
import {
|
||||||
getRolePermissionState,
|
getRolePermissionState,
|
||||||
permissionStateToBoolean,
|
permissionStateToBoolean,
|
||||||
resolveLegacyAllowState
|
resolveLegacyAllowState
|
||||||
} from './access-control.internal';
|
} from '../util/access-control.util';
|
||||||
import type { MemberIdentity } from './access-control.models';
|
import type { MemberIdentity } from '../models/access-control.model';
|
||||||
import {
|
import {
|
||||||
getAssignedRoleIds,
|
getAssignedRoleIds,
|
||||||
normalizeRoomRoleAssignments,
|
normalizeRoomRoleAssignments,
|
||||||
@@ -5,8 +5,8 @@ import {
|
|||||||
RoomRole,
|
RoomRole,
|
||||||
RoomRoleAssignment,
|
RoomRoleAssignment,
|
||||||
ROOM_PERMISSION_KEYS
|
ROOM_PERMISSION_KEYS
|
||||||
} from '../../../shared-kernel';
|
} from '../../../../shared-kernel';
|
||||||
import type { MemberIdentity } from './access-control.models';
|
import type { MemberIdentity } from '../models/access-control.model';
|
||||||
|
|
||||||
export function normalizeName(name: string): string {
|
export function normalizeName(name: string): string {
|
||||||
return name.trim().replace(/\s+/g, ' ');
|
return name.trim().replace(/\s+/g, ' ');
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export * from './domain/access-control.models';
|
export * from './domain/models/access-control.model';
|
||||||
export * from './domain/access-control.constants';
|
export * from './domain/constants/access-control.constants';
|
||||||
export * from './domain/role.rules';
|
export * from './domain/rules/role.rules';
|
||||||
export * from './domain/role-assignment.rules';
|
export * from './domain/rules/role-assignment.rules';
|
||||||
export * from './domain/permission.rules';
|
export * from './domain/rules/permission.rules';
|
||||||
export * from './domain/room.rules';
|
export * from './domain/rules/room.rules';
|
||||||
|
export * from './domain/rules/ban.rules';
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import { type ServerInfo } from '../../domain/server-directory.models';
|
|||||||
import { ServerDirectoryFacade } from '../../application/server-directory.facade';
|
import { ServerDirectoryFacade } from '../../application/server-directory.facade';
|
||||||
import { selectCurrentUser } from '../../../../store/users/users.selectors';
|
import { selectCurrentUser } from '../../../../store/users/users.selectors';
|
||||||
import { ConfirmDialogComponent } from '../../../../shared';
|
import { ConfirmDialogComponent } from '../../../../shared';
|
||||||
import { hasRoomBanForUser } from '../../../../core/helpers/room-ban.helpers';
|
import { hasRoomBanForUser } from '../../../access-control';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-server-search',
|
selector: 'app-server-search',
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { RoomsActions } from '../../store/rooms/rooms.actions';
|
|||||||
import { DatabaseService } from '../../infrastructure/persistence';
|
import { DatabaseService } from '../../infrastructure/persistence';
|
||||||
import { NotificationsFacade } from '../../domains/notifications';
|
import { NotificationsFacade } from '../../domains/notifications';
|
||||||
import { type ServerInfo, ServerDirectoryFacade } from '../../domains/server-directory';
|
import { type ServerInfo, ServerDirectoryFacade } from '../../domains/server-directory';
|
||||||
import { hasRoomBanForUser } from '../../core/helpers/room-ban.helpers';
|
import { hasRoomBanForUser } from '../../domains/access-control';
|
||||||
import {
|
import {
|
||||||
ConfirmDialogComponent,
|
ConfirmDialogComponent,
|
||||||
ContextMenuComponent,
|
ContextMenuComponent,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import {
|
|||||||
VoiceState
|
VoiceState
|
||||||
} from '../../shared-kernel';
|
} from '../../shared-kernel';
|
||||||
import { NotificationAudioService, AppSound } from '../../core/services/notification-audio.service';
|
import { NotificationAudioService, AppSound } from '../../core/services/notification-audio.service';
|
||||||
import { hasRoomBanForUser } from '../../core/helpers/room-ban.helpers';
|
import { hasRoomBanForUser } from '../../domains/access-control';
|
||||||
import { RECONNECT_SOUND_GRACE_MS } from '../../core/constants';
|
import { RECONNECT_SOUND_GRACE_MS } from '../../core/constants';
|
||||||
import { VoiceSessionFacade } from '../../domains/voice-session';
|
import { VoiceSessionFacade } from '../../domains/voice-session';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import {
|
|||||||
saveLastViewedChatToStorage
|
saveLastViewedChatToStorage
|
||||||
} from '../../infrastructure/persistence';
|
} from '../../infrastructure/persistence';
|
||||||
import { ServerDirectoryFacade } from '../../domains/server-directory';
|
import { ServerDirectoryFacade } from '../../domains/server-directory';
|
||||||
import { hasRoomBanForUser } from '../../core/helpers/room-ban.helpers';
|
import { hasRoomBanForUser } from '../../domains/access-control';
|
||||||
import { Room } from '../../shared-kernel';
|
import { Room } from '../../shared-kernel';
|
||||||
import {
|
import {
|
||||||
removeRoomMember,
|
removeRoomMember,
|
||||||
|
|||||||
Reference in New Issue
Block a user