Files
moteva/server/internal/modules/auth/device.go
T
root 58f11302fe 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>
2026-07-11 01:02:05 +08:00

80 lines
2.0 KiB
Go

package auth
import (
"strings"
"github.com/mileusna/useragent"
)
const (
unknownDeviceSystem = "unknown"
unknownDeviceBrowser = "unknown"
)
func deviceSessionFromMetadata(metadata RequestMetadata) DeviceSession {
parsed := useragent.Parse(strings.TrimSpace(metadata.UserAgent))
deviceType := "desktop"
client := "PC-WEB"
if clientHintMobile(metadata.Mobile) || parsed.Mobile || parsed.Tablet {
deviceType = "mobile"
client = "MOBILE-WEB"
}
system := cleanDeviceLabel(parsed.OS, unknownDeviceSystem)
if platform := cleanClientHint(metadata.Platform); platform != "" {
system = platform
}
browser := cleanDeviceLabel(parsed.Name, unknownDeviceBrowser)
if parsed.Version != "" && browser != unknownDeviceBrowser {
browser += " " + majorVersion(parsed.Version)
}
return DeviceSession{
DeviceType: deviceType,
System: system,
Browser: browser,
Client: client,
UserAgent: limitMetadata(strings.TrimSpace(metadata.UserAgent), 1024),
}
}
func clientHintMobile(value string) bool {
value = strings.Trim(strings.TrimSpace(value), `"`)
return value == "1" || strings.EqualFold(value, "true") || strings.EqualFold(value, "?1")
}
func cleanClientHint(value string) string {
value = strings.Trim(strings.TrimSpace(value), `"`)
if value == "" || strings.EqualFold(value, "unknown") {
return ""
}
return limitMetadata(value, 80)
}
func cleanDeviceLabel(value string, fallback string) string {
value = strings.TrimSpace(value)
if value == "" || strings.EqualFold(value, "unknown") {
return fallback
}
return limitMetadata(value, 80)
}
func majorVersion(version string) string {
version = strings.TrimSpace(version)
if index := strings.IndexByte(version, '.'); index >= 0 {
version = version[:index]
}
return limitMetadata(version, 16)
}
func limitMetadata(value string, maxRunes int) string {
if maxRunes <= 0 {
return ""
}
runes := []rune(value)
if len(runes) <= maxRunes {
return value
}
return string(runes[:maxRunes])
}