Compare commits

...

2 Commits

Author SHA1 Message Date
b778469365 fix session management and storage 2025-08-14 17:15:55 -05:00
2bbff01c0d clean up go mod, fix session persistence 2025-08-14 16:32:47 -05:00
7 changed files with 78 additions and 153 deletions

11
go.mod
View File

@ -5,22 +5,11 @@ go 1.24.6
require ( require (
github.com/valyala/fasthttp v1.64.0 github.com/valyala/fasthttp v1.64.0
golang.org/x/crypto v0.41.0 golang.org/x/crypto v0.41.0
zombiezen.com/go/sqlite v1.4.2
) )
require ( require (
github.com/andybalholm/brotli v1.2.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/sys v0.35.0 // indirect golang.org/x/sys v0.35.0 // indirect
modernc.org/libc v1.65.7 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.37.1 // indirect
) )

49
go.sum
View File

@ -1,19 +1,7 @@
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og= github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og=
@ -22,42 +10,5 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00=
modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs=
modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
zombiezen.com/go/sqlite v1.4.2 h1:KZXLrBuJ7tKNEm+VJcApLMeQbhmAUOKA5VWS93DfFRo=
zombiezen.com/go/sqlite v1.4.2/go.mod h1:5Kd4taTAD4MkBzT25mQ9uaAlLjyR0rFhsR6iINO70jc=

View File

