refactor: address PR review feedback for RBAC

This commit is contained in:
2026-05-03 16:37:27 +05:30
committed by hodlbod
parent 98bcf4c398
commit ba07c339eb
2 changed files with 56 additions and 111 deletions
+2 -8
View File
@@ -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})
+54 -103
View File
@@ -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<number>
memberRoles: Map<string, RoleDefinition[]>
}
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<string, RoleDefinition>()
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<string, RoleDefinition>()
@@ -488,28 +416,48 @@ const deriveSpaceRoleState = simpleCache(([url]: [string]) =>
([$pubkey, $events]): SpaceRoleState => {
const userPermissions = new Set<number>()
const memberRoles = new Map<string, RoleDefinition[]>()
const snapshots = buildRoomSnapshots($events)
const hasPermissionTags = snapshots.some(snapshot => snapshot.rolesState.hasPermissionTags)
const rolesByH = new Map<string, ReturnType<typeof parseRoleState>>()
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 =>