diff --git a/certs/localhost-cert.pem b/certs/localhost-cert.pem new file mode 100644 index 0000000..1ced7f9 --- /dev/null +++ b/certs/localhost-cert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFKjCCAxICCQDBJ81DFfWunTANBgkqhkiG9w0BAQsFADBXMQswCQYDVQQGEwJV +UzEOMAwGA1UECAwFU3RhdGUxDTALBgNVBAcMBENpdHkxFTATBgNVBAoMDE9yZ2Fu +aXphdGlvbjESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDkyMjExMzExN1oXDTI2 +MDkyMjExMzExN1owVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYD +VQQHDARDaXR5MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xEjAQBgNVBAMMCWxvY2Fs +aG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANB2Cgp1arb2uA+X +VyLtX5elqiDkzr5yReGlJqKSoXY7aDUKXAXjUQozcsfL3OQdrejWKHk7rEn9zVCt +87TWsZgb6QDPN5m2BnJ2pQm7AwBaugsfX8aA5FYILZrV2JfCgZp3NMltsuxps+9M +mwTPmKe2DU/59LCQdyRFThrpjc0bu5iTe7jfI1qYlVdWO3I9uP6OedLMY0TLNc6k +UA9Fr0RmBhlS1CU4HZxt0txXOMgxdI3wDx8qHrUG9bCV1LM9JeBcdHQ82kKwdGKh +gKxe7xPcRrbwa0g8/tPZuG5rToBiikw3Np306T8BRl96F5SLlFVGbn8eKvStTSsk +7PM/wFlak0huFfeq+96zW0I8mBhBiDdIK6eV7T8YBxe2dF9E5tDGyYfaD12AIPlt +zbggmYMSsq4KXVTPIw2PxvDqgFNufYKK+NaIzIf1an6ZOdB4AACs5S6yHEdo/5gR +saqEo8gxAzKyyUdDQnrR37UV4QWvC4HOuPBLtuaVk/RjHOqjtyEjzqrXhhh/LYw6 +vdDCnckiCZjlML7bp0UmBQ4l93jrfUFYzNPkTqWlq8oncHabME5gpDpySL5oAhST +RvqQcnLMiqAce6UKVy2Cq5NwSnZwp3Jrn/mjiSkJZMYGobv6WVkhXbTnYJNLUQ5y +EYM7cuk2Ue8H+fhZKn5sn1wnYgMXAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAMJu +Kj98QFRrQlfrDvyr27hEHwAxUB+ptgOtdOvnJhq/2fZ1rZpN/O4/B/HpxmjUc3a3 ++yJD9QBwY2+JHwbXdVC7guKhOrxu1rQxoKXeaT4bYjXnrxGKNcEDiQN1CDZ3sDGI +D4gtAbQsYgPsXk67UeJbOKMkN7GoFNQfl3NFlAEMH0CWrXIHRY9tCORgggbxxssJ +y6CPfS8GD65j1kGrlMySYbe3gP6kTFkQvIokqW5dkhZHTxcFF/8s6XaoJ7C3b821 +kl16W/vjTaskBehE+GSTjn2ykpCgLr+cA2vJh24LP9QOO21odSYyr1S8OHn2bS06 +XJe+o7GzD0mw0IZ7e6WRG8Mf8W40e8BJa2qIaJujiw9FSGkYxIH/FAjznEIw9X9s +yDgcRN3ZaNGyyGuGNYXzCTid8OgL2z3Bvo1oa6ird0QxlUS7XRkusTHcEbWvyPqC +g+Cwh7OBXxkwzMjq+j8jMyvOVhLNwkuQDAIslhqY53mX04U2EHoKD8YEuiE+O1QH +XIxp5b6/ynPXc8qZ0l9ZNbpwao0G73gwXs5ydhBwaOKJ/+eY966x8QiaPi2qrchG +JnN9Sxeyf09IOsK4vtsbpEIeo+kNA5oiW+ZlaxzRRkXyWFlKtljdz+5YycfZiUZF +JhLRxbWoZZBTf9Aa6k5X3THCz62v4U4UBqxkCi2j +-----END CERTIFICATE----- diff --git a/certs/localhost-key.pem b/certs/localhost-key.pem new file mode 100644 index 0000000..ea61e2a --- /dev/null +++ b/certs/localhost-key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +awUM4jMlovTrXzA+x2O/IFjdUGix0uU= +-----END PRIVATE KEY----- diff --git a/cmd/server/main.go b/cmd/server/main.go index dce2696..1ce8db0 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -3,6 +3,7 @@ package main import ( "log" "net/http" + "os" _ "github.com/aleksandr/strive-api/docs" "github.com/aleksandr/strive-api/internal/config" @@ -53,10 +54,24 @@ func main() { // Start server server := httphandler.NewServer(cfg, handler, logger) - server.Start() + + // Check if SSL certificates exist for HTTPS (only for localhost development) + if fileExists("certs/localhost-cert.pem") && fileExists("certs/localhost-key.pem") { + server.StartTLS("certs/localhost-cert.pem", "certs/localhost-key.pem") + logger.Info("Starting with HTTPS (localhost development certificates found)") + } else { + server.Start() + logger.Info("Starting with HTTP (production mode - SSL handled by platform)") + } + server.WaitForShutdown() } +func fileExists(filename string) bool { + _, err := os.Stat(filename) + return !os.IsNotExist(err) +} + func loadConfig() *config.Config { cfg, err := config.Load() if err != nil { diff --git a/env.example b/env.example index bdb0b0d..f1d67a9 100644 --- a/env.example +++ b/env.example @@ -46,6 +46,15 @@ CORS_ALLOW_CREDENTIALS=true # Max age for preflight requests in seconds CORS_MAX_AGE=86400 +# Cookie Configuration (for production deployment) +# Secure cookies (true/false) - set to true for HTTPS in production +COOKIE_SECURE=true +# SameSite attribute (None, Lax, Strict) +# Use "Strict" for production (maximum security) +# Use "None" for cross-site cookies (requires Secure=true and HTTPS) +# Use "Lax" for same-site cookies (default, recommended for most cases) +COOKIE_SAMESITE=Strict + # Environment Configuration # Set to 'production' for HTTPS cookies, leave empty for development ENVIRONMENT= diff --git a/internal/config/config.go b/internal/config/config.go index 60828b2..d8ebbd4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -144,8 +144,8 @@ func Load() (*Config, error) { XSSProtection: getEnv("SECURITY_XSS_PROTECTION", "1; mode=block"), }, Cookie: CookieConfig{ - Secure: false, - SameSite: http.SameSiteNoneMode, + Secure: getEnv("COOKIE_SECURE", "true") == trueStr, + SameSite: parseSameSite(getEnv("COOKIE_SAMESITE", "Strict")), }, } @@ -238,3 +238,16 @@ func getEnvSlice(key string, defaultValue []string) []string { } return defaultValue } + +func parseSameSite(value string) http.SameSite { + switch value { + case "None": + return http.SameSiteNoneMode + case "Strict": + return http.SameSiteStrictMode + case "Lax": + return http.SameSiteLaxMode + default: + return http.SameSiteStrictMode + } +} diff --git a/internal/http/server.go b/internal/http/server.go index 996b82d..c177e57 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -42,6 +42,16 @@ func (s *Server) Start() { }() } +func (s *Server) StartTLS(certFile, keyFile string) { + s.logger.Info("Starting HTTPS server", "addr", s.httpServer.Addr) + go func() { + if err := s.httpServer.ListenAndServeTLS(certFile, keyFile); err != nil && err != http.ErrServerClosed { + s.logger.Error("HTTPS server error", "error", err) + log.Fatalf("HTTPS server error: %v", err) + } + }() +} + func (s *Server) Stop(ctx context.Context) error { return s.httpServer.Shutdown(ctx) } diff --git a/render.yaml b/render.yaml index 18bde2b..1f534bb 100644 --- a/render.yaml +++ b/render.yaml @@ -27,4 +27,8 @@ services: value: require - key: JWT_SECRET sync: false + - key: COOKIE_SECURE + value: "true" + - key: COOKIE_SAMESITE + value: "Strict"