spark-server/internal/module/auth/logic/denylist.go
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
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>
2026-09-17 19:52:11 +08:00

92 lines
2.1 KiB
Go

package logic
import (
"context"
"sync"
"time"
"github.com/redis/go-redis/v9"
"spark-server/internal/pkg/errs"
)
// Denylist stores revoked JWT ids until their original expiry.
type Denylist interface {
Has(ctx context.Context, jti string) (bool, error)
Add(ctx context.Context, jti string, ttl time.Duration) error
}
// MemoryDenylist is process-local. Used in tests and when Redis is not configured.
type MemoryDenylist struct {
mu sync.Mutex
items map[string]int64 // jti → expiry unix nano
}
func NewMemoryDenylist() *MemoryDenylist {
return &MemoryDenylist{items: make(map[string]int64)}
}
func (d *MemoryDenylist) Has(_ context.Context, jti string) (bool, error) {
if jti == "" {
return false, nil
}
now := time.Now().UnixNano()
d.mu.Lock()
defer d.mu.Unlock()
exp, ok := d.items[jti]
if !ok {
return false, nil
}
if now >= exp {
delete(d.items, jti)
return false, nil
}
return true, nil
}
func (d *MemoryDenylist) Add(_ context.Context, jti string, ttl time.Duration) error {
if jti == "" || ttl <= 0 {
return nil
}
d.mu.Lock()
d.items[jti] = time.Now().Add(ttl).UnixNano()
d.mu.Unlock()
return nil
}
// RedisDenylist uses SETEX denylist:{jti} with remaining token TTL.
type RedisDenylist struct {
rdb *redis.Client
prefix string
}
func NewRedisDenylist(rdb *redis.Client, prefix string) *RedisDenylist {
if prefix == "" {
prefix = "denylist:"
}
return &RedisDenylist{rdb: rdb, prefix: prefix}
}
func (d *RedisDenylist) key(jti string) string { return d.prefix + jti }
func (d *RedisDenylist) Has(ctx context.Context, jti string) (bool, error) {
if d == nil || d.rdb == nil {
return false, errs.ErrAvailable.WithInnerMsg("jwt denylist redis not ready")
}
n, err := d.rdb.Exists(ctx, d.key(jti)).Result()
if err != nil {
return false, err
}
return n > 0, nil
}
func (d *RedisDenylist) Add(ctx context.Context, jti string, ttl time.Duration) error {
if d == nil || d.rdb == nil {
return errs.ErrAvailable.WithInnerMsg("jwt denylist redis not ready")
}
if jti == "" || ttl <= 0 {
return nil
}
return d.rdb.Set(ctx, d.key(jti), "1", ttl).Err()
}