Files
geo/server/internal/shared/auth/session_store_test.go
T
root e045e00fbf
Frontend CI / Frontend (push) Successful in 3m8s
Backend CI / Backend (push) Successful in 14m48s
feat(auth,prompt-rule): add password change with session revocation and scope prompt rules by brand
Add self-service password change that re-hashes the credential and bumps a
per-user session version so all existing access/refresh tokens are rejected.
Session version is tracked in Redis with an in-memory fallback and enforced in
middleware and refresh.

Scope prompt rules and rule groups to a brand: new brand_id column (migrated to
a "未归属" fallback brand for existing rows), brand-filtered queries/indexes, and
brand-aware admin-web tabs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:09:53 +08:00

128 lines
3.8 KiB
Go

package auth
import (
"context"
"testing"
"time"
"github.com/alicebob/miniredis/v2"
goredis "github.com/redis/go-redis/v9"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newFailingSessionRedisClient(t *testing.T) *goredis.Client {
t.Helper()
client := goredis.NewClient(&goredis.Options{
Addr: "127.0.0.1:1",
MaxRetries: 0,
DialTimeout: 10 * time.Millisecond,
ReadTimeout: 10 * time.Millisecond,
WriteTimeout: 10 * time.Millisecond,
})
t.Cleanup(func() { _ = client.Close() })
return client
}
func TestSessionStoreRotateRefreshSuccess(t *testing.T) {
mr := miniredis.RunT(t)
client := goredis.NewClient(&goredis.Options{Addr: mr.Addr()})
t.Cleanup(func() { _ = client.Close() })
store := NewSessionStore(client)
ctx := context.Background()
require.NoError(t, store.SaveRefresh(ctx, "old-jti", "old-hash", time.Minute))
require.NoError(t, store.RotateRefresh(ctx, "old-jti", "old-hash", "new-jti", "new-hash", 2*time.Minute))
_, err := store.GetRefresh(ctx, "old-jti")
assert.Error(t, err)
value, err := store.GetRefresh(ctx, "new-jti")
require.NoError(t, err)
assert.Equal(t, "new-hash", value)
}
func TestSessionStoreRotateRefreshRejectsMismatch(t *testing.T) {
mr := miniredis.RunT(t)
client := goredis.NewClient(&goredis.Options{Addr: mr.Addr()})
t.Cleanup(func() { _ = client.Close() })
store := NewSessionStore(client)
ctx := context.Background()
require.NoError(t, store.SaveRefresh(ctx, "old-jti", "old-hash", time.Minute))
err := store.RotateRefresh(ctx, "old-jti", "wrong-hash", "new-jti", "new-hash", 2*time.Minute)
require.ErrorIs(t, err, ErrRefreshTokenMismatch)
value, getErr := store.GetRefresh(ctx, "old-jti")
require.NoError(t, getErr)
assert.Equal(t, "old-hash", value)
_, getErr = store.GetRefresh(ctx, "new-jti")
assert.Error(t, getErr)
}
func TestSessionStoreUsesMemoryFallbackWhenRedisUnavailable(t *testing.T) {
store := NewSessionStore(newFailingSessionRedisClient(t))
ctx := context.Background()
require.NoError(t, store.SaveRefresh(ctx, "old-jti", "old-hash", time.Minute))
value, err := store.GetRefresh(ctx, "old-jti")
require.NoError(t, err)
assert.Equal(t, "old-hash", value)
require.NoError(t, store.RotateRefresh(ctx, "old-jti", "old-hash", "new-jti", "new-hash", time.Minute))
_, err = store.GetRefresh(ctx, "old-jti")
assert.Error(t, err)
value, err = store.GetRefresh(ctx, "new-jti")
require.NoError(t, err)
assert.Equal(t, "new-hash", value)
require.NoError(t, store.Blacklist(ctx, "access-jti", time.Minute))
revoked, err := store.IsBlacklisted(ctx, "access-jti")
require.NoError(t, err)
assert.True(t, revoked)
}
func TestSessionStoreBumpSessionVersion(t *testing.T) {
mr := miniredis.RunT(t)
client := goredis.NewClient(&goredis.Options{Addr: mr.Addr()})
t.Cleanup(func() { _ = client.Close() })
store := NewSessionStore(client)
ctx := context.Background()
version, err := store.SessionVersion(ctx, 42)
require.NoError(t, err)
assert.Equal(t, int64(0), version)
version, err = store.BumpSessionVersion(ctx, 42)
require.NoError(t, err)
assert.Equal(t, int64(1), version)
version, err = store.SessionVersion(ctx, 42)
require.NoError(t, err)
assert.Equal(t, int64(1), version)
}
func TestSessionStoreSessionVersionKeepsFallbackBumpWhenRedisLags(t *testing.T) {
mr := miniredis.RunT(t)
client := goredis.NewClient(&goredis.Options{Addr: mr.Addr()})
t.Cleanup(func() { _ = client.Close() })
store := NewSessionStore(client)
ctx := context.Background()
require.NoError(t, mr.Set(sessionVersionKey(42), "1"))
store.fallback.set(sessionVersionKey(42), "3", sessionVersionFallbackTTL)
version, err := store.SessionVersion(ctx, 42)
require.NoError(t, err)
assert.Equal(t, int64(3), version)
stored, err := mr.Get(sessionVersionKey(42))
require.NoError(t, err)
assert.Equal(t, "3", stored)
}