Stack of changes to get gin, scs, nosurf running.
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
66
internal/http/middleware/remember.go
Normal file
66
internal/http/middleware/remember.go
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user