# Session 與指紋

本頁說明 go-ip-sentry 簽發的兩個 Cookie、HMAC 簽章 Session、裝置指紋的組成，以及指紋多 Session 偵測。

## 兩個 Cookie

| Cookie | 內容 | 有效期 | 屬性 |
|---|---|---|---|
| `conn.sess.id` | `s:{id}.{signature}`，`id` 為 32 字元隨機值 | 30 天 | `HttpOnly`、`Secure`、`SameSiteStrictMode`、`Path=/` |
| `conn.device.id` | 128 字元隨機值，未簽章 | 365 天 | 同上 |

兩者在每次 `Check`、`LoginFailure`、`NotFound404` 都會重新寫入以刷新有效期。

## Session 簽章

| 步驟 | 行為 |
|---|---|
| 簽章 | `HMAC-SHA256(secret, id)`，base64url 去除結尾 `=` |
| 驗證 | Cookie 必須以 `s:` 開頭、以 `.` 分成兩段，簽章以常數時間比對 |
| 驗證失敗或缺少 | 直接簽發新 Session，不回報錯誤 |
| 金鑰來源 | 工作目錄的 `.sessionSecret`（權限 `0600`）；不存在或為空時產生 128 字元隨機值並寫入 |
| 載入時機 | 每個行程只讀一次（`sync.Once`） |

### 多實例部署

`.sessionSecret` 以相對路徑讀寫，每個實例的工作目錄若各自產生金鑰，A 實例簽發的 Session 在 B 實例驗證失敗、被換成新 Session，導致 `session:ip` 等關聯失真。部署多個實例時：

- 所有實例使用同一份 `.sessionSecret`（掛載同一檔案或於啟動前寫入）
- 以相同工作目錄啟動，或確保該檔案位於各自的工作目錄
- 輪替金鑰會讓所有既有 Session 失效

## 裝置指紋

```
fingerprint = hex(SHA-256("{Platform}/{Browser}/{Type}/{OS}/{conn.device.id}"))
```

| 欄位 | 解析方式 |
|---|---|
| Platform | User-Agent 含 `android`／`iphone`、`ipad`／`windows`／`macintosh`、`mac os`／`linux` |
| Browser | Chrome（排除 Edge）、Firefox、Safari（排除 Chrome）、Edge、Opera |
| Type | 先比對 mobile 關鍵字，再比對 tablet，其餘為 Desktop |
| OS | iOS、Android、Windows 10/11、8.1、7、macOS 版本，否則回退 Platform |

指紋綁定裝置 Cookie，所以同一台裝置清除 Cookie 後會變成新指紋；同一個裝置 Cookie 換瀏覽器或升級作業系統大版本也會改變指紋。

## 指紋多 Session

`calcFingerprint` 把 Session ID 加入 `fp:session:{minute}:{fp}`（TTL 1 分鐘）：

| 條件 | flag | 分數 |
|---|---|---|
| 同一分鐘內同一指紋超過 2 個 Session | `fp_multi_session` | `ScoreFpMultiSession`（50） |

典型情境是用戶端保留裝置 Cookie 卻不斷丟棄 Session Cookie，或同一份裝置 Cookie 被複製到多個並行的爬蟲。單獨觸發即達可疑分級。

## 邊界

| 情境 | 行為 |
|---|---|
| 純 HTTP（非 localhost） | 瀏覽器不儲存 `Secure` Cookie，每個請求都是新 Session 與新指紋，同一 IP 很快觸發 `ip_multi_device` |
| 跨網域嵌入（iframe、第三方請求） | `SameSiteStrictMode` 不送出 Cookie，同上 |
| 用戶端偽造 `conn.device.id` | 裝置 Cookie 未簽章，可任意指定；偽造值只影響指紋，Session 仍需正確簽章 |
