58f11302fe
Track authenticated sessions per device (parsing user-agent for desktop vs mobile via mileusna/useragent), enforce configurable concurrent device limits (Auth.DeviceLimits), and surface device status and "remove other devices" management in the account dialog. Adds device/session context to the auth module stores and exposes limits through the API. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
75 lines
1.7 KiB
Go
75 lines
1.7 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestPostgresStoreEnforcesConcurrentDeviceLimit(t *testing.T) {
|
|
dataSource := os.Getenv("POSTGRES_TEST_DSN")
|
|
if dataSource == "" {
|
|
t.Skip("POSTGRES_TEST_DSN is not configured")
|
|
}
|
|
ctx := context.Background()
|
|
store, err := NewPostgresStore(ctx, dataSource)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = store.Close() }()
|
|
|
|
user, err := store.UpsertUser(ctx, User{
|
|
ID: newID(),
|
|
Region: RegionGlobal,
|
|
Email: newID() + "@device-limit.test",
|
|
Status: "active",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
_, _ = store.pool.Exec(context.Background(), `DELETE FROM users WHERE id = $1`, toPGUUID(user.ID))
|
|
}()
|
|
|
|
createdAt := time.Date(2026, 7, 11, 13, 0, 0, 0, time.UTC)
|
|
const loginCount = 8
|
|
const activeLimit = 2
|
|
errCh := make(chan error, loginCount)
|
|
var wg sync.WaitGroup
|
|
for index := 0; index < loginCount; index++ {
|
|
wg.Add(1)
|
|
go func(index int) {
|
|
defer wg.Done()
|
|
sessionTime := createdAt.Add(time.Duration(index) * time.Millisecond)
|
|
errCh <- store.CreateDeviceSession(ctx, DeviceSession{
|
|
ID: newID(),
|
|
UserID: user.ID,
|
|
DeviceType: "desktop",
|
|
System: "macOS",
|
|
Browser: "Chrome 149",
|
|
Client: "PC-WEB",
|
|
CreatedAt: sessionTime,
|
|
LastSeenAt: sessionTime,
|
|
ExpiresAt: sessionTime.Add(time.Hour),
|
|
}, activeLimit)
|
|
}(index)
|
|
}
|
|
wg.Wait()
|
|
close(errCh)
|
|
for err := range errCh {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
sessions, err := store.ListDeviceSessions(ctx, user.ID, createdAt)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(sessions) != activeLimit {
|
|
t.Fatalf("expected %d active desktop sessions after concurrent login, got %d", activeLimit, len(sessions))
|
|
}
|
|
}
|