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 != "" {
|
if str, ok := val.(string); ok && str != "" {
|
||||||
return 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 ""
|
return ""
|
||||||
|
|||||||
@@ -1266,6 +1266,25 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
|||||||
if traits == nil {
|
if traits == nil {
|
||||||
traits = map[string]interface{}{}
|
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 {
|
if req.Name != nil {
|
||||||
traits["name"] = strings.TrimSpace(*req.Name)
|
traits["name"] = strings.TrimSpace(*req.Name)
|
||||||
}
|
}
|
||||||
@@ -1286,7 +1305,33 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
|||||||
traits["tenant_id"] = tenant.ID
|
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 {
|
if req.Department != nil {
|
||||||
traits["department"] = strings.TrimSpace(*req.Department)
|
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.
|
// [Self-Healing] Sync all companyCodes to Keto
|
||||||
// This fixes issues where local DB had the tenant, but Keto failed to create the relation previously.
|
if h.KetoOutboxRepo != nil && h.TenantService != nil {
|
||||||
if req.CompanyCode != nil && h.KetoOutboxRepo != nil && updatedLocalUser.TenantID != nil {
|
if codes, ok := updated.Traits["companyCodes"].([]interface{}); ok {
|
||||||
_ = h.KetoOutboxRepo.Create(bgCtx, &domain.KetoOutbox{
|
for _, cVal := range codes {
|
||||||
Namespace: "Tenant",
|
if cStr, ok := cVal.(string); ok && cStr != "" {
|
||||||
Object: *updatedLocalUser.TenantID,
|
if tenant, err := h.TenantService.GetTenantBySlug(bgCtx, cStr); err == nil && tenant != nil {
|
||||||
Relation: "members",
|
_ = h.KetoOutboxRepo.Create(bgCtx, &domain.KetoOutbox{
|
||||||
Subject: "User:" + updatedLocalUser.ID,
|
Namespace: "Tenant",
|
||||||
Action: domain.KetoOutboxActionCreate,
|
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