From ba07c339ebe039db6f233c4d1f1a5830690938dc Mon Sep 17 00:00:00 2001 From: 1amKhush Date: Sun, 3 May 2026 16:37:27 +0530 Subject: [PATCH] refactor: address PR review feedback for RBAC --- src/app/components/EventMenu.svelte | 10 +- src/app/core/roles.ts | 157 ++++++++++------------------ 2 files changed, 56 insertions(+), 111 deletions(-) diff --git a/src/app/components/EventMenu.svelte b/src/app/components/EventMenu.svelte index 262ce96d..95b84902 100644 --- a/src/app/components/EventMenu.svelte +++ b/src/app/components/EventMenu.svelte @@ -17,11 +17,7 @@ import Report from "@app/components/Report.svelte" import EventShare from "@app/components/EventShare.svelte" import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte" - import { - deriveUserIsSpaceAdmin, - deriveHasPermission, - ROOM_PERMISSION_DELETE_EVENT, - } from "@app/core/roles" + import {deriveHasPermission, ROOM_PERMISSION_DELETE_EVENT} from "@app/core/roles" import {hasNip29} from "@app/core/state" import {pushModal} from "@app/util/modal" import {pushToast} from "@app/util/toast" @@ -39,9 +35,7 @@ const isRoot = event.kind !== COMMENT const h = getTagValue("h", event.tags) - const canDelete = h - ? deriveHasPermission(url, h, ROOM_PERMISSION_DELETE_EVENT) - : deriveUserIsSpaceAdmin(url) + const canDelete = deriveHasPermission(url, h, ROOM_PERMISSION_DELETE_EVENT) const report = () => pushModal(Report, {url, event}) diff --git a/src/app/core/roles.ts b/src/app/core/roles.ts index f2df0779..96683af3 100644 --- a/src/app/core/roles.ts +++ b/src/app/core/roles.ts @@ -1,7 +1,7 @@ import {derived, readable, type Readable} from "svelte/store" import {first, memoize, removeUndefined, simpleCache, sortBy, uniq} from "@welshman/lib" -import {deriveArray, deriveEventsByIdForUrl} from "@welshman/store" -import {pubkey, repository, tracker, manageRelay} from "@welshman/app" +import {pubkey, manageRelay} from "@welshman/app" +import {deriveEventsForUrl} from "@app/core/state" import { ManagementMethod, ROOM_ADD_MEMBER, @@ -13,7 +13,7 @@ import { getTagValue, isRelayUrl, } from "@welshman/util" -import type {Filter, TrustedEvent} from "@welshman/util" +import type {TrustedEvent} from "@welshman/util" export const ROOM_ROLES = 39003 @@ -79,22 +79,12 @@ type ParsedRoleState = { hasPermissionTags: boolean } -type RoomSnapshot = { - h: string - rolesState: ParsedRoleState - members: RoomMember[] - admins: RoomMember[] -} - type SpaceRoleState = { hasPermissionTags: boolean userPermissions: Set memberRoles: Map } -const deriveEventsForUrl = (url: string, filters: Filter[] = [{}]) => - deriveArray(deriveEventsByIdForUrl({url, tracker, repository, filters})) - const makeRoleDefinition = (name: string): RoleDefinition => ({ name, permissions: [], @@ -121,14 +111,6 @@ const asAccess = (value: string | undefined): RoleAccess | undefined => { } } -const getLatestEventByKind = (events: TrustedEvent[], kind: number, h: string) => - first( - sortBy( - event => -event.created_at, - events.filter(event => event.kind === kind && getTagValue("d", event.tags) === h), - ), - ) - const parseRoleState = (event?: TrustedEvent): ParsedRoleState => { const roles = new Map() let hasPermissionTags = false @@ -250,27 +232,19 @@ export const parseRoomMembers = (tags: string[][]) => { })) } -const deriveRoomListStore = simpleCache(([url, h]: [string, string]) => - deriveEventsForUrl(url, [{kinds: [ROOM_MEMBERS, ROOM_ADMINS], "#d": [h]}]), -) - export const deriveRoomMembers = (url: string, h: string) => - derived(deriveRoomListStore(url, h), $events => { - const event = getLatestEventByKind($events, ROOM_MEMBERS, h) - - return parseRoomMembers(event?.tags || []) - }) + derived(deriveEventsForUrl(url, [{kinds: [ROOM_MEMBERS], "#d": [h]}]), ([event]) => + parseRoomMembers(event?.tags || []), + ) export const deriveRoomAdmins = (url: string, h: string) => - derived(deriveRoomListStore(url, h), $events => { - const event = getLatestEventByKind($events, ROOM_ADMINS, h) - - return parseRoomMembers(event?.tags || []) - }) + derived(deriveEventsForUrl(url, [{kinds: [ROOM_ADMINS], "#d": [h]}]), ([event]) => + parseRoomMembers(event?.tags || []), + ) const deriveRoomRoleState = simpleCache(([url, h]: [string, string]) => - derived(deriveEventsForUrl(url, [{kinds: [ROOM_ROLES], "#d": [h]}]), $events => - parseRoleState(getLatestEventByKind($events, ROOM_ROLES, h)), + derived(deriveEventsForUrl(url, [{kinds: [ROOM_ROLES], "#d": [h]}]), ([event]) => + parseRoleState(event), ), ) @@ -395,52 +369,6 @@ export const deriveUserPermissions = (url: string, h: string) => }, ) -const buildRoomSnapshots = (events: TrustedEvent[]) => { - const latestByH = new Map< - string, - {roles?: TrustedEvent; members?: TrustedEvent; admins?: TrustedEvent} - >() - - for (const event of sortBy(x => -x.created_at, events)) { - const h = getTagValue("d", event.tags) - - if (!h) { - continue - } - - if (!latestByH.has(h)) { - latestByH.set(h, {}) - } - - const entry = latestByH.get(h)! - - if (event.kind === ROOM_ROLES && !entry.roles) { - entry.roles = event - } - - if (event.kind === ROOM_MEMBERS && !entry.members) { - entry.members = event - } - - if (event.kind === ROOM_ADMINS && !entry.admins) { - entry.admins = event - } - } - - const snapshots: RoomSnapshot[] = [] - - for (const [h, {roles, members, admins}] of latestByH.entries()) { - snapshots.push({ - h, - rolesState: parseRoleState(roles), - members: parseRoomMembers(members?.tags || []), - admins: parseRoomMembers(admins?.tags || []), - }) - } - - return snapshots -} - const mergeRoleDefinitions = (left: RoleDefinition[], right: RoleDefinition[]) => { const merged = new Map() @@ -488,28 +416,48 @@ const deriveSpaceRoleState = simpleCache(([url]: [string]) => ([$pubkey, $events]): SpaceRoleState => { const userPermissions = new Set() const memberRoles = new Map() - const snapshots = buildRoomSnapshots($events) - const hasPermissionTags = snapshots.some(snapshot => snapshot.rolesState.hasPermissionTags) + const rolesByH = new Map>() + let hasPermissionTags = false - for (const snapshot of snapshots) { - const allMembers = [...snapshot.members, ...snapshot.admins] - - for (const member of allMembers) { - const resolvedRoles = getResolvedRoles(snapshot.rolesState.roles, member.roles) - - if (resolvedRoles.length === 0) { - continue + for (const event of $events) { + if (event.kind === ROOM_ROLES) { + const h = getTagValue("d", event.tags) + if (h) { + const parsed = parseRoleState(event) + rolesByH.set(h, parsed) + if (parsed.hasPermissionTags) { + hasPermissionTags = true + } } + } + } - memberRoles.set( - member.pubkey, - mergeRoleDefinitions(memberRoles.get(member.pubkey) || [], resolvedRoles), - ) + for (const event of $events) { + if (event.kind === ROOM_MEMBERS || event.kind === ROOM_ADMINS) { + const h = getTagValue("d", event.tags) + if (!h) continue - if ($pubkey === member.pubkey && hasPermissionTags) { - for (const role of resolvedRoles) { - for (const permission of role.permissions) { - userPermissions.add(permission) + const rolesState = rolesByH.get(h) + if (!rolesState) continue + + const members = parseRoomMembers(event.tags) + for (const member of members) { + const resolvedRoles = getResolvedRoles(rolesState.roles, member.roles) + + if (resolvedRoles.length === 0) { + continue + } + + memberRoles.set( + member.pubkey, + mergeRoleDefinitions(memberRoles.get(member.pubkey) || [], resolvedRoles), + ) + + if ($pubkey === member.pubkey && rolesState.hasPermissionTags) { + for (const role of resolvedRoles) { + for (const permission of role.permissions) { + userPermissions.add(permission) + } } } } @@ -575,11 +523,14 @@ export const deriveUserIsRoomAdmin = (url: string, h: string) => ([$permissions, $isSpaceAdmin]) => $isSpaceAdmin || $permissions.size > 0, ) -export const deriveHasPermission = (url: string, h: string, kind: number) => - derived( +export const deriveHasPermission = (url: string, h: string | undefined, kind: number) => { + if (!h) return deriveUserIsSpaceAdmin(url) + + return derived( [deriveUserPermissions(url, h), deriveUserIsSpaceAdmin(url)], ([$permissions, $isSpaceAdmin]) => $isSpaceAdmin || $permissions.has(kind), ) +} export const deriveRoomRoleDefinitions = (url: string, h: string) => derived(deriveRoomRoles(url, h), $roomRoles =>