16218ee045
- регистрация: политика пароля (≥10 + локальный словарь 10k утечек, SecLists MIT), username и email без учёта регистра, шифрование email с привязкой к аккаунту, blind index для поиска, автовступление в главный сервер с ролью @user - вход: Argon2id+pepper, TOTP при включённом 2FA, блокировка после серии неудач, события безопасности; единый ответ на неверный пароль и неизвестный аккаунт - сессии: opaque-токены (в БД только SHA-256), ротация токена, logout/logout-all, отзыв остальных сессий при смене пароля, step-up с окном 10 минут - 2FA: настройка секрета с QR-URL, подтверждение кодом, 8 резервных кодов (хранятся хэшами), обязательность для инстанс-админов, запрет отключения - тесты на реальной SQLite: регистрация/вход/дубликаты, слабые и утёкшие пароли, ротация и logout-all, TOTP и резервные коды, step-up, смена пароля, выключенная регистрация, отсутствие секретов в логах
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) }
|