forked from baron/baron-sso
fix(user): preserve multi-tenant companyCodes and fix Kratos code parsing
- UpdateUser: Implement 'Preserve & Merge' logic to fetch existing joined tenants from Keto and merge them with UI requests, preventing the loss of multi-tenant affiliations. - Keto Sync: Expand the self-healing background job to iterate over all companyCodes, ensuring 'members' relations are created for every joined tenant (fixes #554). - AuthHandler: Update extractFirstString to gracefully handle numeric JSON types, fixing an issue where Kratos login codes were lost during Courier webhook processing.
This commit is contained in:
@@ -3767,6 +3767,13 @@ func extractFirstString(data map[string]interface{}, keys ...string) string {
|
||||
if str, ok := val.(string); ok && str != "" {
|
||||
return str
|
||||
}
|
||||
// Handle numeric types by converting to string
|
||||
if num, ok := val.(float64); ok {
|
||||
return fmt.Sprint(num)
|
||||
}
|
||||
if num, ok := val.(int); ok {
|
||||
return fmt.Sprint(num)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -1266,6 +1266,25 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
if traits == nil {
|
||||
traits = map[string]interface{}{}
|
||||
}
|
||||
|
||||
// [Preserve & Merge] Multi-Tenant Info
|
||||
var existingCodes []string
|
||||
if codes, ok := traits["companyCodes"].([]interface{}); ok {
|
||||
for _, v := range codes {
|
||||
if str, ok := v.(string); ok && str != "" {
|
||||
existingCodes = append(existingCodes, str)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Keto에서 "실제" 소속 정보를 먼저 확인 (엑셀 임포트 사용자 대응)
|
||||
if len(existingCodes) <= 1 && h.TenantService != nil {
|
||||
if joined, err := h.TenantService.ListJoinedTenants(c.Context(), userID); err == nil {
|
||||
for _, t := range joined {
|
||||
existingCodes = append(existingCodes, t.Slug)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if req.Name != nil {
|
||||
traits["name"] = strings.TrimSpace(*req.Name)
|
||||
}
|
||||
@@ -1286,7 +1305,33 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
traits["tenant_id"] = tenant.ID
|
||||
}
|
||||
}
|
||||
|
||||
// Add to existingCodes if not present
|
||||
found := false
|
||||
for _, existing := range existingCodes {
|
||||
if existing == code {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found && code != "" {
|
||||
existingCodes = append(existingCodes, code)
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate and save back companyCodes
|
||||
var uniqueCodes []string
|
||||
seenCodes := map[string]bool{}
|
||||
for _, c := range existingCodes {
|
||||
if !seenCodes[c] && c != "" {
|
||||
seenCodes[c] = true
|
||||
uniqueCodes = append(uniqueCodes, c)
|
||||
}
|
||||
}
|
||||
if len(uniqueCodes) > 0 {
|
||||
traits["companyCodes"] = uniqueCodes
|
||||
}
|
||||
|
||||
if req.Department != nil {
|
||||
traits["department"] = strings.TrimSpace(*req.Department)
|
||||
}
|
||||
@@ -1420,16 +1465,32 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
// [Self-Healing] If the UI explicitly assigned the tenant, force a Keto relation sync.
|
||||
// This fixes issues where local DB had the tenant, but Keto failed to create the relation previously.
|
||||
if req.CompanyCode != nil && h.KetoOutboxRepo != nil && updatedLocalUser.TenantID != nil {
|
||||
_ = h.KetoOutboxRepo.Create(bgCtx, &domain.KetoOutbox{
|
||||
Namespace: "Tenant",
|
||||
Object: *updatedLocalUser.TenantID,
|
||||
Relation: "members",
|
||||
Subject: "User:" + updatedLocalUser.ID,
|
||||
Action: domain.KetoOutboxActionCreate,
|
||||
})
|
||||
// [Self-Healing] Sync all companyCodes to Keto
|
||||
if h.KetoOutboxRepo != nil && h.TenantService != nil {
|
||||
if codes, ok := updated.Traits["companyCodes"].([]interface{}); ok {
|
||||
for _, cVal := range codes {
|
||||
if cStr, ok := cVal.(string); ok && cStr != "" {
|
||||
if tenant, err := h.TenantService.GetTenantBySlug(bgCtx, cStr); err == nil && tenant != nil {
|
||||
_ = h.KetoOutboxRepo.Create(bgCtx, &domain.KetoOutbox{
|
||||
Namespace: "Tenant",
|
||||
Object: tenant.ID,
|
||||
Relation: "members",
|
||||
Subject: "User:" + updatedLocalUser.ID,
|
||||
Action: domain.KetoOutboxActionCreate,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if updatedLocalUser.TenantID != nil {
|
||||
// Fallback if companyCodes doesn't exist
|
||||
_ = h.KetoOutboxRepo.Create(bgCtx, &domain.KetoOutbox{
|
||||
Namespace: "Tenant",
|
||||
Object: *updatedLocalUser.TenantID,
|
||||
Relation: "members",
|
||||
Subject: "User:" + updatedLocalUser.ID,
|
||||
Action: domain.KetoOutboxActionCreate,
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user