From c8f39c15e0cca3f703f0c176659a0d7b298a8fe1 Mon Sep 17 00:00:00 2001 From: kyy Date: Thu, 26 Feb 2026 12:40:43 +0900 Subject: [PATCH] =?UTF-8?q?conent=20=EC=9D=B4=EB=A0=A5=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20soft=20delete=20=EB=B0=8F=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/handler/auth_handler.go | 10 ++++- backend/internal/handler/dev_handler.go | 40 ++++++++++++++----- .../repository/client_consent_repository.go | 13 +++--- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index d45024fb..4d195458 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -3423,6 +3423,12 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error { continue } + // 삭제된 권한일 경우 + status := "inactive" + if dc.DeletedAt.Valid { + status = "revoked" + } + // Hydra에서 클라이언트 정보 조회 (메타데이터용) client, err := h.Hydra.GetClient(c.Context(), dc.ClientID) if err != nil { @@ -3432,7 +3438,7 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error { linkedRpSummary: linkedRpSummary{ ID: dc.ClientID, Name: dc.ClientID, - Status: "inactive", + Status: status, Scopes: dc.GrantedScopes, }, lastAuth: dc.UpdatedAt, @@ -3458,7 +3464,7 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error { Name: name, Logo: extractHydraClientLogo(client.Metadata), URL: clientURL, - Status: "inactive", + Status: status, Scopes: dc.GrantedScopes, }, lastAuth: dc.UpdatedAt, diff --git a/backend/internal/handler/dev_handler.go b/backend/internal/handler/dev_handler.go index b216001d..bbc31675 100644 --- a/backend/internal/handler/dev_handler.go +++ b/backend/internal/handler/dev_handler.go @@ -93,15 +93,17 @@ 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"` - CreatedAt time.Time `json:"createdAt"` - TenantID string `json:"tenantId,omitempty"` - TenantName string `json:"tenantName,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"` + DeletedAt *time.Time `json:"deletedAt,omitempty"` + Status string `json:"status"` + TenantID string `json:"tenantId,omitempty"` + TenantName string `json:"tenantName,omitempty"` } type consentListResponse struct { @@ -648,6 +650,7 @@ func (h *DevHandler) ListConsents(c *fiber.Ctx) error { // [Isolation] Get admin tenant ID from header or locals adminTenantID := c.Get("X-Tenant-ID") // Assume middleware sets this or trusted in dev + statusFilter := strings.ToLower(strings.TrimSpace(c.Query("status"))) var consents []domain.ClientConsentWithTenantInfo var total int64 @@ -686,6 +689,23 @@ func (h *DevHandler) ListConsents(c *fiber.Ctx) error { continue } + var deletedAt *time.Time + status := "active" + if consent.DeletedAt.Valid { + deletedAt = &consent.DeletedAt.Time + status = "revoked" + } + + // Filter by status if requested + if statusFilter != "" && statusFilter != "all" { + if statusFilter == "active" && status != "active" { + continue + } + if statusFilter == "revoked" && status != "revoked" { + continue + } + } + userName := "" identity, err := h.KratosAdmin.GetIdentity(c.Context(), consent.Subject) if err == nil && identity != nil { @@ -703,6 +723,8 @@ func (h *DevHandler) ListConsents(c *fiber.Ctx) error { GrantedScopes: consent.GrantedScopes, AuthenticatedAt: consent.UpdatedAt.Format(time.RFC3339), CreatedAt: consent.CreatedAt, + DeletedAt: deletedAt, + Status: status, TenantID: consent.TenantID, TenantName: consent.TenantName, }) diff --git a/backend/internal/repository/client_consent_repository.go b/backend/internal/repository/client_consent_repository.go index ee85daba..9ca6a8c5 100644 --- a/backend/internal/repository/client_consent_repository.go +++ b/backend/internal/repository/client_consent_repository.go @@ -24,11 +24,12 @@ func NewClientConsentRepository(db *gorm.DB) ClientConsentRepository { } func (r *clientConsentRepo) Upsert(ctx context.Context, consent *domain.ClientConsent) error { - return r.db.WithContext(ctx). + return r.db.WithContext(ctx).Unscoped(). Where("client_id = ? AND subject = ?", consent.ClientID, consent.Subject). Assign(map[string]interface{}{ "granted_scopes": consent.GrantedScopes, "updated_at": gorm.Expr("NOW()"), + "deleted_at": nil, }). FirstOrCreate(consent).Error } @@ -44,13 +45,13 @@ func (r *clientConsentRepo) List(ctx context.Context, clientID string, limit, of var total int64 // Base query for counting - countQuery := r.db.WithContext(ctx).Model(&domain.ClientConsent{}).Where("client_id = ?", clientID) + countQuery := r.db.WithContext(ctx).Unscoped().Model(&domain.ClientConsent{}).Where("client_id = ?", clientID) if err := countQuery.Count(&total).Error; err != nil { return nil, 0, err } // Query for fetching data - query := r.db.WithContext(ctx). + query := r.db.WithContext(ctx).Unscoped(). Model(&domain.ClientConsent{}). Select("client_consents.*, users.tenant_id, tenants.name as tenant_name"). Joins("LEFT JOIN users ON users.id::text = client_consents.subject"). @@ -66,7 +67,7 @@ func (r *clientConsentRepo) ListByTenant(ctx context.Context, clientID, tenantID var total int64 // Base query for counting - countQuery := r.db.WithContext(ctx). + countQuery := r.db.WithContext(ctx).Unscoped(). Model(&domain.ClientConsent{}). Joins("JOIN users ON users.id::text = client_consents.subject"). Where("client_consents.client_id = ? AND users.tenant_id = ?", clientID, tenantID) @@ -76,7 +77,7 @@ func (r *clientConsentRepo) ListByTenant(ctx context.Context, clientID, tenantID } // Query for fetching data - query := r.db.WithContext(ctx). + query := r.db.WithContext(ctx).Unscoped(). Model(&domain.ClientConsent{}). Select("client_consents.*, users.tenant_id, tenants.name as tenant_name"). Joins("JOIN users ON users.id::text = client_consents.subject"). @@ -94,7 +95,7 @@ func (r *clientConsentRepo) ListByTenant(ctx context.Context, clientID, tenantID func (r *clientConsentRepo) ListBySubject(ctx context.Context, subject string) ([]domain.ClientConsent, error) { var consents []domain.ClientConsent - err := r.db.WithContext(ctx). + err := r.db.WithContext(ctx).Unscoped(). Where("subject = ?", subject). Order("updated_at DESC"). Find(&consents).Error