Files
Enterprise-Knowledge-Base/internal/container
wizardchen 3f7150cb5c feat(rbac): tenant member management + invite_only gate (PR 3 of 3, #1303)
Wire the human-facing surface for the tenant-RBAC system that PR 1
introduced and PR 2 enforced: REST endpoints + UI for managing tenant
members, plus the long-pending invite_only registration gate.

Backend
- internal/handler/auth.go: Register now rejects with 403 when
  auth.registration_mode=invite_only. PR 1 already exposes the mode via
  /auth/config so the frontend hides the sign-up link; this is the
  server-side enforcement for direct API calls.
- internal/handler/tenant_member.go: new TenantMemberHandler with
  ListMembers / AddMember / UpdateMemberRole / RemoveMember. Maps
  service-layer sentinels onto HTTP semantics (ErrLastOwner -> 409,
  ErrMembershipAlreadyExists -> 409, ErrUserNotFound on AddMember -> 404,
  ErrInvalidTenantRole -> 400). AddMember requires the invitee to
  already have an account; SMTP-driven email invites are deferred.
- internal/types/tenant_member.go: new TenantMemberResponse projection
  carrying the fields the management UI renders (email, username,
  avatar, role, joined_at).
- internal/router/router.go: four routes under /tenants/:id/members
  gated per the matrix (Viewer+ for list, Owner+ for every mutation).
- internal/container/container.go: dig provider for the new handler.

Tests
- handler/tenant_member_test.go: 12 table-driven cases covering happy
  path, dangling-user tolerance, ErrLastOwner -> 409, ErrMembershipNotFound
  -> 404, ErrMembershipAlreadyExists -> 409, unknown email -> 404,
  invalid role rejected before user lookup, bad tenant id -> 400.
- handler/auth_register_invite_only_test.go: invite_only -> 403,
  self_serve -> 201, nil Auth config falls through.

Frontend
- src/api/tenant/members.ts: typed client (TenantRole/TenantMember/...)
  for the four endpoints.
- src/views/settings/TenantMembers.vue: members table with add dialog,
  inline role select, confirm-remove modal. All mutation widgets gated
  behind currentTenantRole === 'owner'; server still enforces.
- src/views/settings/Settings.vue: register the Members tab; show only
  when currentTenantRole is loaded (avoids tab flash on cold nav).
- i18n in en-US/zh-CN/ko-KR/ru-RU: new tenantMember namespace covering
  all UI strings and structured error messages (lastOwner / userNotFound
  / alreadyMember / invalidRole etc).

This completes the #1303 RBAC rollout. With cfg.Tenant.EnableRBAC=true
operators now have a full management surface; with EnableRBAC=false
the routes still exist but mutations log-and-pass per PR 2 contract.
2026-05-18 17:28:58 +08:00
..
2025-08-05 15:08:07 +08:00