fbe80ad784
上线账号权限管理(后端): - 迁移 000032:users + user_sessions(token 只存 sha256,服务端可即时撤销) - bcrypt 密码、8 次失败锁 15 分钟、统一 401 错误防用户名枚举 - 三角色 viewer/engineer/admin;HttpOnly+SameSite=Lax 会话 cookie,滑动续期 - 强制改密锁定:must_change_password 会话只能走 auth 端点;首登 dobotai - bootstrap:users 表为空时创建 admin(初始密码 dobotai,强制改密) - 生产双闸授权/回滚/失败重试收敛为 admin 角色;AuthorizedBy 用会话身份 - 全部业务写端点删除手填 actor 字段(operator_ref/reviewer_ref/created_by/ sealed_by/confirmed_by),服务端从会话注入;auth 关闭的开发 socket 回退 system - 成员管理 API:列表/创建/改角色/启停/重置密码,最后一名 active admin 保护 - RBAC 矩阵纯函数契约测试 + 中间件行为测试(登录/锁定/角色/审计戳) - 附带修复:GET /api/v1/users 曾被 GET=viewer 规则误放行(测试抓到)
27 lines
944 B
Go
27 lines
944 B
Go
package httpapi
|
|
|
|
import "net/http"
|
|
|
|
// requireSessionIdentity resolves the authenticated actor for business write
|
|
// handlers. Since sessions arrived, request bodies no longer carry
|
|
// operator_ref / reviewer_ref / created_by; the server stamps the session.
|
|
// Returns false after writing the error response.
|
|
func requireSessionIdentity(w http.ResponseWriter, r *http.Request) (sessionIdentity, bool) {
|
|
identity, ok := sessionFrom(r.Context())
|
|
if !ok {
|
|
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "authentication required"})
|
|
return sessionIdentity{}, false
|
|
}
|
|
return identity, true
|
|
}
|
|
|
|
// actorFor returns the audit actor for a business write: the session username
|
|
// when auth is enabled, or "system" for the auth-disabled development socket.
|
|
// It never trusts client input.
|
|
func actorFor(r *http.Request) string {
|
|
if identity, ok := sessionFrom(r.Context()); ok {
|
|
return identity.Username
|
|
}
|
|
return "system"
|
|
}
|