149c35c21b
替掉"裸 X-User-ID 头当身份"的临时方案,落地无状态 JWT 鉴权后端: - internal/auth:JWT 签发/校验(HS256,密钥 env JWT_SECRET,仅接受 HMAC 防 alg 混淆) + bcrypt 密码哈希/校验。纯包,含单测。 - User 模型加 Name + PasswordHash(json:"-" 不外泄);store 加 CreateUser/GetUserByEmail/ GetUserByID(邮箱唯一冲突 → ErrUserExists)。 - handler/auth:POST /auth/register(建用户+签发)· POST /auth/login(校验+签发, 用户不存在与密码错同一文案防枚举)· GET /auth/me。 - middleware/auth:解析 Bearer JWT,校验通过把已验证 userID 注入上下文(非阻断)。 - userID(c) 改为优先取 JWT 注入的 uid,兜底 X-User-ID 头(前端尚未接登录,保持可用)。 验证: - 单测:JWT 签发/解析往返、过期拒绝、篡改/非法拒绝、bcrypt 哈希校验。 - 实跑(nats+pg+gateway):注册→token+user(无密码)、重复注册 409、错密码 401、 /auth/me 带 token 200 / 无 token 401;owner 隔离改用已验证 uid —— 带 token 建的库 匿名/伪造 header 都看不到(JWT 用户数据归于雪花 id,header 无法臆测)。 片 2 待做:前端登录页 + 存令牌带 Bearer + 处理 401 + 去掉 header 兜底 + 保护路由。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
1.5 KiB
Go
56 lines
1.5 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// ErrUserExists 表示邮箱已注册(唯一约束冲突)。
|
|
var ErrUserExists = errors.New("email already registered")
|
|
|
|
// CreateUser 新建用户(密码已在调用方 bcrypt 哈希)。邮箱重复返回 ErrUserExists。
|
|
func (p *Postgres) CreateUser(ctx context.Context, email, name, passwordHash string) (*User, error) {
|
|
if p.db == nil {
|
|
return nil, errStoreDisabled
|
|
}
|
|
var existed int64
|
|
p.db.WithContext(ctx).Model(&User{}).Where("email = ?", email).Count(&existed)
|
|
if existed > 0 {
|
|
return nil, ErrUserExists
|
|
}
|
|
u := &User{Email: email, Name: name, PasswordHash: passwordHash}
|
|
if err := p.db.WithContext(ctx).Create(u).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
// GetUserByEmail 按邮箱取用户(登录用)。
|
|
func (p *Postgres) GetUserByEmail(ctx context.Context, email string) (*User, error) {
|
|
if p.db == nil {
|
|
return nil, errStoreDisabled
|
|
}
|
|
var u User
|
|
if err := p.db.WithContext(ctx).Where("email = ?", email).First(&u).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &u, nil
|
|
}
|
|
|
|
// GetUserByID 按主键取用户(鉴权后取当前用户)。
|
|
func (p *Postgres) GetUserByID(ctx context.Context, id string) (*User, error) {
|
|
if p.db == nil {
|
|
return nil, errStoreDisabled
|
|
}
|
|
var u User
|
|
if err := p.db.WithContext(ctx).First(&u, "id = ?", id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return &u, nil
|
|
}
|