feat(auth): add device session tracking with configurable limits
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>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user