فهرست منبع

fix: Secure flag куки по протоколу, а не APP_ENV

- Secure: true теперь ставится только если r.TLS != nil
  или X-Forwarded-Proto: https (за прокси)
- Добавлен X-Forwarded-Proto {scheme} в Caddyfile
- Убран мёртвый isProd из AuthHandler и SetupHandler
neyrogovnarik 1 ماه پیش
والد
کامیت
23ca2b6378
5فایلهای تغییر یافته به همراه30 افزوده شده و 24 حذف شده
  1. 2 2
      backend/cmd/api/main.go
  2. 13 16
      backend/internal/handlers/auth.go
  3. 12 2
      backend/internal/handlers/helpers.go
  4. 2 4
      backend/internal/handlers/setup.go
  5. 1 0
      deploy/Caddyfile

+ 2 - 2
backend/cmd/api/main.go

@@ -92,7 +92,7 @@ func main() {
 	}()
 
 	// Хендлеры
-	authHandler := handlers.NewAuthHandler(authSvc, cfg.AppEnv)
+	authHandler := handlers.NewAuthHandler(authSvc)
 	userHandler := handlers.NewUserHandler(userRepo)
 	placeHandler := handlers.NewPlaceHandler(placeSvc, authSvc)
 	serviceHandler := handlers.NewServiceHandler(serviceRepo, tagRepo)
@@ -101,7 +101,7 @@ func main() {
 	tagHandler := handlers.NewTagHandler(tagRepo, featureRepo)
 	wsHub := handlers.NewWSHub(cfg.AllowedOrigins, cfg.AppEnv == "production")
 	go wsHub.Run()
-	setupHandler := handlers.NewSetupHandler(userRepo, authSvc, cfg.AppEnv)
+	setupHandler := handlers.NewSetupHandler(userRepo, authSvc)
 
 	// Redis для rate limiting
 	redisOpts, err := redis.ParseURL(cfg.RedisURL)

+ 13 - 16
backend/internal/handlers/auth.go

@@ -12,15 +12,11 @@ import (
 )
 
 type AuthHandler struct {
-	authSvc  AuthService
-	isProd   bool
+	authSvc AuthService
 }
 
-func NewAuthHandler(authSvc AuthService, appEnv string) *AuthHandler {
-	return &AuthHandler{
-		authSvc: authSvc,
-		isProd:  appEnv == "production",
-	}
+func NewAuthHandler(authSvc AuthService) *AuthHandler {
+	return &AuthHandler{authSvc: authSvc}
 }
 
 type registerRequest struct {
@@ -62,7 +58,7 @@ func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	setRefreshTokenCookie(w, refreshToken, h.isProd)
+	setRefreshTokenCookie(w, refreshToken, r)
 	writeJSON(w, http.StatusCreated, result)
 }
 
@@ -101,7 +97,7 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	setRefreshTokenCookie(w, refreshToken, h.isProd)
+	setRefreshTokenCookie(w, refreshToken, r)
 	writeJSON(w, http.StatusOK, result)
 }
 
@@ -115,16 +111,16 @@ func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
 	result, newRefreshToken, err := h.authSvc.RefreshSession(r.Context(), refreshToken)
 	if err != nil {
 		if errors.Is(err, services.ErrTokenReused) {
-			h.clearRefreshTokenCookie(w)
+			h.clearRefreshTokenCookie(w, r)
 			writeError(w, http.StatusUnauthorized, "token reused, session revoked", err)
 			return
 		}
-		h.clearRefreshTokenCookie(w)
+		h.clearRefreshTokenCookie(w, r)
 		writeError(w, http.StatusUnauthorized, "invalid or expired refresh token", err)
 		return
 	}
 
-	setRefreshTokenCookie(w, newRefreshToken, h.isProd)
+	setRefreshTokenCookie(w, newRefreshToken, r)
 	writeJSON(w, http.StatusOK, result)
 }
 
@@ -133,7 +129,7 @@ func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
 	if refreshToken != "" {
 		_ = h.authSvc.RevokeSession(r.Context(), refreshToken)
 	}
-	h.clearRefreshTokenCookie(w)
+	h.clearRefreshTokenCookie(w, r)
 	w.WriteHeader(http.StatusNoContent)
 }
 
@@ -157,9 +153,10 @@ func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) {
 	writeJSON(w, http.StatusOK, user)
 }
 
-func (h *AuthHandler) clearRefreshTokenCookie(w http.ResponseWriter) {
+func (h *AuthHandler) clearRefreshTokenCookie(w http.ResponseWriter, r *http.Request) {
+	secure := isSecure(r)
 	sameSite := http.SameSiteLaxMode
-	if h.isProd {
+	if secure {
 		sameSite = http.SameSiteStrictMode
 	}
 	cookie := &http.Cookie{
@@ -167,7 +164,7 @@ func (h *AuthHandler) clearRefreshTokenCookie(w http.ResponseWriter) {
 		Value:    "",
 		Path:     "/",
 		HttpOnly: true,
-		Secure:   h.isProd,
+		Secure:   secure,
 		SameSite: sameSite,
 		MaxAge:   -1,
 	}

+ 12 - 2
backend/internal/handlers/helpers.go

@@ -11,13 +11,23 @@ import (
 
 const refreshTokenCookieName = "refresh_token"
 
-func setRefreshTokenCookie(w http.ResponseWriter, token string, isProd bool) {
+func isSecure(r *http.Request) bool {
+	if r.TLS != nil {
+		return true
+	}
+	if r.Header.Get("X-Forwarded-Proto") == "https" {
+		return true
+	}
+	return false
+}
+
+func setRefreshTokenCookie(w http.ResponseWriter, token string, r *http.Request) {
 	http.SetCookie(w, &http.Cookie{
 		Name:     refreshTokenCookieName,
 		Value:    token,
 		Path:     "/",
 		HttpOnly: true,
-		Secure:   isProd,
+		Secure:   isSecure(r),
 		SameSite: http.SameSiteLaxMode,
 		MaxAge:   int((30 * 24 * time.Hour).Seconds()),
 	})

+ 2 - 4
backend/internal/handlers/setup.go

@@ -12,14 +12,12 @@ import (
 type SetupHandler struct {
 	userRepo UserRepo
 	authSvc  AuthService
-	isProd   bool
 }
 
-func NewSetupHandler(userRepo UserRepo, authSvc AuthService, appEnv string) *SetupHandler {
+func NewSetupHandler(userRepo UserRepo, authSvc AuthService) *SetupHandler {
 	return &SetupHandler{
 		userRepo: userRepo,
 		authSvc:  authSvc,
-		isProd:   appEnv == "production",
 	}
 }
 
@@ -76,6 +74,6 @@ func (h *SetupHandler) Setup(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	setRefreshTokenCookie(w, refreshToken, h.isProd)
+	setRefreshTokenCookie(w, refreshToken, r)
 	writeJSON(w, http.StatusCreated, result)
 }

+ 1 - 0
deploy/Caddyfile

@@ -14,6 +14,7 @@ http://api.{$DOMAIN} {
 	reverse_proxy backend:8080 {
 		header_up Host {host}
 		header_up X-Real-IP {remote}
+		header_up X-Forwarded-Proto {scheme}
 	}
 
 	header {