fix: fixes to session storage
All checks were successful
Build and Push App Image / build-and-push (push) Successful in 1m27s

This commit is contained in:
domrichardson
2026-03-26 10:06:07 +00:00
parent 6774c401bf
commit 6e642da57a
17 changed files with 498 additions and 275 deletions

View File

@@ -1,7 +1,6 @@
package handlers
import (
"encoding/base64"
"encoding/json"
"net/http"
"net/url"
@@ -17,16 +16,20 @@ import (
// AuthHandler handles authentication endpoints
type AuthHandler struct {
authService *services.AuthService
authService *services.AuthService
sessionManager *auth.SessionManager
}
// NewAuthHandler creates a new auth handler
func NewAuthHandler(authService *services.AuthService) *AuthHandler {
func NewAuthHandler(authService *services.AuthService, sessionManager *auth.SessionManager) *AuthHandler {
return &AuthHandler{
authService: authService,
authService: authService,
sessionManager: sessionManager,
}
}
const sessionCookieName = "session_id"
// Register handles user registration
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
@@ -56,6 +59,11 @@ func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
return
}
if err := h.setSessionCookie(w, r, response.User); err != nil {
http.Error(w, "Failed to create session", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
@@ -79,16 +87,10 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
return
}
// Set secure HTTP-only cookie for refresh token
http.SetCookie(w, &http.Cookie{
Name: "refresh_token",
Value: response.RefreshToken,
Path: "/",
MaxAge: 7 * 24 * 60 * 60, // 7 days
HttpOnly: true,
Secure: isSecureRequest(r),
SameSite: http.SameSiteLaxMode,
})
if err := h.setSessionCookie(w, r, response.User); err != nil {
http.Error(w, "Failed to create session", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
@@ -96,15 +98,12 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
// Logout handles user logout
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
// Clear refresh token cookie
http.SetCookie(w, &http.Cookie{
Name: "refresh_token",
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
Secure: isSecureRequest(r),
})
sessionCookie, err := r.Cookie(sessionCookieName)
if err == nil {
_ = h.sessionManager.DeleteSession(r.Context(), sessionCookie.Value)
}
h.clearSessionCookie(w, r)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"message": "Logged out successfully"})
@@ -215,7 +214,7 @@ func (h *AuthHandler) CompleteProviderLogin(w http.ResponseWriter, r *http.Reque
response, err := h.authService.CompleteProviderLogin(r.Context(), providerID, r.URL.Query().Get("code"), buildBackendURL(r, "/api/v1/auth/providers/"+providerID.Hex()+"/callback"))
if err != nil {
http.Redirect(w, r, buildFrontendLoginURL("oauth_error", err.Error(), "", nil), http.StatusFound)
http.Redirect(w, r, buildFrontendLoginURL("oauth_error", err.Error()), http.StatusFound)
return
}
@@ -229,17 +228,12 @@ func (h *AuthHandler) CompleteProviderLogin(w http.ResponseWriter, r *http.Reque
SameSite: http.SameSiteLaxMode,
})
http.SetCookie(w, &http.Cookie{
Name: "refresh_token",
Value: response.RefreshToken,
Path: "/",
MaxAge: 7 * 24 * 60 * 60,
HttpOnly: true,
Secure: isSecureRequest(r),
SameSite: http.SameSiteLaxMode,
})
if err := h.setSessionCookie(w, r, response.User); err != nil {
http.Redirect(w, r, buildFrontendLoginURL("oauth_error", "Failed to create session"), http.StatusFound)
return
}
http.Redirect(w, r, buildFrontendLoginURL("oauth_success", "", response.AccessToken, response.User), http.StatusFound)
http.Redirect(w, r, buildFrontendLoginURL("oauth_success", ""), http.StatusFound)
}
// RefreshToken handles token refresh
@@ -249,23 +243,57 @@ func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
return
}
// Get refresh token from cookie
cookie, err := r.Cookie("refresh_token")
cookie, err := r.Cookie(sessionCookieName)
if err != nil {
http.Error(w, "Refresh token not found", http.StatusUnauthorized)
http.Error(w, "Session not found", http.StatusUnauthorized)
return
}
accessToken, err := h.authService.RefreshAccessToken(r.Context(), cookie.Value)
sessionData, err := h.sessionManager.GetSession(r.Context(), cookie.Value)
if err != nil {
http.Error(w, "Invalid refresh token", http.StatusUnauthorized)
http.Error(w, "Invalid session", http.StatusUnauthorized)
return
}
if err := h.sessionManager.RefreshSession(r.Context(), cookie.Value); err == nil {
http.SetCookie(w, h.newSessionCookie(r, cookie.Value))
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"access_token": accessToken,
"expires_in": 3600,
"user": sessionData,
"expires_in": int(h.sessionManager.TTL().Seconds()),
})
}
// Me returns the currently authenticated user profile.
func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) {
sessionCookie, err := r.Cookie(sessionCookieName)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
sessionData, err := h.sessionManager.GetSession(r.Context(), sessionCookie.Value)
if err != nil {
h.clearSessionCookie(w, r)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
user, err := h.authService.GetUserProfile(r.Context(), sessionData.UserID)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err := h.sessionManager.RefreshSession(r.Context(), sessionCookie.Value); err == nil {
http.SetCookie(w, h.newSessionCookie(r, sessionCookie.Value))
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"user": user,
"expires_in": int(h.sessionManager.TTL().Seconds()),
})
}
@@ -292,7 +320,7 @@ func buildBackendURL(r *http.Request, path string) string {
return scheme + "://" + r.Host + path
}
func buildFrontendLoginURL(status, message, accessToken string, user *dto.UserDTO) string {
func buildFrontendLoginURL(status, message string) string {
frontendURL := os.Getenv("FRONTEND_URL")
if frontendURL == "" {
frontendURL = "http://localhost:5173"
@@ -310,14 +338,48 @@ func buildFrontendLoginURL(status, message, accessToken string, user *dto.UserDT
if message != "" {
query.Set("message", message)
}
if accessToken != "" {
query.Set("access_token", accessToken)
}
if user != nil {
payload, _ := json.Marshal(user)
query.Set("user_json", string(payload))
query.Set("user", base64.RawURLEncoding.EncodeToString(payload))
}
parsed.RawQuery = query.Encode()
return parsed.String()
}
func (h *AuthHandler) setSessionCookie(w http.ResponseWriter, r *http.Request, user *dto.UserDTO) error {
if user == nil {
return nil
}
sessionID, err := h.sessionManager.CreateSession(r.Context(), &auth.SessionData{
UserID: user.ID,
Email: user.Email,
Username: user.Username,
})
if err != nil {
return err
}
http.SetCookie(w, h.newSessionCookie(r, sessionID))
return nil
}
func (h *AuthHandler) newSessionCookie(r *http.Request, sessionID string) *http.Cookie {
return &http.Cookie{
Name: sessionCookieName,
Value: sessionID,
Path: "/",
MaxAge: int(h.sessionManager.TTL().Seconds()),
HttpOnly: true,
Secure: isSecureRequest(r),
SameSite: http.SameSiteLaxMode,
}
}
func (h *AuthHandler) clearSessionCookie(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
Secure: isSecureRequest(r),
SameSite: http.SameSiteLaxMode,
})
}