From f94143acf00ce45a4d724dba1dd9613043507f3d Mon Sep 17 00:00:00 2001 From: orohi Date: Wed, 17 Jun 2026 05:39:57 +0300 Subject: [PATCH] fix: login session IP parsing and username lookup --- internal/handler/auth.go | 8 ++++++-- internal/httputil/ip.go | 30 ++++++++++++++++++++++++++++++ internal/repository/session.go | 12 +++++++++--- 3 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 internal/httputil/ip.go diff --git a/internal/handler/auth.go b/internal/handler/auth.go index e5542c1..c9eb99c 100644 --- a/internal/handler/auth.go +++ b/internal/handler/auth.go @@ -3,12 +3,15 @@ package handler import ( "encoding/json" "errors" + "log" "net/http" + "strings" "time" "github.com/panelhosting/panel/internal/auth" "github.com/panelhosting/panel/internal/authsvc" "github.com/panelhosting/panel/internal/config" + "github.com/panelhosting/panel/internal/httputil" "github.com/panelhosting/panel/internal/middleware" ) @@ -34,9 +37,9 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { } result, err := h.svc.Login(r.Context(), authsvc.LoginInput{ - Username: req.Username, + Username: strings.TrimSpace(req.Username), Password: req.Password, - IP: r.RemoteAddr, + IP: httputil.ClientIP(r), UserAgent: r.UserAgent(), }) if err != nil { @@ -44,6 +47,7 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusUnauthorized, "invalid credentials") return } + log.Printf("login error: %v", err) writeError(w, http.StatusInternalServerError, "login failed") return } diff --git a/internal/httputil/ip.go b/internal/httputil/ip.go new file mode 100644 index 0000000..4d1ce2a --- /dev/null +++ b/internal/httputil/ip.go @@ -0,0 +1,30 @@ +package httputil + +import ( + "net" + "net/http" + "strings" +) + +func ClientIP(r *http.Request) string { + if xff := r.Header.Get("X-Forwarded-For"); xff != "" { + if ip := strings.TrimSpace(strings.Split(xff, ",")[0]); ip != "" { + return ip + } + } + if xri := strings.TrimSpace(r.Header.Get("X-Real-IP")); xri != "" { + return xri + } + if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { + return host + } + return r.RemoteAddr +} + +func ValidIP(ip string) (string, bool) { + parsed := net.ParseIP(strings.TrimSpace(ip)) + if parsed == nil { + return "", false + } + return parsed.String(), true +} diff --git a/internal/repository/session.go b/internal/repository/session.go index fdb51f6..9097264 100644 --- a/internal/repository/session.go +++ b/internal/repository/session.go @@ -8,6 +8,7 @@ import ( "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" + "github.com/panelhosting/panel/internal/httputil" "github.com/panelhosting/panel/internal/models" ) @@ -16,7 +17,7 @@ var ErrUserNotFound = errors.New("user not found") func (r *UserRepository) GetByUsername(ctx context.Context, username string) (*models.User, error) { const q = ` SELECT id, uuid, email, username, password_hash, role, status, locale, timezone, last_login_at, created_at, updated_at - FROM users WHERE username = $1 + FROM users WHERE LOWER(username) = LOWER($1) ` var u models.User err := r.pool.QueryRow(ctx, q, username).Scan( @@ -65,11 +66,16 @@ func NewSessionRepository(pool *pgxpool.Pool) *SessionRepository { } func (r *SessionRepository) Create(ctx context.Context, userID int64, tokenHash string, ip, userAgent string, expiresAt time.Time) error { + var ipArg any + if normalized, ok := httputil.ValidIP(ip); ok { + ipArg = normalized + } + const q = ` INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at) - VALUES ($1, $2, NULLIF($3, '')::inet, NULLIF($4, ''), $5) + VALUES ($1, $2, $3, NULLIF($4, ''), $5) ` - _, err := r.pool.Exec(ctx, q, userID, tokenHash, ip, userAgent, expiresAt) + _, err := r.pool.Exec(ctx, q, userID, tokenHash, ipArg, userAgent, expiresAt) if err != nil { return fmt.Errorf("create session: %w", err) }