thread-master/apps/backend/internal/module/member/usecase/password.go

99 lines
2.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}