feat: 近实时远程下线 — 客户端轮询会话有效性(~15s),服务端 GET /v1/me/session
控制面无推送通道,access token 是无状态 JWT(15min),强制退出后被踢设备要等 token 过期
(≤15min)才登出。改成客户端每 15s 轮询会话是否仍有效,被强制退出即登出 → 延迟压到 ~15s。
- 服务端:sessions.HasActiveSession(user,device) + devices.SessionActive(按 UUID,fail-open)
+ GET /v1/me/session?device_id= 返回 {active}(恒 200,判据在 body)。无新迁移。
- 客户端:account_api.sessionActive + main.dart _RootFlowState 15s 轮询,active=false 即 logout
(网络/鉴权异常不据此登出,fail-safe)。
- 测试:TestSQLite_SessionHasActiveSession(建会话=活跃→RevokeByDevice→非活跃)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/wangjia/pangolin/server/internal/nodes"
|
||||
agentv1 "github.com/wangjia/pangolin/server/internal/pb/agentv1"
|
||||
"github.com/wangjia/pangolin/server/internal/provision"
|
||||
"github.com/wangjia/pangolin/server/internal/sessions"
|
||||
"github.com/wangjia/pangolin/server/internal/store"
|
||||
"github.com/wangjia/pangolin/server/internal/usage"
|
||||
)
|
||||
@@ -57,6 +58,43 @@ func TestSQLite_UsageAccumulate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// HasActiveSession 是「近实时远程下线」的服务端判据:有非吊销会话=在线,
|
||||
// 强制退出(RevokeByDevice)后=离线 → 客户端轮询到即登出。
|
||||
func TestSQLite_SessionHasActiveSession(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
db := openSQLite(t)
|
||||
// FK 前置:user + device。
|
||||
res, err := db.ExecContext(ctx,
|
||||
`INSERT INTO users (uuid, email, pw_hash, dp_uuid) VALUES ('u-1','a@b.c','h','dp-1')`)
|
||||
if err != nil {
|
||||
t.Fatalf("user: %v", err)
|
||||
}
|
||||
uid, _ := res.LastInsertId()
|
||||
res, err = db.ExecContext(ctx,
|
||||
`INSERT INTO devices (uuid, user_id, name, platform) VALUES ('d-1', ?, 'Mac', 'macos')`, uid)
|
||||
if err != nil {
|
||||
t.Fatalf("device: %v", err)
|
||||
}
|
||||
did, _ := res.LastInsertId()
|
||||
|
||||
ss := sessions.NewStore(db)
|
||||
if ok, err := ss.HasActiveSession(ctx, uid, did); err != nil || ok {
|
||||
t.Fatalf("无会话应为非活跃: ok=%v err=%v", ok, err)
|
||||
}
|
||||
if err := ss.Create(ctx, uid, did, "jti-1", "1.2.3.4", "v1.0"); err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
if ok, err := ss.HasActiveSession(ctx, uid, did); err != nil || !ok {
|
||||
t.Fatalf("建会话后应活跃: ok=%v err=%v", ok, err)
|
||||
}
|
||||
if _, err := ss.RevokeByDevice(ctx, uid, did); err != nil {
|
||||
t.Fatalf("revoke: %v", err)
|
||||
}
|
||||
if ok, err := ss.HasActiveSession(ctx, uid, did); err != nil || ok {
|
||||
t.Fatalf("强制退出后应非活跃: ok=%v err=%v", ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLite_NodeAccumulateUsage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
db := openSQLite(t)
|
||||
|
||||
Reference in New Issue
Block a user