Files
geo/server/internal/shared/auth/session_store.go
T
root de30497f59 feat: add tenant and user management with migrations, handlers, and tests
- Implemented tenant and user management features including:
  - Tenant creation and management with associated migrations.
  - User creation and management with associated migrations.
  - Tenant membership management with associated migrations.
  - Platform user roles management with associated migrations.
  - Quota management with associated migrations.
  - Article and template management with associated migrations.
- Added HTTP handlers for templates and workspaces.
- Created tests for protected and public routes.
- Introduced a script to check tenant scope in SQL queries.
- Documented task plan for backend completion and frontend foundation.
2026-04-01 00:58:42 +08:00

94 lines
2.1 KiB
Go

package auth
import (
"context"
"errors"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
var (
ErrRefreshSessionNotFound = errors.New("refresh session not found")
ErrRefreshTokenMismatch = errors.New("refresh token mismatch")
)
var rotateRefreshScript = redis.NewScript(`
local current = redis.call("GET", KEYS[1])
if not current then
return 0
end
if current ~= ARGV[1] then
return -1
end
redis.call("DEL", KEYS[1])
redis.call("PSETEX", KEYS[2], ARGV[3], ARGV[2])
return 1
`)
type SessionStore struct {
rdb *redis.Client
}
func NewSessionStore(rdb *redis.Client) *SessionStore {
return &SessionStore{rdb: rdb}
}
func (s *SessionStore) SaveRefresh(ctx context.Context, jti string, tokenHash string, ttl time.Duration) error {
key := fmt.Sprintf("refresh:%s", jti)
return s.rdb.Set(ctx, key, tokenHash, ttl).Err()
}
func (s *SessionStore) GetRefresh(ctx context.Context, jti string) (string, error) {
key := fmt.Sprintf("refresh:%s", jti)
return s.rdb.Get(ctx, key).Result()
}
func (s *SessionStore) DeleteRefresh(ctx context.Context, jti string) error {
key := fmt.Sprintf("refresh:%s", jti)
return s.rdb.Del(ctx, key).Err()
}
func (s *SessionStore) RotateRefresh(ctx context.Context, oldJTI, oldHash, newJTI, newHash string, ttl time.Duration) error {
oldKey := fmt.Sprintf("refresh:%s", oldJTI)
newKey := fmt.Sprintf("refresh:%s", newJTI)
result, err := rotateRefreshScript.Run(
ctx,
s.rdb,
[]string{oldKey, newKey},
oldHash,
newHash,
ttl.Milliseconds(),
).Int()
if err != nil {
return err
}
switch result {
case 1:
return nil
case 0:
return ErrRefreshSessionNotFound
case -1:
return ErrRefreshTokenMismatch
default:
return fmt.Errorf("unexpected rotate refresh result: %d", result)
}
}
func (s *SessionStore) Blacklist(ctx context.Context, accessJTI string, ttl time.Duration) error {
key := fmt.Sprintf("blacklist:%s", accessJTI)
return s.rdb.Set(ctx, key, "1", ttl).Err()
}
func (s *SessionStore) IsBlacklisted(ctx context.Context, accessJTI string) (bool, error) {
key := fmt.Sprintf("blacklist:%s", accessJTI)
n, err := s.rdb.Exists(ctx, key).Result()
if err != nil {
return false, err
}
return n > 0, nil
}