migrated from cup session to core/session.go, Contains the new SessionUser struct and the CreateAuthTokenHashedCacheKey utility function
This commit is contained in:
parent
7c164ef58f
commit
75ddca3e7d
3 changed files with 156 additions and 0 deletions
|
|
@ -34,6 +34,7 @@ type Context struct {
|
||||||
GetMailer func() *Mailer
|
GetMailer func() *Mailer
|
||||||
GetEventsManager func() *EventsManager
|
GetEventsManager func() *EventsManager
|
||||||
GetLogger func() *logger.Logger
|
GetLogger func() *logger.Logger
|
||||||
|
GetSession func() *SessionUser
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO enhance
|
// TODO enhance
|
||||||
|
|
|
||||||
9
core.go
9
core.go
|
|
@ -277,6 +277,7 @@ func (app *App) makeHTTPRouterHandlerFunc(h Controller, ms []Hook) httprouter.Ha
|
||||||
GetMailer: resolveMailer(),
|
GetMailer: resolveMailer(),
|
||||||
GetEventsManager: resolveEventsManager(),
|
GetEventsManager: resolveEventsManager(),
|
||||||
GetLogger: resolveLogger(),
|
GetLogger: resolveLogger(),
|
||||||
|
GetSession: getSession(),
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.prepare(ctx)
|
ctx.prepare(ctx)
|
||||||
|
|
@ -685,6 +686,14 @@ func resolveLogger() func() *logger.Logger {
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getSession returns a function that creates and returns a new SessionUser instance when invoked.
|
||||||
|
func getSession() func() *SessionUser {
|
||||||
|
f := func() *SessionUser {
|
||||||
|
return &SessionUser{}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
// MakeDirs creates the specified directories under the base path with the provided permissions (0766).
|
// MakeDirs creates the specified directories under the base path with the provided permissions (0766).
|
||||||
func (app *App) MakeDirs(dirs ...string) {
|
func (app *App) MakeDirs(dirs ...string) {
|
||||||
o := syscall.Umask(0)
|
o := syscall.Umask(0)
|
||||||
|
|
|
||||||
146
session.go
Normal file
146
session.go
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
// Copyright (c) 2026 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SessionUser handles user session data management with thread-safe operations.
|
||||||
|
// Sessions are stored in the cache (Redis) and are tied to an authenticated user
|
||||||
|
// via JWT tokens stored in cookies.
|
||||||
|
type SessionUser struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
context *Context
|
||||||
|
userID uint
|
||||||
|
hashedSessionKey string
|
||||||
|
authenticated bool
|
||||||
|
sessionStart time.Time
|
||||||
|
values map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes the session by validating the user's JWT token from the cookie.
|
||||||
|
// It checks the cached token, verifies it matches, and loads any existing session data.
|
||||||
|
// Returns true if the session was successfully established.
|
||||||
|
func (s *SessionUser) Init(c *Context) bool {
|
||||||
|
s.context = c
|
||||||
|
|
||||||
|
// get cookie
|
||||||
|
usercookie, err := c.GetCookie()
|
||||||
|
if err != nil || usercookie.Token == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := c.GetJWT().DecodeToken(usercookie.Token)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := uint(c.CastToInt(payload["userID"]))
|
||||||
|
userAgent := c.GetUserAgent()
|
||||||
|
|
||||||
|
// verify token against cached value
|
||||||
|
hashedCacheKey := CreateAuthTokenHashedCacheKey(userID, userAgent)
|
||||||
|
cachedToken, err := c.GetCache().Get(hashedCacheKey)
|
||||||
|
if err != nil || cachedToken != usercookie.Token {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// session established - load session data from cache
|
||||||
|
userAgent = c.GetUserAgent()
|
||||||
|
sessionKey := fmt.Sprintf("sess_%v", userAgent)
|
||||||
|
s.hashedSessionKey = CreateAuthTokenHashedCacheKey(userID, sessionKey)
|
||||||
|
s.values = make(map[string]interface{})
|
||||||
|
s.authenticated = true
|
||||||
|
s.userID = userID
|
||||||
|
|
||||||
|
value, _ := c.GetCache().Get(s.hashedSessionKey)
|
||||||
|
if len(value) > 0 {
|
||||||
|
_ = json.Unmarshal([]byte(value), &s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set stores a value in the session and persists it to the cache.
|
||||||
|
func (s *SessionUser) Set(key string, value interface{}) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.values[key] = value
|
||||||
|
s.mu.Unlock()
|
||||||
|
return s.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a value from the session. Returns the value and a boolean indicating if the key exists.
|
||||||
|
func (s *SessionUser) Get(key string) (interface{}, bool) {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
val, ok := s.values[key]
|
||||||
|
return val, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes a specific key from the session and persists the change.
|
||||||
|
// Returns the deleted value if it existed.
|
||||||
|
func (s *SessionUser) Delete(key string) interface{} {
|
||||||
|
s.mu.RLock()
|
||||||
|
v, ok := s.values[key]
|
||||||
|
s.mu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
s.mu.Lock()
|
||||||
|
delete(s.values, key)
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
s.Save()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush deletes all session data from the cache.
|
||||||
|
func (s *SessionUser) Flush() error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if s.hashedSessionKey != "" {
|
||||||
|
_ = s.context.GetCache().Delete(s.hashedSessionKey)
|
||||||
|
}
|
||||||
|
s.values = make(map[string]interface{})
|
||||||
|
s.authenticated = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save persists the current session values to the cache.
|
||||||
|
func (s *SessionUser) Save() error {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
|
||||||
|
if len(s.values) > 0 {
|
||||||
|
buf, err := json.Marshal(&s.values)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.context.GetCache().Set(s.hashedSessionKey, string(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = s.context.GetCache().Delete(s.hashedSessionKey)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserID returns the user ID of the authenticated session user.
|
||||||
|
func (s *SessionUser) GetUserID() uint {
|
||||||
|
return s.userID
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAuthenticated returns whether the session has been successfully authenticated.
|
||||||
|
func (s *SessionUser) IsAuthenticated() bool {
|
||||||
|
return s.authenticated
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAuthTokenHashedCacheKey generates a hashed cache key used to store JWT tokens and session data.
|
||||||
|
// The key is based on the user ID and user agent, hashed with MD5.
|
||||||
|
func CreateAuthTokenHashedCacheKey(userID uint, userAgent string) string {
|
||||||
|
cacheKey := fmt.Sprintf("userid:_%v_useragent:_%v_jwt_token", userID, userAgent)
|
||||||
|
return fmt.Sprintf("%x", md5.Sum([]byte(cacheKey)))
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue