forked from baron/baron-sso
fix: Admin UI에서 전송한 커스텀 필드(metadata)가 백엔드 Kratos 트레이츠에 빈 배열로 깨져서 저장되는 문제 해결 (#440)
This commit is contained in:
@@ -139,7 +139,9 @@ function TenantMetadataFields({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserFormValues = UserUpdateRequest & { metadata: Record<string, any> };
|
type UserFormValues = Omit<UserUpdateRequest, "metadata"> & {
|
||||||
|
metadata: Record<string, Record<string, any>>;
|
||||||
|
};
|
||||||
|
|
||||||
function UserDetailPage() {
|
function UserDetailPage() {
|
||||||
const params = useParams<{ id: string }>();
|
const params = useParams<{ id: string }>();
|
||||||
@@ -299,7 +301,20 @@ function UserDetailPage() {
|
|||||||
const onSubmit = (data: UserFormValues) => {
|
const onSubmit = (data: UserFormValues) => {
|
||||||
setError(null);
|
setError(null);
|
||||||
setSuccessMsg(null);
|
setSuccessMsg(null);
|
||||||
mutation.mutate(data);
|
|
||||||
|
// Filter out undefined/null/empty strings from metadata
|
||||||
|
const cleanMetadata = Object.fromEntries(
|
||||||
|
Object.entries(data.metadata).map(([tenantId, fields]) => {
|
||||||
|
const cleanFields = Object.fromEntries(
|
||||||
|
Object.entries(fields).filter(
|
||||||
|
([_, v]) => v !== undefined && v !== null && v !== "",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return [tenantId, cleanFields];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
mutation.mutate({ ...data, metadata: cleanMetadata });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
|
|||||||
@@ -1194,15 +1194,28 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For namespaced metadata, we don't delete everything, we merge.
|
// For namespaced metadata, we don't delete everything, we merge.
|
||||||
// But we should remove legacy flat traits that are not in the new req.Metadata if we want strict sync.
|
|
||||||
// For now, let's just merge.
|
|
||||||
for k, v := range req.Metadata {
|
for k, v := range req.Metadata {
|
||||||
if !coreTraits[k] {
|
if !coreTraits[k] {
|
||||||
traits[k] = v
|
// Ensure we are merging maps (tenant namespaces) correctly, not overwriting with slices
|
||||||
|
if incomingMap, ok := v.(map[string]any); ok {
|
||||||
|
if existingMap, ok := traits[k].(map[string]interface{}); ok {
|
||||||
|
for subK, subV := range incomingMap {
|
||||||
|
existingMap[subK] = subV
|
||||||
|
}
|
||||||
|
traits[k] = existingMap
|
||||||
|
} else {
|
||||||
|
traits[k] = incomingMap // New namespace
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
traits[k] = v // Fallback for flat metadata
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state := normalizeKratosState(req.Status)
|
state := normalizeKratosState(req.Status)
|
||||||
|
|
||||||
|
slog.Info("[UpdateUser] Calling Kratos UpdateIdentity", "userID", userID, "traits", traits, "state", state)
|
||||||
|
|
||||||
updated, err := h.KratosAdmin.UpdateIdentity(c.Context(), userID, traits, state)
|
updated, err := h.KratosAdmin.UpdateIdentity(c.Context(), userID, traits, state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
|
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
|
||||||
|
|||||||
Reference in New Issue
Block a user