Ver código fonte

security: Try to set cookies secure, with force override option

jamesread 4 meses atrás
pai
commit
03da2ff2e7

+ 14 - 2
service/internal/api/api.go

@@ -158,7 +158,12 @@ func (api *oliveTinAPI) PasswordHash(ctx ctx.Context, req *connect.Request[apiv1
 	return connect.NewResponse(ret), nil
 }
 
-func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest, response *connect.Response[apiv1.LocalUserLoginResponse], match bool) {
+func (api *oliveTinAPI) cookieSecure(header http.Header) bool {
+	useTLS := header.Get("X-Forwarded-Proto") == "https"
+	return useTLS || api.cfg.Security.ForceSecureCookies
+}
+
+func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest, response *connect.Response[apiv1.LocalUserLoginResponse], match bool, secure bool) {
 	if match {
 		user := api.cfg.FindUserByUsername(req.Username)
 		if user != nil {
@@ -171,6 +176,8 @@ func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest,
 				MaxAge:   31556952,
 				HttpOnly: true,
 				Path:     "/",
+				Secure:   secure,
+				SameSite: http.SameSiteLaxMode,
 			}
 			response.Header().Set("Set-Cookie", cookie.String())
 		}
@@ -192,7 +199,7 @@ func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[api
 		return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("checking password: %w", err))
 	}
 	response := connect.NewResponse(&apiv1.LocalUserLoginResponse{Success: match})
-	api.applyLocalLoginResult(req.Msg, response, match)
+	api.applyLocalLoginResult(req.Msg, response, match, api.cookieSecure(req.Header()))
 	return response, nil
 }
 
@@ -389,6 +396,7 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
 	}).Info("Logout: User logged out")
 
 	response := connect.NewResponse(&apiv1.LogoutResponse{})
+	secure := api.cookieSecure(req.Header())
 
 	// Clear the local authentication cookie by setting it to expire
 	localCookie := &http.Cookie{
@@ -397,6 +405,8 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
 		MaxAge:   -1, // This tells the browser to delete the cookie
 		HttpOnly: true,
 		Path:     "/",
+		Secure:   secure,
+		SameSite: http.SameSiteLaxMode,
 	}
 	response.Header().Set("Set-Cookie", localCookie.String())
 
@@ -407,6 +417,8 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
 		MaxAge:   -1, // This tells the browser to delete the cookie
 		HttpOnly: true,
 		Path:     "/",
+		Secure:   secure,
+		SameSite: http.SameSiteLaxMode,
 	}
 	response.Header().Add("Set-Cookie", oauth2Cookie.String())
 

+ 7 - 1
service/internal/auth/otoauth2/restapi_auth_oauth2.go

@@ -108,14 +108,20 @@ func randString(nByte int) (string, error) {
 	return base64.URLEncoding.EncodeToString(b), nil
 }
 
+func (h *OAuth2Handler) cookieSecure(r *http.Request) bool {
+	useTLS := r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https"
+	return useTLS || h.cfg.Security.ForceSecureCookies
+}
+
 func (h *OAuth2Handler) setOAuthCallbackCookie(w http.ResponseWriter, r *http.Request, name, value string) {
 	cookie := &http.Cookie{
 		Name:     name,
 		Value:    value,
 		MaxAge:   900, // 15 minutes
-		Secure:   r.TLS != nil,
+		Secure:   h.cookieSecure(r),
 		HttpOnly: true,
 		Path:     "/",
+		SameSite: http.SameSiteLaxMode,
 	}
 
 	http.SetCookie(w, cookie)

+ 2 - 1
service/internal/config/config.go

@@ -107,13 +107,14 @@ type PrometheusConfig struct {
 	DefaultGoMetrics bool `koanf:"defaultGoMetrics"`
 }
 
-// SecurityConfig allows users to fine tune the security related HTTP headers.
+// SecurityConfig allows users to fine tune the security related HTTP headers and cookie options.
 type SecurityConfig struct {
 	HeaderContentSecurityPolicy bool   `koanf:"headerContentSecurityPolicy"`
 	ContentSecurityPolicy       string `koanf:"contentSecurityPolicy"`
 	HeaderXContentTypeOptions   bool   `koanf:"headerXContentTypeOptions"`
 	HeaderXFrameOptions         bool   `koanf:"headerXFrameOptions"`
 	XFrameOptions               string `koanf:"xFrameOptions"`
+	ForceSecureCookies          bool   `koanf:"forceSecureCookies"`
 }
 
 // Config is the global config used through the whole app.