feat: 安全设置展示有效登录会话,支持剔除其它设备

按设备指纹去重并直出 IP;非当前会话可踢下线,对方需重新输入密码。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-18 01:48:52 +08:00
parent 2ccfb5f330
commit 3a7bfe59df
16 changed files with 808 additions and 28 deletions

View File

@@ -0,0 +1,75 @@
package service
import "testing"
func TestParseUserAgentChromeWindows(t *testing.T) {
ua := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
p := ParseUserAgent(ua)
if p.Kind != DeviceKindComputer || p.Label != "Windows 电脑" || p.Browser != "Google Chrome" {
t.Fatalf("got %+v", p)
}
}
func TestParseUserAgentEdgeWindows(t *testing.T) {
ua := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0"
p := ParseUserAgent(ua)
if p.Browser != "Microsoft Edge" || p.Label != "Windows 电脑" {
t.Fatalf("got %+v", p)
}
}
func TestParseUserAgentAndroidChrome(t *testing.T) {
ua := "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36"
p := ParseUserAgent(ua)
if p.Kind != DeviceKindMobile || p.Label != "Android 手机" || p.Browser != "Google Chrome" {
t.Fatalf("got %+v", p)
}
}
func TestParseUserAgentIPhoneSafari(t *testing.T) {
ua := "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
p := ParseUserAgent(ua)
if p.Kind != DeviceKindMobile || p.Label != "iOS 手机" || p.Browser != "Safari" {
t.Fatalf("got %+v", p)
}
}
func TestParseUserAgentWeChat(t *testing.T) {
ua := "Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/128.0.0.0 Mobile Safari/537.36 MicroMessenger/8.0.50"
p := ParseUserAgent(ua)
if p.Browser != "微信" || p.Kind != DeviceKindMobile {
t.Fatalf("got %+v", p)
}
}
func TestParseUserAgentEmpty(t *testing.T) {
p := ParseUserAgent("")
if p.Label != "未知设备" {
t.Fatalf("got %+v", p)
}
}
func TestCanonicalIPLoopback(t *testing.T) {
if got := canonicalIP("::ffff:127.0.0.1"); got != "127.0.0.1" {
t.Fatalf("mapped got %q", got)
}
if got := canonicalIP("::1"); got != "127.0.0.1" {
t.Fatalf("::1 got %q", got)
}
if got := canonicalIP("127.0.0.1"); got != "127.0.0.1" {
t.Fatalf("v4 got %q", got)
}
}
func TestDeviceFingerprintMergesLoopback(t *testing.T) {
ua := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
a := deviceFingerprint("::1", ua)
b := deviceFingerprint("127.0.0.1", ua)
if a != b {
t.Fatalf("loopback fingerprint mismatch %q vs %q", a, b)
}
empty := deviceFingerprint("127.0.0.1", "")
if empty == a {
t.Fatalf("empty UA should not match Chrome")
}
}