feat(M1): 认证与用户管理 — 双通道登录、cookie 会话、用户 CRUD 与真实系统账号对接

认证:
- 图形验证码 GET /auth/captcha(内置 PNG 渲染,零第三方依赖)
- 外部用户 OTP 双通道:DB 存储(otp_codes)使邮件与 CLI 共用同一验证码/冷却/失败限速
- 管理员 bcrypt 登录 + 连续失败限速锁定;admin/forgot + admin/reset 邮件重置(SMTP 或日志)
- cookie 会话(HttpOnly/SameSite)、me/logout、admin/user 鉴权中间件

用户管理(admin):
- CRUD + disable/enable/extend/delete,对接 system 层真实 useradd/usermod/userdel/passwd
- system 层三执行模式:dry-run(默认,安全)/ direct(容器/测试用户)/ sudo(生产 sudoers 白名单)
- Exists 系统账号一致性检查;deploy/sudoers.example 白名单模板
- 关键操作接入 append-only 审计

其他:
- CLI user otp 改 DB store,与邮件通道真正对齐
- 容器镜像补 shadow(alpine 无 useradd);Makefile VERSION 0.2.0-m1
- 测试:auth/service 单测 + api httptest 集成 + 容器内真实系统账号端到端验证
This commit is contained in:
2026-08-29 23:40:20 +08:00
parent ae45aba607
commit 630d240dc0
32 changed files with 2923 additions and 188 deletions
+29 -11
View File
@@ -28,13 +28,15 @@ type Config struct {
Database DatabaseConfig `toml:"database"`
Log LogConfig `toml:"log"`
Policy PolicyConfig `toml:"policy"`
Auth AuthConfig `toml:"auth"`
SMTP SMTPConfig `toml:"smtp"`
System SystemConfig `toml:"system"`
}
type AppConfig struct {
Name string `toml:"name"`
Env string `toml:"env"` // development / production
Name string `toml:"name"`
Env string `toml:"env"` // development / production
BaseURL string `toml:"base_url"` // 对外访问地址(邮件中的链接使用)
}
type ServerConfig struct {
@@ -62,6 +64,13 @@ type PolicyConfig struct {
OTPCooldown time.Duration `toml:"otp_cooldown"` // OTP 发送冷却
}
// AuthConfig 认证与防暴力参数。
type AuthConfig struct {
MaxLoginFailures int `toml:"max_login_failures"` // 管理员登录连续失败阈值,达到后锁定
LockDuration time.Duration `toml:"lock_duration"` // 失败达到阈值后的锁定时长
CaptchaTTL time.Duration `toml:"captcha_ttl"` // 图形验证码有效期
}
type SMTPConfig struct {
Host string `toml:"host"`
Port int `toml:"port"`
@@ -72,21 +81,22 @@ type SMTPConfig struct {
// SystemConfig 为系统账号操作层的本地实现配置(sudoers 白名单模式)。
type SystemConfig struct {
Sudo bool `toml:"sudo"` // 是否通过 sudo -n 执行系统命令;开发环境 false 时 dry-run
UserPrefix string `toml:"user_prefix"` // 外部用户系统账号前缀,默认 ext_
Group string `toml:"group"` // 外部用户所属组,默认 external
Shell string `toml:"shell"` // 默认 shell
HomeBase string `toml:"home_base"` // 家目录基路径
AuthorizedKeysDir string `toml:"authorized_keys_dir"` // authorized_keys 所在目录(测试可覆盖)
Sudo bool `toml:"sudo"` // 是否通过 sudo -n 执行系统命令(生产)
DryRun bool `toml:"dry_run"` // true = 只打印计划命令不执行(开发演练);false 且 sudo=false 时直接执行(容器/测试用户验证)
UserPrefix string `toml:"user_prefix"` // 外部用户系统账号前缀,默认 ext_
Group string `toml:"group"` // 外部用户所属组,默认 external
Shell string `toml:"shell"` // 默认 shell
HomeBase string `toml:"home_base"` // 家目录基路径
AuthorizedKeysDir string `toml:"authorized_keys_dir"` // authorized_keys 所在目录(测试可覆盖)
}
// Default 返回带开发环境默认值的配置,作为 config.example.toml 与未配置项的兜底。
func Default() *Config {
return &Config{
App: AppConfig{Name: "ws_usernode", Env: "development"},
App: AppConfig{Name: "ws_usernode", Env: "development", BaseURL: "http://127.0.0.1:8080"},
Server: ServerConfig{
Listen: "127.0.0.1:8080",
SessionTTL: 24 * time.Hour,
Listen: "127.0.0.1:8080",
SessionTTL: 24 * time.Hour,
TrustedProxies: []string{"127.0.0.1", "::1"},
},
Database: DatabaseConfig{Driver: "sqlite", DSN: "data/usernode.db"},
@@ -98,9 +108,17 @@ func Default() *Config {
OTPTTL: 10 * time.Minute,
OTPCooldown: 60 * time.Second,
},
Auth: AuthConfig{
MaxLoginFailures: 5,
LockDuration: 15 * time.Minute,
CaptchaTTL: 5 * time.Minute,
},
SMTP: SMTPConfig{Port: 587},
System: SystemConfig{
// 开发默认 dry-run:未配置 config 直接跑 serve 时只打印计划,避免误操作系统账号。
// 生产必须显式 dry_run=false 且 sudo=true(见 deploy/sudoers.example)。
Sudo: false,
DryRun: true,
UserPrefix: "ext_",
Group: "external",
Shell: "/bin/sh",