forked from baron/baron-sso
DevHandler 동의 내역 조회 API를 로컬 DB 기반으로 변경
This commit is contained in:
@@ -2,6 +2,7 @@ package handler
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"baron-sso-backend/internal/repository"
|
||||
"baron-sso-backend/internal/service"
|
||||
"context"
|
||||
"errors"
|
||||
@@ -17,14 +18,16 @@ type DevHandler struct {
|
||||
Redis *service.RedisService
|
||||
SecretRepo domain.ClientSecretRepository
|
||||
KratosAdmin *service.KratosAdminService
|
||||
ConsentRepo repository.ClientConsentRepository
|
||||
}
|
||||
|
||||
func NewDevHandler(redis *service.RedisService, secretRepo domain.ClientSecretRepository) *DevHandler {
|
||||
func NewDevHandler(redis *service.RedisService, secretRepo domain.ClientSecretRepository, consentRepo repository.ClientConsentRepository) *DevHandler {
|
||||
return &DevHandler{
|
||||
Hydra: service.NewHydraAdminService(),
|
||||
Redis: redis,
|
||||
SecretRepo: secretRepo,
|
||||
KratosAdmin: service.NewKratosAdminService(),
|
||||
ConsentRepo: consentRepo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +63,15 @@ type clientEndpoints struct {
|
||||
}
|
||||
|
||||
type consentSummary struct {
|
||||
Subject string `json:"subject"`
|
||||
UserName string `json:"userName,omitempty"`
|
||||
ClientID string `json:"clientId"`
|
||||
ClientName string `json:"clientName,omitempty"`
|
||||
GrantedScopes []string `json:"grantedScopes"`
|
||||
AuthenticatedAt string `json:"authenticatedAt,omitempty"`
|
||||
Subject string `json:"subject"`
|
||||
UserName string `json:"userName,omitempty"`
|
||||
ClientID string `json:"clientId"`
|
||||
ClientName string `json:"clientName,omitempty"`
|
||||
GrantedScopes []string `json:"grantedScopes"`
|
||||
AuthenticatedAt string `json:"authenticatedAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
TenantName string `json:"tenantName,omitempty"`
|
||||
}
|
||||
|
||||
type consentListResponse struct {
|
||||
@@ -394,70 +400,84 @@ func (h *DevHandler) DeleteClient(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
// If subject is not a UUID, try to resolve it as an identifier (email/username)
|
||||
if _, err := uuid.Parse(subject); err != nil {
|
||||
resolved, err := h.KratosAdmin.FindIdentityIDByIdentifier(c.Context(), subject)
|
||||
if err == nil && resolved != "" {
|
||||
subject = resolved
|
||||
}
|
||||
}
|
||||
|
||||
clientID := strings.TrimSpace(c.Query("client_id"))
|
||||
if clientID == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client_id is required"})
|
||||
}
|
||||
|
||||
subject := strings.TrimSpace(c.Query("subject"))
|
||||
limit := c.QueryInt("limit", 50)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
// [Isolation] Get admin tenant ID from header or locals
|
||||
adminTenantID := c.Get("X-Tenant-ID") // Assume middleware sets this or trusted in dev
|
||||
|
||||
var consents []domain.ClientConsentWithTenantInfo
|
||||
var total int64
|
||||
var err error
|
||||
|
||||
if subject != "" {
|
||||
// Resolve subject if it's email/name (Legacy support)
|
||||
if _, err := uuid.Parse(subject); err != nil {
|
||||
resolved, _ := h.KratosAdmin.FindIdentityIDByIdentifier(c.Context(), subject)
|
||||
if resolved != "" {
|
||||
subject = resolved
|
||||
}
|
||||
}
|
||||
|
||||
// Single user fetch from Hydra (to get latest status) or Local DB
|
||||
// Issue says: "List All", so we prefer Local DB for consistency in listing
|
||||
// But for a single user, we could still use Hydra.
|
||||
// Let's use Local DB to support tenant filtering even for search.
|
||||
// For simplicity, we just filter the list later if search is used.
|
||||
}
|
||||
|
||||
if adminTenantID != "" {
|
||||
consents, total, err = h.ConsentRepo.ListByTenant(c.Context(), clientID, adminTenantID, limit, offset)
|
||||
} else {
|
||||
consents, total, err = h.ConsentRepo.List(c.Context(), clientID, limit, offset)
|
||||
}
|
||||
|
||||
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 {
|
||||
client := session.Client
|
||||
if client.ClientID == "" && session.ConsentRequest != nil {
|
||||
client = session.ConsentRequest.Client
|
||||
}
|
||||
subject := session.Subject
|
||||
if subject == "" && session.ConsentRequest != nil {
|
||||
subject = session.ConsentRequest.Subject
|
||||
items := make([]consentSummary, 0, len(consents))
|
||||
for _, consent := range consents {
|
||||
// Filter by subject if search is active
|
||||
if subject != "" && consent.Subject != subject {
|
||||
continue
|
||||
}
|
||||
|
||||
userName := ""
|
||||
if subject != "" {
|
||||
identity, err := h.KratosAdmin.GetIdentity(c.Context(), subject)
|
||||
if err == nil && identity != nil {
|
||||
if name, ok := identity.Traits["name"].(string); ok {
|
||||
userName = name
|
||||
} else if displayName, ok := identity.Traits["displayname"].(string); ok {
|
||||
userName = displayName
|
||||
} else if email, ok := identity.Traits["email"].(string); ok {
|
||||
userName = email
|
||||
}
|
||||
identity, err := h.KratosAdmin.GetIdentity(c.Context(), consent.Subject)
|
||||
if err == nil && identity != nil {
|
||||
if name, ok := identity.Traits["name"].(string); ok {
|
||||
userName = name
|
||||
} else if email, ok := identity.Traits["email"].(string); ok {
|
||||
userName = email
|
||||
}
|
||||
}
|
||||
|
||||
authAt := ""
|
||||
if session.AuthenticatedAt != nil {
|
||||
authAt = session.AuthenticatedAt.Format(time.RFC3339)
|
||||
} else if session.RequestedAt != nil {
|
||||
authAt = session.RequestedAt.Format(time.RFC3339)
|
||||
} else if session.HandledAt != nil {
|
||||
authAt = session.HandledAt.Format(time.RFC3339)
|
||||
}
|
||||
items = append(items, consentSummary{
|
||||
Subject: subject,
|
||||
Subject: consent.Subject,
|
||||
UserName: userName,
|
||||
ClientID: client.ClientID,
|
||||
ClientName: client.ClientName,
|
||||
GrantedScopes: session.GrantedScope,
|
||||
AuthenticatedAt: authAt,
|
||||
ClientID: consent.ClientID,
|
||||
GrantedScopes: consent.GrantedScopes,
|
||||
AuthenticatedAt: consent.UpdatedAt.Format(time.RFC3339),
|
||||
CreatedAt: consent.CreatedAt,
|
||||
TenantID: consent.TenantID,
|
||||
TenantName: consent.TenantName,
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(consentListResponse{Items: items})
|
||||
return c.JSON(fiber.Map{
|
||||
"items": items,
|
||||
"total": total,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *DevHandler) RevokeConsents(c *fiber.Ctx) error {
|
||||
@@ -465,6 +485,7 @@ func (h *DevHandler) RevokeConsents(c *fiber.Ctx) error {
|
||||
if subject == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "subject is required"})
|
||||
}
|
||||
clientID := strings.TrimSpace(c.Query("client_id"))
|
||||
|
||||
// If subject is not a UUID, try to resolve it as an identifier (email/username)
|
||||
if _, err := uuid.Parse(subject); err != nil {
|
||||
@@ -474,12 +495,16 @@ func (h *DevHandler) RevokeConsents(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
clientID := strings.TrimSpace(c.Query("client_id"))
|
||||
|
||||
// 1. Revoke in Hydra
|
||||
if err := h.Hydra.RevokeConsentSessions(c.Context(), subject, clientID); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
// 2. Sync to Local DB (Delete)
|
||||
if h.ConsentRepo != nil {
|
||||
_ = h.ConsentRepo.Delete(c.Context(), subject, clientID)
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user