From 94d7f14b5f72435154e4bfa5977e2e0be37737b3 Mon Sep 17 00:00:00 2001 From: Bennett Date: Thu, 10 Sep 2026 16:48:35 +0300 Subject: [PATCH 1/2] release: Fix global filter for login --- .../exceptions/GlobalExceptionHandler.java | 25 +++++++++++++------ .../GlobalExceptionHandlerTest.java | 15 +++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandler.java b/src/main/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandler.java index 9ecd111..f6466d3 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandler.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandler.java @@ -142,7 +142,11 @@ public ResponseEntity handleConstraintViolationException(ConstraintViola public ResponseEntity handleDatabaseError(DataIntegrityViolationException ex) { Throwable rootCause = ex.getRootCause(); String detail = (rootCause != null) ? rootCause.getMessage() : ex.getMessage(); - return buildResponse(sanitizeDatabaseError(detail), getResponseStatus(detail, HttpStatus.BAD_REQUEST), ex); + // Classify off the sanitized message, not the raw one: engine-specific wording (e.g. MySQL's + // "Duplicate entry ... for key ..." vs Postgres's "... already exists") only reliably normalizes + // to a wording getResponseStatus() recognizes ("X already exists") after sanitizeDatabaseError. + String sanitized = sanitizeDatabaseError(detail); + return buildResponse(sanitized, getResponseStatus(sanitized, HttpStatus.BAD_REQUEST), ex); } @ExceptionHandler(MethodArgumentNotValidException.class) @@ -307,23 +311,28 @@ private ResponseEntity buildResponse(String message, HttpStatus status) return buildResponse(message, status, null); } + // Callers pass the status the exception actually carries (or, for DataIntegrityViolationException's + // raw DB error text, one already inferred via getResponseStatus() before calling in) -- it must be + // used as-is here. Re-inferring from the message text on top of that (as this used to do) meant any + // explicit status whose message happened to contain a word like "invalid" or "missing" -- e.g. the + // 401/403 "Invalid username or password" from a failed login -- got silently downgraded to 400, which + // also defeated UNLOGGED_STATUSES below (401/403 are meant to be routine and not error-logged, but + // arriving here already remapped to 400 they'd get logged anyway). private ResponseEntity buildResponse(String message, HttpStatus status, Throwable ex) { - HttpStatus finalStatus = getResponseStatus(message, status); - Map body = new HashMap<>(); body.put("timestamp", LocalDateTime.now()); - body.put("error", finalStatus.getReasonPhrase()); + body.put("error", status.getReasonPhrase()); body.put("message", message != null ? capitalize(message) : "No message available"); - if (!UNLOGGED_STATUSES.contains(finalStatus)) { + if (!UNLOGGED_STATUSES.contains(status)) { if (ex != null) { - log.error("Request failed with {}: {}", finalStatus, message, ex); + log.error("Request failed with {}: {}", status, message, ex); } else { - log.error("Request failed with {}: {}", finalStatus, message); + log.error("Request failed with {}: {}", status, message); } } - return new ResponseEntity<>(body, finalStatus); + return new ResponseEntity<>(body, status); } private String capitalize(String str) { diff --git a/src/test/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandlerTest.java b/src/test/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandlerTest.java index f325d51..74ec5e7 100644 --- a/src/test/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandlerTest.java +++ b/src/test/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandlerTest.java @@ -40,6 +40,11 @@ public void throwResponseStatus() { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bad request error"); } + @GetMapping("/test/response-status-forbidden-with-sniffable-message") + public void throwResponseStatusForbiddenWithSniffableMessage() { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Invalid username or password"); + } + @GetMapping("/test/general") public void throwGeneral() throws Exception { throw new Exception("General error"); @@ -132,6 +137,16 @@ void handleResponseStatusException_shouldReturnCorrectStatusAndMessage() throws .andExpect(jsonPath("$.message").value("Bad request error")); } + @Test + void handleResponseStatusException_shouldNotDowngradeStatusBasedOnMessageWording() throws Exception { + // Regression test: the exception's own status (here FORBIDDEN) must win even though the + // message contains "invalid", which getResponseStatus() would otherwise read as BAD_REQUEST. + mockMvc.perform(get("/test/response-status-forbidden-with-sniffable-message") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.message").value("Invalid username or password")); + } + @Test void handleGeneralException_shouldReturnInternalServerError() throws Exception { mockMvc.perform(get("/test/general") From 9cce9b1a8f23442e06aa82403ffe4a73d8a660b6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 10 Sep 2026 13:49:16 +0000 Subject: [PATCH 2/2] Release v0.0.68 [skip ci] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d8e3cca..c09fe96 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'com.flexcodelabs' -version = '0.0.67' +version = '0.0.68' description = 'Flextuma App' java {