refactor: address PR review feedback for RBAC
This commit is contained in:
@@ -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
@@ -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 =>
|
||||
|
||||
Reference in New Issue
Block a user