99 lines
2.4 KiB
Go
99 lines
2.4 KiB
Go
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)
|
||
}
|