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