Files

121 lines
3.0 KiB
Go

package middleware
import (
"net/http"
"strings"
"time"
sessionHelper "synlotto-website/internal/helpers/session"
"synlotto-website/internal/platform/bootstrap"
"synlotto-website/internal/platform/sessionkeys"
"github.com/gin-gonic/gin"
)
// 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()
if v := sm.Get(ctx, sessionkeys.LastActivity); v != nil {
if last, ok := v.(time.Time); ok && time.Since(last) > sm.Lifetime {
// don't destroy here; just rotate and bounce to login with a flash
_ = sm.RenewToken(ctx)
sm.Put(ctx, sessionkeys.Flash, "Your session has timed out.")
c.Redirect(http.StatusSeeOther, "/account/login")
c.Abort()
return
}
}
// if logged in, update last activity
if sm.Exists(ctx, sessionkeys.UserID) {
sm.Put(ctx, sessionkeys.LastActivity, time.Now().UTC())
}
c.Next()
}
}
// 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 {
_ = sessionHelper.RevokeToken(app.DB, selector) // tampered
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())
// (Optional) if you can look up username/is_admin here, also set:
// sm.Put(ctx, sessionkeys.Username, uname)
// sm.Put(ctx, sessionkeys.IsAdmin, isAdmin)
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
ctx := c.Request.Context()
// ✅ Use Exists to be robust to int vs int64 storage
if !sm.Exists(ctx, sessionkeys.UserID) {
c.Redirect(http.StatusSeeOther, "/account/login")
c.Abort()
return
}
c.Next()
}
}
// Redirects authenticated users away from public auth pages.
func PublicOnly() gin.HandlerFunc {
return func(c *gin.Context) {
app := c.MustGet("app").(*bootstrap.App)
sm := app.SessionManager
if sm.Exists(c.Request.Context(), sessionkeys.UserID) {
c.Redirect(http.StatusSeeOther, "/")
c.Abort()
return
}
c.Next()
}
}