From b582c82c6fec5648c7f708e7d14eed414e061a94 Mon Sep 17 00:00:00 2001 From: chan Date: Thu, 2 Apr 2026 16:07:33 +0900 Subject: [PATCH] feat: implement multi-identifier architecture (Issue #496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../tenants/routes/TenantSchemaPage.tsx | 27 +- .../src/features/users/UserCreatePage.tsx | 27 +- .../src/features/users/UserDetailPage.tsx | 1089 ++++++----------- .../src/features/users/UserListPage.tsx | 8 - adminfront/src/lib/adminApi.ts | 14 + backend/cmd/server/main.go | 3 +- backend/internal/bootstrap/bootstrap.go | 1 + backend/internal/domain/auth_models.go | 1 + backend/internal/domain/idp_models.go | 11 +- backend/internal/domain/user.go | 15 +- backend/internal/handler/auth_handler.go | 241 ++-- .../handler/auth_handler_async_test.go | 26 +- backend/internal/handler/dev_handler.go | 3 +- .../internal/handler/tenant_handler_test.go | 16 + backend/internal/handler/user_handler.go | 429 ++++--- backend/internal/handler/user_handler_test.go | 125 +- .../internal/repository/user_repository.go | 48 + .../repository/user_repository_test.go | 50 + backend/internal/service/descope_service.go | 11 +- backend/internal/service/ory_service.go | 28 +- .../internal/service/user_group_service.go | 3 - backend/internal/utils/audit.go | 17 + docker/ory/kratos/identity.schema.json | 17 +- .../lib/core/services/auth_proxy_service.dart | 4 +- .../auth/presentation/signup_screen.dart | 100 +- 25 files changed, 1154 insertions(+), 1160 deletions(-) create mode 100644 backend/internal/utils/audit.go diff --git a/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx b/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx index 17ff2bdf..07e53e9a 100644 --- a/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx @@ -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() {