@ -24,17 +24,20 @@ func Middleware() router.Middleware {
if existingSess, exists := session.Get(sessionID); exists { if existingSess, exists := session.Get(sessionID); exists {
sess = existingSess sess = existingSess
sess.Touch() sess.Touch()
session.Store(sess)
if sess.UserID > 0 { // User session if sess.UserID > 0 { // User session
user, err := users.Find(sess.UserID) user, err := users.Find(sess.UserID)
if err == nil && user != nil { if err == nil && user != nil {
ctx.SetUserValue("user", user) ctx.SetUserValue("user", user)
} else {
// User not found, reset to guest session
sess.SetUserID(0)
}
}
session.Store(sess)
setSessionCookie(ctx, sessionID) setSessionCookie(ctx, sessionID)
} }
} }
}
}
// Create guest session if none exists // Create guest session if none exists
if sess == nil { if sess == nil {
@ -89,8 +92,10 @@ func RequireGuest(paths ...string) router.Middleware {
} }
func IsAuthenticated(ctx router.Ctx) bool { func IsAuthenticated(ctx router.Ctx) bool {
_, exists := ctx.UserValue("user").(*users.User) if user, ok := ctx.UserValue("user").(*users.User); ok && user != nil {
return exists return true
}
return false
} }
func GetCurrentUser(ctx router.Ctx) *users.User { func GetCurrentUser(ctx router.Ctx) *users.User {
@ -109,24 +114,42 @@ func GetCurrentSession(ctx router.Ctx) *session.Session {
func Login(ctx router.Ctx, user *users.User) { func Login(ctx router.Ctx, user *users.User) {
sess := ctx.UserValue("session").(*session.Session) sess := ctx.UserValue("session").(*session.Session)
sess.RegenerateID()
sess.Set("user_id", user.ID) // Update the session to be authenticated
sess.SetUserID(user.ID) // This updates the struct field
sess.RegenerateID() // Generate new ID for security
sess.SetFlash("success", fmt.Sprintf("Welcome back, %s!", user.Username)) sess.SetFlash("success", fmt.Sprintf("Welcome back, %s!", user.Username))
// Remove any old user_id from session data if it exists
sess.Delete("user_id")
session.Store(sess) session.Store(sess)
// Update context values
ctx.SetUserValue("session", sess) ctx.SetUserValue("session", sess)
ctx.SetUserValue("user", user) ctx.SetUserValue("user", user)
// Update cookie with new session ID
setSessionCookie(ctx, sess.ID)
} }
func Logout(ctx router.Ctx) { func Logout(ctx router.Ctx) {
sessionID := cookies.GetCookie(ctx, SessionCookieName) sess := ctx.UserValue("session").(*session.Session)
if sessionID != "" { if sess != nil {
session.Delete(sessionID) // Convert back to guest session
sess.SetUserID(0) // Reset to guest
sess.RegenerateID() // Generate new ID for security
// Clean up any user-related session data
sess.Delete("user_id")
session.Store(sess)
ctx.SetUserValue("session", sess)
// Update cookie with new session ID
setSessionCookie(ctx, sess.ID)
} }
deleteSessionCookie(ctx)
ctx.SetUserValue("session", nil)
ctx.SetUserValue("user", nil) ctx.SetUserValue("user", nil)
} }

View File

@ -4,20 +4,20 @@
// # Basic Usage // # Basic Usage
// //
// // Generate token and store in session // // Generate token and store in session
// token := csrf.GenerateToken(ctx, sessionManager) // token := csrf.GenerateToken(ctx)
// //
// // In templates - generate hidden input field // // In templates - generate hidden input field
// hiddenField := csrf.HiddenField(ctx, sessionManager) // hiddenField := csrf.HiddenField(ctx)
// //
// // Verify form submission // // Verify form submission
// if !csrf.ValidateToken(ctx, sessionManager, formToken) { // if !csrf.ValidateToken(ctx, formToken) {
// // Handle CSRF validation failure // // Handle CSRF validation failure
// } // }
// //
// # Middleware Integration // # Middleware Integration
// //
// // Add CSRF middleware to protected routes // // Add CSRF middleware to protected routes
// r.Use(middleware.CSRF(authManager)) // r.Use(csrf.Middleware())
package csrf package csrf
import ( import (
@ -25,7 +25,6 @@ import (
"crypto/subtle" "crypto/subtle"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"time"
"dk/internal/router" "dk/internal/router"
"dk/internal/session" "dk/internal/session"
@ -37,11 +36,10 @@ const (
TokenLength = 32 TokenLength = 32
TokenFieldName = "_csrf_token" TokenFieldName = "_csrf_token"
SessionKey = "csrf_token" SessionKey = "csrf_token"
SessionCtxKey = "session" // Same as middleware.SessionKey SessionCtxKey = "session"
CookieName = "_csrf"
) )
// GetCurrentSession retrieves the session from context (mirrors middleware function) // GetCurrentSession retrieves the session from context
func GetCurrentSession(ctx router.Ctx) *session.Session { func GetCurrentSession(ctx router.Ctx) *session.Session {
if sess, ok := ctx.UserValue(SessionCtxKey).(*session.Session); ok { if sess, ok := ctx.UserValue(SessionCtxKey).(*session.Session); ok {
return sess return sess
@ -49,7 +47,7 @@ func GetCurrentSession(ctx router.Ctx) *session.Session {
return nil return nil
} }
// GenerateToken creates a new CSRF token and stores it in the session or cookie // GenerateToken creates a new CSRF token and stores it in the session
func GenerateToken(ctx router.Ctx) string { func GenerateToken(ctx router.Ctx) string {
// Generate cryptographically secure random bytes // Generate cryptographically secure random bytes
tokenBytes := make([]byte, TokenLength) tokenBytes := make([]byte, TokenLength)
@ -60,54 +58,43 @@ func GenerateToken(ctx router.Ctx) string {
token := base64.URLEncoding.EncodeToString(tokenBytes) token := base64.URLEncoding.EncodeToString(tokenBytes)
// Store token in session if user is authenticated, otherwise use cookie // Store token in session (both guests and authenticated users have sessions)
if session := GetCurrentSession(ctx); session != nil { if sess := GetCurrentSession(ctx); sess != nil {
StoreToken(session, token) StoreToken(sess, token)
} else { session.Store(sess)
// Store in cookie for guest users
StoreTokenInCookie(ctx, token)
} }
return token return token
} }
// GetToken retrieves the current CSRF token from session or cookie, generating one if needed // GetToken retrieves the current CSRF token from session, generating one if needed
func GetToken(ctx router.Ctx) string { func GetToken(ctx router.Ctx) string {
session := GetCurrentSession(ctx) sess := GetCurrentSession(ctx)
if sess == nil {
return "" // No session available
}
if session != nil { // Check for existing token
// Authenticated user - check session first if existingToken := GetStoredToken(sess); existingToken != "" {
if existingToken := GetStoredToken(session); existingToken != "" {
return existingToken return existingToken
} }
} else {
// Guest user - check cookie first
if existingToken := GetTokenFromCookie(ctx); existingToken != "" {
return existingToken
}
}
// Generate new token if none exists // Generate new token if none exists
return GenerateToken(ctx) return GenerateToken(ctx)
} }
// ValidateToken verifies a CSRF token against the stored session or cookie token // ValidateToken verifies a CSRF token against the stored session token
func ValidateToken(ctx router.Ctx, submittedToken string) bool { func ValidateToken(ctx router.Ctx, submittedToken string) bool {
if submittedToken == "" { if submittedToken == "" {
return false return false
} }
var storedToken string sess := GetCurrentSession(ctx)
session := GetCurrentSession(ctx) if sess == nil {
return false // No session
if session != nil {
// Authenticated user - get token from session
storedToken = GetStoredToken(session)
} else {
// Guest user - get token from cookie
storedToken = GetTokenFromCookie(ctx)
} }
storedToken := GetStoredToken(sess)
if storedToken == "" { if storedToken == "" {
return false // No stored token return false // No stored token
} }
@ -133,14 +120,13 @@ func GetStoredToken(sess *session.Session) string {
// RotateToken generates a new token and replaces the old one in the session // RotateToken generates a new token and replaces the old one in the session
func RotateToken(ctx router.Ctx) string { func RotateToken(ctx router.Ctx) string {
session := GetCurrentSession(ctx) sess := GetCurrentSession(ctx)
if session == nil { if sess == nil {
return "" return ""
} }
// Generate new token // Generate new token (this will automatically store it)
newToken := GenerateToken(ctx) newToken := GenerateToken(ctx)
return newToken return newToken
} }
@ -181,23 +167,9 @@ func ValidateFormToken(ctx router.Ctx) bool {
return ValidateToken(ctx, string(tokenBytes)) return ValidateToken(ctx, string(tokenBytes))
} }
// StoreTokenInCookie stores a CSRF token in a cookie for guest users // GetTokenFromCookie retrieves a CSRF token from cookie (legacy support)
func StoreTokenInCookie(ctx router.Ctx, token string) {
cookie := &fasthttp.Cookie{}
cookie.SetKey(CookieName)
cookie.SetValue(token)
cookie.SetHTTPOnly(true)
cookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
cookie.SetSecure(false) // Set to true in production with HTTPS
cookie.SetExpire(time.Now().Add(24 * time.Hour)) // Expire in 24 hours
cookie.SetPath("/")
ctx.Response.Header.SetCookie(cookie)
}
// GetTokenFromCookie retrieves a CSRF token from cookie for guest users
func GetTokenFromCookie(ctx router.Ctx) string { func GetTokenFromCookie(ctx router.Ctx) string {
return string(ctx.Request.Header.Cookie(CookieName)) return string(ctx.Request.Header.Cookie("_csrf"))
} }
// Middleware returns a middleware function that automatically validates CSRF tokens // Middleware returns a middleware function that automatically validates CSRF tokens

View File

@ -5,7 +5,6 @@ import (
"strings" "strings"
"dk/internal/auth" "dk/internal/auth"
"dk/internal/csrf"
"dk/internal/models/users" "dk/internal/models/users"
"dk/internal/password" "dk/internal/password"
"dk/internal/router" "dk/internal/router"
@ -77,12 +76,7 @@ func processLogin(ctx router.Ctx, _ []string) {
auth.Login(ctx, user) auth.Login(ctx, user)
// Transfer CSRF token from cookie to session for authenticated user // CSRF token is already in session, no need to transfer from cookie
if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" {
if sess := ctx.UserValue("session").(*session.Session); sess != nil {
csrf.StoreToken(sess, cookieToken)
}
}
ctx.Redirect("/", fasthttp.StatusFound) ctx.Redirect("/", fasthttp.StatusFound)
} }
@ -158,28 +152,16 @@ func processRegister(ctx router.Ctx, _ []string) {
return return
} }
// Store old session ID before creating new one // Auto-login after registration (this will update the current session)
oldSess := ctx.UserValue("session").(*session.Session)
oldSessionID := oldSess.ID
// Auto-login after registration
auth.Login(ctx, user) auth.Login(ctx, user)
// Clean up old guest session // Update success message (Login already sets a message, so override it)
session.Delete(oldSessionID)
// Set success message
if sess := ctx.UserValue("session").(*session.Session); sess != nil { if sess := ctx.UserValue("session").(*session.Session); sess != nil {
sess.SetFlash("success", fmt.Sprintf("Greetings, %s!", user.Username)) sess.SetFlash("success", fmt.Sprintf("Greetings, %s!", user.Username))
session.Store(sess) session.Store(sess)
} }
// Transfer CSRF token from cookie to session for authenticated user // CSRF token is already in session, no need to transfer from cookie
if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" {
if sess := ctx.UserValue("session").(*session.Session); sess != nil {
csrf.StoreToken(sess, cookieToken)
}
}
ctx.Redirect("/", fasthttp.StatusFound) ctx.Redirect("/", fasthttp.StatusFound)
} }

View File

@ -131,10 +131,13 @@ func (sm *SessionManager) load() {
if data != nil && data.ExpiresAt > now { if data != nil && data.ExpiresAt > now {
sess := &Session{ sess := &Session{
ID: id, ID: id,
UserID: data.UserID, UserID: data.UserID, // Make sure we restore the UserID properly
ExpiresAt: data.ExpiresAt, ExpiresAt: data.ExpiresAt,
Data: data.Data, Data: data.Data,
} }
if sess.Data == nil {
sess.Data = make(map[string]any)
}
sm.sessions[id] = sess sm.sessions[id] = sess
} }
} }
@ -155,7 +158,7 @@ func (sm *SessionManager) Save() error {
sessionsData := make(map[string]*sessionData, len(sm.sessions)) sessionsData := make(map[string]*sessionData, len(sm.sessions))
for id, sess := range sm.sessions { for id, sess := range sm.sessions {
sessionsData[id] = &sessionData{ sessionsData[id] = &sessionData{
UserID: sess.UserID, UserID: sess.UserID, // Save the actual UserID from the struct
ExpiresAt: sess.ExpiresAt, ExpiresAt: sess.ExpiresAt,
Data: sess.Data, Data: sess.Data,
} }

View File

@ -84,6 +84,11 @@ func (s *Session) RegenerateID() {
} }
} }
// SetUserID updates the session's user ID (for login/logout)
func (s *Session) SetUserID(userID int) {
s.UserID = userID
}
// generateID creates a random session ID // generateID creates a random session ID
func generateID() string { func generateID() string {
bytes := make([]byte, IDLength) bytes := make([]byte, IDLength)