1
0
forked from baron/baron-sso

merge feat/304-userfront-wasm-e2e into dev

This commit is contained in:
Lectom C Han
2026-02-24 15:40:51 +09:00
55 changed files with 3425 additions and 431 deletions

View File

@@ -275,15 +275,13 @@ func (h *DevHandler) ListClients(c *fiber.Ctx) error {
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"})
return errorJSON(c, fiber.StatusNotFound, "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 errorJSON(c, fiber.StatusServiceUnavailable, "Hydra service is unavailable. Please check if Ory Hydra is running.")
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": errMsg})
return errorJSON(c, fiber.StatusInternalServerError, errMsg)
}
items := make([]clientSummary, 0, len(clients))
@@ -306,15 +304,15 @@ func (h *DevHandler) ListClients(c *fiber.Ctx) error {
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"})
return errorJSON(c, fiber.StatusBadRequest, "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 errorJSON(c, fiber.StatusNotFound, "client not found")
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
summary := h.mapClientSummary(*client)
@@ -323,10 +321,10 @@ func (h *DevHandler) GetClient(c *fiber.Ctx) error {
if summary.Type == "private" {
isAppManager, err := h.checkAppManagerPermission(c)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "permission check error"})
return errorJSON(c, fiber.StatusInternalServerError, "permission check error")
}
if !isAppManager {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden: insufficient permissions for private client"})
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permissions for private client")
}
}
@@ -345,19 +343,19 @@ func (h *DevHandler) GetClient(c *fiber.Ctx) error {
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"})
return errorJSON(c, fiber.StatusBadRequest, "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"})
return errorJSON(c, fiber.StatusBadRequest, "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"})
return errorJSON(c, fiber.StatusBadRequest, "status must be active or inactive")
}
// [Security] Check permission before patching
@@ -367,7 +365,7 @@ func (h *DevHandler) UpdateClientStatus(c *fiber.Ctx) error {
if summary.Type == "private" {
isAppManager, _ := h.checkAppManagerPermission(c)
if !isAppManager {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden: insufficient permissions for private client"})
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permissions for private client")
}
}
}
@@ -375,9 +373,9 @@ func (h *DevHandler) UpdateClientStatus(c *fiber.Ctx) error {
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 errorJSON(c, fiber.StatusNotFound, "client not found")
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
summary := h.mapClientSummary(*updated)
@@ -396,7 +394,7 @@ func (h *DevHandler) UpdateClientStatus(c *fiber.Ctx) error {
func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
var req clientUpsertRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
return errorJSON(c, fiber.StatusBadRequest, "invalid request body")
}
clientID := strings.TrimSpace(valueOr(req.ID, ""))
@@ -411,7 +409,7 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
redirectURIs := derefSlice(req.RedirectURIs, nil)
if len(redirectURIs) == 0 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "redirectUris is required"})
return errorJSON(c, fiber.StatusBadRequest, "redirectUris is required")
}
scopes := derefSlice(req.Scopes, defaultClientScopes())
@@ -420,23 +418,23 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
clientType := strings.ToLower(strings.TrimSpace(valueOr(req.Type, "private")))
if clientType != "pkce" && clientType != "private" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "type must be pkce or private"})
return errorJSON(c, fiber.StatusBadRequest, "type must be pkce or private")
}
// [Security] Check permission for private clients
if clientType == "private" {
isAppManager, err := h.checkAppManagerPermission(c)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "permission check error"})
return errorJSON(c, fiber.StatusInternalServerError, "permission check error")
}
if !isAppManager {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden: insufficient permissions to create private client"})
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permissions to create private client")
}
}
status := strings.ToLower(strings.TrimSpace(valueOr(req.Status, "active")))
if status != "active" && status != "inactive" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "status must be active or inactive"})
return errorJSON(c, fiber.StatusBadRequest, "status must be active or inactive")
}
metadata := mergeMetadata(nil, req.Metadata)
@@ -468,7 +466,7 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
created, err := h.Hydra.CreateClient(c.Context(), clientReq)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
// Store secret in metadata for later retrieval
@@ -500,27 +498,27 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
func (h *DevHandler) UpdateClient(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"})
return errorJSON(c, fiber.StatusBadRequest, "client id is required")
}
var req clientUpsertRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
return errorJSON(c, fiber.StatusBadRequest, "invalid request body")
}
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 errorJSON(c, fiber.StatusNotFound, "client not found")
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
clientType := ""
if req.Type != nil {
clientType = strings.ToLower(strings.TrimSpace(*req.Type))
if clientType != "pkce" && clientType != "private" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "type must be pkce or private"})
return errorJSON(c, fiber.StatusBadRequest, "type must be pkce or private")
}
}
@@ -529,10 +527,10 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
if currentSummary.Type == "private" || clientType == "private" {
isAppManager, err := h.checkAppManagerPermission(c)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "permission check error"})
return errorJSON(c, fiber.StatusInternalServerError, "permission check error")
}
if !isAppManager {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden: insufficient permissions for private client"})
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permissions for private client")
}
}
@@ -540,7 +538,7 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
if req.Status != nil {
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"})
return errorJSON(c, fiber.StatusBadRequest, "status must be active or inactive")
}
}
@@ -554,7 +552,7 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
}
if req.RedirectURIs != nil && len(*req.RedirectURIs) == 0 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "redirectUris cannot be empty"})
return errorJSON(c, fiber.StatusBadRequest, "redirectUris cannot be empty")
}
metadata := mergeMetadata(current.Metadata, req.Metadata)
@@ -579,9 +577,9 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
updatedClient, err := h.Hydra.UpdateClient(c.Context(), clientID, updated)
if err != nil {
if errors.Is(err, service.ErrHydraNotFound) {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"})
return errorJSON(c, fiber.StatusNotFound, "client not found")
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
summary := h.mapClientSummary(*updatedClient)
@@ -600,7 +598,7 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
func (h *DevHandler) DeleteClient(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"})
return errorJSON(c, fiber.StatusBadRequest, "client id is required")
}
// [Security] Check permission for private clients
@@ -610,16 +608,16 @@ func (h *DevHandler) DeleteClient(c *fiber.Ctx) error {
if summary.Type == "private" {
isAppManager, _ := h.checkAppManagerPermission(c)
if !isAppManager {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden: insufficient permissions for private client"})
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permissions for private client")
}
}
}
if err := h.Hydra.DeleteClient(c.Context(), clientID); err != nil {
if errors.Is(err, service.ErrHydraNotFound) {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"})
return errorJSON(c, fiber.StatusNotFound, "client not found")
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
// 1. Clean up PostgreSQL
@@ -638,7 +636,7 @@ func (h *DevHandler) DeleteClient(c *fiber.Ctx) error {
func (h *DevHandler) ListConsents(c *fiber.Ctx) error {
clientID := strings.TrimSpace(c.Query("client_id"))
if clientID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client_id is required"})
return errorJSON(c, fiber.StatusBadRequest, "client_id is required")
}
subject := strings.TrimSpace(c.Query("subject"))
@@ -678,7 +676,7 @@ func (h *DevHandler) ListConsents(c *fiber.Ctx) error {
}
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
items := make([]consentSummary, 0, len(consents))
@@ -719,7 +717,7 @@ func (h *DevHandler) ListConsents(c *fiber.Ctx) error {
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"})
return errorJSON(c, fiber.StatusBadRequest, "subject is required")
}
clientID := strings.TrimSpace(c.Query("client_id"))
@@ -733,7 +731,7 @@ func (h *DevHandler) RevokeConsents(c *fiber.Ctx) error {
// 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()})
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
// 2. Sync to Local DB (Delete)
@@ -747,7 +745,7 @@ func (h *DevHandler) RevokeConsents(c *fiber.Ctx) error {
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"})
return errorJSON(c, fiber.StatusBadRequest, "client id is required")
}
// [Security] Check permission for private clients
@@ -757,7 +755,7 @@ func (h *DevHandler) RotateClientSecret(c *fiber.Ctx) error {
if summary.Type == "private" {
isAppManager, _ := h.checkAppManagerPermission(c)
if !isAppManager {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden: insufficient permissions for private client"})
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permissions for private client")
}
}
}
@@ -765,22 +763,22 @@ func (h *DevHandler) RotateClientSecret(c *fiber.Ctx) error {
// 1. Generate new secret
newSecret, err := generateRandomSecret(20)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to generate secret"})
return errorJSON(c, fiber.StatusInternalServerError, "failed to generate secret")
}
// 2. Get current client to preserve other fields (already fetched above)
if err != nil {
if errors.Is(err, service.ErrHydraNotFound) {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"})
return errorJSON(c, fiber.StatusNotFound, "client not found")
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
return errorJSON(c, fiber.StatusInternalServerError, 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()})
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
// 4. Update Persistence (DB & Redis)