package auth import ( "crypto/rand" "crypto/subtle" "encoding/base64" "fmt" "net" "net/http" "strings" "golang.org/x/crypto/bcrypt" ) const ( CsrfLength = 32 SessionLength = 32 ) func GenerateToken(length int) (string, error) { b := make([]byte, length) _, err := rand.Read(b) if err != nil { return "", fmt.Errorf("failed to generate random bytes: %w", err) } return base64.RawURLEncoding.EncodeToString(b), nil } func HashPassword(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) return string(bytes), err } func CheckPasswordHash(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } func SetCsrfCookie(w http.ResponseWriter, r *http.Request, cookieName string) (string, error) { token, err := GenerateToken(CsrfLength) if err != nil { return "", fmt.Errorf("error generating csrf token: %w", err) } cookie := &http.Cookie{ Name: cookieName, Value: token, Path: "/", HttpOnly: false, Secure: true, SameSite: http.SameSiteLaxMode, } http.SetCookie(w, cookie) return token, nil } func ValidateCsrf(r *http.Request, cookieName, fieldName string) error { cookie, err := r.Cookie(cookieName) if err != nil { return fmt.Errorf("missing CSRF cookie") } cookieToken := cookie.Value if err := r.ParseForm(); err != nil { return fmt.Errorf("failed to parse form data") } formToken := r.FormValue(fieldName) if cookieToken == "" || formToken == "" { return fmt.Errorf("empty CSRF token") } if subtle.ConstantTimeCompare([]byte(cookieToken), []byte(formToken)) != 1 { return fmt.Errorf("mismatch CSRF token") } return nil } func extractIP(r *http.Request) string { if xff := r.Header.Get("X-Forwarded-For"); xff != "" { ips := strings.Split(xff, ",") return strings.TrimSpace(ips[0]) } if realIP := r.Header.Get("X-Real-IP"); realIP != "" { return realIP } ip, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return r.RemoteAddr } return ip } func CreateSession(r *http.Request, userID int64) (string, string, string, error) { token, err := GenerateToken(SessionLength) if err != nil { return "", "", "", err } userAgent := r.UserAgent() if userAgent == "" { userAgent = "Unknown" } ipAddress := extractIP(r) return token, userAgent, ipAddress, nil }