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>
92 lines
2.1 KiB
Go
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()
|
|
}
|