Merge pull request #2272 from n200534/fix/issue-2016-translate-backend-errors

Translate login authentication errors
This commit is contained in:
Jérémie Panzer
2026-08-19 22:31:29 +02:00
committed by GitHub
37 changed files with 263 additions and 6 deletions

View File

@@ -0,0 +1,36 @@
import { i18n } from "@lingui/core"
import type { AxiosError } from "axios"
import { afterEach, describe, expect, it, vi } from "vitest"
import { errorToStrings } from "./client"
const axiosError = (status: number, data: unknown) =>
({
isAxiosError: true,
response: { status, data },
}) as AxiosError
describe("errorToStrings", () => {
afterEach(() => vi.restoreAllMocks())
it("translates known application error types", () => {
vi.spyOn(i18n, "_").mockReturnValue("Translated authentication error")
const error = axiosError(401, {
type: "WRONG_USERNAME_OR_PASSWORD",
message: "wrong username or password",
})
expect(errorToStrings(error)).toEqual(["Translated authentication error"])
})
it("preserves backend messages for unexpected errors", () => {
const error = axiosError(500, { message: "unexpected error" })
expect(errorToStrings(error)).toEqual(["unexpected error"])
})
it("preserves backend messages for unknown application error types", () => {
const error = axiosError(400, { type: "UNKNOWN_ERROR", message: "unknown error" })
expect(errorToStrings(error)).toEqual(["unknown error"])
})
})

View File

