package handler import ( "baron-sso-backend/internal/service" "errors" "strings" "time" "github.com/gofiber/fiber/v2" ) type DevHandler struct { Hydra *service.HydraAdminService } func NewDevHandler() *DevHandler { return &DevHandler{ Hydra: service.NewHydraAdminService(), } } type clientSummary struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` Status string `json:"status"` CreatedAt *time.Time `json:"createdAt,omitempty"` RedirectURIs []string `json:"redirectUris"` Scopes []string `json:"scopes"` Metadata map[string]interface{} `json:"metadata,omitempty"` } type clientListResponse struct { Items []clientSummary `json:"items"` Limit int `json:"limit"` Offset int `json:"offset"` } type clientDetailResponse struct { Client clientSummary `json:"client"` Endpoints clientEndpoints `json:"endpoints"` } type clientEndpoints struct { Discovery string `json:"discovery"` Issuer string `json:"issuer"` Authorization string `json:"authorization"` Token string `json:"token"` UserInfo string `json:"userinfo"` } type consentSummary struct { Subject string `json:"subject"` ClientID string `json:"clientId"` ClientName string `json:"clientName,omitempty"` GrantedScopes []string `json:"grantedScopes"` AuthenticatedAt string `json:"authenticatedAt,omitempty"` } type consentListResponse struct { Items []consentSummary `json:"items"` } func (h *DevHandler) ListClients(c *fiber.Ctx) error { limit := c.QueryInt("limit", 50) offset := c.QueryInt("offset", 0) if limit <= 0 { limit = 50 } if offset < 0 { offset = 0 } clients, err := h.Hydra.ListClients(c.Context(), limit, offset) if err != nil { if errors.Is(err, service.ErrHydraNotFound) { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "clients not found"}) } errMsg := err.Error() if strings.Contains(errMsg, "connection refused") || strings.Contains(errMsg, "dial tcp") { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{ "error": "Hydra service is unavailable. Please check if Ory Hydra is running.", }) } return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": errMsg}) } items := make([]clientSummary, 0, len(clients)) for _, client := range clients { items = append(items, mapClientSummary(client)) } return c.JSON(clientListResponse{ Items: items, Limit: limit, Offset: offset, }) } func (h *DevHandler) GetClient(c *fiber.Ctx) error { clientID := c.Params("id") if clientID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client id is required"}) } client, err := h.Hydra.GetClient(c.Context(), clientID) if err != nil { if errors.Is(err, service.ErrHydraNotFound) { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"}) } return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } summary := mapClientSummary(*client) return c.JSON(clientDetailResponse{ Client: summary, Endpoints: clientEndpoints{ Discovery: strings.TrimRight(h.Hydra.PublicURL, "/") + "/.well-known/openid-configuration", Issuer: h.Hydra.PublicURL, Authorization: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/auth", Token: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/token", UserInfo: strings.TrimRight(h.Hydra.PublicURL, "/") + "/userinfo", }, }) } func (h *DevHandler) UpdateClientStatus(c *fiber.Ctx) error { clientID := c.Params("id") if clientID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client id is required"}) } var req struct { Status string `json:"status"` } if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"}) } status := strings.ToLower(strings.TrimSpace(req.Status)) if status != "active" && status != "inactive" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "status must be active or inactive"}) } updated, err := h.Hydra.PatchClientStatus(c.Context(), clientID, status) if err != nil { if errors.Is(err, service.ErrHydraNotFound) { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"}) } return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } summary := mapClientSummary(*updated) return c.JSON(clientDetailResponse{ Client: summary, Endpoints: clientEndpoints{ Discovery: strings.TrimRight(h.Hydra.PublicURL, "/") + "/.well-known/openid-configuration", Issuer: h.Hydra.PublicURL, Authorization: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/auth", Token: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/token", UserInfo: strings.TrimRight(h.Hydra.PublicURL, "/") + "/userinfo", }, }) } func (h *DevHandler) ListConsents(c *fiber.Ctx) error { subject := strings.TrimSpace(c.Query("subject")) if subject == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "subject is required"}) } clientID := strings.TrimSpace(c.Query("client_id")) sessions, err := h.Hydra.ListConsentSessions(c.Context(), subject, clientID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } items := make([]consentSummary, 0, len(sessions)) for _, session := range sessions { authAt := "" if session.AuthenticatedAt != nil { authAt = session.AuthenticatedAt.Format(time.RFC3339) } else if session.RequestedAt != nil { authAt = session.RequestedAt.Format(time.RFC3339) } items = append(items, consentSummary{ Subject: session.Subject, ClientID: session.Client.ClientID, ClientName: session.Client.ClientName, GrantedScopes: session.GrantedScope, AuthenticatedAt: authAt, }) } return c.JSON(consentListResponse{Items: items}) } func (h *DevHandler) RevokeConsents(c *fiber.Ctx) error { subject := strings.TrimSpace(c.Query("subject")) if subject == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "subject is required"}) } clientID := strings.TrimSpace(c.Query("client_id")) if err := h.Hydra.RevokeConsentSessions(c.Context(), subject, clientID); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.SendStatus(fiber.StatusNoContent) } func mapClientSummary(client service.HydraClient) clientSummary { status := "active" if client.Metadata != nil { if value, ok := client.Metadata["status"].(string); ok && strings.ToLower(value) == "inactive" { status = "inactive" } } clientType := "confidential" if strings.EqualFold(client.TokenEndpointAuthMethod, "none") { clientType = "public" } name := strings.TrimSpace(client.ClientName) if name == "" { name = client.ClientID } scopes := strings.Fields(client.Scope) return clientSummary{ ID: client.ClientID, Name: name, Type: clientType, Status: status, RedirectURIs: client.RedirectURIs, Scopes: scopes, Metadata: client.Metadata, } }