56 lines
1.5 KiB
Go
56 lines
1.5 KiB
Go
|
|
package auth
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bufio"
|
||
|
|
"embed"
|
||
|
|
"fmt"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"glchat/internal/crypto"
|
||
|
|
)
|
||
|
|
|
||
|
|
//go:embed data/leaked-passwords.txt
|
||
|
|
var leakedPasswordsFS embed.FS
|
||
|
|
|
||
|
|
// passwordPolicy проверяет пароль по локальному словарю утечек: внешние API
|
||
|
|
// запрещены (AGENT.md 7.1).
|
||
|
|
type passwordPolicy struct {
|
||
|
|
leaked map[string]struct{}
|
||
|
|
}
|
||
|
|
|
||
|
|
func newPasswordPolicy() (*passwordPolicy, error) {
|
||
|
|
file, err := leakedPasswordsFS.Open("data/leaked-passwords.txt")
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("open leaked password list: %w", err)
|
||
|
|
}
|
||
|
|
defer file.Close()
|
||
|
|
|
||
|
|
leaked := make(map[string]struct{}, 10000)
|
||
|
|
scanner := bufio.NewScanner(file)
|
||
|
|
for scanner.Scan() {
|
||
|
|
word := strings.TrimSpace(scanner.Text())
|
||
|
|
if word == "" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
leaked[strings.ToLower(word)] = struct{}{}
|
||
|
|
}
|
||
|
|
if err := scanner.Err(); err != nil {
|
||
|
|
return nil, fmt.Errorf("read leaked password list: %w", err)
|
||
|
|
}
|
||
|
|
return &passwordPolicy{leaked: leaked}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// validate проверяет длину и отсутствие пароля в словаре утечек.
|
||
|
|
func (p *passwordPolicy) validate(password string) error {
|
||
|
|
if err := crypto.ValidatePassword(password); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if _, found := p.leaked[strings.ToLower(password)]; found {
|
||
|
|
return fmt.Errorf("password occurs in the leaked password list")
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// LeakedCount возвращает размер словаря (для диагностики и метрик).
|
||
|
|
func (p *passwordPolicy) LeakedCount() int { return len(p.leaked) }
|