forked from baron/baron-sso
클라이언트 비밀키 재발급(Rotate) API 구현
This commit is contained in:
@@ -613,6 +613,7 @@ func main() {
|
||||
dev.Post("/clients", devHandler.CreateClient)
|
||||
dev.Get("/clients/:id", devHandler.GetClient)
|
||||
dev.Put("/clients/:id", devHandler.UpdateClient)
|
||||
dev.Post("/clients/:id/secret/rotate", devHandler.RotateClientSecret)
|
||||
dev.Patch("/clients/:id/status", devHandler.UpdateClientStatus)
|
||||
dev.Delete("/clients/:id", devHandler.DeleteClient)
|
||||
dev.Get("/consents", devHandler.ListConsents)
|
||||
|
||||
@@ -5,7 +5,10 @@ import (
|
||||
"baron-sso-backend/internal/repository"
|
||||
"baron-sso-backend/internal/service"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -508,6 +511,71 @@ func (h *DevHandler) RevokeConsents(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *DevHandler) RotateClientSecret(c *fiber.Ctx) error {
|
||||
clientID := strings.TrimSpace(c.Params("id"))
|
||||
if clientID == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client id is required"})
|
||||
}
|
||||
|
||||
// 1. Generate new secret
|
||||
newSecret, err := generateRandomSecret(20)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to generate secret"})
|
||||
}
|
||||
|
||||
// 2. Get current client to preserve other fields
|
||||
current, 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()})
|
||||
}
|
||||
|
||||
// 3. Update Hydra
|
||||
current.ClientSecret = newSecret
|
||||
updated, err := h.Hydra.UpdateClient(c.Context(), clientID, *current)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
// 4. Update Persistence (DB & Redis)
|
||||
if h.SecretRepo != nil {
|
||||
if err := h.SecretRepo.Upsert(c.Context(), clientID, newSecret); err != nil {
|
||||
// Log error but don't fail the request as Hydra is already updated
|
||||
fmt.Printf("failed to update secret in repo: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
if h.Redis != nil {
|
||||
_ = h.Redis.Set("client_secret:"+clientID, newSecret, 0)
|
||||
}
|
||||
|
||||
// Return the new secret
|
||||
summary := h.mapClientSummary(*updated)
|
||||
summary.ClientSecret = newSecret
|
||||
|
||||
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 generateRandomSecret(length int) (string, error) {
|
||||
bytes := make([]byte, length)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Use Base64 URL encoding (no padding) to look like Hydra's native secrets
|
||||
return base64.RawURLEncoding.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
func (h *DevHandler) mapClientSummary(client domain.HydraClient) clientSummary {
|
||||
status := "active"
|
||||
if client.Metadata != nil {
|
||||
|
||||
Reference in New Issue
Block a user