From d10f80d41d9204fa555cbdc354b0cb3089e42562 Mon Sep 17 00:00:00 2001 From: chan Date: Wed, 25 Mar 2026 15:27:44 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EA=B8=B0=EB=B0=98=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?ID=20=EC=97=B0=EB=8F=99=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#440)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Kratos Identity 스키마에 로그인 전용 `id` 속성 추가 - 테넌트 Config의 `loginIdField` 설정에 따라 User의 `login_id` 및 Kratos `traits.id` 동기화 로직 구현 - Admin UI 테넌트 스키마 설정 내 '로그인 ID로 사용' 체크박스 추가 - Admin UI 사용자 생성/수정/조회 화면에 로그인 ID 관리 필드 및 컬럼 반영 - Userfront 로그인 화면 접속 시 테넌트 설정에 따라 동적 로그인 ID 라벨 적용 - 관련 다국어(ko/en) 번역 추가 및 로그인 ID 설계 문서 업데이트 --- .../tenants/routes/TenantSchemaPage.tsx | 33 +- .../src/features/users/UserCreatePage.tsx | 21 + .../src/features/users/UserDetailPage.tsx | 880 ++++++++++-------- .../src/features/users/UserListPage.tsx | 8 + adminfront/src/lib/adminApi.ts | 4 + backend/internal/domain/auth_models.go | 2 + backend/internal/domain/idp_models.go | 1 + backend/internal/domain/user.go | 3 +- backend/internal/handler/auth_handler.go | 21 +- backend/internal/handler/user_handler.go | 78 +- backend/internal/service/ory_service.go | 16 +- docker/ory/kratos/identity.schema.json | 11 + docs/employee_id_login_db_design.md | 53 ++ docs/employee_id_login_design.md | 47 + locales/en.toml | 6 + locales/ko.toml | 6 + .../lib/core/services/auth_proxy_service.dart | 13 + .../auth/presentation/login_screen.dart | 16 +- 18 files changed, 799 insertions(+), 420 deletions(-) create mode 100644 docs/employee_id_login_db_design.md create mode 100644 docs/employee_id_login_design.md diff --git a/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx b/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx index c81854b7..17ff2bdf 100644 --- a/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx @@ -34,6 +34,7 @@ type SchemaField = { adminOnly: boolean; validation?: string; unsigned?: boolean; + isLoginId?: boolean; }; function createFieldId() { @@ -96,6 +97,8 @@ export function TenantSchemaPage() { useEffect(() => { const rawSchema = tenantQuery.data?.config?.userSchema; + const loginIdField = tenantQuery.data?.config?.loginIdField; + if (Array.isArray(rawSchema)) { setFields( rawSchema.map((field) => ({ @@ -115,19 +118,23 @@ export function TenantSchemaPage() { validation: typeof field?.validation === "string" ? field.validation : "", unsigned: Boolean(field?.unsigned), + isLoginId: field?.key === loginIdField, })), ); } }, [tenantQuery.data]); const updateMutation = useMutation({ - mutationFn: (newFields: SchemaField[]) => - updateTenant(tenantId, { + mutationFn: (newFields: SchemaField[]) => { + const loginIdField = newFields.find((f) => f.isLoginId)?.key || ""; + return updateTenant(tenantId, { config: { ...tenantQuery.data?.config, userSchema: newFields, + loginIdField: loginIdField, }, - }), + }); + }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["tenant", tenantId] }); toast.success( @@ -334,6 +341,26 @@ export function TenantSchemaPage() { )} + {(field.type === "number" || field.type === "float") && (