From 296f32aa6d77080577d423c0075efe6564e7cc01 Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Tue, 22 Jul 2025 22:50:12 -0700 Subject: [PATCH 1/2] (FIX): Forgot to update this. Need to change cookies too. --- internal/app/server/server.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/app/server/server.go b/internal/app/server/server.go index 233c031..07b9af6 100644 --- a/internal/app/server/server.go +++ b/internal/app/server/server.go @@ -28,9 +28,6 @@ type Server struct { // Init initializes the server with the provided port. CORS settings are defined here. // A pointer to a server object is returned which allows for method chaining. func Init(port int) *Server { - // TODO: Set this to release in prod - gin.SetMode(gin.DebugMode) - server := &Server{ Router: gin.Default(), port: port, @@ -66,6 +63,14 @@ func (s *Server) Setup() *Server { panic("Environment configuration is nil, crashing.") } + if cfg.Environment == "dev" { + gin.SetMode(gin.DebugMode) + } else if cfg.Environment == "prod" { + gin.SetMode(gin.ReleaseMode) + } else { + gin.SetMode(gin.TestMode) + } + // SETUP GOOGLE AUTH var ( redirectUrl string = fmt.Sprintf("%s%s", cfg.Domain, domain.API_AUTH_CALLBACK) From 157fd8d3c1633c78f3b7a33a2dcb5e588c159dd0 Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Wed, 23 Jul 2025 18:10:12 -0700 Subject: [PATCH 2/2] (FIX): Fixed the cookies! They are now abstracted and env controlled Need to test this in production to ensure they are working properly. --- internal/app/handlers/auth_handler.go | 25 +++++++----- internal/app/handlers/recipe_handler.go | 38 +++++++++--------- internal/domain/server/server.go | 53 +++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 29 deletions(-) diff --git a/internal/app/handlers/auth_handler.go b/internal/app/handlers/auth_handler.go index ebdf7bc..e21d3cf 100644 --- a/internal/app/handlers/auth_handler.go +++ b/internal/app/handlers/auth_handler.go @@ -37,15 +37,16 @@ func GoogleCallback(ctx *gin.Context) { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } else { // TODO: Update these values when using a real domain. Maybe an ENV? - ctx.SetCookie( - "jwt_token", - jwt, - int(time.Now().Add(7*24*time.Hour).Sub(time.Now()).Seconds()), - "/", - "", // TODO: Real live domain - false, // TODO: True in prod - true, - ) + domain.SetCookie(ctx, "jwt_token", jwt, time.Hour*24*7) + // ctx.SetCookie( + // "jwt_token", + // jwt, + // int(time.Now().Add(7*24*time.Hour).Sub(time.Now()).Seconds()), + // "/", + // "", // TODO: Real live domain + // false, // TODO: True in prod + // true, + // ) // ctx.JSON(http.StatusOK, gin.H{"jwt": jwt, "googleUserInfo": googleUserInfo, "dbUser": dbUser}) _ = dbUser @@ -60,8 +61,10 @@ func GoogleCallback(ctx *gin.Context) { // This route will direct the user back to the home page. func Logout(ctx *gin.Context) { // TODO: Use same values as the GoogleCallback function - ctx.SetCookie("jwt_token", "", -1, "/", "", false, true) // TODO: Update settings - ctx.SetCookie("search-filters", "", -1, "/", "", false, true) + domain.SetCookie(ctx, "jwt_token", "", -1) + domain.SetCookie(ctx, "search-filters", "", -1) + // ctx.SetCookie("jwt_token", "", -1, "/", "", false, true) // TODO: Update settings + // ctx.SetCookie("search-filters", "", -1, "/", "", false, true) ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) } diff --git a/internal/app/handlers/recipe_handler.go b/internal/app/handlers/recipe_handler.go index 681e36f..3a85be0 100644 --- a/internal/app/handlers/recipe_handler.go +++ b/internal/app/handlers/recipe_handler.go @@ -58,15 +58,16 @@ func SearchRecipes(ctx *gin.Context) { // Set the filters into the cookies, so they can be reloaded if bytes, err := json.Marshal(filters); err == nil { - ctx.SetCookie( - "search-filters", - string(bytes), - int(time.Now().Add(24*time.Hour).Sub(time.Now()).Seconds()), - "/", - "", // TODO: Need an actual domain - false, // TODO: True in prod - true, - ) + domain.SetCookie(ctx, "search-filters", string(bytes), time.Hour*24) + // ctx.SetCookie( + // "search-filters", + // string(bytes), + // int(time.Now().Add(24*time.Hour).Sub(time.Now()).Seconds()), + // "/", + // "", // TODO: Need an actual domain + // false, // TODO: True in prod + // true, + // ) } redirect := ctx.PostForm("redirect") @@ -108,15 +109,16 @@ func SearchRecipesFavorites(ctx *gin.Context) { // Set the filters into the cookies, so they can be reloaded if bytes, err := json.Marshal(filters); err == nil { - ctx.SetCookie( - "search-filters", - string(bytes), - int(time.Now().Add(24*time.Hour).Sub(time.Now()).Seconds()), - "/", - "", // TODO: Need an actual domain - false, // TODO: True in prod - true, - ) + domain.SetCookie(ctx, "search-filters", string(bytes), time.Hour*24) + // ctx.SetCookie( + // "search-filters", + // string(bytes), + // int(time.Now().Add(24*time.Hour).Sub(time.Now()).Seconds()), + // "/", + // "", // TODO: Need an actual domain + // false, // TODO: True in prod + // true, + // ) } // TODO: Error here if they're not logged in? diff --git a/internal/domain/server/server.go b/internal/domain/server/server.go index e2d2712..b9a8210 100644 --- a/internal/domain/server/server.go +++ b/internal/domain/server/server.go @@ -3,6 +3,7 @@ package domain import ( "fmt" "os" + "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" @@ -50,6 +51,10 @@ func IsLoggedIn(ctx *gin.Context) bool { return id && email } +// LoadEnvironment loads the environment values from either an .env file or docker environment. In +// the event that required fields are not provided, an error will return and the caller should handle +// the missing value or panic. Toggles between 'dev', 'prod', etc are also handled by this method, +// the values can be access assuming they are the proper values based on the provided environment. func LoadEnvironment() (*EnvironmentConfig, error) { err := godotenv.Load(".env") if err != nil { @@ -117,3 +122,51 @@ func LoadEnvironment() (*EnvironmentConfig, error) { return cfg, nil } + +// SetCookie sets a cookie value with a duration provided. This function handles setting the security +// configuration as well as the domain. These values are based on the EnvironmentConfig, therefore +// the value should be set. Nothing is returned by this function, but the cookie will be set. +// +// This function can also be used to clear cookies, if a blank value ("") and invalid duration (-1) +// is provided. +// +// If 0 is provided as the duration, then a session cookie is created, which will be cleared when +// the browser is closed. +func SetCookie(ctx *gin.Context, name, value string, duration time.Duration) { + deps := ctx.MustGet("deps").(*InjectedDependencies) + + var ( + path string = "/" + httpOnly bool = true + maxAge int + secure bool + domain string + ) + + if duration < 0 { + // Delete the cookie + maxAge = -1 + } else if duration == 0 { + // Session cookie, clears when browser is closed + maxAge = 0 + } else { + // Normal calculation + maxAge = int(time.Now().Add(duration).Sub(time.Now()).Seconds()) + } + + if deps.EnvironmentConfig.Environment == "prod" { + secure = true + domain = deps.EnvironmentConfig.Domain + + } else if deps.EnvironmentConfig.Environment == "dev" { + secure = false + domain = deps.EnvironmentConfig.Domain + + } else { + // Defaults + secure = false + domain = "" + } + + ctx.SetCookie(name, value, maxAge, path, domain, secure, httpOnly) +}