Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout. Co-authored-by: Cursor <cursoragent@cursor.com>
136 lines
3.4 KiB
Go
136 lines
3.4 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
|
|
"spark-server/internal/pkg/errs"
|
|
)
|
|
|
|
const (
|
|
tokenUseAccess = "access"
|
|
tokenUseRefresh = "refresh"
|
|
)
|
|
|
|
// TokenPair is the current access+refresh jti for one user device.
|
|
type TokenPair struct {
|
|
Access string `json:"a"`
|
|
Refresh string `json:"r"`
|
|
}
|
|
|
|
// PairStore holds the live token pair per device so refresh can rotate and logout can revoke both.
|
|
type PairStore interface {
|
|
Put(ctx context.Context, userID int64, deviceID uint64, pair TokenPair, ttl time.Duration) error
|
|
Get(ctx context.Context, userID int64, deviceID uint64) (TokenPair, error)
|
|
Delete(ctx context.Context, userID int64, deviceID uint64) error
|
|
}
|
|
|
|
type MemoryPairs struct {
|
|
mu sync.Mutex
|
|
items map[string]storedPair
|
|
}
|
|
|
|
type storedPair struct {
|
|
pair TokenPair
|
|
exp int64
|
|
}
|
|
|
|
func NewMemoryPairs() *MemoryPairs {
|
|
return &MemoryPairs{items: make(map[string]storedPair)}
|
|
}
|
|
|
|
func pairKey(userID int64, deviceID uint64) string {
|
|
return strconv.FormatInt(userID, 10) + ":" + strconv.FormatUint(deviceID, 10)
|
|
}
|
|
|
|
func (s *MemoryPairs) Put(_ context.Context, userID int64, deviceID uint64, pair TokenPair, ttl time.Duration) error {
|
|
if userID <= 0 || deviceID == 0 || ttl <= 0 {
|
|
return nil
|
|
}
|
|
s.mu.Lock()
|
|
s.items[pairKey(userID, deviceID)] = storedPair{pair: pair, exp: time.Now().Add(ttl).UnixNano()}
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (s *MemoryPairs) Get(_ context.Context, userID int64, deviceID uint64) (TokenPair, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
row, ok := s.items[pairKey(userID, deviceID)]
|
|
if !ok {
|
|
return TokenPair{}, nil
|
|
}
|
|
if time.Now().UnixNano() >= row.exp {
|
|
delete(s.items, pairKey(userID, deviceID))
|
|
return TokenPair{}, nil
|
|
}
|
|
return row.pair, nil
|
|
}
|
|
|
|
func (s *MemoryPairs) Delete(_ context.Context, userID int64, deviceID uint64) error {
|
|
s.mu.Lock()
|
|
delete(s.items, pairKey(userID, deviceID))
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
type RedisPairs struct {
|
|
rdb *redis.Client
|
|
prefix string
|
|
}
|
|
|
|
func NewRedisPairs(rdb *redis.Client, prefix string) *RedisPairs {
|
|
if prefix == "" {
|
|
prefix = "refresh:"
|
|
}
|
|
return &RedisPairs{rdb: rdb, prefix: prefix}
|
|
}
|
|
|
|
func (s *RedisPairs) key(userID int64, deviceID uint64) string {
|
|
return s.prefix + pairKey(userID, deviceID)
|
|
}
|
|
|
|
func (s *RedisPairs) Put(ctx context.Context, userID int64, deviceID uint64, pair TokenPair, ttl time.Duration) error {
|
|
if s == nil || s.rdb == nil {
|
|
return errs.ErrAvailable.WithInnerMsg("jwt refresh redis not ready")
|
|
}
|
|
if userID <= 0 || deviceID == 0 || ttl <= 0 {
|
|
return nil
|
|
}
|
|
raw, err := json.Marshal(pair)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.rdb.Set(ctx, s.key(userID, deviceID), raw, ttl).Err()
|
|
}
|
|
|
|
func (s *RedisPairs) Get(ctx context.Context, userID int64, deviceID uint64) (TokenPair, error) {
|
|
if s == nil || s.rdb == nil {
|
|
return TokenPair{}, errs.ErrAvailable.WithInnerMsg("jwt refresh redis not ready")
|
|
}
|
|
raw, err := s.rdb.Get(ctx, s.key(userID, deviceID)).Bytes()
|
|
if err == redis.Nil {
|
|
return TokenPair{}, nil
|
|
}
|
|
if err != nil {
|
|
return TokenPair{}, err
|
|
}
|
|
var pair TokenPair
|
|
if err := json.Unmarshal(raw, &pair); err != nil {
|
|
return TokenPair{}, err
|
|
}
|
|
return pair, nil
|
|
}
|
|
|
|
func (s *RedisPairs) Delete(ctx context.Context, userID int64, deviceID uint64) error {
|
|
if s == nil || s.rdb == nil {
|
|
return errs.ErrAvailable.WithInnerMsg("jwt refresh redis not ready")
|
|
}
|
|
return s.rdb.Del(ctx, s.key(userID, deviceID)).Err()
|
|
}
|