forked from baron/baron-sso
ory용 MCP 제작, devfront/adminfront 백엔드 연결
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"baron-sso-backend/internal/repository"
|
||||
"baron-sso-backend/internal/service"
|
||||
"baron-sso-backend/internal/validator"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
@@ -160,11 +161,45 @@ func main() {
|
||||
auditHandler := handler.NewAuditHandler(auditRepo)
|
||||
authHandler := handler.NewAuthHandler(redisService, idpProvider)
|
||||
adminHandler := handler.NewAdminHandler()
|
||||
devHandler := handler.NewDevHandler()
|
||||
|
||||
// 3. Initialize Fiber
|
||||
appEnv := getEnv("APP_ENV", "dev")
|
||||
app := fiber.New(fiber.Config{
|
||||
AppName: "Baron SSO Backend",
|
||||
DisableStartupMessage: true, // Clean logs
|
||||
// Global Error Handler for Production Masking
|
||||
ErrorHandler: func(c *fiber.Ctx, err error) error {
|
||||
// Default status code
|
||||
code := fiber.StatusInternalServerError
|
||||
|
||||
// Check if it's a known fiber.Error
|
||||
var e *fiber.Error
|
||||
if errors.As(err, &e) {
|
||||
code = e.Code
|
||||
}
|
||||
|
||||
// In production or stage, mask detailed 500+ errors
|
||||
if appEnv == "production" || appEnv == "stage" {
|
||||
if code >= 500 {
|
||||
// Log the actual error for developers
|
||||
slog.Error("Internal Server Error",
|
||||
"error", err.Error(),
|
||||
"path", c.Path(),
|
||||
"method", c.Method(),
|
||||
)
|
||||
// Return masked message
|
||||
return c.Status(code).JSON(fiber.Map{
|
||||
"error": "Internal Server Error",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// For development or non-500 errors, return the actual error message
|
||||
return c.Status(code).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
// Middleware
|
||||
@@ -281,6 +316,7 @@ func main() {
|
||||
// API Group
|
||||
api := app.Group("/api/v1")
|
||||
api.Post("/audit", auditHandler.CreateLog)
|
||||
api.Get("/audit", auditHandler.ListLogs)
|
||||
|
||||
// Auth Proxy Routes
|
||||
auth := api.Group("/auth")
|
||||
@@ -320,6 +356,14 @@ func main() {
|
||||
admin := api.Group("/admin")
|
||||
admin.Get("/check", adminHandler.CheckAuth)
|
||||
|
||||
// 개발자 포털 라우트 (RP/Consent 관리)
|
||||
dev := api.Group("/dev")
|
||||
dev.Get("/clients", devHandler.ListClients)
|
||||
dev.Get("/clients/:id", devHandler.GetClient)
|
||||
dev.Patch("/clients/:id/status", devHandler.UpdateClientStatus)
|
||||
dev.Get("/consents", devHandler.ListConsents)
|
||||
dev.Delete("/consents", devHandler.RevokeConsents)
|
||||
|
||||
// Webhook for Descope Generic SMS Gateway
|
||||
auth.Post("/webhooks/descope-sms", authHandler.HandleDescopeSmsRelay)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -19,5 +20,6 @@ type AuditLog struct {
|
||||
// AuditRepository defines interface for storing logs
|
||||
type AuditRepository interface {
|
||||
Create(log *AuditLog) error
|
||||
// FindAll(filter Filter) ([]*AuditLog, error) // Future scope
|
||||
FindAll(ctx context.Context, limit, offset int) ([]AuditLog, error)
|
||||
Ping(ctx context.Context) error
|
||||
}
|
||||
|
||||
@@ -46,3 +46,22 @@ func (h *AuditHandler) CreateLog(c *fiber.Ctx) error {
|
||||
"message": "Audit log saved",
|
||||
})
|
||||
}
|
||||
|
||||
// ListLogs handles GET /api/v1/audit
|
||||
func (h *AuditHandler) ListLogs(c *fiber.Ctx) error {
|
||||
limit := c.QueryInt("limit", 50)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
|
||||
logs, err := h.repo.FindAll(c.Context(), limit, offset)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Failed to retrieve audit logs",
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"items": logs,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
}
|
||||
|
||||
241
backend/internal/handler/dev_handler.go
Normal file
241
backend/internal/handler/dev_handler.go
Normal file
@@ -0,0 +1,241 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,46 @@ func (r *ClickHouseRepository) Create(log *domain.AuditLog) error {
|
||||
)
|
||||
}
|
||||
|
||||
func (r *ClickHouseRepository) FindAll(ctx context.Context, limit, offset int) ([]domain.AuditLog, error) {
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT timestamp, user_id, event_type, status, ip_address, user_agent, device_id, details
|
||||
FROM audit_logs
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`
|
||||
rows, err := r.conn.Query(ctx, query, limit, offset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query audit logs: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var logs []domain.AuditLog
|
||||
for rows.Next() {
|
||||
var log domain.AuditLog
|
||||
if err := rows.Scan(
|
||||
&log.Timestamp,
|
||||
&log.UserID,
|
||||
&log.EventType,
|
||||
&log.Status,
|
||||
&log.IPAddress,
|
||||
&log.UserAgent,
|
||||
&log.DeviceID,
|
||||
&log.Details,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan audit log: %w", err)
|
||||
}
|
||||
logs = append(logs, log)
|
||||
}
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func (r *ClickHouseRepository) Ping(ctx context.Context) error {
|
||||
if r.conn == nil {
|
||||
return fmt.Errorf("clickhouse connection is nil")
|
||||
|
||||
265
backend/internal/service/hydra_admin_service.go
Normal file
265
backend/internal/service/hydra_admin_service.go
Normal file
@@ -0,0 +1,265 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrHydraNotFound = errors.New("hydra admin: resource not found")
|
||||
|
||||
// HydraAdminService는 Hydra Admin API 호출을 래핑합니다.
|
||||
type HydraAdminService struct {
|
||||
AdminURL string
|
||||
PublicURL string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
type HydraClient struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientName string `json:"client_name,omitempty"`
|
||||
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
||||
GrantTypes []string `json:"grant_types,omitempty"`
|
||||
ResponseTypes []string `json:"response_types,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type HydraConsentSession struct {
|
||||
Subject string `json:"subject"`
|
||||
GrantedScope []string `json:"granted_scope"`
|
||||
GrantedAudience []string `json:"granted_audience,omitempty"`
|
||||
Remember bool `json:"remember"`
|
||||
AuthenticatedAt *time.Time `json:"authenticated_at,omitempty"`
|
||||
RequestedAt *time.Time `json:"requested_at,omitempty"`
|
||||
Client HydraClient `json:"client"`
|
||||
}
|
||||
|
||||
func NewHydraAdminService() *HydraAdminService {
|
||||
return &HydraAdminService{
|
||||
AdminURL: getenv("HYDRA_ADMIN_URL", "http://hydra:4445"),
|
||||
PublicURL: getenv("HYDRA_PUBLIC_URL", "http://hydra:4444"),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) ListClients(ctx context.Context, limit, offset int) ([]HydraClient, error) {
|
||||
endpoint, err := s.buildURL("/clients", map[string]int{
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, ErrHydraNotFound
|
||||
}
|
||||
if resp.StatusCode >= 300 {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
return nil, fmt.Errorf("hydra admin: list clients failed status=%d body=%s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var clients []HydraClient
|
||||
if err := json.NewDecoder(resp.Body).Decode(&clients); err != nil {
|
||||
return nil, fmt.Errorf("hydra admin: decode clients failed: %w", err)
|
||||
}
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) GetClient(ctx context.Context, clientID string) (*HydraClient, error) {
|
||||
endpoint := fmt.Sprintf("%s/clients/%s", strings.TrimRight(s.AdminURL, "/"), url.PathEscape(clientID))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, ErrHydraNotFound
|
||||
}
|
||||
if resp.StatusCode >= 300 {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
return nil, fmt.Errorf("hydra admin: get client failed status=%d body=%s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var client HydraClient
|
||||
if err := json.NewDecoder(resp.Body).Decode(&client); err != nil {
|
||||
return nil, fmt.Errorf("hydra admin: decode client failed: %w", err)
|
||||
}
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) PatchClientStatus(ctx context.Context, clientID, status string) (*HydraClient, error) {
|
||||
payload := map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"status": status,
|
||||
},
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
endpoint := fmt.Sprintf("%s/clients/%s", strings.TrimRight(s.AdminURL, "/"), url.PathEscape(clientID))
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, endpoint, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/merge-patch+json")
|
||||
|
||||
resp, err := s.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, ErrHydraNotFound
|
||||
}
|
||||
if resp.StatusCode >= 300 {
|
||||
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
return nil, fmt.Errorf("hydra admin: patch client failed status=%d body=%s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var updated HydraClient
|
||||
if err := json.NewDecoder(resp.Body).Decode(&updated); err != nil {
|
||||
return nil, fmt.Errorf("hydra admin: decode patched client failed: %w", err)
|
||||
}
|
||||
return &updated, nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) ListConsentSessions(ctx context.Context, subject, clientID string) ([]HydraConsentSession, error) {
|
||||
params := map[string]string{
|
||||
"subject": subject,
|
||||
}
|
||||
if clientID != "" {
|
||||
params["client"] = clientID
|
||||
}
|
||||
endpoint, err := s.buildURLWithParams("/oauth2/auth/sessions/consent", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
return nil, fmt.Errorf("hydra admin: list consent sessions failed status=%d body=%s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var sessions []HydraConsentSession
|
||||
if err := json.NewDecoder(resp.Body).Decode(&sessions); err != nil {
|
||||
return nil, fmt.Errorf("hydra admin: decode consent sessions failed: %w", err)
|
||||
}
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) RevokeConsentSessions(ctx context.Context, subject, clientID string) error {
|
||||
params := map[string]string{
|
||||
"subject": subject,
|
||||
}
|
||||
if clientID != "" {
|
||||
params["client"] = clientID
|
||||
}
|
||||
endpoint, err := s.buildURLWithParams("/oauth2/auth/sessions/consent", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := s.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
return fmt.Errorf("hydra admin: revoke consent failed status=%d body=%s", resp.StatusCode, string(body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) httpClient() *http.Client {
|
||||
if s.HTTPClient != nil {
|
||||
return s.HTTPClient
|
||||
}
|
||||
return &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) buildURL(path string, ints map[string]int) (string, error) {
|
||||
base := strings.TrimRight(s.AdminURL, "/")
|
||||
u, err := url.Parse(base + path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
q := u.Query()
|
||||
for key, value := range ints {
|
||||
if value > 0 {
|
||||
q.Set(key, strconv.Itoa(value))
|
||||
}
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func (s *HydraAdminService) buildURLWithParams(path string, params map[string]string) (string, error) {
|
||||
base := strings.TrimRight(s.AdminURL, "/")
|
||||
u, err := url.Parse(base + path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
q := u.Query()
|
||||
for key, value := range params {
|
||||
if value != "" {
|
||||
q.Set(key, value)
|
||||
}
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user