From 62d956c7d6e65000d73b66cb0c9ec20d6664d8cd Mon Sep 17 00:00:00 2001 From: rustmailer Date: Tue, 30 Dec 2025 11:05:07 +0800 Subject: [PATCH] feat: increase password max to 256, fix i18n, and force re-login #83 - Raise password maximum length from 32 to 256 characters - fix profileSchema to accept `t` for proper internationalization - Invalidate user's WebUI token on password change, requiring re-login --- src/modules/mailbox/list.rs | 8 ++++++- src/modules/users/mod.rs | 6 +++++ src/modules/users/payload.rs | 8 +++---- .../settings/profile/profile-form.tsx | 22 +++++++++---------- .../users/users/components/action-dialog.tsx | 4 ++-- web/src/locales/ar.json | 4 ++-- web/src/locales/da.json | 4 ++-- web/src/locales/de.json | 4 ++-- web/src/locales/en.json | 4 ++-- web/src/locales/es.json | 4 ++-- web/src/locales/fi.json | 4 ++-- web/src/locales/fr.json | 4 ++-- web/src/locales/it.json | 4 ++-- web/src/locales/jp.json | 4 ++-- web/src/locales/ko.json | 4 ++-- web/src/locales/nl.json | 4 ++-- web/src/locales/no.json | 4 ++-- web/src/locales/pl.json | 4 ++-- web/src/locales/pt.json | 4 ++-- web/src/locales/ru.json | 4 ++-- web/src/locales/sv.json | 4 ++-- web/src/locales/zh-tw.json | 4 ++-- web/src/locales/zh.json | 4 ++-- 23 files changed, 66 insertions(+), 54 deletions(-) diff --git a/src/modules/mailbox/list.rs b/src/modules/mailbox/list.rs index 7d2a218..3f0e420 100644 --- a/src/modules/mailbox/list.rs +++ b/src/modules/mailbox/list.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use crate::modules::account::migration::{AccountModel, AccountType}; use crate::modules::cache::imap::mailbox::{Attribute, AttributeEnum, MailBox}; use crate::modules::context::executors::MAIL_CONTEXT; @@ -64,8 +63,15 @@ pub async fn convert_names_to_mailboxes( for name in names.into_iter() { // Convert the name into a MailBox structure let mailbox_name = name.name().to_string(); + let mut mailbox: MailBox = name.into(); + tracing::debug!( + raw = &mailbox_name, + decoded = &mailbox.name, + "mailbox name comparison" + ); + if contains_no_select(&mailbox.attributes) { continue; } diff --git a/src/modules/users/mod.rs b/src/modules/users/mod.rs index 5431ba7..331ffdc 100644 --- a/src/modules/users/mod.rs +++ b/src/modules/users/mod.rs @@ -444,6 +444,7 @@ impl BichonUser { pub async fn update(id: u64, request: UserUpdateRequest) -> BichonResult<()> { let _ = &request.validate().await?; + let password_changed = request.password.is_some(); if DEFAULT_ADMIN_USER_ID == id && request.global_roles.is_some() { return Err(raise_error!( @@ -537,6 +538,11 @@ impl BichonUser { }, ) .await?; + + if password_changed { + AccessTokenModel::reset_webui_token(id).await?; + } + Ok(()) } diff --git a/src/modules/users/payload.rs b/src/modules/users/payload.rs index 869ead1..02eb22c 100644 --- a/src/modules/users/payload.rs +++ b/src/modules/users/payload.rs @@ -204,9 +204,9 @@ impl UserCreateRequest { ErrorCode::InvalidParameter )); } - if password_len > 32 { + if password_len > 256 { return Err(raise_error!( - "Password cannot exceed 32 characters.".into(), + "Password cannot exceed 256 characters.".into(), ErrorCode::InvalidParameter )); } @@ -317,9 +317,9 @@ impl UserUpdateRequest { if let Some(password) = &self.password { let len = password.len(); - if len < 8 || len > 32 { + if len < 8 || len > 256 { return Err(raise_error!( - "Password must be 8-32 characters.".into(), + "Password must be 8-256 characters.".into(), ErrorCode::InvalidParameter )); } diff --git a/web/src/features/settings/profile/profile-form.tsx b/web/src/features/settings/profile/profile-form.tsx index 4ee484d..ae0117d 100644 --- a/web/src/features/settings/profile/profile-form.tsx +++ b/web/src/features/settings/profile/profile-form.tsx @@ -47,40 +47,40 @@ import AvatarUpload from './avatar-upload' import useMinimalAccountList from '@/hooks/use-minimal-account-list' import { PermissionsDialog } from './permissions-dialog' -const profileSchema = z.object({ +const profileSchema = (t: (key: string) => string) => z.object({ username: z .string({ - required_error: 'settings.profile.validation.username.required', + required_error: t('settings.profile.validation.username.required'), }) .min(5, { - message: 'settings.profile.validation.username.min', + message: t('settings.profile.validation.username.min'), }) .max(32, { - message: 'settings.profile.validation.username.max', + message: t('settings.profile.validation.username.max'), }), email: z .string({ - required_error: 'settings.profile.validation.email.required', + required_error: t('settings.profile.validation.email.required'), }) .email({ - message: 'settings.profile.validation.email.invalid', + message: t('settings.profile.validation.email.invalid'), }), password: z .string() .min(8, { - message: 'settings.profile.validation.password.min', + message: t('settings.profile.validation.password.min'), }) - .max(32, { - message: 'settings.profile.validation.password.max', + .max(256, { + message: t('settings.profile.validation.password.max'), }) .or(z.literal('')) .optional() .transform((v) => (v ? v : undefined)), }) -export type ProfileFormValues = z.infer +export type ProfileFormValues = z.infer> function fileToBase64(file: File): Promise { return new Promise((resolve, reject) => { @@ -110,7 +110,7 @@ export function UserProfileForm({ user }: UserProfileFormProps) { useState(undefined) const form = useForm({ - resolver: zodResolver(profileSchema), + resolver: zodResolver(profileSchema(t)), mode: 'onChange', defaultValues: { username: user.username, diff --git a/web/src/features/users/users/components/action-dialog.tsx b/web/src/features/users/users/components/action-dialog.tsx index 49a8669..7578192 100644 --- a/web/src/features/users/users/components/action-dialog.tsx +++ b/web/src/features/users/users/components/action-dialog.tsx @@ -111,14 +111,14 @@ const createUserSchema = (t: any) => z.object({ password: z.string() .min(1, t('users.actions.schema.password_required')) .min(8, t('users.actions.schema.password_min')) - .max(32, t('users.actions.schema.password_max')), + .max(256, t('users.actions.schema.password_max')), }); const updateUserSchema = (t: any) => z.object({ ...baseUserSchema(t), password: z.string() .min(8, t('users.actions.schema.password_min')) - .max(32, t('users.actions.schema.password_max')) + .max(256, t('users.actions.schema.password_max')) .or(z.literal('')) .optional() .transform(v => v || undefined), diff --git a/web/src/locales/ar.json b/web/src/locales/ar.json index 28d7c7a..00b357d 100644 --- a/web/src/locales/ar.json +++ b/web/src/locales/ar.json @@ -829,7 +829,7 @@ }, "password": { "min": "يجب أن تكون كلمة المرور 8 أحرف على الأقل", - "max": "يجب ألا تزيد كلمة المرور عن 32 حرفًا" + "max": "يجب ألا تزيد كلمة المرور عن 256 حرفًا" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "تم اكتشاف عنوان IP غير صالح", "password_required": "كلمة المرور مطلوبة", "password_min": "8 أحرف على الأقل", - "password_max": "32 حرفاً كحد أقصى" + "password_max": "256 حرفاً كحد أقصى" }, "toast": { "updated": "تم تحديث المستخدم", diff --git a/web/src/locales/da.json b/web/src/locales/da.json index 09ca56e..9e899c9 100644 --- a/web/src/locales/da.json +++ b/web/src/locales/da.json @@ -829,7 +829,7 @@ }, "password": { "min": "Adgangskoden skal være mindst 8 tegn", - "max": "Adgangskoden må højst være 32 tegn" + "max": "Adgangskoden må højst være 256 tegn" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Ugyldig IP-adresse", "password_required": "Adgangskode er påkrævet", "password_min": "Mindst 8 tegn", - "password_max": "Højst 32 tegn" + "password_max": "Højst 256 tegn" }, "toast": { "updated": "Bruger opdateret", diff --git a/web/src/locales/de.json b/web/src/locales/de.json index 6bc0a4b..2c20b7e 100644 --- a/web/src/locales/de.json +++ b/web/src/locales/de.json @@ -829,7 +829,7 @@ }, "password": { "min": "Das Passwort muss mindestens 8 Zeichen lang sein", - "max": "Das Passwort darf höchstens 32 Zeichen lang sein" + "max": "Das Passwort darf höchstens 256 Zeichen lang sein" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Ungültige IP-Adresse erkannt", "password_required": "Passwort ist erforderlich", "password_min": "Passwort muss mindestens 8 Zeichen lang sein", - "password_max": "Passwort darf maximal 32 Zeichen lang sein" + "password_max": "Passwort darf maximal 256 Zeichen lang sein" }, "toast": { "updated": "Benutzer aktualisiert", diff --git a/web/src/locales/en.json b/web/src/locales/en.json index c60b01e..5c328b5 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -829,7 +829,7 @@ }, "password": { "min": "Password must be at least 8 characters", - "max": "Password must be at most 32 characters" + "max": "Password must be at most 256 characters" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Invalid IP address detected", "password_required": "Password is required", "password_min": "Password must be at least 8 characters", - "password_max": "Password cannot exceed 32 characters" + "password_max": "Password cannot exceed 256 characters" }, "toast": { "updated": "User updated", diff --git a/web/src/locales/es.json b/web/src/locales/es.json index a4b8287..1e87a64 100644 --- a/web/src/locales/es.json +++ b/web/src/locales/es.json @@ -829,7 +829,7 @@ }, "password": { "min": "La contraseña debe tener al menos 8 caracteres", - "max": "La contraseña debe tener como máximo 32 caracteres" + "max": "La contraseña debe tener como máximo 256 caracteres" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Dirección IP inválida", "password_required": "La contraseña es obligatoria", "password_min": "Mínimo 8 caracteres", - "password_max": "Máximo 32 caracteres" + "password_max": "Máximo 256 caracteres" }, "toast": { "updated": "Usuario actualizado", diff --git a/web/src/locales/fi.json b/web/src/locales/fi.json index 0b1d335..fe1ecdd 100644 --- a/web/src/locales/fi.json +++ b/web/src/locales/fi.json @@ -829,7 +829,7 @@ }, "password": { "min": "Salasanan on oltava vähintään 8 merkkiä", - "max": "Salasana voi olla enintään 32 merkkiä" + "max": "Salasana voi olla enintään 256 merkkiä" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Virheellinen IP-osoite", "password_required": "Salasana on pakollinen", "password_min": "Vähintään 8 merkkiä", - "password_max": "Enintään 32 merkkiä" + "password_max": "Enintään 256 merkkiä" }, "toast": { "updated": "Käyttäjä päivitetty", diff --git a/web/src/locales/fr.json b/web/src/locales/fr.json index 9aafaff..45b9630 100644 --- a/web/src/locales/fr.json +++ b/web/src/locales/fr.json @@ -829,7 +829,7 @@ }, "password": { "min": "Le mot de passe doit contenir au moins 8 caractères", - "max": "Le mot de passe doit contenir au maximum 32 caractères" + "max": "Le mot de passe doit contenir au maximum 256 caractères" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Adresse IP invalide détectée", "password_required": "Le mot de passe est requis", "password_min": "Le mot de passe doit contenir au moins 8 caractères", - "password_max": "Le mot de passe ne peut pas dépasser 32 caractères" + "password_max": "Le mot de passe ne peut pas dépasser 256 caractères" }, "toast": { "updated": "Utilisateur mis à jour", diff --git a/web/src/locales/it.json b/web/src/locales/it.json index 5815544..ee5ebd0 100644 --- a/web/src/locales/it.json +++ b/web/src/locales/it.json @@ -829,7 +829,7 @@ }, "password": { "min": "La password deve contenere almeno 8 caratteri", - "max": "La password deve contenere al massimo 32 caratteri" + "max": "La password deve contenere al massimo 256 caratteri" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Indirizzo IP non valido", "password_required": "La password è obbligatoria", "password_min": "Minimo 8 caratteri", - "password_max": "Massimo 32 caratteri" + "password_max": "Massimo 256 caratteri" }, "toast": { "updated": "Utente aggiornato", diff --git a/web/src/locales/jp.json b/web/src/locales/jp.json index 9811892..0a01d50 100644 --- a/web/src/locales/jp.json +++ b/web/src/locales/jp.json @@ -829,7 +829,7 @@ }, "password": { "min": "パスワードは8文字以上である必要があります", - "max": "パスワードは32文字以内である必要があります" + "max": "パスワードは256文字以内である必要があります" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "無効な IP アドレスが検出されました", "password_required": "パスワードは必須です", "password_min": "パスワードは 8 文字以上である必要があります", - "password_max": "パスワードは 32 文字以内である必要があります" + "password_max": "パスワードは 256 文字以内である必要があります" }, "toast": { "updated": "ユーザーを更新しました", diff --git a/web/src/locales/ko.json b/web/src/locales/ko.json index ae298e5..78eef93 100644 --- a/web/src/locales/ko.json +++ b/web/src/locales/ko.json @@ -829,7 +829,7 @@ }, "password": { "min": "비밀번호는 최소 8자 이상이어야 합니다", - "max": "비밀번호는 최대 32자까지 가능합니다" + "max": "비밀번호는 최대 256자까지 가능합니다" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "유효하지 않은 IP 주소가 감지되었습니다", "password_required": "비밀번호는 필수입니다", "password_min": "비밀번호는 8자 이상이어야 합니다", - "password_max": "비밀번호는 32자를 초과할 수 없습니다" + "password_max": "비밀번호는 256자를 초과할 수 없습니다" }, "toast": { "updated": "사용자 업데이트됨", diff --git a/web/src/locales/nl.json b/web/src/locales/nl.json index b52e080..00775f4 100644 --- a/web/src/locales/nl.json +++ b/web/src/locales/nl.json @@ -829,7 +829,7 @@ }, "password": { "min": "Wachtwoord moet minimaal 8 tekens bevatten", - "max": "Wachtwoord mag maximaal 32 tekens bevatten" + "max": "Wachtwoord mag maximaal 256 tekens bevatten" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Ongeldig IP-adres gedetecteerd", "password_required": "Wachtwoord is verplicht", "password_min": "Minimaal 8 tekens", - "password_max": "Maximaal 32 tekens" + "password_max": "Maximaal 256 tekens" }, "toast": { "updated": "Gebruiker bijgewerkt", diff --git a/web/src/locales/no.json b/web/src/locales/no.json index b9a2ce0..890d065 100644 --- a/web/src/locales/no.json +++ b/web/src/locales/no.json @@ -829,7 +829,7 @@ }, "password": { "min": "Passordet må være minst 8 tegn", - "max": "Passordet kan være maks 32 tegn" + "max": "Passordet kan være maks 256 tegn" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Ugyldig IP-adresse", "password_required": "Passord er påkrevd", "password_min": "Minst 8 tegn", - "password_max": "Maks 32 tegn" + "password_max": "Maks 256 tegn" }, "toast": { "updated": "Bruker oppdatert", diff --git a/web/src/locales/pl.json b/web/src/locales/pl.json index baab8e0..36e3422 100644 --- a/web/src/locales/pl.json +++ b/web/src/locales/pl.json @@ -829,7 +829,7 @@ }, "password": { "min": "Hasło musi mieć co najmniej 8 znaków", - "max": "Hasło może mieć maksymalnie 32 znaki" + "max": "Hasło może mieć maksymalnie 256 znaki" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Wykryto niepoprawny adres IP", "password_required": "Hasło jest wymagane", "password_min": "Min. 8 znaków", - "password_max": "Maks. 32 znaki" + "password_max": "Maks. 256 znaki" }, "toast": { "updated": "Użytkownik zaktualizowany", diff --git a/web/src/locales/pt.json b/web/src/locales/pt.json index cd44192..03f8b66 100644 --- a/web/src/locales/pt.json +++ b/web/src/locales/pt.json @@ -829,7 +829,7 @@ }, "password": { "min": "A senha deve ter pelo menos 8 caracteres", - "max": "A senha deve ter no máximo 32 caracteres" + "max": "A senha deve ter no máximo 256 caracteres" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "IP inválido detectado", "password_required": "Senha é obrigatória", "password_min": "Mínimo 8 caracteres", - "password_max": "Máximo 32 caracteres" + "password_max": "Máximo 256 caracteres" }, "toast": { "updated": "Usuário atualizado", diff --git a/web/src/locales/ru.json b/web/src/locales/ru.json index 7eb399a..121a98c 100644 --- a/web/src/locales/ru.json +++ b/web/src/locales/ru.json @@ -829,7 +829,7 @@ }, "password": { "min": "Пароль должен содержать не менее 8 символов", - "max": "Пароль должен содержать не более 32 символов" + "max": "Пароль должен содержать не более 256 символов" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Неверный IP-адрес", "password_required": "Пароль обязателен", "password_min": "Минимум 8 символов", - "password_max": "Максимум 32 символа" + "password_max": "Максимум 256 символа" }, "toast": { "updated": "Пользователь обновлен", diff --git a/web/src/locales/sv.json b/web/src/locales/sv.json index db4c4a2..11acb2e 100644 --- a/web/src/locales/sv.json +++ b/web/src/locales/sv.json @@ -829,7 +829,7 @@ }, "password": { "min": "Lösenordet måste vara minst 8 tecken", - "max": "Lösenordet får vara högst 32 tecken" + "max": "Lösenordet får vara högst 256 tecken" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "Ogiltig IP-adress upptäckt", "password_required": "Lösenord krävs", "password_min": "Lösenordet måste vara minst 8 tecken", - "password_max": "Lösenordet får vara max 32 tecken" + "password_max": "Lösenordet får vara max 256 tecken" }, "toast": { "updated": "Användaren uppdaterad", diff --git a/web/src/locales/zh-tw.json b/web/src/locales/zh-tw.json index ec88770..59466d2 100644 --- a/web/src/locales/zh-tw.json +++ b/web/src/locales/zh-tw.json @@ -822,7 +822,7 @@ }, "password": { "min": "密碼至少需要 8 個字元", - "max": "密碼最多 32 個字元" + "max": "密碼最多 256 個字元" } } } @@ -1231,7 +1231,7 @@ "ip_invalid": "發現無效的 IP 地址", "password_required": "密碼不能為空", "password_min": "密碼長度至少為 8 個字元", - "password_max": "密碼長度不能超過 32 個字元" + "password_max": "密碼長度不能超過 256 個字元" }, "toast": { "updated": "使用者已更新", diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index dd7789b..df26a25 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -829,7 +829,7 @@ }, "password": { "min": "密码长度至少为 8 个字符", - "max": "密码长度不能超过 32 个字符" + "max": "密码长度不能超过 256 个字符" } } } @@ -1238,7 +1238,7 @@ "ip_invalid": "发现无效的 IP 地址", "password_required": "密码不能为空", "password_min": "密码长度至少为 8 个字符", - "password_max": "密码长度不能超过 32 个字符" + "password_max": "密码长度不能超过 256 个字符" }, "toast": { "updated": "用户已更新",