package usecase import ( "crypto/rand" "math/big" "unicode" "apps/backend/internal/module/member/domain" "golang.org/x/crypto/bcrypt" ) // ValidatePasswordPolicy:≥12、含大寫、小寫、數字、符號。 // 規則與 apps/web/src/lib/passwordPolicy.ts 一致;文案見 domain.PasswordPolicyEN/ZH。 func ValidatePasswordPolicy(password string) error { if password == "" || len([]rune(password)) < domain.PasswordMinLen { return domain.ErrWeakPassword } var upper, lower, digit, symbol bool for _, r := range password { switch { case unicode.IsUpper(r): upper = true case unicode.IsLower(r): lower = true case unicode.IsDigit(r): digit = true case unicode.IsPunct(r) || unicode.IsSymbol(r): symbol = true default: // 空白等非字母數字也算符號類 if !unicode.IsLetter(r) && !unicode.IsDigit(r) { symbol = true } } } if !upper || !lower || !digit || !symbol { return domain.ErrWeakPassword } return nil } // HashPassword validates policy then bcrypt. func HashPassword(password string, cost int) (string, error) { if err := ValidatePasswordPolicy(password); err != nil { return "", err } if cost < bcrypt.MinCost { cost = bcrypt.DefaultCost } bytes, err := bcrypt.GenerateFromPassword([]byte(password), cost) return string(bytes), err } func CheckPasswordHash(password, hash string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil } func GetHashingCost(hashedPassword []byte) int { cost, _ := bcrypt.Cost(hashedPassword) return cost } // GenerateTempPassword 產生符合政策的臨時密碼(16 字元)。 func GenerateTempPassword() string { const ( lower = "abcdefghijkmnopqrstuvwxyz" upper = "ABCDEFGHJKLMNPQRSTUVWXYZ" digits = "23456789" symbols = "!@#$%^&*+-=" all = lower + upper + digits + symbols ) pick := func(set string) byte { n, err := rand.Int(rand.Reader, big.NewInt(int64(len(set)))) if err != nil { return set[0] } return set[n.Int64()] } // 保證四類各至少 1 out := []byte{ pick(lower), pick(upper), pick(digits), pick(symbols), } // 16 ≥ PasswordMinLen(12) for len(out) < 16 { out = append(out, pick(all)) } // shuffle for i := len(out) - 1; i > 0; i-- { jBig, err := rand.Int(rand.Reader, big.NewInt(int64(i+1))) j := 0 if err == nil { j = int(jBig.Int64()) } out[i], out[j] = out[j], out[i] } return string(out) }