diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index ec73b256..11b5ee11 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -4,6 +4,7 @@ import ( "baron-sso-backend/internal/domain" "baron-sso-backend/internal/logger" "baron-sso-backend/internal/repository" + "baron-sso-backend/internal/response" "baron-sso-backend/internal/service" "baron-sso-backend/internal/utils" "bytes" @@ -1560,7 +1561,7 @@ func (h *AuthHandler) PasswordLogin(c *fiber.Ctx) error { ale.LatencyMs = time.Since(startTime) ale.ProviderError = err.Error() ale.Log(slog.LevelError, "Body parse error") - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"}) + return response.Error(c, fiber.StatusBadRequest, "bad_request", "Invalid request body") } loginID := strings.TrimSpace(req.LoginID) @@ -1574,22 +1575,22 @@ func (h *AuthHandler) PasswordLogin(c *fiber.Ctx) error { ale.LatencyMs = time.Since(startTime) ale.ProviderError = "IDP Provider is nil" ale.Log(slog.LevelError, "IDP Provider is nil") - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Authentication service not configured"}) + return response.Error(c, fiber.StatusInternalServerError, "service_unavailable", "Authentication service not configured") } authInfo, err := h.IdpProvider.SignIn(loginID, req.Password) if err != nil { if errors.Is(err, domain.ErrNotSupported) { - return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"error": "Login method not supported"}) + return response.Error(c, fiber.StatusNotImplemented, "not_supported", "Login method not supported") } ale.Status = fiber.StatusUnauthorized ale.LatencyMs = time.Since(startTime) ale.ProviderError = err.Error() ale.Log(slog.LevelWarn, "IDP sign-in failed", slog.String("provider", h.IdpProvider.Name())) if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "identity") { - return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not registered"}) + return response.Error(c, fiber.StatusNotFound, "not_found", "User not registered") } - return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"}) + return response.Error(c, fiber.StatusUnauthorized, "password_or_email_mismatch", "Invalid credentials") } subject, resolveErr := h.resolveKratosIdentityIDFromLoginID(c.Context(), loginID) diff --git a/backend/internal/handler/auth_handler_login_test.go b/backend/internal/handler/auth_handler_login_test.go index 9845e738..3bb6b989 100644 --- a/backend/internal/handler/auth_handler_login_test.go +++ b/backend/internal/handler/auth_handler_login_test.go @@ -299,3 +299,44 @@ func TestPasswordLogin_NoOIDC_Success(t *testing.T) { t.Errorf("expected no redirectTo, got %s", got["redirectTo"]) } } + +func TestPasswordLogin_InvalidCredentials_ReturnsCode(t *testing.T) { + mockIdp := new(MockIdentityProvider) + mockIdp.On("SignIn", "user@example.com", "wrong-password").Return(nil, errors.New("비밀번호가 일치하지 않습니다")) + + h := &AuthHandler{ + IdpProvider: mockIdp, + KratosAdmin: service.NewKratosAdminService(), + Hydra: service.NewHydraAdminService(), + } + + app := newAuthLoginTestApp(h) + + body, _ := json.Marshal(map[string]string{ + "loginId": "user@example.com", + "password": "wrong-password", + }) + req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + + resp, err := app.Test(req) + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusUnauthorized { + t.Fatalf("expected 401, got %d", resp.StatusCode) + } + + var got map[string]any + if err := json.NewDecoder(resp.Body).Decode(&got); err != nil { + t.Fatalf("failed to decode response: %v", err) + } + if got["code"] != "password_or_email_mismatch" { + t.Fatalf("expected code=password_or_email_mismatch, got=%v", got["code"]) + } + if got["error"] != "Invalid credentials" { + t.Fatalf("expected error=Invalid credentials, got=%v", got["error"]) + } +} diff --git a/locales/en.toml b/locales/en.toml index 34f59690..6740edd9 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -316,6 +316,7 @@ approved_device = "Approved Device" approved_ip = "Approve IP: {{ip}}" audit_empty = "Audit Empty" audit_load_error = "Audit Load Error" +render_error = "Dashboard render error: {{error}}" auth_method = "Auth Method" client_id = "Client ID: {{id}}" client_id_missing = "Client Id Missing" diff --git a/locales/ko.toml b/locales/ko.toml index a26268f5..01417815 100644 --- a/locales/ko.toml +++ b/locales/ko.toml @@ -316,6 +316,7 @@ approved_device = "승인 기기: {{device}}" approved_ip = "승인 IP: {{ip}}" audit_empty = "최근 접속 이력이 없습니다." audit_load_error = "접속이력을 불러오지 못했습니다." +render_error = "대시보드 렌더링 오류: {{error}}" auth_method = "인증수단: {{method}}" client_id = "Client ID: {{id}}" client_id_missing = "Client ID 없음" diff --git a/locales/template.toml b/locales/template.toml index f11f6cbd..39c88db5 100644 --- a/locales/template.toml +++ b/locales/template.toml @@ -316,6 +316,7 @@ approved_device = "" approved_ip = "" audit_empty = "" audit_load_error = "" +render_error = "" auth_method = "" client_id = "" client_id_missing = "" diff --git a/userfront/assets/translations/en.toml b/userfront/assets/translations/en.toml index e33f46ee..5cd91c17 100644 --- a/userfront/assets/translations/en.toml +++ b/userfront/assets/translations/en.toml @@ -38,6 +38,7 @@ approved_device = "Approved Device" approved_ip = "Approve IP: {ip}" audit_empty = "Audit Empty" audit_load_error = "Audit Load Error" +render_error = "Dashboard render error: {error}" auth_method = "Auth Method" client_id = "Client ID: {id}" client_id_missing = "Client Id Missing" @@ -557,4 +558,3 @@ verify = "Verify" [ui.userfront.signup.success] action = "Action" - diff --git a/userfront/assets/translations/ko.toml b/userfront/assets/translations/ko.toml index ee1df42f..20824c45 100644 --- a/userfront/assets/translations/ko.toml +++ b/userfront/assets/translations/ko.toml @@ -38,6 +38,7 @@ approved_device = "승인 기기: {device}" approved_ip = "승인 IP: {ip}" audit_empty = "최근 접속 이력이 없습니다." audit_load_error = "접속이력을 불러오지 못했습니다." +render_error = "대시보드 렌더링 오류: {error}" auth_method = "인증수단: {method}" client_id = "Client ID: {id}" client_id_missing = "Client ID 없음" @@ -557,4 +558,3 @@ verify = "본인인증" [ui.userfront.signup.success] action = "로그인하기" - diff --git a/userfront/assets/translations/template.toml b/userfront/assets/translations/template.toml index 9ee024cb..cb29cf60 100644 --- a/userfront/assets/translations/template.toml +++ b/userfront/assets/translations/template.toml @@ -38,6 +38,7 @@ approved_device = "" approved_ip = "" audit_empty = "" audit_load_error = "" +render_error = "" auth_method = "" client_id = "" client_id_missing = ""