1
0
forked from baron/baron-sso

feat(backend): allow regular users and tenant admins to list their full tenant trees

Changes the /v1/admin/tenants endpoint to be accessible by all authenticated users (requireAnyUser). In the handler, it dynamically resolves the user's affiliations and filters the response to return the complete hierarchical tree (root, parent, child, sibling nodes) for any tenant they belong to.
This commit is contained in:
2026-04-13 11:56:35 +09:00
parent 0cd43f0aea
commit 010719eee9
2 changed files with 67 additions and 11 deletions

View File

@@ -609,10 +609,11 @@ func main() {
KetoService: ketoService, KetoService: ketoService,
}) })
admin.Get("/check", adminHandler.CheckAuth) // 기본 Admin 체크는 requireAdmin 없이 ApiKeyAuth로만 보호될 수 있음 (또는 추가 가능) admin.Get("/stats", requireSuperAdmin, adminHandler.GetSystemStats) admin.Get("/check", adminHandler.CheckAuth) // 기본 Admin 체크는 requireAdmin 없이 ApiKeyAuth로만 보호될 수 있음 (또는 추가 가능)
admin.Get("/stats", requireSuperAdmin, adminHandler.GetSystemStats)
// Tenant Management (Mixed roles, handler filters results) // Tenant Management (Mixed roles, handler filters results)
admin.Get("/tenants", requireAdmin, tenantHandler.ListTenants) admin.Get("/tenants", requireAnyUser, tenantHandler.ListTenants)
admin.Post("/tenants", requireSuperAdmin, tenantHandler.CreateTenant) admin.Post("/tenants", requireSuperAdmin, tenantHandler.CreateTenant)
admin.Delete("/tenants/bulk", requireSuperAdmin, tenantHandler.DeleteTenantsBulk) admin.Delete("/tenants/bulk", requireSuperAdmin, tenantHandler.DeleteTenantsBulk)
admin.Post("/tenants/:id/approve", requireSuperAdmin, tenantHandler.ApproveTenant) admin.Post("/tenants/:id/approve", requireSuperAdmin, tenantHandler.ApproveTenant)

View File

@@ -6,7 +6,6 @@ import (
"baron-sso-backend/internal/service" "baron-sso-backend/internal/service"
"baron-sso-backend/internal/utils" "baron-sso-backend/internal/utils"
"errors" "errors"
"log/slog"
"strings" "strings"
"time" "time"
@@ -114,16 +113,72 @@ func (h *TenantHandler) ListTenants(c *fiber.Ctx) error {
var err error var err error
profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse) profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse)
role := ""
if profile != nil {
role = domain.NormalizeRole(profile.Role)
}
// If Tenant Admin, only list manageable tenants if role != domain.RoleSuperAdmin {
if profile != nil && domain.NormalizeRole(profile.Role) == domain.RoleTenantAdmin { // Not a super admin: Only return the entire tree(s) of the tenants they belong to
slog.Info("Listing manageable tenants for tenant admin", "userID", profile.ID) allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "")
tenants, err = h.Service.ListManageableTenants(c.Context(), profile.ID)
if err != nil { if err != nil {
return errorJSON(c, fiber.StatusInternalServerError, "failed to list manageable tenants: "+err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
} }
if profile != nil {
baseTenantIDs := []string{}
for _, t := range profile.ManageableTenants {
baseTenantIDs = append(baseTenantIDs, t.ID)
}
for _, t := range profile.JoinedTenants {
baseTenantIDs = append(baseTenantIDs, t.ID)
}
if profile.TenantID != nil {
baseTenantIDs = append(baseTenantIDs, *profile.TenantID)
}
// Try to find by companyCode if needed
if profile.CompanyCode != "" {
for _, t := range allTenants {
if strings.EqualFold(t.Slug, profile.CompanyCode) {
baseTenantIDs = append(baseTenantIDs, t.ID)
}
}
}
parentMap := make(map[string]string)
for _, t := range allTenants {
if t.ParentID != nil {
parentMap[t.ID] = *t.ParentID
}
}
findRoot := func(id string) string {
curr := id
for {
p, exists := parentMap[curr]
if !exists || p == "" {
break
}
curr = p
}
return curr
}
roots := make(map[string]bool)
for _, id := range baseTenantIDs {
roots[findRoot(id)] = true
}
// Filter tenants that belong to the same tree family
for _, t := range allTenants {
if roots[findRoot(t.ID)] {
tenants = append(tenants, t)
}
}
}
total = int64(len(tenants)) total = int64(len(tenants))
// Apply basic pagination if needed (optional for usually small number of manageable tenants)
if offset < len(tenants) { if offset < len(tenants) {
end := offset + limit end := offset + limit
if end > len(tenants) { if end > len(tenants) {
@@ -751,8 +806,8 @@ func (h *TenantHandler) DeleteTenantsBulk(c *fiber.Ctx) error {
return errorJSON(c, fiber.StatusBadRequest, "no IDs provided") return errorJSON(c, fiber.StatusBadRequest, "no IDs provided")
} }
// Permission check: Super Admin can delete anything. // Permission check: Super Admin can delete anything.
// Tenant Admin should theoretically only delete manageable sub-tenants, // Tenant Admin should theoretically only delete manageable sub-tenants,
// but currently bulk delete is intended for Super Admin. // but currently bulk delete is intended for Super Admin.
profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse) profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse)
if profile == nil || domain.NormalizeRole(profile.Role) != domain.RoleSuperAdmin { if profile == nil || domain.NormalizeRole(profile.Role) != domain.RoleSuperAdmin {