// Package auth enthält die reine Logik für Anmeldung: Passwort-Hashing, // Session-Token-Erzeugung. Es fasst keine Datenbank an — das macht // internal/store (account, app_user, session), damit Persistenz an // einer Stelle im Projekt gebündelt bleibt. package auth import ( "crypto/rand" "encoding/hex" "fmt" "time" "golang.org/x/crypto/bcrypt" ) // MinPasswordLength ist die einzige Passwort-Regel, die wir prüfen — // keine erzwungenen Sonderzeichen/Ziffern-Vorgaben, die Nutzer nur zu // vorhersehbaren Mustern verleiten. const MinPasswordLength = 8 // SessionDuration ist die Gültigkeitsdauer einer neu erstellten Sitzung. const SessionDuration = 30 * 24 * time.Hour // HashPassword hasht ein Klartext-Passwort für die Speicherung. Lehnt // zu kurze Passwörter ab, statt sie stillschweigend zu hashen. func HashPassword(password string) (string, error) { if len(password) < MinPasswordLength { return "", fmt.Errorf("auth: passwort muss mindestens %d Zeichen haben", MinPasswordLength) } hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", fmt.Errorf("auth: passwort hashen: %w", err) } return string(hash), nil } // VerifyPassword prüft ein Klartext-Passwort gegen einen gespeicherten // Hash. Ein Fehler bedeutet: falsches Passwort oder ungültiger Hash — // beides führt zu "Anmeldung abgelehnt", nie zu einem Unterschied, den // ein Angreifer für User-Enumeration nutzen könnte. func VerifyPassword(hash, password string) error { if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil { return fmt.Errorf("auth: passwort ungültig: %w", err) } return nil } // NewSessionToken erzeugt ein kryptographisch zufälliges Session-Token // (32 Byte, hex-kodiert). func NewSessionToken() (string, error) { buf := make([]byte, 32) if _, err := rand.Read(buf); err != nil { return "", fmt.Errorf("auth: token generieren: %w", err) } return hex.EncodeToString(buf), nil }