diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 8923aed9..3be0125b 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -609,10 +609,11 @@ func main() { 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) - admin.Get("/tenants", requireAdmin, tenantHandler.ListTenants) + admin.Get("/tenants", requireAnyUser, tenantHandler.ListTenants) admin.Post("/tenants", requireSuperAdmin, tenantHandler.CreateTenant) admin.Delete("/tenants/bulk", requireSuperAdmin, tenantHandler.DeleteTenantsBulk) admin.Post("/tenants/:id/approve", requireSuperAdmin, tenantHandler.ApproveTenant) diff --git a/backend/internal/handler/tenant_handler.go b/backend/internal/handler/tenant_handler.go index 73bafe8e..03b4fb17 100644 --- a/backend/internal/handler/tenant_handler.go +++ b/backend/internal/handler/tenant_handler.go @@ -6,7 +6,6 @@ import ( "baron-sso-backend/internal/service" "baron-sso-backend/internal/utils" "errors" - "log/slog" "strings" "time" @@ -114,16 +113,72 @@ func (h *TenantHandler) ListTenants(c *fiber.Ctx) error { var err error profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse) + role := "" + if profile != nil { + role = domain.NormalizeRole(profile.Role) + } - // If Tenant Admin, only list manageable tenants - if profile != nil && domain.NormalizeRole(profile.Role) == domain.RoleTenantAdmin { - slog.Info("Listing manageable tenants for tenant admin", "userID", profile.ID) - tenants, err = h.Service.ListManageableTenants(c.Context(), profile.ID) + if role != domain.RoleSuperAdmin { + // Not a super admin: Only return the entire tree(s) of the tenants they belong to + allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "") 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)) - // Apply basic pagination if needed (optional for usually small number of manageable tenants) if offset < len(tenants) { end := offset + limit if end > len(tenants) { @@ -751,8 +806,8 @@ func (h *TenantHandler) DeleteTenantsBulk(c *fiber.Ctx) error { return errorJSON(c, fiber.StatusBadRequest, "no IDs provided") } - // Permission check: Super Admin can delete anything. - // Tenant Admin should theoretically only delete manageable sub-tenants, + // Permission check: Super Admin can delete anything. + // Tenant Admin should theoretically only delete manageable sub-tenants, // but currently bulk delete is intended for Super Admin. profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse) if profile == nil || domain.NormalizeRole(profile.Role) != domain.RoleSuperAdmin {