From aa60a22d57390056e766c226d6f3fc3cb8fabf9c Mon Sep 17 00:00:00 2001 From: chan Date: Thu, 26 Mar 2026 14:22:43 +0900 Subject: [PATCH] feat: restore explicit loginId field and add to userfront signup flow - Revert the removal of loginId from adminfront and backend. - Prevent phone normalization logic from mangling custom employee ID login fields. - Add an explicit 'loginId' optional input field to the userfront signup UI. - Update AuthProxyService.signup and backend AuthHandler.Signup to transmit and map the 'loginId' parameter properly. --- backend/internal/handler/auth_handler.go | 15 +++++++++++++++ backend/internal/handler/user_handler.go | 6 ------ .../lib/core/services/auth_proxy_service.dart | 2 ++ .../auth/presentation/signup_screen.dart | 16 ++++++++++++++++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index cb7c26b9..d5634dd5 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -451,8 +451,23 @@ func (h *AuthHandler) Signup(c *fiber.Ctx) error { // grade는 기존 스키마 필수 키이므로 기본값을 설정 "grade": "member", } + + if req.LoginID != "" { + attributes["id"] = req.LoginID + } + + // Sync custom field to LoginID if configured + if tenantID != nil && h.TenantService != nil { + if tenant, err := h.TenantService.GetTenant(c.Context(), *tenantID); err == nil && tenant != nil { + if loginIdField, ok := tenant.Config["loginIdField"].(string); ok && loginIdField != "" { + syncLoginID(attributes, req.Metadata, *tenantID, loginIdField) + } + } + } + brokerUser := &domain.BrokerUser{ Email: req.Email, + LoginID: extractTraitString(attributes, "id"), Name: req.Name, PhoneNumber: normalizedPhone, Attributes: attributes, diff --git a/backend/internal/handler/user_handler.go b/backend/internal/handler/user_handler.go index 8191275f..224f8ad0 100644 --- a/backend/internal/handler/user_handler.go +++ b/backend/internal/handler/user_handler.go @@ -1533,12 +1533,6 @@ func syncLoginID(traits map[string]interface{}, metadata map[string]any, tenantI } if loginID != "" { - // Normalize if it looks like a phone number to be consistent with other identifiers - normalized := normalizePhoneNumber(loginID) - if normalized != "" { - loginID = normalized - } - slog.Info("Syncing LoginID from custom field", "field", loginIDField, "value", loginID, "tenantID", tenantID) traits["id"] = loginID } diff --git a/userfront/lib/core/services/auth_proxy_service.dart b/userfront/lib/core/services/auth_proxy_service.dart index ec2c1a24..2171de51 100644 --- a/userfront/lib/core/services/auth_proxy_service.dart +++ b/userfront/lib/core/services/auth_proxy_service.dart @@ -934,6 +934,7 @@ class AuthProxyService { static Future signup({ required String email, + String? loginId, required String password, required String name, required String phone, @@ -949,6 +950,7 @@ class AuthProxyService { headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'email': email, + if (loginId != null && loginId.isNotEmpty) 'loginId': loginId, 'password': password, 'name': name, 'phone': phone, diff --git a/userfront/lib/features/auth/presentation/signup_screen.dart b/userfront/lib/features/auth/presentation/signup_screen.dart index 0a406182..57b5b5da 100644 --- a/userfront/lib/features/auth/presentation/signup_screen.dart +++ b/userfront/lib/features/auth/presentation/signup_screen.dart @@ -31,6 +31,7 @@ class _SignupScreenState extends State { // Controllers final _emailController = TextEditingController(); + final _loginIdController = TextEditingController(); final _emailCodeController = TextEditingController(); final _phoneController = TextEditingController(); final _phoneCodeController = TextEditingController(); @@ -98,6 +99,7 @@ class _SignupScreenState extends State { _emailTimer?.cancel(); _phoneTimer?.cancel(); _emailController.dispose(); + _loginIdController.dispose(); _emailCodeController.dispose(); _phoneController.dispose(); _phoneCodeController.dispose(); @@ -311,6 +313,7 @@ class _SignupScreenState extends State { try { await AuthProxyService.signup( email: _emailController.text.trim(), + loginId: _loginIdController.text.trim(), password: _passwordController.text, name: _nameController.text.trim(), phone: _phoneController.text.trim(), @@ -1421,6 +1424,19 @@ class _SignupScreenState extends State { ), ), const SizedBox(height: 18), + _buildProfileFieldGroup( + title: '로그인 ID (선택)', + description: '이메일/전화번호 외에 별도의 식별자로 로그인할 때 사용합니다.', + isDesktop: isDesktop, + child: TextFormField( + controller: _loginIdController, + decoration: const InputDecoration( + labelText: '사번 또는 아이디', + border: OutlineInputBorder(), + ), + ), + ), + const SizedBox(height: 18), _buildProfileFieldGroup( title: tr('ui.userfront.signup.profile.affiliation_type'), description: _isAffiliateEmail