forked from baron/baron-sso
refactor: backend tenant_group 제거 및 리팩터 반영
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user