Files
tamabox/internal/form/user.go
T
tamakyi 7802bb2078 fix(security): 修复安全审计的 P1/P2/P3 项,含全局复查新发现的两处越权与外跳
P1:
- 外链图片:markdown 渲染层只放行站内与自有 CDN 图片。配置里的
  image_bucket_cdn_host 常写成 host/path,统一归一化后再比对,避免把自家图一起剥掉
- 密码:页面/提问密码最小 6 位;密码比对挪到验证码之后;页面密码失败锁定 5 次/10 分钟
- 客户端 IP:CF-Connecting-IP / EO-Client-IP 只在 TCP 对端可信(回环或 trusted_proxies)
  时采信,公网对端带来的权威头一律忽略

P2/P3:
- 跳转参数收敛到 shared.SafeRedirectTarget:拒反斜杠与控制字符(url.Parse 会把 %0d%0a
  解成 CRLF,伪造 Location),最后再用 path.Clean 验一次前缀
- 注册不再暴露「邮箱已注册 / 已停用」:与全新注册共用同一个成功出口,只在日志留痕
- CSP:默认 enforce,白名单由配置派生,覆盖 umami(运行时注入)、Turnstile 与
  按 sentry_dsn 条件放行的 Sentry CDN
- /user 与 /_ 下发 no-store;补 X-XSS-Protection、Permissions-Policy,并把安全头前移到
  静态资源中间件之前(原先静态文件拿不到 nosniff/HSTS)
- 封禁事实落库 ban_records;429 统一带 Retry-After 与本地化文案
- token 比较改常量时间;探索页页码上限;JSON 响应显式 Content-Type

全局复查补的两处:
- 私聊 SendMessage 的 POST 缺 conv.OwnerID 校验,被拉黑者可换 domain 继续发消息
- 页面密码 next 与登录 to 同源的反斜杠外跳

验收:verify_p1_fixes 4/4、verify_round2 19/19、verify_page_next 6/6、
verify_chat_owner 3/3(含变异对照);单测与 go build ./... 全通过。
2026-10-27 12:37:40 +08:00

76 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2022 E99p1ant. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package form
type UpdateProfile struct {
Name string `valid:"required;maxlen:20" label:"昵称" msg:"form.profile.name"`
OldPassword string `label:"旧密码"`
NewPassword string `valid:"maxlen:30" label:"新密码" msg:"form.profile.passwordMax"`
Intro string `valid:"required;maxlen:100" label:"介绍" msg:"form.profile.intro"`
Usernamecolor string `valid:"required;minlen:7;maxlen:7" label:"用户名颜色" msg:"form.profile.colorHex"`
Introcolor string `valid:"required;minlen:7;maxlen:7" label:"个人说明颜色" msg:"form.profile.colorHex"`
Qrcodebackcolor string `valid:"required;minlen:7;maxlen:7" label:"二维码背景色" msg:"form.profile.colorHex"`
Qrcodecolor string `valid:"required;minlen:7;maxlen:7" label:"二维码前景色" msg:"form.profile.colorHex"`
Dotscale string `valid:"required" label:"二维码点大小" msg:"form.profile.dotscale"`
Backgroundimagealpha string `valid:"required" label:"二维码背景透明度" msg:"form.profile.backgroundimagealpha"`
Qrcodepdpcolor string `valid:"required;minlen:7;maxlen:7" label:"二维码探测图形颜色" msg:"form.profile.colorHex"`
NotifyEmail string `label:"开启邮箱通知"`
Acfunlink string `valid:"maxlen:30" label:"AcFun主页" msg:"form.profile.linkMax"`
Bililink string `valid:"maxlen:30" label:"Bili主页" msg:"form.profile.linkMax"`
Weibolink string `valid:"maxlen:30" label:"微博主页" msg:"form.profile.linkMax"`
Bloglink string `valid:"maxlen:30" label:"博客主页" msg:"form.profile.linkMax"`
Xiaohongshulink string `valid:"maxlen:30" label:"小红书主页" msg:"form.profile.linkMax"`
UseCustomQRColor string `form:"use_custom_qr_color"`
Language string `form:"language"`
}
// UpdateProfileTheme 主题外观。空串表示跟随站点默认主题;
// 取值在白名单之外时保存被拒绝(见 route/user.UpdateTheme),
// 这样箱主不会「选了个不存在的主题,然后看起来什么都没变」。
type UpdateProfileTheme struct {
Theme string `valid:"maxlen:32" label:"主题" msg:"profile.theme.invalid"`
}
// UpdateHarassment 仅「允许注册用户向我提问」开关(防骚扰类型)。
// 全局屏蔽词为独立入口 UpdateBlockWords:二者分处不同设置面板保存,
// 若仍共用一个结构体会互相覆盖对方字段(见 route/user/harassment.go 注释)。
type UpdateHarassment struct {
RegisterOnly string `label:"仅允许注册用户"`
}
type UpdateBlockWords struct {
BlockWords string `label:"屏蔽词"`
}
// UpdateModerationStrictness 内容审核严格度(lax / standard / strict)。
// 与屏蔽词一样是独立的设置入口:两者分处不同的表单,必须分开写,
// 否则单侧保存会把另一侧的值清空(见 route/user/harassment.go 顶部注释)。
type UpdateModerationStrictness struct {
Strictness string `form:"strictness"`
}
type UpdateTags struct {
Tags string `form:"tags"`
ShowInHome string `form:"show_in_home"`
}
// 页面访问密码 / 提问密码的最小长度。
//
// P1-2:这两个字段此前只有 maxlen,箱主可以设成 1 位或 1234 —— 而页面密码正是产品
// 对外宣传的访问控制,提问密码是箱主过滤骚扰的唯一门槛。6 位是最低可接受值
// (纯数字 10^6,配合失败锁定后穷举不可行);留空仍然合法,表示关闭该密码
// (govalid 对空值跳过 minlen / maxlen)。
const PasswordMinLen = 6
// 新增:页面访问密码表单
type UpdatePagePassword struct {
Password string `valid:"minlen:6;maxlen:30" label:"页面访问密码" msg:"form.profile.passwordLen"` // 留空表示关闭
}
// 新增:提问密码表单
type UpdateAskPassword struct {
Password string `valid:"minlen:6;maxlen:30" label:"提问密码" msg:"form.profile.passwordLen"` // 留空表示关闭
}