From 2e1af8d20bb4b6db289ca43e23bf282d598875f6 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 16 Feb 2026 16:49:43 -0500 Subject: [PATCH] feat(auth): extend session duration to 7 days using constants - Add SessionDuration constant (7 days) and SessionDurationSec computed value - Update JWT token expiration to use SessionDuration instead of 1 hour - Update register/login cookie MaxAge to use SessionDurationSec (604800) - Update register/login API response ExpiresIn to use SessionDurationSec - Update refresh token endpoint ExpiresIn to use SessionDurationSec - Remove redundant client-side document.cookie lines from login/register - Add TODO comment for HTTPS cookie Secure flag This provides Google-like persistent sessions with a single source of truth for session duration, eliminating hardcoded values throughout the codebase. --- internal/handlers/auth.go | 30 +++++++++++++++++++----------- internal/handlers/refresh_token.go | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index 12db564..6d1f731 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -18,6 +18,16 @@ import ( "golang.org/x/crypto/bcrypt" ) +const ( + // Session duration constants + // Follows same pattern as refresh_token.go + SessionDuration = 7 * 24 * time.Hour // 7 days +) + +// SessionDurationSec is the session duration in seconds for use in cookies and API responses +// Note: This is computed from SessionDuration to avoid magic numbers +var SessionDurationSec = int(SessionDuration.Seconds()) + type AuthHandler struct { db *database.Queries jwtKey []byte @@ -219,8 +229,8 @@ func (h *AuthHandler) Register(c echo.Context) error { Value: accessToken, Path: "/", HttpOnly: true, - Secure: false, - MaxAge: 3600, + Secure: false, // TODO: Set to true in production with HTTPS + MaxAge: SessionDurationSec, } c.SetCookie(cookie) @@ -238,9 +248,8 @@ func (h *AuthHandler) Register(c echo.Context) error { localStorage.setItem('token', '%s'); localStorage.setItem('refreshToken', '%s'); localStorage.setItem('user', JSON.stringify(%s)); -document.cookie = 'token=%s; path=/; max-age=3600'; window.location.href = '/dashboard'; -`, accessToken, refreshToken, fmt.Sprintf(`{"id":"%s","email":"%s","username":"%s"}`, uuid.UUID(user.ID.Bytes).String(), user.Email, user.Username), accessToken) +`, accessToken, refreshToken, fmt.Sprintf(`{"id":"%s","email":"%s","username":"%s"}`, uuid.UUID(user.ID.Bytes).String(), user.Email, user.Username)) return c.HTML(http.StatusCreated, html) } @@ -254,7 +263,7 @@ window.location.href = '/dashboard'; Token: accessToken, RefreshToken: refreshToken, TokenType: "Bearer", - ExpiresIn: 3600, + ExpiresIn: SessionDurationSec, User: UserProfile{ ID: uuid.UUID(user.ID.Bytes).String(), Email: user.Email, @@ -365,8 +374,8 @@ func (h *AuthHandler) Login(c echo.Context) error { Value: accessToken, Path: "/", HttpOnly: true, - Secure: false, - MaxAge: 3600, + Secure: false, // TODO: Set to true in production with HTTPS + MaxAge: SessionDurationSec, } c.SetCookie(cookie) @@ -388,9 +397,8 @@ func (h *AuthHandler) Login(c echo.Context) error { localStorage.setItem('token', '%s'); localStorage.setItem('refreshToken', '%s'); localStorage.setItem('user', JSON.stringify(%s)); -document.cookie = 'token=%s; path=/; max-age=3600'; window.location.href = '%s'; -`, accessToken, refreshToken, fmt.Sprintf(`{"id":"%s","email":"%s","username":"%s","first_name":"%s","last_name":"%s"}`, uuid.UUID(user.ID.Bytes).String(), user.Email, user.Username, user.FirstName.String, user.LastName.String), accessToken, redirect) +`, accessToken, refreshToken, fmt.Sprintf(`{"id":"%s","email":"%s","username":"%s","first_name":"%s","last_name":"%s"}`, uuid.UUID(user.ID.Bytes).String(), user.Email, user.Username, user.FirstName.String, user.LastName.String), redirect) return c.HTML(http.StatusOK, html) } @@ -406,7 +414,7 @@ window.location.href = '%s'; Token: accessToken, RefreshToken: refreshToken, TokenType: "Bearer", - ExpiresIn: 3600, + ExpiresIn: SessionDurationSec, User: UserProfile{ ID: uuid.UUID(user.ID.Bytes).String(), Email: user.Email, @@ -780,7 +788,7 @@ func (h *AuthHandler) generateJWTWithAllClaims(userID, userRole, userEmail, user "user_role": userRole, "user_email": userEmail, "user_username": userUsername, - "exp": time.Now().Add(1 * time.Hour).Unix(), + "exp": time.Now().Add(SessionDuration).Unix(), "iat": time.Now().Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) diff --git a/internal/handlers/refresh_token.go b/internal/handlers/refresh_token.go index d9da78d..4f44b5e 100644 --- a/internal/handlers/refresh_token.go +++ b/internal/handlers/refresh_token.go @@ -71,7 +71,7 @@ func (h *AuthHandler) RefreshAccessToken(c echo.Context) error { return c.JSON(http.StatusOK, RefreshTokenResponse{ AccessToken: accessToken, TokenType: "Bearer", - ExpiresIn: 3600, + ExpiresIn: SessionDurationSec, }) }