forked from baron/baron-sso
235 lines
6.3 KiB
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
|
|
}
|