From 5eb6deb26cc38d8460aabe0cfbb3a79cee1189a4 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:50:56 +0100 Subject: [PATCH] fix: show password and two-factor settings after a password and TOTP sign-in #1432 --- .../account-security-signin.test.tsx | 117 ++++++++++++++++++ .../settings/account-security-settings.tsx | 20 ++- components/settings/account-settings.tsx | 7 +- lib/auth/password-session.ts | 18 +++ locales/ar/common.json | 1 + locales/ca/common.json | 1 + locales/cs/common.json | 1 + locales/da/common.json | 1 + locales/de/common.json | 1 + locales/en/common.json | 1 + locales/es/common.json | 1 + locales/et/common.json | 1 + locales/fa/common.json | 1 + locales/fr/common.json | 1 + locales/he/common.json | 1 + locales/hu/common.json | 1 + locales/it/common.json | 1 + locales/ja/common.json | 1 + locales/ko/common.json | 1 + locales/lv/common.json | 1 + locales/mn/common.json | 1 + locales/nb/common.json | 1 + locales/nl/common.json | 1 + locales/pl/common.json | 1 + locales/pt/common.json | 1 + locales/ro/common.json | 1 + locales/ru/common.json | 1 + locales/sk/common.json | 1 + locales/th/common.json | 1 + locales/tr/common.json | 1 + locales/uk/common.json | 1 + locales/zh-TW/common.json | 1 + locales/zh/common.json | 1 + 33 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 components/settings/__tests__/account-security-signin.test.tsx create mode 100644 lib/auth/password-session.ts diff --git a/components/settings/__tests__/account-security-signin.test.tsx b/components/settings/__tests__/account-security-signin.test.tsx new file mode 100644 index 000000000..9e6c8f6a5 --- /dev/null +++ b/components/settings/__tests__/account-security-signin.test.tsx @@ -0,0 +1,117 @@ +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { AccountSecuritySettings } from '../account-security-settings'; +import { useAccountStore, type AccountEntry } from '@/stores/account-store'; +import { useAccountSecurityStore } from '@/stores/account-security-store'; +import { useAuthStore } from '@/stores/auth-store'; + +const mocks = vi.hoisted(() => ({ + toastSuccess: vi.fn(), + toastError: vi.fn(), +})); + +vi.mock('next-intl', () => { + const t = Object.assign((key: string) => key, { raw: (key: string) => key }); + return { useTranslations: () => t, useLocale: () => 'en' }; +}); + +vi.mock('@/hooks/use-config', () => ({ + useConfig: () => ({ oauthEnabled: false }), +})); + +vi.mock('@/stores/toast-store', () => ({ + toast: { success: mocks.toastSuccess, error: mocks.toastError, info: vi.fn() }, +})); + +function signIn(authMode: AccountEntry['authMode'], providerSession: boolean | undefined) { + const entry = { + id: 'me@example.com@mail.example.com', + label: 'me@example.com', + serverUrl: 'https://mail.example.com', + username: 'me@example.com', + authMode, + cookieSlot: 0, + rememberMe: false, + providerSession, + displayName: '', + email: 'me@example.com', + avatarColor: '#000000', + lastLoginAt: 0, + isConnected: true, + hasError: false, + isDefault: true, + } as AccountEntry; + useAccountStore.setState({ accounts: [entry], activeAccountId: entry.id }); + // No client keeps the probe effect quiet; the verdict is set directly. + useAuthStore.setState({ isAuthenticated: false, authMode, client: null, activeAccountId: entry.id }); +} + +beforeEach(() => { + vi.clearAllMocks(); + useAccountSecurityStore.setState({ isStalwart: true, isProbing: false, otpEnabled: false, isSaving: false }); +}); + +describe('AccountSecuritySettings sign-in section', () => { + it('shows password and two-factor to a password sign-in', () => { + signIn('basic', false); + render(); + expect(screen.getByText('password.title')).toBeInTheDocument(); + expect(screen.getByText('totp.section_title')).toBeInTheDocument(); + }); + + it('shows them to a password plus TOTP sign-in, which runs on OAuth tokens (#1432)', () => { + signIn('oauth', false); + render(); + expect(screen.getByText('password.title')).toBeInTheDocument(); + expect(screen.getByText('totp.section_title')).toBeInTheDocument(); + }); + + it.each([ + ['an identity-provider sign-in', 'oauth', true], + ['a session from before providerSession was recorded', 'oauth', undefined], + ['an access-token sign-in', 'token', false], + ] as const)('hides them for %s', (_name, authMode, providerSession) => { + signIn(authMode, providerSession); + render(); + expect(screen.queryByText('password.title')).toBeNull(); + expect(screen.queryByText('totp.section_title')).toBeNull(); + expect(screen.getByText('email_client.title')).toBeInTheDocument(); + }); +}); + +describe('AccountSecuritySettings password change', () => { + function changePassword(container: HTMLElement) { + const [current, ...fresh] = [ + container.querySelector('input[autocomplete="current-password"]'), + ...container.querySelectorAll('input[autocomplete="new-password"]'), + ] as HTMLInputElement[]; + fireEvent.change(current, { target: { value: 'old-password' } }); + for (const input of fresh) fireEvent.change(input, { target: { value: 'new-password-1' } }); + fireEvent.click(screen.getByRole('button', { name: 'password.submit' })); + } + + it('signs a token session out, since Stalwart revokes its tokens with the old password', async () => { + signIn('oauth', false); + const change = vi.fn(async () => {}); + const logout = vi.fn(async () => {}); + useAccountSecurityStore.setState({ changePassword: change }); + useAuthStore.setState({ logout }); + const { container } = render(); + changePassword(container); + await waitFor(() => expect(logout).toHaveBeenCalledWith({ endProviderSession: false })); + expect(change).toHaveBeenCalledWith('old-password', 'new-password-1', undefined); + expect(mocks.toastSuccess).toHaveBeenCalledWith('password.success', 'password.sign_in_again'); + }); + + it('keeps a password session signed in', async () => { + signIn('basic', false); + const change = vi.fn(async () => {}); + const logout = vi.fn(async () => {}); + useAccountSecurityStore.setState({ changePassword: change }); + useAuthStore.setState({ logout }); + const { container } = render(); + changePassword(container); + await waitFor(() => expect(mocks.toastSuccess).toHaveBeenCalledWith('password.success')); + expect(logout).not.toHaveBeenCalled(); + }); +}); diff --git a/components/settings/account-security-settings.tsx b/components/settings/account-security-settings.tsx index 3050067d8..9e1e8fbaf 100644 --- a/components/settings/account-security-settings.tsx +++ b/components/settings/account-security-settings.tsx @@ -17,6 +17,7 @@ import { toast } from '@/stores/toast-store'; import { cn } from '@/lib/utils'; import { sanitizeI18nHtml } from '@/lib/email-sanitization'; import { IS_LITE } from '@/lib/lite'; +import { isPasswordSession } from '@/lib/auth/password-session'; import { useConfig } from '@/hooks/use-config'; import { totpIssuer } from '@/lib/totp-issuer'; import { ConfirmDialog } from '@/components/ui/confirm-dialog'; @@ -54,6 +55,13 @@ function PasswordChangeSection() { setOtpCode(''); setNewPassword(''); setConfirmPassword(''); + // Stalwart revokes the account's tokens with the old password, refresh + // token included, so a password plus TOTP session ends here (#1432). + if (useAuthStore.getState().authMode !== 'basic') { + toast.success(t('password.success'), t('password.sign_in_again')); + void useAuthStore.getState().logout({ endProviderSession: false }); + return; + } toast.success(t('password.success')); } catch (err) { const msg = err instanceof Error ? err.message : t('password.error_generic'); @@ -1475,9 +1483,10 @@ export function AccountSecuritySettings() { const t = useTranslations('settings.security'); const { isStalwart, isProbing, probe, fetchAll, fetchAuthInfo, fetchPublicKeys, fetchCryptoInfo } = useAccountSecurityStore(); const { isAuthenticated, authMode, client } = useAuthStore(); - // OAuth and access-token sign-ins hold no password to change, and mail - // apps need an app password instead. - const withoutPassword = authMode === 'oauth' || authMode === 'token'; + const activeAccount = useAccountStore((s) => s.getActiveAccount()); + // Identity-provider and access-token sign-ins hold no password to change + // here, and mail apps need an app password instead. + const withoutPassword = !isPasswordSession(authMode, activeAccount); // Wait for `client` before probing. On reload the persisted `isAuthenticated` // flips true before the async OAuth reconnect sets `client`; probing in that @@ -1541,8 +1550,9 @@ export function AccountSecuritySettings() { return (
- {/* Sign-in: password and two-factor. OAuth and token sessions have - neither here, so the page opens with the apps and devices section. */} + {/* Sign-in: password and two-factor. Identity-provider and token + sessions have neither here, so the page opens with the apps and + devices section. */} {!withoutPassword && (
diff --git a/components/settings/account-settings.tsx b/components/settings/account-settings.tsx index 07288f3a8..5d9f6790e 100644 --- a/components/settings/account-settings.tsx +++ b/components/settings/account-settings.tsx @@ -16,6 +16,7 @@ import { getMaxAccounts } from '@/lib/account-utils'; import { formatFileSize, cn } from '@/lib/utils'; import { toUnicodeDomain, toUnicodeEmail } from '@/lib/idn'; import { IS_LITE } from '@/lib/lite'; +import { isPasswordSession } from '@/lib/auth/password-session'; import { usePolicyStore } from '@/stores/policy-store'; import { AccountArchiveSettings } from './account-archive-settings'; import { DisplayNameSection } from './account-security-settings'; @@ -48,9 +49,11 @@ export function AccountSettings() { const archiveEnabled = usePolicyStore((s) => s.isFeatureEnabled('accountArchiveEnabled')) && !IS_LITE && !isDemoMode; const isAuthenticated = useAuthStore((s) => s.isAuthenticated); // The display name is editable on Stalwart (the principal's name) for - // password sign-ins; OAuth and token sessions cannot manage the principal. + // password sign-ins; identity-provider and token sessions cannot manage + // the principal. const { isStalwart, probe, fetchPrincipal } = useAccountSecurityStore(); - const canEditDisplayName = isStalwart === true && authMode !== 'oauth' && authMode !== 'token'; + const activeAccount = useAccountStore((s) => (activeAccountId ? s.getAccountById(activeAccountId) : undefined)); + const canEditDisplayName = isStalwart === true && isPasswordSession(authMode, activeAccount); // Wait for `client` before probing, as the Security page does: on reload // the persisted session flips authenticated before the client reconnects. useEffect(() => { diff --git a/lib/auth/password-session.ts b/lib/auth/password-session.ts new file mode 100644 index 000000000..6f7c53fdd --- /dev/null +++ b/lib/auth/password-session.ts @@ -0,0 +1,18 @@ +import type { AccountEntry } from '@/stores/account-store'; + +/** + * The session was signed in with the account's own password, so the server + * holds a password (and maybe TOTP) the user can manage here. A password plus + * TOTP login runs on OAuth tokens (authMode 'oauth') but is still one of them + * (#1432). A sign-in through the identity provider may come from an outside + * IdP that holds the password instead, and a pasted access token says nothing + * about how the account signs in. Accounts signed in before `providerSession` + * was recorded count as provider sign-ins. + */ +export function isPasswordSession( + authMode: AccountEntry['authMode'] | null | undefined, + account: Pick | null | undefined, +): boolean { + if (authMode === 'basic') return true; + return authMode === 'oauth' && account?.providerSession === false; +} diff --git a/locales/ar/common.json b/locales/ar/common.json index 5a6ba93bc..1ada2356d 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -1980,6 +1980,7 @@ "confirm": "تأكيد كلمة المرور الجديدة", "submit": "تغيير كلمة المرور", "success": "تم تغيير كلمة المرور بنجاح", + "sign_in_again": "سجّل الدخول مرة أخرى باستخدام كلمة المرور الجديدة.", "error_title": "فشل تغيير كلمة المرور", "error_mismatch": "كلمتا المرور الجديدتان غير متطابقتين", "error_min_length": "يجب أن تتكون كلمة المرور من 8 أحرف على الأقل", diff --git a/locales/ca/common.json b/locales/ca/common.json index 451183c50..53532690a 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -1980,6 +1980,7 @@ "confirm": "Confirma la contrasenya nova", "submit": "Canvia la contrasenya", "success": "Contrasenya canviada correctament", + "sign_in_again": "Torna a iniciar la sessió amb la contrasenya nova.", "error_title": "No s'ha pogut canviar la contrasenya", "error_mismatch": "Les contrasenyes noves no coincideixen", "error_min_length": "La contrasenya ha de tenir com a mínim 8 caràcters", diff --git a/locales/cs/common.json b/locales/cs/common.json index be52b2dff..a69d51884 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -1973,6 +1973,7 @@ "confirm": "Potvrdit nové heslo", "submit": "Změnit heslo", "success": "Heslo bylo změněno", + "sign_in_again": "Přihlaste se znovu pomocí nového hesla.", "error_title": "Změna hesla selhala", "error_mismatch": "Nová hesla se neshodují", "error_min_length": "Heslo musí obsahovat alespoň 8 znaků", diff --git a/locales/da/common.json b/locales/da/common.json index 63b751bce..4d53bdcae 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -1976,6 +1976,7 @@ "confirm": "Bekræft ny adgangskode", "submit": "Skift adgangskode", "success": "Adgangskode ændret succesfuldt", + "sign_in_again": "Log ind igen med din nye adgangskode.", "error_title": "Adgangskodeændring mislykkedes", "error_mismatch": "De nye adgangskoder er ikke ens", "error_min_length": "Adgangskoden skal være mindst 8 tegn", diff --git a/locales/de/common.json b/locales/de/common.json index 018dff874..c6fc49a42 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -1973,6 +1973,7 @@ "confirm": "Neues Passwort bestätigen", "submit": "Passwort ändern", "success": "Passwort erfolgreich geändert", + "sign_in_again": "Melden Sie sich mit Ihrem neuen Passwort erneut an.", "error_title": "Passwortänderung fehlgeschlagen", "error_mismatch": "Neue Passwörter stimmen nicht überein", "error_min_length": "Das Passwort muss mindestens 8 Zeichen lang sein", diff --git a/locales/en/common.json b/locales/en/common.json index 063908f2c..52fea0f7c 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -1980,6 +1980,7 @@ "confirm": "Confirm new password", "submit": "Change password", "success": "Password changed successfully", + "sign_in_again": "Sign in again with your new password.", "error_title": "Password change failed", "error_mismatch": "New passwords do not match", "error_min_length": "Password must be at least 8 characters", diff --git a/locales/es/common.json b/locales/es/common.json index cd6afed44..52de4fd82 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -1973,6 +1973,7 @@ "confirm": "Confirmar nueva contraseña", "submit": "Cambiar contraseña", "success": "Contraseña cambiada exitosamente", + "sign_in_again": "Vuelve a iniciar sesión con tu nueva contraseña.", "error_title": "Error al cambiar la contraseña", "error_mismatch": "Las nuevas contraseñas no coinciden", "error_min_length": "La contraseña debe tener al menos 8 caracteres", diff --git a/locales/et/common.json b/locales/et/common.json index f59f55a2e..19d669018 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -1980,6 +1980,7 @@ "confirm": "Kinnita uus parool", "submit": "Muuda parooli", "success": "Parool muudeti edukalt", + "sign_in_again": "Logi uuesti sisse oma uue parooliga.", "error_title": "Parooli muutmine ebaõnnestus", "error_mismatch": "Uued paroolid ei kattu", "error_min_length": "Parool peab olema vähemalt 8 tähemärki pikk", diff --git a/locales/fa/common.json b/locales/fa/common.json index b13bb3933..1004c4a0d 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -1980,6 +1980,7 @@ "confirm": "تأیید رمز جدید", "submit": "تغییر رمز", "success": "رمز تغییر کرد", + "sign_in_again": "با گذرواژه جدید دوباره وارد شوید.", "error_title": "خطا", "error_mismatch": "مطابقت ندارد", "error_min_length": "حداقل ۸ کاراکتر", diff --git a/locales/fr/common.json b/locales/fr/common.json index 7899f22ef..b1c405f93 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -1973,6 +1973,7 @@ "confirm": "Confirmer le nouveau mot de passe", "submit": "Changer le mot de passe", "success": "Mot de passe changé avec succès", + "sign_in_again": "Reconnectez-vous avec votre nouveau mot de passe.", "error_title": "Échec du changement de mot de passe", "error_mismatch": "Les nouveaux mots de passe ne correspondent pas", "error_min_length": "Le mot de passe doit comporter au moins 8 caractères", diff --git a/locales/he/common.json b/locales/he/common.json index 8a1216348..42cf9cf74 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -1938,6 +1938,7 @@ "confirm": "אשר סיסמה חדשה", "submit": "שנה סיסמה", "success": "הסיסמה שונתה בהצלחה", + "sign_in_again": "יש להתחבר שוב עם הסיסמה החדשה.", "error_title": "שינוי הסיסמה נכשל", "error_mismatch": "סיסמאות חדשות אינן תואמות", "error_min_length": "הסיסמה חייבת להיות לפחות 8 תווים", diff --git a/locales/hu/common.json b/locales/hu/common.json index ba09827f8..da9a6bd4c 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -1976,6 +1976,7 @@ "confirm": "Új jelszó megerősítése", "submit": "Jelszó módosítása", "success": "Jelszó sikeresen módosítva", + "sign_in_again": "Jelentkezz be újra az új jelszavaddal.", "error_title": "Jelszó módosítás sikertelen", "error_mismatch": "Az új jelszavak nem egyeznek", "error_min_length": "A jelszónak legalább 8 karakter hosszúnak kell lennie", diff --git a/locales/it/common.json b/locales/it/common.json index 34ed8b28c..8ed7b1104 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -1973,6 +1973,7 @@ "confirm": "Conferma nuova password", "submit": "Cambia password", "success": "Password cambiata con successo", + "sign_in_again": "Accedi di nuovo con la nuova password.", "error_title": "Cambio password fallito", "error_mismatch": "Le nuove password non corrispondono", "error_min_length": "La password deve contenere almeno 8 caratteri", diff --git a/locales/ja/common.json b/locales/ja/common.json index 7f8e2cfee..35b3d16b7 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -1973,6 +1973,7 @@ "confirm": "新しいパスワードの確認", "submit": "パスワードを変更", "success": "パスワードが正常に変更されました", + "sign_in_again": "新しいパスワードでもう一度サインインしてください。", "error_title": "パスワードの変更に失敗しました", "error_mismatch": "新しいパスワードが一致しません", "error_min_length": "パスワードは8文字以上である必要があります", diff --git a/locales/ko/common.json b/locales/ko/common.json index 10d5a20cc..05aa4637e 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -1973,6 +1973,7 @@ "confirm": "새 비밀번호 확인", "submit": "비밀번호 변경", "success": "비밀번호가 성공적으로 변경되었어요", + "sign_in_again": "새 비밀번호로 다시 로그인하세요.", "error_title": "비밀번호 변경 실패", "error_mismatch": "새 비밀번호가 서로 일치하지 않아요", "error_min_length": "비밀번호는 최소 8자 이상이어야 해요", diff --git a/locales/lv/common.json b/locales/lv/common.json index 87bb2f062..aa160b001 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -1973,6 +1973,7 @@ "confirm": "Apstipriniet jauno paroli", "submit": "Mainīt paroli", "success": "Parole veiksmīgi mainīta", + "sign_in_again": "Piesakieties vēlreiz ar savu jauno paroli.", "error_title": "Neizdevās mainīt paroli", "error_mismatch": "Jaunās paroles nesakrīt", "error_min_length": "Parolei jābūt vismaz 8 rakstzīmes garai", diff --git a/locales/mn/common.json b/locales/mn/common.json index 813e0f5b9..16a011495 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -1980,6 +1980,7 @@ "confirm": "Шинэ нууц үгээ баталгаажуулах", "submit": "Нууц үг солих", "success": "Нууц үг амжилттай өөрчлөгдсөн", + "sign_in_again": "Шинэ нууц үгээрээ дахин нэвтэрнэ үү.", "error_title": "Нууц үг солигдож чадсангүй", "error_mismatch": "Шинэ нууц үг таарахгүй байна", "error_min_length": "Нууц үг хамгийн багадаа 8 тэмдэгт байх ёстой", diff --git a/locales/nb/common.json b/locales/nb/common.json index c9a91702f..6f97cd355 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -1980,6 +1980,7 @@ "confirm": "Bekreft nytt passord", "submit": "Endre passord", "success": "Passordet ble endret", + "sign_in_again": "Logg inn på nytt med det nye passordet ditt.", "error_title": "Passordendring mislyktes", "error_mismatch": "De nye passordene er ikke like", "error_min_length": "Passordet må inneholde minst 8 tegn", diff --git a/locales/nl/common.json b/locales/nl/common.json index 4c5a0596d..d5fe9666a 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -1973,6 +1973,7 @@ "confirm": "Bevestig nieuw wachtwoord", "submit": "Wachtwoord wijzigen", "success": "Wachtwoord succesvol gewijzigd", + "sign_in_again": "Meld je opnieuw aan met je nieuwe wachtwoord.", "error_title": "Wachtwoord wijzigen mislukt", "error_mismatch": "Nieuwe wachtwoorden komen niet overeen", "error_min_length": "Wachtwoord moet minimaal 8 tekens bevatten", diff --git a/locales/pl/common.json b/locales/pl/common.json index 91ccd9ee8..1ea48a2ba 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -1973,6 +1973,7 @@ "confirm": "Potwierdź nowe hasło", "submit": "Zmień hasło", "success": "Hasło zostało zmienione", + "sign_in_again": "Zaloguj się ponownie, używając nowego hasła.", "error_title": "Zmiana hasła nie powiodła się", "error_mismatch": "Nowe hasła nie są zgodne", "error_min_length": "Hasło musi mieć co najmniej 8 znaków", diff --git a/locales/pt/common.json b/locales/pt/common.json index e95b743cf..eab2194a0 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -1973,6 +1973,7 @@ "confirm": "Confirmar nova senha", "submit": "Alterar senha", "success": "Senha alterada com sucesso", + "sign_in_again": "Inicie sessão novamente com a sua nova palavra-passe.", "error_title": "Falha ao alterar a senha", "error_mismatch": "As novas senhas não coincidem", "error_min_length": "A senha deve ter pelo menos 8 caracteres", diff --git a/locales/ro/common.json b/locales/ro/common.json index c978d728e..cb7d23be1 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -1980,6 +1980,7 @@ "confirm": "Confirmă noua parolă", "submit": "Schimbă parola", "success": "Parola a fost modificată cu succes", + "sign_in_again": "Conectează-te din nou cu parola nouă.", "error_title": "Schimbarea parolei a eșuat", "error_mismatch": "Noile parole nu se potrivesc", "error_min_length": "Parola trebuie să conțină cel puțin 8 caractere", diff --git a/locales/ru/common.json b/locales/ru/common.json index 2cca2d7d1..8eb73f0f3 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -1973,6 +1973,7 @@ "confirm": "Подтвердите новый пароль", "submit": "Изменить пароль", "success": "Пароль успешно изменён", + "sign_in_again": "Войдите снова с новым паролем.", "error_title": "Не удалось изменить пароль", "error_mismatch": "Новые пароли не совпадают", "error_min_length": "Пароль должен содержать не менее 8 символов", diff --git a/locales/sk/common.json b/locales/sk/common.json index c91bed5b5..ebf98c2a0 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -1980,6 +1980,7 @@ "confirm": "Potvrdiť nové heslo", "submit": "Zmeniť heslo", "success": "Heslo bolo zmenené", + "sign_in_again": "Prihláste sa znova pomocou nového hesla.", "error_title": "Zmena hesla zlyhala", "error_mismatch": "Nové heslá sa nezhodujú", "error_min_length": "Heslo musí mať aspoň 8 znakov", diff --git a/locales/th/common.json b/locales/th/common.json index d36e7b7e6..6a47011aa 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -4506,6 +4506,7 @@ "confirm": "ยืนยันรหัสผ่านใหม่", "submit": "เปลี่ยนรหัสผ่าน", "success": "เปลี่ยนรหัสผ่านสำเร็จแล้ว", + "sign_in_again": "โปรดลงชื่อเข้าใช้อีกครั้งด้วยรหัสผ่านใหม่", "error_title": "เปลี่ยนรหัสผ่านไม่สำเร็จ", "error_mismatch": "รหัสผ่านใหม่ไม่ตรงกัน", "error_min_length": "รหัสผ่านต้องมีความยาวอย่างน้อย 8 อักขระ", diff --git a/locales/tr/common.json b/locales/tr/common.json index cc73bc1c4..456d539a7 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -1973,6 +1973,7 @@ "confirm": "Yeni Parolayı Onayla", "submit": "Parolayı Değiştir", "success": "Parola başarıyla değiştirildi", + "sign_in_again": "Yeni şifrenizle tekrar oturum açın.", "error_title": "Parola değiştirme başarısız", "error_mismatch": "Yeni parolalar eşleşmiyor", "error_min_length": "Parola en az 8 karakter olmalıdır", diff --git a/locales/uk/common.json b/locales/uk/common.json index 14fd7f9ce..cba7343db 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -1973,6 +1973,7 @@ "confirm": "Підтвердьте новий пароль", "submit": "Змінити пароль", "success": "Пароль успішно змінено", + "sign_in_again": "Увійдіть знову з новим паролем.", "error_title": "Не вдалося змінити пароль", "error_mismatch": "Нові паролі не збігаються", "error_min_length": "Пароль має бути не менше 8 символів", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index cd26c2b74..f3f33bee4 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -1980,6 +1980,7 @@ "confirm": "確認新密碼", "submit": "變更密碼", "success": "已成功變更密碼", + "sign_in_again": "請使用新密碼重新登入。", "error_title": "變更密碼失敗", "error_mismatch": "兩次輸入的新密碼不相符", "error_min_length": "密碼必須至少為 8 個字元", diff --git a/locales/zh/common.json b/locales/zh/common.json index c19a3cea7..455f09051 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -1973,6 +1973,7 @@ "confirm": "确认新密码", "submit": "更改密码", "success": "密码修改成功", + "sign_in_again": "请使用新密码重新登录。", "error_title": "密码修改失败", "error_mismatch": "新密码不匹配", "error_min_length": "密码必须至少为 8 个字符",