Stack of changes to get gin, scs, nosurf running.

This commit is contained in:
2025-10-28 11:56:42 +00:00
parent 07117ba35e
commit 86be6479f1
65 changed files with 1890 additions and 1503 deletions

View File

@@ -1,50 +1,97 @@
package middleware
// ToDo: will no doubt need to fix as now using new session not the olf gorilla one
import (
"net/http"
"strings"
"time"
httpHelpers "synlotto-website/internal/helpers/http"
sessionHelper "synlotto-website/internal/helpers/session"
"synlotto-website/internal/platform/bootstrap"
"synlotto-website/internal/platform/sessionkeys"
"synlotto-website/internal/platform/constants"
"github.com/gin-gonic/gin"
)
func Auth(required bool) func(http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, _ := httpHelpers.GetSession(w, r)
// Tracks idle timeout using LastActivity; redirects on timeout.
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
app := c.MustGet("app").(*bootstrap.App)
sm := app.SessionManager
ctx := c.Request.Context()
_, ok := session.Values["user_id"].(int)
if required && !ok {
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
if v := sm.Get(ctx, sessionkeys.LastActivity); v != nil {
if last, ok := v.(time.Time); ok && time.Since(last) > sm.Lifetime {
_ = sm.RenewToken(ctx)
sm.Put(ctx, sessionkeys.Flash, "Your session has timed out.")
c.Redirect(http.StatusSeeOther, "/account/login")
c.Abort()
return
}
if ok {
last, hasLast := session.Values["last_activity"].(time.Time)
if hasLast && time.Since(last) > constants.SessionDuration {
session.Options.MaxAge = -1
session.Save(r, w)
newSession, _ := httpHelpers.GetSession(w, r)
newSession.Values["flash"] = "Your session has timed out."
newSession.Save(r, w)
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
return
}
session.Values["last_activity"] = time.Now()
session.Save(r, w)
}
next(w, r)
}
sm.Put(ctx, sessionkeys.LastActivity, time.Now().UTC())
c.Next()
}
}
func Protected(h http.HandlerFunc) http.HandlerFunc {
return Auth(true)(SessionTimeout(h))
// Optional remember-me using selector:verifier token pair.
func RememberMiddleware(app *bootstrap.App) gin.HandlerFunc {
return func(c *gin.Context) {
sm := app.SessionManager
ctx := c.Request.Context()
// Already logged in? Skip.
if sm.Exists(ctx, sessionkeys.UserID) {
c.Next()
return
}
cookie, err := c.Request.Cookie(app.Config.Session.RememberCookieName)
if err != nil {
c.Next()
return
}
parts := strings.SplitN(cookie.Value, ":", 2)
if len(parts) != 2 {
c.Next()
return
}
selector, verifier := parts[0], parts[1]
userID, hash, expires, revokedAt, err := sessionHelper.FindToken(app.DB, selector)
if err != nil || revokedAt != nil || time.Now().After(expires) {
c.Next()
return
}
if sessionHelper.HashVerifier(verifier) != hash {
// Tampered token revoke for safety.
_ = sessionHelper.RevokeToken(app.DB, selector)
c.Next()
return
}
// Success → create fresh SCS session
_ = sm.RenewToken(ctx)
sm.Put(ctx, sessionkeys.UserID, userID)
sm.Put(ctx, sessionkeys.LastActivity, time.Now().UTC())
c.Next()
}
}
// Blocks anonymous users; redirects to login.
func RequireAuth() gin.HandlerFunc {
return func(c *gin.Context) {
app := c.MustGet("app").(*bootstrap.App)
sm := app.SessionManager
if sm.GetInt(c.Request.Context(), sessionkeys.UserID) == 0 {
c.Redirect(http.StatusSeeOther, "/account/login")
c.Abort()
return
}
c.Next()
}
}

View File

@@ -0,0 +1,66 @@
package middleware
import (
"strings"
"time"
"github.com/gin-gonic/gin"
sessionHelper "synlotto-website/internal/helpers/session"
"synlotto-website/internal/platform/bootstrap"
"synlotto-website/internal/platform/sessionkeys"
)
// Remember checks if a remember-me cookie exists and restores the session if valid.
func Remember(app *bootstrap.App) gin.HandlerFunc {
return func(c *gin.Context) {
sm := app.SessionManager
ctx := c.Request.Context()
// Already logged in? skip.
if sm.Exists(ctx, sessionkeys.UserID) {
c.Next()
return
}
// Look for remember-me cookie
cookie, err := c.Request.Cookie(app.Config.Session.RememberCookieName)
if err != nil {
c.Next()
return
}
parts := strings.SplitN(cookie.Value, ":", 2)
if len(parts) != 2 {
c.Next()
return
}
selector, verifier := parts[0], parts[1]
if selector == "" || verifier == "" {
c.Next()
return
}
userID, hash, expiresAt, revokedAt, err := sessionHelper.FindToken(app.DB, selector)
if err != nil || revokedAt != nil || time.Now().After(expiresAt) {
c.Next()
return
}
// Constant-time compare via hashing the verifier
if sessionHelper.HashVerifier(verifier) != hash {
_ = sessionHelper.RevokeToken(app.DB, selector) // tampered → revoke
c.Next()
return
}
// ✅ Valid token → create a new session for the user
_ = sm.RenewToken(ctx)
sm.Put(ctx, sessionkeys.UserID, userID)
sm.Put(ctx, sessionkeys.LastActivity, time.Now().UTC())
// (Optional TODO): rotate token and set a fresh cookie.
c.Next()
}
}

View File

@@ -1,41 +0,0 @@
package middleware
// ToDo: This is more than likele now redunant with the session change
import (
"log"
"net/http"
"time"
session "synlotto-website/internal/handlers/session"
"synlotto-website/internal/platform/constants"
)
func SessionTimeout(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
sess, err := session.GetSession(w, r)
if err != nil {
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
return
}
last, ok := sess.Values["last_activity"].(time.Time)
if !ok || time.Since(last) > constants.SessionDuration {
sess.Options.MaxAge = -1
_ = sess.Save(r, w)
newSession, _ := session.GetSession(w, r)
newSession.Values["flash"] = "Your session has timed out."
_ = newSession.Save(r, w)
log.Printf("Session timeout triggered")
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
return
}
sess.Values["last_activity"] = time.Now().UTC()
_ = sess.Save(r, w)
next(w, r)
}
}