diff --git a/README.md b/README.md index 002b664..2e31b01 100644 --- a/README.md +++ b/README.md @@ -2,26 +2,20 @@ 核心流程: -- [x] 注册与登录 - - [x] 对接接口 - - [ ] 人机风险分级验证 - - [ ] jwt 签发 -- [x] 鉴权 -- [x] 实名认证 - - [x] 对接接口 -- [x] 充值或购买 - - [x] 对接接口 - - [ ] 提取记录 -- [x] 提取 IP - - [ ] 长效提取 - - [ ] 使用记录 -- [x] 连接 +- [ ] 提取记录 +- [ ] 使用记录 中间件: - [ ] Limiter - [ ] Compress +考虑统计接口调用频率并通过接口展示 + +考虑登录时曾经输入过验证码的用户,登录成功后允许一段时间内免输验证码 + +修改 postgres 默认事务级别到 RR + 撤销令牌需要改成用户权限,只有本人可以撤销 扩展 device 权限验证方式,提供一种方法区分内部和外部服务 diff --git a/cmd/gen/main.go b/cmd/gen/main.go index 4f347cb..84b6e9d 100644 --- a/cmd/gen/main.go +++ b/cmd/gen/main.go @@ -38,6 +38,7 @@ func main() { } return field }), + gen.FieldRename("contact_qq", "ContactQQ"), } customs := make(map[string]any) diff --git a/web/handlers/auth.go b/web/handlers/auth.go index aa09854..572572c 100644 --- a/web/handlers/auth.go +++ b/web/handlers/auth.go @@ -313,7 +313,28 @@ func Introspect(c *fiber.Ctx) error { return err } + // 掩码敏感信息 + if profile.Phone != "" { + profile.Phone = maskPhone(profile.Phone) + } + if profile.IDNo != "" { + profile.IDNo = maskIdNo(profile.IDNo) + } return c.JSON(IntrospectResp{*profile}) } +func maskPhone(phone string) string { + if len(phone) < 11 { + return phone + } + return phone[:3] + "****" + phone[7:] +} + +func maskIdNo(idNo string) string { + if len(idNo) < 18 { + return idNo + } + return idNo[:3] + "*********" + idNo[14:] +} + // endregion diff --git a/web/handlers/user.go b/web/handlers/user.go index 66472ea..9af4450 100644 --- a/web/handlers/user.go +++ b/web/handlers/user.go @@ -2,15 +2,145 @@ package handlers import ( "platform/web/auth" + "platform/web/common" + m "platform/web/models" q "platform/web/queries" s "platform/web/services" "strconv" "time" "github.com/gofiber/fiber/v2" + "golang.org/x/crypto/bcrypt" ) -// region recharge +// region /update + +type UpdateUserReq struct { + Username string `json:"username" validate:"omitempty,min=3,max=20"` + Email string `json:"email" validate:"omitempty,email"` + ContactQQ string `json:"contact_qq" validate:"omitempty,qq"` + ContactWechat string `json:"contact_wechat" validate:"omitempty,wechat"` +} + +func UpdateUser(c *fiber.Ctx) error { + // 检查权限 + authCtx, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{}) + if err != nil { + return err + } + + // 解析请求参数 + req := new(UpdateUserReq) + if err := c.BodyParser(req); err != nil { + return err + } + + // 更新用户信息 + _, err = q.User. + Where(q.User.ID.Eq(authCtx.Payload.Id)). + Updates(m.User{ + Username: req.Username, + Email: req.Email, + ContactQQ: req.ContactQQ, + ContactWechat: req.ContactWechat, + }) + if err != nil { + return err + } + + // 返回结果 + return c.SendStatus(fiber.StatusNoContent) +} + +// endregion + +// region /update/account + +type UpdateAccountReq struct { + Username string `json:"username" validate:"omitempty,min=3,max=20"` + Password string `json:"password" validate:"omitempty,min=6,max=20"` +} + +func UpdateAccount(c *fiber.Ctx) error { + // 检查权限 + authCtx, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{}) + if err != nil { + return err + } + + // 解析请求参数 + req := new(UpdateAccountReq) + if err := c.BodyParser(req); err != nil { + return err + } + + // 更新用户信息 + _, err = q.User. + Where(q.User.ID.Eq(authCtx.Payload.Id)). + Updates(m.User{ + Username: req.Username, + Password: req.Password, + }) + if err != nil { + return err + } + + // 返回结果 + return c.SendStatus(fiber.StatusNoContent) +} + +// endregion + +// region /update/password + +type UpdatePasswordReq struct { + Phone string `json:"phone"` + Code string `json:"code"` + Password string `json:"password"` +} + +func UpdatePassword(c *fiber.Ctx) error { + // 检查权限 + authCtx, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{}) + if err != nil { + return err + } + + // 解析请求参数 + req := new(UpdatePasswordReq) + if err := c.BodyParser(req); err != nil { + return err + } + + // 验证手机令牌 + if req.Phone == "" || req.Code == "" { + return common.NewErr("user", "手机号码和验证码不能为空") + } + err = s.Verifier.VerifySms(c.Context(), req.Phone, req.Code) + if err != nil { + return err + } + + // 更新密码 + newHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + return err + } + + _, err = q.User. + Where(q.User.ID.Eq(authCtx.Payload.Id)). + UpdateColumn(q.User.Password, newHash) + if err != nil { + return err + } + + // 返回结果 + return c.SendStatus(fiber.StatusNoContent) +} + +// endregion + +// region /recharge type RechargePrepareReq struct { Amount float64 `json:"amount" validate:"required,min=0.01"` diff --git a/web/models/user.gen.go b/web/models/user.gen.go index 4a95a4d..0272648 100644 --- a/web/models/user.gen.go +++ b/web/models/user.gen.go @@ -27,7 +27,7 @@ type User struct { IDType int32 `gorm:"column:id_type;not null;comment:认证类型:0-未认证,1-个人认证,2-企业认证" json:"id_type"` // 认证类型:0-未认证,1-个人认证,2-企业认证 IDNo string `gorm:"column:id_no;comment:身份证号或营业执照号" json:"id_no"` // 身份证号或营业执照号 IDToken string `gorm:"column:id_token;comment:身份验证标识" json:"id_token"` // 身份验证标识 - ContactQq string `gorm:"column:contact_qq;comment:QQ联系方式" json:"contact_qq"` // QQ联系方式 + ContactQQ string `gorm:"column:contact_qq;comment:QQ联系方式" json:"contact_qq"` // QQ联系方式 ContactWechat string `gorm:"column:contact_wechat;comment:微信联系方式" json:"contact_wechat"` // 微信联系方式 LastLogin common.LocalDateTime `gorm:"column:last_login;comment:最后登录时间" json:"last_login"` // 最后登录时间 LastLoginHost string `gorm:"column:last_login_host;comment:最后登录地址" json:"last_login_host"` // 最后登录地址 diff --git a/web/queries/user.gen.go b/web/queries/user.gen.go index f87649f..a8500a5 100644 --- a/web/queries/user.gen.go +++ b/web/queries/user.gen.go @@ -40,7 +40,7 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user { _user.IDType = field.NewInt32(tableName, "id_type") _user.IDNo = field.NewString(tableName, "id_no") _user.IDToken = field.NewString(tableName, "id_token") - _user.ContactQq = field.NewString(tableName, "contact_qq") + _user.ContactQQ = field.NewString(tableName, "contact_qq") _user.ContactWechat = field.NewString(tableName, "contact_wechat") _user.LastLogin = field.NewField(tableName, "last_login") _user.LastLoginHost = field.NewString(tableName, "last_login_host") @@ -71,7 +71,7 @@ type user struct { IDType field.Int32 // 认证类型:0-未认证,1-个人认证,2-企业认证 IDNo field.String // 身份证号或营业执照号 IDToken field.String // 身份验证标识 - ContactQq field.String // QQ联系方式 + ContactQQ field.String // QQ联系方式 ContactWechat field.String // 微信联系方式 LastLogin field.Field // 最后登录时间 LastLoginHost field.String // 最后登录地址 @@ -108,7 +108,7 @@ func (u *user) updateTableName(table string) *user { u.IDType = field.NewInt32(table, "id_type") u.IDNo = field.NewString(table, "id_no") u.IDToken = field.NewString(table, "id_token") - u.ContactQq = field.NewString(table, "contact_qq") + u.ContactQQ = field.NewString(table, "contact_qq") u.ContactWechat = field.NewString(table, "contact_wechat") u.LastLogin = field.NewField(table, "last_login") u.LastLoginHost = field.NewString(table, "last_login_host") @@ -146,7 +146,7 @@ func (u *user) fillFieldMap() { u.fieldMap["id_type"] = u.IDType u.fieldMap["id_no"] = u.IDNo u.fieldMap["id_token"] = u.IDToken - u.fieldMap["contact_qq"] = u.ContactQq + u.fieldMap["contact_qq"] = u.ContactQQ u.fieldMap["contact_wechat"] = u.ContactWechat u.fieldMap["last_login"] = u.LastLogin u.fieldMap["last_login_host"] = u.LastLoginHost diff --git a/web/router.go b/web/router.go index 518d522..d5c1d6b 100644 --- a/web/router.go +++ b/web/router.go @@ -18,6 +18,9 @@ func ApplyRouters(app *fiber.App) { // 用户 user := api.Group("/user") + user.Post("/update", handlers.UpdateUser) + user.Post("/update/account", handlers.UpdateAccount) + user.Post("/update/password", handlers.UpdatePassword) user.Post("/identify", handlers.Identify) user.Post("/identify/callback", handlers.IdentifyCallback) user.Post("/recharge/prepare/alipay", handlers.RechargePrepareAlipay)