From 63c9172ebcf703836318a891a344f18d2c7dfd34 Mon Sep 17 00:00:00 2001 From: kyy Date: Wed, 4 Feb 2026 13:45:28 +0900 Subject: [PATCH] =?UTF-8?q?Consent=20=EC=8A=B9=EC=9D=B8=20=EB=B0=8F=20?= =?UTF-8?q?=ED=95=B4=EC=A7=80=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EA=B0=90?= =?UTF-8?q?=EC=82=AC=20=EB=A1=9C=EA=B7=B8=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/handler/auth_handler.go | 135 +++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index 04f43879..d5b5734e 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -3342,6 +3342,24 @@ func (h *AuthHandler) RevokeLinkedRp(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusInternalServerError, "Failed to revoke link") } + if h.AuditRepo != nil { + detailsMap := map[string]interface{}{ + "client_id": clientID, + } + detailsBytes, _ := json.Marshal(detailsMap) + + _ = h.AuditRepo.Create(&domain.AuditLog{ + EventID: GenerateSecureToken(16), + Timestamp: time.Now(), + UserID: subject, + EventType: "consent.revoked", + Status: "success", + IPAddress: c.IP(), + UserAgent: string(c.Request().Header.UserAgent()), + Details: string(detailsBytes), + }) + } + return c.Status(fiber.StatusOK).JSON(fiber.Map{ "status": "success", "message": "Link revoked successfully", @@ -3467,6 +3485,26 @@ func (h *AuthHandler) AcceptConsentRequest(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusInternalServerError, "Failed to accept consent request") } + if h.AuditRepo != nil { + detailsMap := map[string]interface{}{ + "client_id": consentRequest.Client.ClientID, + "scopes": consentRequest.RequestedScope, + "client_name": consentRequest.Client.ClientName, + } + detailsBytes, _ := json.Marshal(detailsMap) + + _ = h.AuditRepo.Create(&domain.AuditLog{ + EventID: GenerateSecureToken(16), + Timestamp: time.Now(), + UserID: consentRequest.Subject, + EventType: "consent.granted", + Status: "success", + IPAddress: c.IP(), + UserAgent: string(c.Request().Header.UserAgent()), + Details: string(detailsBytes), + }) + } + return c.JSON(acceptResp) } @@ -4998,3 +5036,100 @@ func mergeScopes(current []string, next []string) []string { } return current } + +type rpHistoryItem struct { + ClientID string `json:"client_id"` + ClientName string `json:"client_name"` + Scopes []string `json:"scopes"` + LastApprovedAt *time.Time `json:"last_approved_at"` + LastRevokedAt *time.Time `json:"last_revoked_at"` + Status string `json:"status"` +} + +func (h *AuthHandler) ListRpHistory(c *fiber.Ctx) error { + subject, err := h.resolveConsentSubject(c) + if err != nil || subject == "" { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid session"}) + } + + if h.AuditRepo == nil { + return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "Audit service unavailable"}) + } + + logs, err := h.AuditRepo.FindByUserAndEvents(c.Context(), subject, []string{"consent.granted", "consent.revoked"}, 100) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to fetch history"}) + } + + historyMap := make(map[string]*rpHistoryItem) + + // Logs are DESC (newest first). Iterate in reverse (oldest first) to build state. + for i := len(logs) - 1; i >= 0; i-- { + log := logs[i] + details, _ := parseAuditDetails(log.Details) + clientID, _ := details["client_id"].(string) + if clientID == "" { + continue + } + + item, ok := historyMap[clientID] + if !ok { + item = &rpHistoryItem{ + ClientID: clientID, + Status: "unknown", + } + historyMap[clientID] = item + } + + if name, ok := details["client_name"].(string); ok && name != "" { + item.ClientName = name + } + + if log.EventType == "consent.granted" { + item.Status = "active" + ts := log.Timestamp + item.LastApprovedAt = &ts + + if scopesRaw, ok := details["scopes"].([]interface{}); ok { + scopes := make([]string, 0, len(scopesRaw)) + for _, s := range scopesRaw { + if str, ok := s.(string); ok { + scopes = append(scopes, str) + } + } + item.Scopes = scopes + } + } else if log.EventType == "consent.revoked" { + item.Status = "revoked" + ts := log.Timestamp + item.LastRevokedAt = &ts + } + } + + items := make([]rpHistoryItem, 0, len(historyMap)) + for _, item := range historyMap { + items = append(items, *item) + } + + sort.Slice(items, func(i, j int) bool { + t1 := time.Time{} + if items[i].LastApprovedAt != nil { + t1 = *items[i].LastApprovedAt + } + if items[i].LastRevokedAt != nil && items[i].LastRevokedAt.After(t1) { + t1 = *items[i].LastRevokedAt + } + + t2 := time.Time{} + if items[j].LastApprovedAt != nil { + t2 = *items[j].LastApprovedAt + } + if items[j].LastRevokedAt != nil && items[j].LastRevokedAt.After(t2) { + t2 = *items[j].LastRevokedAt + } + + return t1.After(t2) + }) + + return c.JSON(fiber.Map{"items": items}) +}