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 个字符",