1
0
forked from baron/baron-sso

feat: implement multi-identifier architecture (Issue #496)

- Database: Add user_login_ids table for 1:N identifier mapping and remove legacy login_id column
- Kratos: Update identity schema to use custom_login_ids array instead of a single id trait
- Backend: Implement syncCustomLoginIDs to collect isLoginId fields across tenant schemas
- Backend: Add backtracking logic to auto-assign session tenant based on used login identifier
- Backend: Add 409 Conflict exception handling for Create/Update operations
- AdminFront: Refactor UserDetailPage to a tabbed grid layout (Info, Tenants, Security)
- AdminFront: Show '로그인 ID' badge on tenant schema fields used for authentication
- UserFront: Remove legacy optional 'Login ID' input from signup flow
- Tests: Add multi-identifier repository tests and update handler tests
This commit is contained in:
2026-04-02 16:07:33 +09:00
parent 71a006cd7b
commit b582c82c6f
25 changed files with 1154 additions and 1160 deletions

View File

@@ -97,7 +97,6 @@ export function TenantSchemaPage() {
useEffect(() => {
const rawSchema = tenantQuery.data?.config?.userSchema;
const loginIdField = tenantQuery.data?.config?.loginIdField;
if (Array.isArray(rawSchema)) {
setFields(
@@ -118,7 +117,7 @@ export function TenantSchemaPage() {
validation:
typeof field?.validation === "string" ? field.validation : "",
unsigned: Boolean(field?.unsigned),
isLoginId: field?.key === loginIdField,
isLoginId: Boolean(field?.isLoginId),
})),
);
}
@@ -126,13 +125,13 @@ export function TenantSchemaPage() {
const updateMutation = useMutation({
mutationFn: (newFields: SchemaField[]) => {
const loginIdField = newFields.find((f) => f.isLoginId)?.key || "";
// Remove legacy loginIdField, keep isLoginId natively in userSchema
const newConfig = { ...tenantQuery.data?.config };
delete newConfig.loginIdField;
newConfig.userSchema = newFields;
return updateTenant(tenantId, {
config: {
...tenantQuery.data?.config,
userSchema: newFields,
loginIdField: loginIdField,
},
config: newConfig,
});
},
onSuccess: () => {
@@ -344,14 +343,10 @@ export function TenantSchemaPage() {
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={field.isLoginId}
onChange={(e) => {
const newFields = fields.map((f, i) => ({
...f,
isLoginId: i === index ? e.target.checked : false,
}));
setFields(newFields);
}}
checked={field.isLoginId || false}
onChange={(e) =>
updateField(index, { isLoginId: e.target.checked })
}
className="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<span className="text-sm font-medium text-blue-600">