Use typed backend errors for translations

This commit is contained in:
n200534
2026-08-20 01:11:30 +05:30
parent eff8bb16a0
commit 3efec5ce58
38 changed files with 164 additions and 79 deletions

View File

@@ -1,6 +1,7 @@
import { i18n } from "@lingui/core"
import type { AxiosError } from "axios" import type { AxiosError } from "axios"
import { describe, expect, it } from "vitest" import { afterEach, describe, expect, it, vi } from "vitest"
import { loginErrorToStrings } from "./client" import { errorToStrings } from "./client"
const axiosError = (status: number, data: unknown) => const axiosError = (status: number, data: unknown) =>
({ ({
@@ -8,16 +9,28 @@ const axiosError = (status: number, data: unknown) =>
response: { status, data }, response: { status, data },
}) as AxiosError }) as AxiosError
describe("loginErrorToStrings", () => { describe("errorToStrings", () => {
it("uses the translated message for authentication errors", () => { afterEach(() => vi.restoreAllMocks())
const error = axiosError(401, { message: "wrong username or password" })
expect(loginErrorToStrings(error, "Translated authentication error")).toEqual(["Translated authentication error"]) 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", () => { it("preserves backend messages for unexpected errors", () => {
const error = axiosError(500, { message: "unexpected error" }) const error = axiosError(500, { message: "unexpected error" })
expect(loginErrorToStrings(error, "Translated authentication error")).toEqual(["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 axios, { type AxiosError } from "axios"
import type { import type {
AddCategoryRequest, AddCategoryRequest,
@@ -6,6 +8,8 @@ import type {
Category, Category,
CategoryModificationRequest, CategoryModificationRequest,
CollapseRequest, CollapseRequest,
CommaFeedApplicationError,
CommaFeedExceptionType,
Entries, Entries,
FeedInfo, FeedInfo,
FeedInfoRequest, FeedInfoRequest,
@@ -31,6 +35,10 @@ import type {
UserModel, UserModel,
} from "./types" } 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 }) const axiosInstance = axios.create({ baseURL: "./rest", withCredentials: true })
axiosInstance.interceptors.response.use( axiosInstance.interceptors.response.use(
response => response, response => response,
@@ -128,21 +136,23 @@ export const errorToStrings = (err: unknown) => {
let strings: string[] = [] let strings: string[] = []
if (axios.isAxiosError(err) && err.response) { if (axios.isAxiosError(err) && err.response) {
if (typeof err.response.data === "string") strings.push(err.response.data) if (isCommaFeedApplicationError(err)) {
if (isMessageError(err)) strings.push(err.response.data.message) strings.push(i18n._(applicationErrorMessages[err.response.data.type]))
if (isMessageArrayError(err)) strings = [...strings, ...err.response.data.errors] } 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 return strings
} }
/** function isCommaFeedApplicationError(err: AxiosError): err is AxiosError<CommaFeedApplicationError> {
* Transform a login error into messages that can be displayed to the user. const data = err.response?.data
* Authentication failures use a client-provided message so it can be translated. if (!data || typeof data !== "object" || !("type" in data)) return false
*/ const type = data.type
export const loginErrorToStrings = (err: unknown, authenticationErrorMessage: string) => { return typeof type === "string" && Object.hasOwn(applicationErrorMessages, type)
if (isAuthenticationError(err)) return [authenticationErrorMessage]
return errorToStrings(err)
} }
function isMessageError(err: AxiosError): err is AxiosError<{ message: string }> { function isMessageError(err: AxiosError): err is AxiosError<{ message: string }> {

View File

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

View File

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

View File

@@ -1150,8 +1150,7 @@ msgstr "Lloc web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Benvingut! Sembla que aquesta és la primera vegada que executeu CommaFeed. Creeu un compte d'administrador per començar."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ 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." 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." 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/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Gwefan"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Croeso! Ymddengys mai dyma'r tro cyntaf i chi redeg CommaFeed. Creuwch gyfrif gweinyddwr i ddechrau."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Hjemmeside"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." 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/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Webseite"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Willkommen! Dies scheint das erste Mal zu sein, dass Sie CommaFeed ausführen. Bitte erstellen Sie ein Administrator-Konto, um zu beginnen."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Website"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "Wrong username or password" msgstr "Wrong username or password"

View File

@@ -1151,8 +1151,7 @@ msgstr "Sitio web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "¡Bienvenido! Parece que esta es la primera vez que ejecutas CommaFeed. Por favor, crea una cuenta de administrador para empezar."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

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

View File

@@ -1150,8 +1150,7 @@ msgstr "Verkkosivusto"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Tervetuloa! Näyttää siltä, että suoritat CommaFeediä ensimmäistä kertaa. Luo ylläpitäjän tili aloittaaksesi."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Site web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Bienvenue ! Il semble que ce soit le premier démarrage de Commafeed. Avant tout, vous devez créer un compte administrateur."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1151,8 +1151,7 @@ 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." 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." msgstr "Benvida! Semella que é a primeira vez que executas CommaFeed. Para comezar, crea unha conta de administración."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Webhely"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." 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/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Situs Web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Selamat datang! Ini sepertinya pertama kalinya Anda menjalankan CommaFeed. Silakan buat akun administrator untuk memulai."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Sito web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Benvenuto! Sembra che sia la prima volta che esegui CommaFeed. Crea un account amministratore per iniziare."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

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

View File

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

View File

@@ -1150,8 +1150,7 @@ msgstr "Laman web"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Selamat datang! Ini nampaknya kali pertama anda menjalankan CommaFeed. Sila cipta akaun pentadbir untuk bermula."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Nettsted"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Velkommen! Det ser ut til å være første gang du kjører CommaFeed. Vennligst opprett en administratorkonto for å komme i gang."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Website"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Welkom! Dit lijkt de eerste keer te zijn dat je CommaFeed draait. Maak een beheerdersaccount aan om aan de slag te gaan."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Nettstad"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Velkomen! Det ser ut til å vera fyrste gongen du køyrer CommaFeed. Ver venleg og opprett ein administratorkonto for å koma i gang."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Strona internetowa"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Witaj! Wygląda na to, że uruchamiasz CommaFeed po raz pierwszy. Aby rozpocząć, utwórz konto administratora."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Site"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." msgstr "Bem-vindo! Esta parece ser a primeira vez que você está executando o CommaFeed. Crie uma conta de administrador para começar."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

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

View File

@@ -1150,8 +1150,7 @@ 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." 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." msgstr "Vitajte! Zdá sa, že CommaFeed spúšťate prvýkrát. Ak chcete začať, vytvorte si účet správcu."
#: src/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Webbplats"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." 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/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

@@ -1150,8 +1150,7 @@ msgstr "Web sitesi"
msgid "Welcome! This appears to be the first time you're running CommaFeed. Please create an administrator account to get started." 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." 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/pages/auth/LoginPage.tsx #: src/app/client.ts
#: src/pages/auth/RegistrationPage.tsx
msgid "Wrong username or password" msgid "Wrong username or password"
msgstr "" msgstr ""

View File

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

View File

@@ -5,7 +5,7 @@ import { Anchor, Box, Button, Center, Container, Group, Paper, PasswordInput, St
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { useAsyncCallback } from "react-async-hook" import { useAsyncCallback } from "react-async-hook"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import { client, loginErrorToStrings } from "@/app/client" import { client, errorToStrings } from "@/app/client"
import { redirectToRootCategory } from "@/app/redirect/thunks" import { redirectToRootCategory } from "@/app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "@/app/store" import { useAppDispatch, useAppSelector } from "@/app/store"
import type { LoginRequest } from "@/app/types" import type { LoginRequest } from "@/app/types"
@@ -39,7 +39,7 @@ export function LoginPage() {
</Title> </Title>
{login.error && ( {login.error && (
<Box mb="md"> <Box mb="md">
<Alert messages={loginErrorToStrings(login.error, _(msg`Wrong username or password`))} /> <Alert messages={errorToStrings(login.error)} />
</Box> </Box>
)} )}
<form onSubmit={form.onSubmit(login.execute)}> <form onSubmit={form.onSubmit(login.execute)}>

View File

@@ -5,7 +5,7 @@ import { Anchor, Box, Button, Center, Container, Group, Paper, PasswordInput, St
import { useForm } from "@mantine/form" import { useForm } from "@mantine/form"
import { useAsyncCallback } from "react-async-hook" import { useAsyncCallback } from "react-async-hook"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import { client, errorToStrings, loginErrorToStrings } from "@/app/client" import { client, errorToStrings } from "@/app/client"
import { redirectToRootCategory } from "@/app/redirect/thunks" import { redirectToRootCategory } from "@/app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "@/app/store" import { useAppDispatch, useAppSelector } from "@/app/store"
import type { RegistrationRequest } from "@/app/types" import type { RegistrationRequest } from "@/app/types"
@@ -65,7 +65,7 @@ export function RegistrationPage() {
{login.error && ( {login.error && (
<Box mb="md"> <Box mb="md">
<Alert messages={loginErrorToStrings(login.error, _(msg`Wrong username or password`))} /> <Alert messages={errorToStrings(login.error)} />
</Box> </Box>
)} )}

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 CookieService cookieService;
private final CommaFeedConfiguration config; 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) @ServerExceptionMapper(UnauthorizedException.class)
public RestResponse<UnauthorizedResponse> unauthorized(UnauthorizedException e) { public RestResponse<UnauthorizedResponse> unauthorized(UnauthorizedException e) {
return RestResponse.status( return RestResponse.status(
@@ -55,4 +67,7 @@ public class ExceptionMappers {
@RegisterForReflection @RegisterForReflection
public record ValidationFailed(String message) {} public record ValidationFailed(String message) {}
@RegisterForReflection
public record CommaFeedApplicationError(CommaFeedExceptionType type, String message) {}
} }

View File

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

View File

@@ -1,5 +1,7 @@
package com.commafeed.integration; package com.commafeed.integration;
import com.commafeed.CommaFeedExceptionType;
import com.commafeed.ExceptionMappers.CommaFeedApplicationError;
import com.commafeed.ExceptionMappers.UnauthorizedResponse; import com.commafeed.ExceptionMappers.UnauthorizedResponse;
import com.commafeed.TestConstants; import com.commafeed.TestConstants;
import com.commafeed.frontend.model.Entries; import com.commafeed.frontend.model.Entries;
@@ -57,6 +59,27 @@ class SecurityIT extends BaseIT {
.statusCode(HttpStatus.SC_OK); .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 @Test
void basicAuthLogin() { void basicAuthLogin() {
RestAssured.given() RestAssured.given()