Files
nginx-ui/internal/user/cache.go

260 lines
5.9 KiB
Go

package user
import (
"context"
"fmt"
"sync"
"time"
"github.com/0xJacky/Nginx-UI/internal/cache"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/uozi-tech/cosy/logger"
)
const (
// Cache key prefixes
tokenCachePrefix = "auth_token:"
shortTokenCachePrefix = "short_token:"
userCachePrefix = "user:"
// Cache TTL
tokenCacheTTL = 24 * time.Hour
)
// TokenCacheData stores token information in cache
type TokenCacheData struct {
UserID uint64 `json:"user_id"`
Token string `json:"token"`
ShortToken string `json:"short_token"`
ExpiredAt int64 `json:"expired_at"`
CreatedAt time.Time `json:"created_at"`
}
// UserCacheData stores user information in cache
type UserCacheData struct {
*model.User
CachedAt time.Time `json:"cached_at"`
}
var (
cacheMutex = &sync.RWMutex{}
)
// InitTokenCache loads all active tokens into cache on startup
func InitTokenCache(ctx context.Context) {
logger.Info("Initializing token cache...")
q := query.AuthToken
authTokens, err := q.Where(q.ExpiredAt.Gte(time.Now().Unix())).Find()
if err != nil {
logger.Error("Failed to load auth tokens:", err)
return
}
cacheMutex.Lock()
defer cacheMutex.Unlock()
loaded := 0
for _, authToken := range authTokens {
cacheData := &TokenCacheData{
UserID: authToken.UserID,
Token: authToken.Token,
ShortToken: authToken.ShortToken,
ExpiredAt: authToken.ExpiredAt,
CreatedAt: time.Now(),
}
// Cache by token
if authToken.Token != "" {
tokenKey := tokenCachePrefix + authToken.Token
cache.Set(tokenKey, cacheData, tokenCacheTTL)
}
// Cache by short token
if authToken.ShortToken != "" {
shortTokenKey := shortTokenCachePrefix + authToken.ShortToken
cache.Set(shortTokenKey, cacheData, tokenCacheTTL)
}
loaded++
}
logger.Info(fmt.Sprintf("Loaded %d auth tokens into cache", loaded))
}
// CacheToken stores a token in cache
func CacheToken(authToken *model.AuthToken) {
if authToken == nil {
return
}
cacheMutex.Lock()
defer cacheMutex.Unlock()
cacheData := &TokenCacheData{
UserID: authToken.UserID,
Token: authToken.Token,
ShortToken: authToken.ShortToken,
ExpiredAt: authToken.ExpiredAt,
CreatedAt: time.Now(),
}
// Cache by token
if authToken.Token != "" {
tokenKey := tokenCachePrefix + authToken.Token
cache.Set(tokenKey, cacheData, tokenCacheTTL)
}
// Cache by short token
if authToken.ShortToken != "" {
shortTokenKey := shortTokenCachePrefix + authToken.ShortToken
cache.Set(shortTokenKey, cacheData, tokenCacheTTL)
}
}
// GetCachedTokenData retrieves token data from cache
func GetCachedTokenData(token string) (*TokenCacheData, bool) {
cacheMutex.RLock()
defer cacheMutex.RUnlock()
tokenKey := tokenCachePrefix + token
data, found := cache.Get(tokenKey)
if !found {
return nil, false
}
tokenData, ok := data.(*TokenCacheData)
if !ok {
// Invalid cache data, remove it
cache.Del(tokenKey)
return nil, false
}
// Check if token is expired
if tokenData.ExpiredAt < time.Now().Unix() {
// Token expired, remove from cache
cache.Del(tokenKey)
if tokenData.ShortToken != "" {
cache.Del(shortTokenCachePrefix + tokenData.ShortToken)
}
return nil, false
}
return tokenData, true
}
// GetCachedShortTokenData retrieves short token data from cache
func GetCachedShortTokenData(shortToken string) (*TokenCacheData, bool) {
cacheMutex.RLock()
defer cacheMutex.RUnlock()
shortTokenKey := shortTokenCachePrefix + shortToken
data, found := cache.Get(shortTokenKey)
if !found {
return nil, false
}
tokenData, ok := data.(*TokenCacheData)
if !ok {
// Invalid cache data, remove it
cache.Del(shortTokenKey)
return nil, false
}
// Check if token is expired
if tokenData.ExpiredAt < time.Now().Unix() {
// Token expired, remove from cache
cache.Del(shortTokenKey)
if tokenData.Token != "" {
cache.Del(tokenCachePrefix + tokenData.Token)
}
return nil, false
}
return tokenData, true
}
// CacheUser stores user data in cache
func CacheUser(user *model.User) {
if user == nil {
return
}
cacheMutex.Lock()
defer cacheMutex.Unlock()
userKey := fmt.Sprintf("%s%d", userCachePrefix, user.ID)
cacheData := &UserCacheData{
User: user,
CachedAt: time.Now(),
}
cache.Set(userKey, cacheData, tokenCacheTTL)
}
// GetCachedUser retrieves user data from cache
func GetCachedUser(userID uint64) (*model.User, bool) {
cacheMutex.RLock()
defer cacheMutex.RUnlock()
userKey := fmt.Sprintf("%s%d", userCachePrefix, userID)
data, found := cache.Get(userKey)
if !found {
return nil, false
}
userData, ok := data.(*UserCacheData)
if !ok {
// Invalid cache data, remove it
cache.Del(userKey)
return nil, false
}
// Check if cache is too old (refresh every hour)
if time.Since(userData.CachedAt) > time.Hour {
cache.Del(userKey)
return nil, false
}
return userData.User, true
}
// InvalidateTokenCache removes token from cache
func InvalidateTokenCache(token string) {
cacheMutex.Lock()
defer cacheMutex.Unlock()
// Try to get token data first to also remove short token
tokenKey := tokenCachePrefix + token
if data, found := cache.Get(tokenKey); found {
if tokenData, ok := data.(*TokenCacheData); ok && tokenData.ShortToken != "" {
cache.Del(shortTokenCachePrefix + tokenData.ShortToken)
}
}
cache.Del(tokenKey)
}
// InvalidateUserCache removes user from cache
func InvalidateUserCache(userID uint64) {
cacheMutex.Lock()
defer cacheMutex.Unlock()
userKey := fmt.Sprintf("%s%d", userCachePrefix, userID)
cache.Del(userKey)
}
// ClearExpiredTokens removes expired tokens from cache
func ClearExpiredTokens() {
cacheMutex.Lock()
defer cacheMutex.Unlock()
now := time.Now().Unix()
// Note: ristretto doesn't provide a way to iterate over all keys
// Expired tokens will be removed when accessed via GetCachedTokenData/GetCachedShortTokenData
// or when the cache reaches capacity limits
logger.Debug(fmt.Sprintf("Cache cleanup completed at %d", now))
}