1
0
forked from baron/baron-sso

refactor: backend tenant_group 제거 및 리팩터 반영

This commit is contained in:
Lectom C Han
2026-02-12 22:14:34 +09:00
parent b0792113ae
commit a8a219d7ef
26 changed files with 494 additions and 1001 deletions

View File

@@ -22,17 +22,15 @@ type DevHandler struct {
SecretRepo domain.ClientSecretRepository
KratosAdmin *service.KratosAdminService
ConsentRepo repository.ClientConsentRepository
RPService service.RelyingPartyService
}
func NewDevHandler(redis domain.RedisRepository, secretRepo domain.ClientSecretRepository, consentRepo repository.ClientConsentRepository, rpService service.RelyingPartyService) *DevHandler {
func NewDevHandler(redis domain.RedisRepository, secretRepo domain.ClientSecretRepository, consentRepo repository.ClientConsentRepository) *DevHandler {
return &DevHandler{
Hydra: service.NewHydraAdminService(),
Redis: redis,
SecretRepo: secretRepo,
KratosAdmin: service.NewKratosAdminService(),
ConsentRepo: consentRepo,
RPService: rpService,
}
}
@@ -97,58 +95,38 @@ type clientUpsertRequest struct {
}
func (h *DevHandler) ListClients(c *fiber.Ctx) error {
profile, ok := c.Locals("user_profile").(*domain.UserProfileResponse)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "unauthorized: user profile not found"})
limit := c.QueryInt("limit", 50)
offset := c.QueryInt("offset", 0)
if limit <= 0 {
limit = 50
}
if offset < 0 {
offset = 0
}
// Super Admin sees all (best effort via Hydra list for now, or we can use RPService if it's improved)
if profile.Role == domain.RoleSuperAdmin {
limit := c.QueryInt("limit", 50)
offset := c.QueryInt("offset", 0)
clients, err := h.Hydra.ListClients(c.Context(), limit, offset)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
items := make([]clientSummary, 0, len(clients))
for _, client := range clients {
items = append(items, h.mapClientSummary(client))
}
return c.JSON(clientListResponse{Items: items, Limit: limit, Offset: offset})
}
// For others, only show manageable tenants' clients
var tenantIDs []string
for _, t := range profile.ManageableTenants {
tenantIDs = append(tenantIDs, t.ID)
}
if len(tenantIDs) == 0 && profile.TenantID != nil {
tenantIDs = append(tenantIDs, *profile.TenantID)
}
if len(tenantIDs) == 0 {
return c.JSON(clientListResponse{Items: []clientSummary{}, Limit: 50, Offset: 0})
}
rps, err := h.RPService.ListByTenantIDs(c.Context(), tenantIDs)
clients, err := h.Hydra.ListClients(c.Context(), limit, offset)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
if errors.Is(err, service.ErrHydraNotFound) {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "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 c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": errMsg})
}
items := make([]clientSummary, 0, len(rps))
for _, rp := range rps {
// We need HydraClient details for the summary
client, err := h.Hydra.GetClient(c.Context(), rp.ClientID)
if err == nil {
items = append(items, h.mapClientSummary(*client))
}
items := make([]clientSummary, 0, len(clients))
for _, client := range clients {
items = append(items, h.mapClientSummary(client))
}
return c.JSON(clientListResponse{
Items: items,
Limit: len(items),
Offset: 0,
Limit: limit,
Offset: offset,
})
}
@@ -166,11 +144,6 @@ func (h *DevHandler) GetClient(c *fiber.Ctx) error {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
// Set for audit logging
if tid, ok := client.Metadata["tenant_id"].(string); ok {
c.Locals("tenant_id", tid)
}
summary := h.mapClientSummary(*client)
return c.JSON(clientDetailResponse{
Client: summary,
@@ -224,49 +197,11 @@ func (h *DevHandler) UpdateClientStatus(c *fiber.Ctx) error {
}
func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
profile, ok := c.Locals("user_profile").(*domain.UserProfileResponse)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "unauthorized"})
}
var req clientUpsertRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
}
// Determine Tenant ID
targetTenantID := c.Get("X-Tenant-ID")
if targetTenantID == "" && profile.TenantID != nil {
targetTenantID = *profile.TenantID
}
if targetTenantID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "X-Tenant-ID header is required"})
}
// Set for audit logging
c.Locals("tenant_id", targetTenantID)
// Validate Permission
isAllowed := false
if profile.Role == domain.RoleSuperAdmin {
isAllowed = true
} else {
for _, t := range profile.ManageableTenants {
if t.ID == targetTenantID {
isAllowed = true
break
}
}
if !isAllowed && profile.TenantID != nil && *profile.TenantID == targetTenantID {
isAllowed = true
}
}
if !isAllowed {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "you do not have permission to create clients for this tenant"})
}
clientID := strings.TrimSpace(valueOr(req.ID, ""))
if clientID == "" {
clientID = uuid.NewString()
@@ -322,18 +257,11 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
Metadata: metadata,
}
// Use RPService to ensure Keto relations are created
rp, err := h.RPService.Create(c.Context(), targetTenantID, clientReq)
created, err := h.Hydra.CreateClient(c.Context(), clientReq)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
// Fetch back the Hydra client to get the secret (RPService.Create returns domain.RelyingParty which has limited fields)
created, err := h.Hydra.GetClient(c.Context(), rp.ClientID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "client created but failed to retrieve details"})
}
// Store secret in metadata for later retrieval
if created.ClientSecret != "" {
// 1. Store in PostgreSQL (Source of Truth)
@@ -379,11 +307,6 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
// Set for audit logging
if tid, ok := current.Metadata["tenant_id"].(string); ok {
c.Locals("tenant_id", tid)
}
clientType := ""
if req.Type != nil {
clientType = strings.ToLower(strings.TrimSpace(*req.Type))
@@ -459,14 +382,6 @@ func (h *DevHandler) DeleteClient(c *fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client id is required"})
}
// Fetch first for audit log tenant_id
client, err := h.Hydra.GetClient(c.Context(), clientID)
if err == nil {
if tid, ok := client.Metadata["tenant_id"].(string); ok {
c.Locals("tenant_id", tid)
}
}
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"})
@@ -488,24 +403,11 @@ func (h *DevHandler) DeleteClient(c *fiber.Ctx) error {
}
func (h *DevHandler) ListConsents(c *fiber.Ctx) error {
profile, ok := c.Locals("user_profile").(*domain.UserProfileResponse)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "unauthorized"})
}
clientID := strings.TrimSpace(c.Query("client_id"))
if clientID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client_id is required"})
}
// Permission Check
if profile.Role != domain.RoleSuperAdmin {
allowed, err := h.RPService.CheckPermission(c.Context(), profile.ID, clientID, "view")
if err != nil || !allowed {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden: you do not have permission to view consents for this client"})
}
}
subject := strings.TrimSpace(c.Query("subject"))
limit := c.QueryInt("limit", 50)
offset := c.QueryInt("offset", 0)
@@ -582,28 +484,12 @@ func (h *DevHandler) ListConsents(c *fiber.Ctx) error {
}
func (h *DevHandler) RevokeConsents(c *fiber.Ctx) error {
profile, ok := c.Locals("user_profile").(*domain.UserProfileResponse)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "unauthorized"})
}
subject := strings.TrimSpace(c.Query("subject"))
if subject == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "subject is required"})
}
clientID := strings.TrimSpace(c.Query("client_id"))
// Permission Check (if clientID is provided)
if clientID != "" && profile.Role != domain.RoleSuperAdmin {
allowed, err := h.RPService.CheckPermission(c.Context(), profile.ID, clientID, "manage")
if err != nil || !allowed {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden: you do not have permission to revoke consents for this client"})
}
} else if clientID == "" && profile.Role != domain.RoleSuperAdmin {
// If clientID is not provided, we might need a more global check or just disallow it for non-superadmins
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client_id is required for non-superadmins"})
}
// 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)
@@ -646,11 +532,6 @@ func (h *DevHandler) RotateClientSecret(c *fiber.Ctx) error {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
// Set for audit logging
if tid, ok := current.Metadata["tenant_id"].(string); ok {
c.Locals("tenant_id", tid)
}
// 3. Update Hydra
current.ClientSecret = newSecret
updated, err := h.Hydra.UpdateClient(c.Context(), clientID, *current)