feat(rbac): implement NIP-29 room roles and permission gating (#47)

This commit is contained in:
2026-04-17 05:57:10 +05:30
committed by hodlbod
parent bbbc6f7363
commit 559db6b930
11 changed files with 951 additions and 140 deletions
+31 -12
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import {onMount} from "svelte"
import {readable} from "svelte/store"
import {removeUndefined} from "@welshman/lib"
import {ManagementMethod} from "@welshman/util"
import {
@@ -28,7 +29,9 @@
import ProfileInfo from "@app/components/ProfileInfo.svelte"
import EventInfo from "@app/components/EventInfo.svelte"
import ProfileBadges from "@app/components/ProfileBadges.svelte"
import {pubkeyLink, deriveUserIsSpaceAdmin, deriveSpaceBannedPubkeyItems} from "@app/core/state"
import RoleBadge from "@app/components/RoleBadge.svelte"
import {pubkeyLink, deriveSpaceBannedPubkeyItems} from "@app/core/state"
import {deriveUserHasSpacePermission, deriveSpaceMemberRoleInfo} from "@app/core/roles"
import {addSpaceMembers} from "@app/core/commands"
import {pushModal} from "@app/util/modal"
import {pushToast} from "@app/util/toast"
@@ -43,10 +46,16 @@
const profile = deriveProfile(pubkey, removeUndefined([url]))
const userIsAdmin = deriveUserIsSpaceAdmin(url)
const canBan = url ? deriveUserHasSpacePermission(url, 9009) : readable(false)
const canRestore = url ? deriveUserHasSpacePermission(url, 9000) : readable(false)
const bannedPubkeys = url ? deriveSpaceBannedPubkeyItems(url) : undefined
const spaceMemberRoles = url ? deriveSpaceMemberRoleInfo(url) : readable(new Map())
const assignedRoles = $derived($spaceMemberRoles.get(pubkey)?.roles || [])
const isBanned = $derived($bannedPubkeys?.some(item => item.pubkey === pubkey) ?? false)
const back = () => history.back()
@@ -105,7 +114,7 @@
<div class="flex flex-col gap-4">
<div class="flex justify-between">
<Profile showPubkey avatarSize={14} {pubkey} {url} />
{#if $profile || $userIsAdmin}
{#if $profile || $canBan || $canRestore}
<div class="relative">
<Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}>
<Icon icon={MenuDots} />
@@ -123,22 +132,22 @@
</Button>
</li>
{/if}
{#if $userIsAdmin}
{#if isBanned}
{#if isBanned}
{#if $canRestore}
<li>
<Button onclick={restoreMember}>
<Icon icon={Restart} />
Restore User
</Button>
</li>
{:else}
<li>
<Button class="text-error" onclick={banMember}>
<Icon icon={MinusCircle} />
Ban User
</Button>
</li>
{/if}
{:else if $canBan}
<li>
<Button class="text-error" onclick={banMember}>
<Icon icon={MinusCircle} />
Ban User
</Button>
</li>
{/if}
</ul>
</Popover>
@@ -147,6 +156,16 @@
{/if}
</div>
<ProfileInfo {pubkey} {url} />
{#if assignedRoles.length > 0}
<div class="card2 card2-sm bg-alt col-3">
<h3 class="text-lg font-semibold">Roles</h3>
<div class="flex flex-wrap gap-2">
{#each assignedRoles as role (role.name)}
<RoleBadge role={role.name} label={role.label} color={role.color} class="badge-md" />
{/each}
</div>
</div>
{/if}
<ProfileBadges {pubkey} {url} />
</div>
</ModalBody>