mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 16:18:29 +08:00
应对管理员访问凭证失守导致的数据外泄风险,新增三层防护: 审计日志(admin-only 可见,用户不可见) - 新增 append-only audit_logs 表(migration 180)+ 异步批量写入 + 保留期清理 - 审计中间件挂在 admin/user/auth/admin-payment 组认证之后:记录所有变更类 请求 + 白名单敏感读取(账号/代理导出、备份下载、admin/user API key 读取) - 请求头凭证首尾掩码;请求体 JSON 递归脱敏(api_key/password 等擦除,base_url 保留以便追责);非 JSON body 不入库 - 无单条删除;全量清空需现场 TOTP 校验、拒绝 admin API key、未启用 2FA 不允许, 清空后同步写入留痕记录 会话 IP/UA 绑定(默认开启,可在系统设置关闭) - JWT 携带 session id + IP/UA 指纹哈希;IP 或 UA 任一变化即撤销会话家族并要求 重新登录;旧 token 无指纹时放行以平滑升级 敏感操作 step-up 2FA(sudo 窗口 15 分钟) - 账号/代理导出、DB 备份创建/下载、S3 目标修改要求近期 TOTP 二次验证;admin API key 一律拒绝;前端 useStepUp 组合式 + TotpStepUpDialog 弹码后自动重试 - API key 查看按需求暂不加强管控 前端:新增 /admin/audit-logs 操作日志页面(筛选/详情/2FA 清空)、侧边栏入口、 step-up 弹窗接入导出与备份流程、安全设置项(绑定开关 + 日志保留天数)、zh/en i18n
75 lines
3.1 KiB
Go
75 lines
3.1 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"time"
|
||
)
|
||
|
||
// ErrRefreshTokenNotFound is returned when a refresh token is not found in cache.
|
||
// This is used to abstract away the underlying cache implementation (e.g., redis.Nil).
|
||
var ErrRefreshTokenNotFound = errors.New("refresh token not found")
|
||
|
||
// RefreshTokenData 存储在Redis中的Refresh Token数据
|
||
type RefreshTokenData struct {
|
||
UserID int64 `json:"user_id"`
|
||
TokenVersion int64 `json:"token_version"` // 用于检测密码更改后的Token失效
|
||
FamilyID string `json:"family_id"` // Token家族ID,用于防重放攻击
|
||
BindingHash string `json:"binding_hash,omitempty"` // 会话指纹哈希(IP+UA),会话绑定开启时校验
|
||
CreatedAt time.Time `json:"created_at"`
|
||
ExpiresAt time.Time `json:"expires_at"`
|
||
}
|
||
|
||
// RefreshTokenCache 管理Refresh Token的Redis缓存
|
||
// 用于JWT Token刷新机制,支持Token轮转和防重放攻击
|
||
//
|
||
// Key 格式:
|
||
// - refresh_token:{token_hash} -> RefreshTokenData (JSON)
|
||
// - user_refresh_tokens:{user_id} -> Set<token_hash>
|
||
// - token_family:{family_id} -> Set<token_hash>
|
||
type RefreshTokenCache interface {
|
||
// StoreRefreshToken 存储Refresh Token
|
||
// tokenHash: Token的SHA256哈希值(不存储原始Token)
|
||
// data: Token关联的数据
|
||
// ttl: Token过期时间
|
||
StoreRefreshToken(ctx context.Context, tokenHash string, data *RefreshTokenData, ttl time.Duration) error
|
||
|
||
// GetRefreshToken 获取Refresh Token数据
|
||
// 返回 (data, nil) 如果Token存在
|
||
// 返回 (nil, ErrRefreshTokenNotFound) 如果Token不存在
|
||
// 返回 (nil, err) 如果发生其他错误
|
||
GetRefreshToken(ctx context.Context, tokenHash string) (*RefreshTokenData, error)
|
||
|
||
// DeleteRefreshToken 删除单个Refresh Token
|
||
// 用于Token轮转时使旧Token失效
|
||
DeleteRefreshToken(ctx context.Context, tokenHash string) error
|
||
|
||
// DeleteUserRefreshTokens 删除用户的所有Refresh Token
|
||
// 用于密码更改或用户主动登出所有设备
|
||
DeleteUserRefreshTokens(ctx context.Context, userID int64) error
|
||
|
||
// DeleteTokenFamily 删除整个Token家族
|
||
// 用于检测到Token重放攻击时,撤销整个会话链
|
||
DeleteTokenFamily(ctx context.Context, familyID string) error
|
||
|
||
// AddToUserTokenSet 将Token添加到用户的Token集合
|
||
// 用于跟踪用户的所有活跃Refresh Token
|
||
AddToUserTokenSet(ctx context.Context, userID int64, tokenHash string, ttl time.Duration) error
|
||
|
||
// AddToFamilyTokenSet 将Token添加到家族Token集合
|
||
// 用于跟踪同一登录会话的所有Token
|
||
AddToFamilyTokenSet(ctx context.Context, familyID string, tokenHash string, ttl time.Duration) error
|
||
|
||
// GetUserTokenHashes 获取用户的所有Token哈希
|
||
// 用于批量删除用户Token
|
||
GetUserTokenHashes(ctx context.Context, userID int64) ([]string, error)
|
||
|
||
// GetFamilyTokenHashes 获取家族的所有Token哈希
|
||
// 用于批量删除家族Token
|
||
GetFamilyTokenHashes(ctx context.Context, familyID string) ([]string, error)
|
||
|
||
// IsTokenInFamily 检查Token是否属于指定家族
|
||
// 用于验证Token家族关系
|
||
IsTokenInFamily(ctx context.Context, familyID string, tokenHash string) (bool, error)
|
||
}
|