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
This commit is contained in:
rustmailer
2025-12-30 11:05:07 +08:00
parent c01872284e
commit 62d956c7d6
23 changed files with 66 additions and 54 deletions
+7 -1
View File
@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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;
}
+6
View File
@@ -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(())
}
+4 -4
View File
@@ -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
));
}
@@ -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<typeof profileSchema>
export type ProfileFormValues = z.infer<ReturnType<typeof profileSchema>>
function fileToBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
@@ -110,7 +110,7 @@ export function UserProfileForm({ user }: UserProfileFormProps) {
useState<number | undefined>(undefined)
const form = useForm<ProfileFormValues>({
resolver: zodResolver(profileSchema),
resolver: zodResolver(profileSchema(t)),
mode: 'onChange',
defaultValues: {
username: user.username,
@@ -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),
+2 -2
View File
@@ -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": "تم تحديث المستخدم",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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": "ユーザーを更新しました",
+2 -2
View File
@@ -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": "사용자 업데이트됨",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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": "Пользователь обновлен",
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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": "使用者已更新",
+2 -2
View File
@@ -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": "用户已更新",