forked from baron/baron-sso
chore: consolidate local integration changes
This commit is contained in:
@@ -23,6 +23,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// OryProviderAPI defines the subset of Ory Provider used by UserHandler
|
||||
@@ -99,6 +100,76 @@ func sanitizeUserMetadata(metadata map[string]any) map[string]any {
|
||||
return sanitized
|
||||
}
|
||||
|
||||
func userAppointmentSliceFromRaw(raw any) []any {
|
||||
switch values := raw.(type) {
|
||||
case []any:
|
||||
return append([]any(nil), values...)
|
||||
case []map[string]any:
|
||||
appointments := make([]any, 0, len(values))
|
||||
for _, value := range values {
|
||||
appointments = append(appointments, value)
|
||||
}
|
||||
return appointments
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func userAppointmentTenantKey(raw any) string {
|
||||
appointment, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if value := normalizeMetadataString(appointment["tenantId"]); value != "" {
|
||||
return "id:" + strings.ToLower(value)
|
||||
}
|
||||
if value := normalizeMetadataString(appointment["tenantSlug"]); value != "" {
|
||||
return "slug:" + strings.ToLower(value)
|
||||
}
|
||||
if value := normalizeMetadataString(appointment["slug"]); value != "" {
|
||||
return "slug:" + strings.ToLower(value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func mergeUserAddTenantAppointment(traits map[string]any, metadata map[string]any, tenant *domain.Tenant) map[string]any {
|
||||
if tenant == nil {
|
||||
return metadata
|
||||
}
|
||||
if metadata == nil {
|
||||
metadata = map[string]any{}
|
||||
}
|
||||
|
||||
appointments := userAppointmentSliceFromRaw(traits["additionalAppointments"])
|
||||
if len(appointments) == 0 {
|
||||
if legacyMetadata, ok := traits["metadata"].(map[string]any); ok {
|
||||
appointments = userAppointmentSliceFromRaw(legacyMetadata["additionalAppointments"])
|
||||
}
|
||||
}
|
||||
if incoming := userAppointmentSliceFromRaw(metadata["additionalAppointments"]); len(incoming) > 0 {
|
||||
appointments = incoming
|
||||
}
|
||||
|
||||
seen := make(map[string]bool, len(appointments)+1)
|
||||
for _, appointment := range appointments {
|
||||
if key := userAppointmentTenantKey(appointment); key != "" {
|
||||
seen[key] = true
|
||||
}
|
||||
}
|
||||
tenantIDKey := "id:" + strings.ToLower(strings.TrimSpace(tenant.ID))
|
||||
tenantSlugKey := "slug:" + strings.ToLower(strings.TrimSpace(tenant.Slug))
|
||||
if !seen[tenantIDKey] && !seen[tenantSlugKey] {
|
||||
appointments = append(appointments, map[string]any{
|
||||
"tenantId": tenant.ID,
|
||||
"tenantSlug": tenant.Slug,
|
||||
"tenantName": tenant.Name,
|
||||
"isPrimary": false,
|
||||
})
|
||||
}
|
||||
metadata["additionalAppointments"] = appointments
|
||||
return metadata
|
||||
}
|
||||
|
||||
func sanitizeUserRepresentativeTenants(ctx context.Context, tenantService service.TenantService, metadata map[string]any, appointments []map[string]any) (bool, error) {
|
||||
if tenantService == nil || metadata == nil {
|
||||
return false, nil
|
||||
@@ -534,6 +605,66 @@ func (h *UserHandler) ListUsers(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
if h.UserRepo != nil {
|
||||
var tenantIDs []string
|
||||
if tenantSlug != "" {
|
||||
if targetTenantID == "" {
|
||||
return c.JSON(userListResponse{
|
||||
Items: []userSummary{},
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
Total: 0,
|
||||
Cursor: cursorRaw,
|
||||
})
|
||||
}
|
||||
if requesterRole != domain.RoleSuperAdmin && !manageableSlugs[targetTenantID] && !manageableSlugs[strings.ToLower(tenantSlug)] {
|
||||
return c.JSON(userListResponse{
|
||||
Items: []userSummary{},
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
Total: 0,
|
||||
Cursor: cursorRaw,
|
||||
})
|
||||
}
|
||||
tenantIDs = append(tenantIDs, targetTenantID)
|
||||
} else if requesterRole != domain.RoleSuperAdmin {
|
||||
for key := range manageableSlugs {
|
||||
if _, err := uuid.Parse(key); err == nil {
|
||||
tenantIDs = append(tenantIDs, key)
|
||||
}
|
||||
}
|
||||
if len(tenantIDs) == 0 {
|
||||
return c.JSON(userListResponse{
|
||||
Items: []userSummary{},
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
Total: 0,
|
||||
Cursor: cursorRaw,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
users, total, nextCursor, err := h.UserRepo.List(c.Context(), offset, limit, search, tenantIDs, cursorRaw)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, "failed to list users")
|
||||
}
|
||||
items := make([]userSummary, 0, len(users))
|
||||
for _, user := range users {
|
||||
items = append(items, h.mapLocalUserSummary(c.Context(), user))
|
||||
}
|
||||
if cursorRaw != "" {
|
||||
offset = 0
|
||||
}
|
||||
return c.JSON(userListResponse{
|
||||
Items: items,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
Total: total,
|
||||
Cursor: cursorRaw,
|
||||
NextCursor: nextCursor,
|
||||
})
|
||||
}
|
||||
|
||||
if h.KratosAdmin == nil {
|
||||
return errorJSON(c, fiber.StatusServiceUnavailable, "identity provider not available")
|
||||
}
|
||||
@@ -1615,11 +1746,18 @@ func (h *UserHandler) ExportUsersCSV(c *fiber.Ctx) error {
|
||||
|
||||
// 1. Fetch Users using Repo for efficiency
|
||||
var exportTenantIDs []string
|
||||
if tenantSlug != "" && h.TenantService != nil {
|
||||
t, err := h.TenantService.GetTenantBySlug(c.Context(), tenantSlug)
|
||||
if err == nil && t != nil {
|
||||
exportTenantIDs = []string{t.ID}
|
||||
if tenantSlug != "" {
|
||||
if h.TenantService == nil {
|
||||
return errorJSON(c, fiber.StatusServiceUnavailable, "tenant service unavailable for scoped export")
|
||||
}
|
||||
t, err := h.TenantService.GetTenantBySlug(c.Context(), tenantSlug)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, "failed to resolve tenant for export")
|
||||
}
|
||||
if t == nil || strings.TrimSpace(t.ID) == "" {
|
||||
return errorJSON(c, fiber.StatusNotFound, "tenant not found for export")
|
||||
}
|
||||
exportTenantIDs = []string{t.ID}
|
||||
}
|
||||
users, _, _, err := h.UserRepo.List(c.Context(), 0, 10000, search, exportTenantIDs, "")
|
||||
if err != nil {
|
||||
@@ -2087,7 +2225,7 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
|
||||
// All non-superadmins can only move users within tenants they can manage.
|
||||
if requester != nil && domain.NormalizeRole(requester.Role) != domain.RoleSuperAdmin {
|
||||
if !req.IsAddTenant && !req.IsRemoveTenant && req.CompanyCode != nil {
|
||||
if !req.IsRemoveTenant && req.CompanyCode != nil {
|
||||
targetSlug := strings.TrimSpace(*req.CompanyCode)
|
||||
targetAllowed := profileCanAccessTenant(requester, "", targetSlug)
|
||||
if !targetAllowed && h.TenantService != nil && targetSlug != "" {
|
||||
@@ -2096,7 +2234,7 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
if !targetAllowed {
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden: non-superadmins cannot change user's tenant to an unmanageable one")
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden: non-superadmins cannot assign user's tenant to an unmanageable one")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2221,6 +2359,21 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
traits["tenant_id"] = ""
|
||||
}
|
||||
}
|
||||
} else if h.TenantService != nil && code != "" {
|
||||
tenant, err := h.TenantService.GetTenantBySlug(c.Context(), code)
|
||||
if err != nil || tenant == nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, "invalid tenant assignment")
|
||||
}
|
||||
req.Metadata = mergeUserAddTenantAppointment(traits, req.Metadata, tenant)
|
||||
if h.KetoOutboxRepo != nil {
|
||||
_ = h.KetoOutboxRepo.Create(c.Context(), &domain.KetoOutbox{
|
||||
Namespace: "Tenant",
|
||||
Object: tenant.ID,
|
||||
Relation: "members",
|
||||
Subject: "User:" + userID,
|
||||
Action: domain.KetoOutboxActionCreate,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
delete(traits, "companyCode")
|
||||
@@ -2775,6 +2928,45 @@ func (h *UserHandler) mapIdentitySummary(ctx context.Context, identity service.K
|
||||
return summary
|
||||
}
|
||||
|
||||
func (h *UserHandler) mapLocalUserSummary(ctx context.Context, user domain.User) userSummary {
|
||||
tenantSlug := userTenantSlug(user)
|
||||
customLoginIDs := make([]string, 0, len(user.UserLoginIDs))
|
||||
for _, loginID := range user.UserLoginIDs {
|
||||
if strings.TrimSpace(loginID.LoginID) != "" {
|
||||
customLoginIDs = append(customLoginIDs, strings.TrimSpace(loginID.LoginID))
|
||||
}
|
||||
}
|
||||
|
||||
summary := userSummary{
|
||||
ID: user.ID,
|
||||
Email: user.Email,
|
||||
LoginID: user.Email,
|
||||
CustomLoginIDs: customLoginIDs,
|
||||
Name: user.Name,
|
||||
Phone: user.Phone,
|
||||
Role: domain.NormalizeRole(user.Role),
|
||||
Status: normalizeStatus(user.Status),
|
||||
TenantSlug: tenantSlug,
|
||||
CompanyCode: tenantSlug,
|
||||
Department: user.Department,
|
||||
Grade: user.Grade,
|
||||
Position: user.Position,
|
||||
JobTitle: user.JobTitle,
|
||||
Metadata: user.Metadata,
|
||||
Tenant: user.Tenant,
|
||||
CreatedAt: formatTime(user.CreatedAt),
|
||||
UpdatedAt: formatTime(user.UpdatedAt),
|
||||
}
|
||||
|
||||
if h.TenantService != nil {
|
||||
if joined, err := h.TenantService.ListJoinedTenants(ctx, user.ID); err == nil {
|
||||
summary.JoinedTenants = joined
|
||||
}
|
||||
}
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
func (h *UserHandler) normalizePhoneNumber(phone string) string {
|
||||
return normalizePhoneNumber(phone)
|
||||
}
|
||||
@@ -3302,18 +3494,7 @@ func normalizeKratosState(status *string) string {
|
||||
}
|
||||
|
||||
func normalizePhoneNumber(phone string) string {
|
||||
normalized := strings.ReplaceAll(phone, "-", "")
|
||||
normalized = strings.ReplaceAll(normalized, " ", "")
|
||||
if normalized == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.HasPrefix(normalized, "010") {
|
||||
return "+82" + normalized[1:]
|
||||
}
|
||||
if strings.HasPrefix(normalized, "82") {
|
||||
return "+" + normalized
|
||||
}
|
||||
return normalized
|
||||
return domain.NormalizePhoneNumber(phone)
|
||||
}
|
||||
|
||||
func (h *UserHandler) validateMetadata(metadata map[string]any, schema []any, checkRequired bool) error {
|
||||
|
||||
Reference in New Issue
Block a user