Files
Enterprise-Knowledge-Base/internal/application/service
wizardchen 543e9709be fix(rbac): address PR 3 + leave-endpoint review feedback
Security
- Reject URL :id when it does not match the caller's active tenant on
  every /tenants/:id/members/* and /tenants/:id/leave endpoint. Without
  this an Owner of tenant A could drive operations on tenant B simply
  by changing the URL, because RequireRole resolves the caller's role
  against the active tenant from context, not the URL param. The
  cross-tenant superuser carve-out mirrors middleware/rbac.go
  (CanAccessAllTenants + EnableCrossTenantAccess required).
- AddMember no longer records the synthetic "system-<tenantID>" caller
  (X-API-Key auth path) as invited_by, matching the same filter
  KB.CreatorID got in PR 2.
- AuthHandler now logs a loud one-shot error when constructed with a
  nil/incomplete config so a wiring bug that silently disables the
  invite_only gate is visible at boot.

Race fixes
- New repo helpers DemoteOwnerAtomically / RemoveOwnerAtomically wrap
  the count-other-owners + write step in a transaction that holds
  SELECT … FOR UPDATE locks on every other active Owner row. The
  service routes Owner demotion / Owner removal through them, closing
  the TOCTOU window where two concurrent demotes of two different
  Owners could each observe count=2 and both commit, leaving the
  tenant ownerless.

Performance
- ListMembers hydration now batches user lookups via a new
  GetUsersByIDs interface method (one SELECT … WHERE id IN (…)
  instead of N round-trips). Failure degrades to "no email/username"
  rather than dropping rows.

Frontend
- canManage also fires for cross-tenant superusers
  (authStore.canAccessAllTenants), mirroring the backend carve-out so
  the UI controls match what the server actually allows.

Tests
- Cross-tenant URL guard regression cases for List / Add / Update /
  Remove / Leave.
- Superuser bypass tests (flag on -> service reached, flag off -> 403).
- Batched lookup test ensures GetUsersByIDs is called exactly once and
  GetUserByID is never used as a fallback.
- Synthetic caller invited_by filter regression test.
- Service-layer atomic last-Owner regression tests for both
  UpdateRole and RemoveMember.
2026-05-18 17:28:58 +08:00
..
2025-08-05 15:08:07 +08:00