1
0
forked from baron/baron-sso
Files
baron-sso/backend/internal/handler/admin_handler.go

235 lines
6.3 KiB
Go

package handler
import (
"baron-sso-backend/internal/domain"
"baron-sso-backend/internal/repository"
"baron-sso-backend/internal/service"
"context"
"runtime"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
)
type adminHydraClientLister interface {
ListClients(ctx context.Context, limit, offset int) ([]domain.HydraClient, error)
}
type AdminHandler struct {
Keto service.KetoService
KetoOutbox repository.KetoOutboxRepository
RPUsageQueries domain.RPUsageQueryRepository
TenantRepo repository.TenantRepository
Hydra adminHydraClientLister
AuditRepo domain.AuditRepository
UserProjectionRepo repository.UserProjectionRepository
UserProjectionSyncer service.UserProjectionReconciler
}
func NewAdminHandler(keto service.KetoService, ketoOutbox repository.KetoOutboxRepository) *AdminHandler {
return &AdminHandler{
Keto: keto,
KetoOutbox: ketoOutbox,
}
}
func (h *AdminHandler) GetRPUsageDaily(c *fiber.Ctx) error {
if h == nil || h.RPUsageQueries == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "rp usage query service unavailable",
})
}
days := 14
if raw := c.Query("days"); raw != "" {
if parsed, err := strconv.Atoi(raw); err == nil {
days = parsed
}
}
period := normalizeRPUsagePeriod(c.Query("period"))
tenantID, allowed := h.authorizedRPUsageTenantID(c, strings.TrimSpace(c.Query("tenantId")))
if !allowed {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "forbidden: tenant rp usage stats permission denied",
})
}
items, err := h.RPUsageQueries.FindRPUsage(c.Context(), domain.RPUsageQuery{
Days: days,
Period: period,
TenantID: tenantID,
})
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
return c.JSON(fiber.Map{
"items": items,
"days": days,
"period": period,
"tenantId": tenantID,
})
}
func normalizeRPUsagePeriod(period string) string {
switch strings.ToLower(strings.TrimSpace(period)) {
case "week":
return "week"
case "month":
return "month"
default:
return "day"
}
}
func (h *AdminHandler) authorizedRPUsageTenantID(c *fiber.Ctx, requestedTenantID string) (string, bool) {
profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse)
if profile != nil && domain.NormalizeRole(profile.Role) == domain.RoleSuperAdmin {
return requestedTenantID, true
}
tenantID := requestedTenantID
if tenantID == "" && profile != nil && profile.TenantID != nil {
tenantID = strings.TrimSpace(*profile.TenantID)
}
if tenantID == "" {
return "", false
}
if h == nil || h.Keto == nil || profile == nil || strings.TrimSpace(profile.ID) == "" {
return "", false
}
allowed, err := h.Keto.CheckPermission(c.Context(), "User:"+profile.ID, "Tenant", tenantID, "view_rp_usage_stats")
if err != nil || !allowed {
return "", false
}
return tenantID, true
}
func (h *AdminHandler) CheckAuth(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "ok"})
}
func requireSuperAdminProfile(c *fiber.Ctx) error {
profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse)
if profile == nil || domain.NormalizeRole(profile.Role) != domain.RoleSuperAdmin {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden: super_admin required"})
}
return nil
}
func (h *AdminHandler) GetUserProjectionStatus(c *fiber.Ctx) error {
if err := requireSuperAdminProfile(c); err != nil {
return err
}
if h == nil || h.UserProjectionRepo == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "user projection service unavailable"})
}
status, err := h.UserProjectionRepo.GetStatus(c.Context())
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(status)
}
func (h *AdminHandler) ReconcileUserProjection(c *fiber.Ctx) error {
if err := requireSuperAdminProfile(c); err != nil {
return err
}
if h == nil || h.UserProjectionSyncer == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "user projection sync service unavailable"})
}
count, err := h.UserProjectionSyncer.Reconcile(c.Context())
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{
"status": "success",
"syncedUsers": count,
"updatedAt": time.Now().UTC().Format(time.RFC3339),
})
}
func (h *AdminHandler) ResetUserProjection(c *fiber.Ctx) error {
return h.ReconcileUserProjection(c)
}
// GetSystemStats returns runtime statistics for monitoring
func (h *AdminHandler) GetSystemStats(c *fiber.Ctx) error {
var m runtime.MemStats
runtime.ReadMemStats(&m)
ctx := c.Context()
stats := fiber.Map{
"totalTenants": h.countTenants(ctx),
"oidcClients": h.countOIDCClients(ctx),
"auditEvents24h": h.countAuditEventsSince(ctx, time.Now().UTC().Add(-24*time.Hour)),
"goroutines": runtime.NumGoroutine(),
"cpus": runtime.NumCPU(),
"memory": fiber.Map{
"alloc": m.Alloc,
"totalAlign": m.TotalAlloc,
"sys": m.Sys,
"numGC": m.NumGC,
},
"timestamp": time.Now(),
}
return c.Status(fiber.StatusOK).JSON(stats)
}
func (h *AdminHandler) countTenants(ctx context.Context) int64 {
if h == nil || h.TenantRepo == nil {
return 0
}
_, total, err := h.TenantRepo.List(ctx, 1, 0, "")
if err != nil {
return 0
}
return total
}
func (h *AdminHandler) countOIDCClients(ctx context.Context) int64 {
if h == nil || h.Hydra == nil {
return 0
}
const pageSize = 500
var total int64
for offset := 0; ; offset += pageSize {
clients, err := h.Hydra.ListClients(ctx, pageSize, offset)
if err != nil {
return total
}
for _, client := range clients {
if isHiddenSystemClient(client) {
continue
}
total++
}
if len(clients) < pageSize {
break
}
}
return total
}
func (h *AdminHandler) countAuditEventsSince(ctx context.Context, since time.Time) int64 {
if h == nil || h.AuditRepo == nil {
return 0
}
count, err := h.AuditRepo.CountEventsSince(ctx, since)
if err == nil && count > 0 {
return count
}
logs, pageErr := h.AuditRepo.FindPage(ctx, 10000, nil, "")
if pageErr != nil {
return count
}
var fallbackCount int64
for _, log := range logs {
if !log.Timestamp.Before(since) {
fallbackCount++
}
}
return fallbackCount
}