@@ -1,3 +1,5 @@
import { i18n, type MessageDescriptor } from "@lingui/core"
import { msg } from "@lingui/core/macro"
import axios, { type AxiosError } from "axios"
import type {
AddCategoryRequest,
@@ -6,6 +8,8 @@ import type {
Category,
CategoryModificationRequest,
CollapseRequest,
CommaFeedApplicationError,
CommaFeedExceptionType,
Entries,
FeedInfo,
FeedInfoRequest,
@@ -31,6 +35,10 @@ import type {
UserModel,
} from "./types"
const applicationErrorMessages = {
WRONG_USERNAME_OR_PASSWORD: msg`Wrong username or password`,
} satisfies Record<CommaFeedExceptionType, MessageDescriptor>
const axiosInstance = axios.create({ baseURL: "./rest", withCredentials: true })
axiosInstance.interceptors.response.use(
response => response,
@@ -128,14 +136,25 @@ export const errorToStrings = (err: unknown) => {
let strings: string[] = []
if (axios.isAxiosError(err) && err.response) {
if (typeof err.response.data === "string") strings.push(err.response.data)
if (isMessageError(err)) strings.push(err.response.data.message)
if (isMessageArrayError(err)) strings = [...strings, ...err.response.data.errors]
if (isCommaFeedApplicationError(err)) {
strings.push(i18n._(applicationErrorMessages[err.response.data.type]))
} else {
if (typeof err.response.data === "string") strings.push(err.response.data)
if (isMessageError(err)) strings.push(err.response.data.message)
if (isMessageArrayError(err)) strings = [...strings, ...err.response.data.errors]
}
}
return strings
}
function isCommaFeedApplicationError(err: AxiosError): err is AxiosError<CommaFeedApplicationError> {
const data = err.response?.data
if (!data || typeof data !== "object" || !("type" in data)) return false
const type = data.type
return typeof type === "string" && Object.hasOwn(applicationErrorMessages, type)
}
function isMessageError(err: AxiosError): err is AxiosError<{ message: string }> {
return !!err.response && !!err.response.data && typeof err.response.data === "object" && "message" in err.response.data
}

View File

@@ -338,3 +338,10 @@ export interface AuthenticationError {
message: string
allowRegistrations: boolean
}
export type CommaFeedExceptionType = "WRONG_USERNAME_OR_PASSWORD"
export interface CommaFeedApplicationError {
type: CommaFeedExceptionType
message: string
}

View File

@@ -1150,6 +1150,10 @@ msgstr "موقع الكتروني"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "مرحباً! يبدو أن هذه هي المرة الأولى التي تقوم فيها بتشغيل CommaFeed. يرجى إنشاء حساب مسؤول للبدء."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "أصفر"

View File

@@ -1150,6 +1150,10 @@ msgstr "Lloc web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Benvingut! Sembla que aquesta és la primera vegada que executeu CommaFeed. Creeu un compte d'administrador per començar."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Groc"

View File

@@ -1150,6 +1150,10 @@ msgstr "Webová stránka"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Vítejte! Zdá se, že je to poprvé, co spouštíte CommaFeed. Chcete-li začít, vytvořte si účet správce."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Žlutá"

View File

@@ -1150,6 +1150,10 @@ msgstr "Gwefan"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Croeso! Ymddengys mai dyma'r tro cyntaf i chi redeg CommaFeed. Creuwch gyfrif gweinyddwr i ddechrau."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Melyn"

View File

@@ -1150,6 +1150,10 @@ msgstr "Hjemmeside"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Velkommen! Det ser ud til at være første gang, du kører CommaFeed. Opret venligst en administratorkonto for at komme i gang."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Gul"

View File

@@ -1150,6 +1150,10 @@ msgstr "Webseite"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Willkommen! Dies scheint das erste Mal zu sein, dass Sie CommaFeed ausführen. Bitte erstellen Sie ein Administrator-Konto, um zu beginnen."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Gelb"

View File

@@ -1150,6 +1150,10 @@ msgstr "Website"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr "Wrong username or password"
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Yellow"

View File

@@ -1151,6 +1151,10 @@ msgstr "Sitio web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "¡Bienvenido! Parece que esta es la primera vez que ejecutas CommaFeed. Por favor, crea una cuenta de administrador para empezar."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Amarillo"

View File

@@ -1150,6 +1150,10 @@ msgstr "وب سایت"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "خوش آمدید! به نظر می‌رسد این اولین بار است که CommaFeed را اجرا می‌کنید. لطفاً برای شروع یک حساب مدیر ایجاد کنید."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "زرد"

View File

@@ -1150,6 +1150,10 @@ msgstr "Verkkosivusto"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Tervetuloa! Näyttää siltä, että suoritat CommaFeediä ensimmäistä kertaa. Luo ylläpitäjän tili aloittaaksesi."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Keltainen"

View File

@@ -1150,6 +1150,10 @@ msgstr "Site web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Bienvenue ! Il semble que ce soit le premier démarrage de Commafeed. Avant tout, vous devez créer un compte administrateur."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Jaune"

View File

@@ -1151,6 +1151,10 @@ msgstr "Páxina web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Benvida! Semella que é a primeira vez que executas CommaFeed. Para comezar, crea unha conta de administración."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Amarelo"

View File

@@ -1150,6 +1150,10 @@ msgstr "Webhely"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Üdvözöljük! Úgy tűnik, ez az első alkalom, hogy a CommaFeedet futtatja. Kérjük, hozzon létre egy rendszergazdai fiókot a kezdéshez."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Sárga"

View File

@@ -1150,6 +1150,10 @@ msgstr "Situs Web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Selamat datang! Ini sepertinya pertama kalinya Anda menjalankan CommaFeed. Silakan buat akun administrator untuk memulai."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Kuning"

View File

@@ -1150,6 +1150,10 @@ msgstr "Sito web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Benvenuto! Sembra che sia la prima volta che esegui CommaFeed. Crea un account amministratore per iniziare."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Giallo"

View File

@@ -1150,6 +1150,10 @@ msgstr "ウェブサイト"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "ようこそCommaFeedを初めて実行するようです。開始するには管理者アカウントを作成してください。"
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "イエロー"

View File

@@ -1150,6 +1150,10 @@ msgstr "웹사이트"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "환영합니다! CommaFeed를 처음 실행하는 것 같습니다. 시작하려면 관리자 계정을 만드십시오."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "옐로우"

View File

@@ -1150,6 +1150,10 @@ msgstr "Laman web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Selamat datang! Ini nampaknya kali pertama anda menjalankan CommaFeed. Sila cipta akaun pentadbir untuk bermula."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Kuning"

View File

@@ -1150,6 +1150,10 @@ msgstr "Nettsted"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Velkommen! Det ser ut til å være første gang du kjører CommaFeed. Vennligst opprett en administratorkonto for å komme i gang."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Gul"

View File

@@ -1150,6 +1150,10 @@ msgstr "Website"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Welkom! Dit lijkt de eerste keer te zijn dat je CommaFeed draait. Maak een beheerdersaccount aan om aan de slag te gaan."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Geel"

View File

@@ -1150,6 +1150,10 @@ msgstr "Nettstad"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Velkomen! Det ser ut til å vera fyrste gongen du køyrer CommaFeed. Ver venleg og opprett ein administratorkonto for å koma i gang."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Gul"

View File

@@ -1150,6 +1150,10 @@ msgstr "Strona internetowa"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Witaj! Wygląda na to, że uruchamiasz CommaFeed po raz pierwszy. Aby rozpocząć, utwórz konto administratora."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Żółty"

View File

@@ -1150,6 +1150,10 @@ msgstr "Site"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Bem-vindo! Esta parece ser a primeira vez que você está executando o CommaFeed. Crie uma conta de administrador para começar."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Amarelo"

View File

@@ -1150,6 +1150,10 @@ msgstr "Веб-сайт"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Добро пожаловать! Похоже, вы запускаете CommaFeed в первый раз. Пожалуйста, создайте учетную запись администратора, чтобы начать работу."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Желтый"

View File

@@ -1150,6 +1150,10 @@ msgstr "Webová stránka"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Vitajte! Zdá sa, že CommaFeed spúšťate prvýkrát. Ak chcete začať, vytvorte si účet správcu."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Žltá"

View File

@@ -1150,6 +1150,10 @@ msgstr "Webbplats"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Välkommen! Det verkar vara första gången du kör CommaFeed. Skapa ett administratörskonto för att komma igång."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Gul"

View File

@@ -1150,6 +1150,10 @@ msgstr "Web sitesi"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "Hoş geldiniz! Görünüşe göre CommaFeed'i ilk kez çalıştırıyorsunuz. Başlamak için lütfen bir yönetici hesabı oluşturun."
#: src/app/client.ts
msgid "Wrong username or password"
msgstr ""
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "Sarı"

View File

@@ -1150,6 +1150,10 @@ msgstr "网站"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
msgstr "欢迎当前页仅当您第一次使用CommaFeed时出现请创建一个管理员帐号以开始使用。"
#: src/app/client.ts
msgid "Wrong username or password"
msgstr "用户名或密码错误"
#: src/components/settings/DisplaySettings.tsx
msgid "Yellow"
msgstr "黄"

View File

@@ -0,0 +1,20 @@
package com.commafeed;
import java.io.Serial;
import java.util.Objects;
public class CommaFeedApplicationException extends RuntimeException {
@Serial private static final long serialVersionUID = 1L;
private final CommaFeedExceptionType type;
public CommaFeedApplicationException(CommaFeedExceptionType type) {
super(Objects.requireNonNull(type).message());
this.type = type;
}
public CommaFeedExceptionType type() {
return type;
}
}

View File

@@ -0,0 +1,23 @@
package com.commafeed;
import org.jboss.resteasy.reactive.RestResponse.Status;
public enum CommaFeedExceptionType {
WRONG_USERNAME_OR_PASSWORD(Status.UNAUTHORIZED, "wrong username or password");
private final Status status;
private final String message;
CommaFeedExceptionType(Status status, String message) {
this.status = status;
this.message = message;
}
public Status status() {
return status;
}
public String message() {
return message;
}
}

View File

@@ -26,6 +26,18 @@ public class ExceptionMappers {
private final CookieService cookieService;
private final CommaFeedConfiguration config;
@ServerExceptionMapper(CommaFeedApplicationException.class)
public RestResponse<CommaFeedApplicationError> applicationError(
CommaFeedApplicationException e) {
ResponseBuilder<CommaFeedApplicationError> response =
ResponseBuilder.create(
e.type().status(), new CommaFeedApplicationError(e.type(), e.getMessage()));
if (e.type().status() == Status.UNAUTHORIZED) {
response.cookie(cookieService.buildLogoutCookie());
}
return response.build();
}
@ServerExceptionMapper(UnauthorizedException.class)
public RestResponse<UnauthorizedResponse> unauthorized(UnauthorizedException e) {
return RestResponse.status(
@@ -55,4 +67,7 @@ public class ExceptionMappers {
@RegisterForReflection
public record ValidationFailed(String message) {}
@RegisterForReflection
public record CommaFeedApplicationError(CommaFeedExceptionType type, String message) {}
}

View File

@@ -1,11 +1,12 @@
package com.commafeed.security.identity;
import com.commafeed.CommaFeedApplicationException;
import com.commafeed.CommaFeedExceptionType;
import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole.Role;
import com.commafeed.backend.service.UserService;
import io.quarkus.security.AuthenticationFailedException;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.IdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
@@ -48,7 +49,8 @@ public class DatabaseUsernamePasswordIdentityProvider
new String(
request.getPassword().getPassword())));
if (user.isEmpty()) {
throw new AuthenticationFailedException("wrong username or password");
throw new CommaFeedApplicationException(
CommaFeedExceptionType.WRONG_USERNAME_OR_PASSWORD);
}
Set<Role> roles = unitOfWork.call(() -> userService.getRoles(user.get()));

View File

@@ -36,7 +36,7 @@ class AuthentificationIT {
page.navigate(getLoginPageUrl());
PlaywrightTestUtils.login(page, TestConstants.ADMIN_USERNAME, "wrong_password");
PlaywrightAssertions.assertThat(page.getByRole(AriaRole.ALERT))
.containsText("wrong username or password");
.containsText("Wrong username or password");
}
@Test

View File

@@ -1,5 +1,7 @@
package com.commafeed.integration;
import com.commafeed.CommaFeedExceptionType;
import com.commafeed.ExceptionMappers.CommaFeedApplicationError;
import com.commafeed.ExceptionMappers.UnauthorizedResponse;
import com.commafeed.TestConstants;
import com.commafeed.frontend.model.Entries;
@@ -57,6 +59,27 @@ class SecurityIT extends BaseIT {
.statusCode(HttpStatus.SC_OK);
}
@Test
void formLoginWrongPassword() {
CommaFeedApplicationError error =
RestAssured.given()
.auth()
.none()
.formParams(
"j_username",
TestConstants.ADMIN_USERNAME,
"j_password",
"wrong-password")
.post("j_security_check")
.then()
.statusCode(HttpStatus.SC_UNAUTHORIZED)
.extract()
.as(CommaFeedApplicationError.class);
Assertions.assertEquals(CommaFeedExceptionType.WRONG_USERNAME_OR_PASSWORD, error.type());
Assertions.assertEquals("wrong username or password", error.message());
}
@Test
void basicAuthLogin() {
RestAssured.given()