Refactor: Use common password validator

Refactors password validation logic to use a common `PasswordValidator` utility. This ensures consistent password validation rules across different parts of the application, including:

- User login
- Email sign-up
- Password change
- Account removal

Also adds a string resource for password too long error message.
This commit is contained in:
2025-09-01 15:10:14 +08:00
parent 4d319351b7
commit 5c12982908
6 changed files with 159 additions and 34 deletions

View File

@@ -30,6 +30,7 @@ import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
import com.aiosman.ravenow.ui.composables.ActionButton
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.composables.TextInputField
import com.aiosman.ravenow.utils.PasswordValidator
import kotlinx.coroutines.launch
/**
@@ -58,7 +59,7 @@ fun ChangePasswordScreen() {
var currentPassword by remember { mutableStateOf("") }
var newPassword by remember { mutableStateOf("") }
var confirmPassword by remember { mutableStateOf("") }
var errorMessage by remember { mutableStateOf("") }
val scope = rememberCoroutineScope()
val navController = LocalNavController.current
var oldPasswordError by remember { mutableStateOf<String?>(null) }
@@ -66,17 +67,18 @@ fun ChangePasswordScreen() {
var passwordError by remember { mutableStateOf<String?>(null) }
val AppColors = LocalAppTheme.current
fun validate(): Boolean {
oldPasswordError =
if (currentPassword.isEmpty()) "Please enter your current password" else null
passwordError = when {
newPassword.length < 8 -> "Password must be at least 8 characters long"
!newPassword.any { it.isDigit() } -> "Password must contain at least one digit"
!newPassword.any { it.isUpperCase() } -> "Password must contain at least one uppercase letter"
!newPassword.any { it.isLowerCase() } -> "Password must contain at least one lowercase letter"
else -> null
}
confirmPasswordError =
if (newPassword != confirmPassword) "Passwords do not match" else null
// 使用通用密码校验器校验当前密码
val currentPasswordValidation = PasswordValidator.validateCurrentPassword(currentPassword, context)
oldPasswordError = if (!currentPasswordValidation.isValid) currentPasswordValidation.errorMessage else null
// 使用通用密码校验器校验新密码
val newPasswordValidation = PasswordValidator.validatePassword(newPassword, context)
passwordError = if (!newPasswordValidation.isValid) newPasswordValidation.errorMessage else null
// 使用通用密码确认校验器
val confirmPasswordValidation = PasswordValidator.validatePasswordConfirmation(newPassword, confirmPassword, context)
confirmPasswordError = if (!confirmPasswordValidation.isValid) confirmPasswordValidation.errorMessage else null
return passwordError == null && confirmPasswordError == null && oldPasswordError == null
}
Column(

View File

@@ -46,6 +46,7 @@ import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
import com.aiosman.ravenow.ui.composables.ActionButton
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.utils.PasswordValidator
import kotlinx.coroutines.launch
@Composable
@@ -58,8 +59,10 @@ fun RemoveAccountScreen() {
val context = LocalContext.current
fun removeAccount(password: String) {
if (password.isEmpty()) {
passwordError = "Please enter your correct password"
// 使用通用密码校验器
val passwordValidation = PasswordValidator.validateCurrentPassword(password, context)
if (!passwordValidation.isValid) {
passwordError = passwordValidation.errorMessage
return
}

View File

@@ -39,6 +39,7 @@ import com.aiosman.ravenow.ui.composables.CheckboxWithLabel
import com.aiosman.ravenow.ui.composables.PolicyCheckbox
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.composables.TextInputField
import com.aiosman.ravenow.utils.PasswordValidator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -70,24 +71,13 @@ fun EmailSignupScreen() {
else -> null
}
passwordError = when {
// 非空
password.isEmpty() -> context.getString(R.string.text_error_password_required)
// 至少6位
password.length < 6 -> context.getString(R.string.text_error_password_format)
// 至少一个数字
!password.matches(Regex(".*\\d.*")) -> context.getString(R.string.text_error_password_format)
// 包含字母
!password.matches(Regex(".*[a-zA-Z].*")) -> context.getString(R.string.text_error_password_format)
else -> null
}
confirmPasswordError = when {
// 非空
confirmPassword.isEmpty() -> context.getString(R.string.text_error_confirm_password_required)
// 与密码一致
confirmPassword != password -> context.getString(R.string.text_error_confirm_password_mismatch)
else -> null
}
// 使用通用密码校验器
val passwordValidation = PasswordValidator.validatePassword(password, context)
passwordError = if (!passwordValidation.isValid) passwordValidation.errorMessage else null
// 使用通用密码确认校验器
val confirmPasswordValidation = PasswordValidator.validatePasswordConfirmation(password, confirmPassword, context)
confirmPasswordError = if (!confirmPasswordValidation.isValid) confirmPasswordValidation.errorMessage else null
if (!acceptTerms) {
scope.launch(Dispatchers.Main) {
Toast.makeText(

View File

@@ -51,6 +51,7 @@ import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.composables.TextInputField
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.GoogleLogin
import com.aiosman.ravenow.utils.PasswordValidator
import kotlinx.coroutines.launch
@@ -71,8 +72,11 @@ fun UserAuthScreen() {
fun validateForm(): Boolean {
emailError =
if (email.isEmpty()) context.getString(R.string.text_error_email_required) else null
passwordError =
if (password.isEmpty()) context.getString(R.string.text_error_password_required) else null
// 使用通用密码校验器
val passwordValidation = PasswordValidator.validateCurrentPassword(password, context)
passwordError = if (!passwordValidation.isValid) passwordValidation.errorMessage else null
return emailError == null && passwordError == null
}

View File

@@ -0,0 +1,125 @@
package com.aiosman.ravenow.utils
import android.content.Context
import com.aiosman.ravenow.R
/**
* 密码校验工具类
* 提供统一的密码校验规则和错误信息
*/
object PasswordValidator {
/**
* 密码最大长度限制
*/
const val MAX_PASSWORD_LENGTH = 64
/**
* 密码最小长度限制
*/
const val MIN_PASSWORD_LENGTH = 6
/**
* 密码校验结果
*/
data class ValidationResult(
val isValid: Boolean,
val errorMessage: String? = null
)
/**
* 校验密码格式
* @param password 密码
* @param context 上下文,用于获取本地化错误信息
* @return 校验结果
*/
fun validatePassword(password: String, context: Context): ValidationResult {
return when {
// 检查是否为空
password.isEmpty() -> ValidationResult(
false,
context.getString(R.string.text_error_password_required)
)
// 检查长度不能超过64个字符
password.length > MAX_PASSWORD_LENGTH -> ValidationResult(
false,
context.getString(R.string.text_error_password_too_long, MAX_PASSWORD_LENGTH)
)
// 检查最小长度
password.length < MIN_PASSWORD_LENGTH -> ValidationResult(
false,
context.getString(R.string.text_error_password_format)
)
// 检查是否包含至少一个数字
!password.matches(Regex(".*\\d.*")) -> ValidationResult(
false,
context.getString(R.string.text_error_password_format)
)
// 检查是否包含字母
!password.matches(Regex(".*[a-zA-Z].*")) -> ValidationResult(
false,
context.getString(R.string.text_error_password_format)
)
else -> ValidationResult(true)
}
}
/**
* 校验密码确认
* @param password 原密码
* @param confirmPassword 确认密码
* @param context 上下文
* @return 校验结果
*/
fun validatePasswordConfirmation(
password: String,
confirmPassword: String,
context: Context
): ValidationResult {
return when {
confirmPassword.isEmpty() -> ValidationResult(
false,
context.getString(R.string.text_error_confirm_password_required)
)
confirmPassword.length > MAX_PASSWORD_LENGTH -> ValidationResult(
false,
context.getString(R.string.text_error_password_too_long, MAX_PASSWORD_LENGTH)
)
password != confirmPassword -> ValidationResult(
false,
context.getString(R.string.text_error_confirm_password_mismatch)
)
else -> ValidationResult(true)
}
}
/**
* 校验当前密码(用于更改密码和删除账户场景)
* @param currentPassword 当前密码
* @param context 上下文
* @return 校验结果
*/
fun validateCurrentPassword(currentPassword: String, context: Context): ValidationResult {
return when {
currentPassword.isEmpty() -> ValidationResult(
false,
"Please enter your current password"
)
currentPassword.length > MAX_PASSWORD_LENGTH -> ValidationResult(
false,
context.getString(R.string.text_error_password_too_long, MAX_PASSWORD_LENGTH)
)
else -> ValidationResult(true)
}
}
}