You've already forked agentic-coding-workflow
fix(user): withoutcancel audit, x-forwarded-for client ip
This commit is contained in:
@@ -97,7 +97,7 @@ func (h *Handler) login(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
tok, u, err := h.svc.Login(r.Context(), req.Email, req.Password, r.RemoteAddr)
|
||||
tok, u, err := h.svc.Login(r.Context(), req.Email, req.Password, clientIP(r))
|
||||
if err != nil {
|
||||
httpx.WriteError(w, rid, err)
|
||||
return
|
||||
@@ -142,3 +142,22 @@ func bearerFrom(r *http.Request) string {
|
||||
}
|
||||
return strings.TrimSpace(h[len(bearerPrefix):])
|
||||
}
|
||||
|
||||
// clientIP 从请求头提取最接近真实客户端的 IP。优先级:X-Forwarded-For 首段
|
||||
// > X-Real-IP > r.RemoteAddr。
|
||||
//
|
||||
// **安全提示**:当前未实现 trusted proxy 名单,X-Forwarded-For/X-Real-IP 可
|
||||
// 被任意客户端伪造。生产部署必须在反向代理层 strip 客户端送来的这些头,仅
|
||||
// 由代理本身写入;否则审计 IP 不可信。该问题在引入 ingress 配置时一并处理。
|
||||
func clientIP(r *http.Request) string {
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
if comma := strings.IndexByte(xff, ','); comma > 0 {
|
||||
return strings.TrimSpace(xff[:comma])
|
||||
}
|
||||
return strings.TrimSpace(xff)
|
||||
}
|
||||
if xri := r.Header.Get("X-Real-IP"); xri != "" {
|
||||
return strings.TrimSpace(xri)
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
@@ -77,9 +77,12 @@ func (s *service) Login(ctx context.Context, email, password, ip string) (string
|
||||
return "", nil, err
|
||||
}
|
||||
// 审计是 best-effort:_ = 吞掉错误,审计落库失败不能导致登录失败。
|
||||
// 用 context.WithoutCancel 派生:HTTP handler 在响应返回后 r.Context() 会
|
||||
// 被取消,但审计写入必须完成(漏行比写错更危险)。timeout 由调用方在
|
||||
// ctx 上不再控制,audit recorder 自身需保证不无限阻塞。
|
||||
if s.audit != nil {
|
||||
uid := u.ID
|
||||
_ = s.audit.Record(ctx, audit.Entry{
|
||||
_ = s.audit.Record(context.WithoutCancel(ctx), audit.Entry{
|
||||
UserID: &uid,
|
||||
Action: "user.login",
|
||||
TargetType: "user",
|
||||
@@ -102,9 +105,12 @@ func (s *service) Logout(ctx context.Context, token string) error {
|
||||
return err
|
||||
}
|
||||
// 审计是 best-effort:_ = 吞掉错误;uid == uuid.Nil 表示 token 无效,跳过。
|
||||
// 用 context.WithoutCancel 派生:HTTP handler 在响应返回后 r.Context() 会
|
||||
// 被取消,但审计写入必须完成(漏行比写错更危险)。timeout 由调用方在
|
||||
// ctx 上不再控制,audit recorder 自身需保证不无限阻塞。
|
||||
if s.audit != nil && uid != uuid.Nil {
|
||||
u := uid
|
||||
_ = s.audit.Record(ctx, audit.Entry{
|
||||
_ = s.audit.Record(context.WithoutCancel(ctx), audit.Entry{
|
||||
UserID: &u,
|
||||
Action: "user.logout",
|
||||
TargetType: "user",
|
||||
|
||||
Reference in New Issue
Block a user