136 lines
4.0 KiB
Go
136 lines
4.0 KiB
Go
package web
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"quyun/v2/app/errorx"
|
|
"quyun/v2/app/http/web/dto"
|
|
"quyun/v2/app/services"
|
|
"quyun/v2/database/models"
|
|
"quyun/v2/pkg/consts"
|
|
"quyun/v2/providers/jwt"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// @provider
|
|
type auth struct {
|
|
jwt *jwt.JWT
|
|
}
|
|
|
|
var reUsername = regexp.MustCompile(`^[a-zA-Z0-9_]{3,32}$`)
|
|
|
|
// Login 用户登录(平台侧,非超级管理员)。
|
|
//
|
|
// @Summary 用户登录
|
|
// @Tags Web
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param form body dto.LoginForm true "form"
|
|
// @Success 200 {object} dto.LoginResponse "成功"
|
|
// @Router /v1/auth/login [post]
|
|
// @Bind form body
|
|
func (ctl *auth) login(ctx fiber.Ctx, form *dto.LoginForm) (*dto.LoginResponse, error) {
|
|
m, err := services.User.FindByUsername(ctx, form.Username)
|
|
if err != nil {
|
|
return nil, errorx.Wrap(err).WithMsg("用户名或密码错误")
|
|
}
|
|
if ok := m.ComparePassword(ctx, form.Password); !ok {
|
|
return nil, errorx.Wrap(errorx.ErrInvalidCredentials).WithMsg("用户名或密码错误")
|
|
}
|
|
|
|
token, err := ctl.jwt.CreateToken(ctl.jwt.CreateClaims(jwt.BaseClaims{
|
|
UserID: m.ID,
|
|
}))
|
|
if err != nil {
|
|
return nil, errorx.Wrap(err).WithMsg("登录凭证生成失败")
|
|
}
|
|
|
|
return &dto.LoginResponse{Token: token}, nil
|
|
}
|
|
|
|
// Register 用户注册(平台侧,非超级管理员)。
|
|
//
|
|
// @Summary 用户注册
|
|
// @Tags Web
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param form body dto.RegisterForm true "form"
|
|
// @Success 200 {object} dto.LoginResponse "成功"
|
|
// @Router /v1/auth/register [post]
|
|
// @Bind form body
|
|
func (ctl *auth) register(ctx fiber.Ctx, form *dto.RegisterForm) (*dto.LoginResponse, error) {
|
|
username := strings.TrimSpace(form.Username)
|
|
if username == "" {
|
|
return nil, errorx.ErrMissingParameter.WithMsg("请输入用户名")
|
|
}
|
|
if !reUsername.MatchString(username) {
|
|
return nil, errorx.ErrInvalidParameter.WithMsg("用户名需为 3-32 位字母/数字/下划线")
|
|
}
|
|
if form.Password == "" {
|
|
return nil, errorx.ErrMissingParameter.WithMsg("请输入密码")
|
|
}
|
|
if len(form.Password) < 8 {
|
|
return nil, errorx.ErrParameterTooShort.WithMsg("密码至少 8 位,请设置更安全的密码")
|
|
}
|
|
if form.Password != form.ConfirmPassword {
|
|
return nil, errorx.ErrInvalidParameter.WithMsg("两次输入的密码不一致,请重新确认")
|
|
}
|
|
|
|
// 先查询用户名是否已存在,避免直接插入导致不友好的数据库错误信息。
|
|
_, err := services.User.FindByUsername(ctx, username)
|
|
if err == nil {
|
|
return nil, errorx.ErrRecordDuplicated.WithMsg("用户名已被占用,换一个试试")
|
|
}
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errorx.Wrap(err).WithMsg("用户信息校验失败,请稍后再试")
|
|
}
|
|
|
|
m := &models.User{
|
|
Username: username,
|
|
Password: form.Password,
|
|
Roles: []consts.Role{consts.RoleUser},
|
|
Status: consts.UserStatusVerified,
|
|
}
|
|
if _, err := services.User.Create(ctx, m); err != nil {
|
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
|
return nil, errorx.ErrRecordDuplicated.WithMsg("用户名已被占用,换一个试试")
|
|
}
|
|
return nil, errorx.Wrap(err).WithMsg("注册失败,请稍后再试")
|
|
}
|
|
|
|
token, err := ctl.jwt.CreateToken(ctl.jwt.CreateClaims(jwt.BaseClaims{UserID: m.ID}))
|
|
if err != nil {
|
|
return nil, errorx.Wrap(err).WithMsg("登录凭证生成失败")
|
|
}
|
|
|
|
return &dto.LoginResponse{Token: token}, nil
|
|
}
|
|
|
|
// Token 刷新登录凭证。
|
|
//
|
|
// @Summary 刷新 Token
|
|
// @Tags Web
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} dto.LoginResponse "成功"
|
|
// @Router /v1/auth/token [get]
|
|
func (ctl *auth) token(ctx fiber.Ctx) (*dto.LoginResponse, error) {
|
|
claims, ok := ctx.Locals(consts.CtxKeyClaims).(*jwt.Claims)
|
|
if !ok || claims == nil || claims.UserID <= 0 {
|
|
return nil, errorx.ErrTokenInvalid
|
|
}
|
|
|
|
token, err := ctl.jwt.CreateToken(ctl.jwt.CreateClaims(jwt.BaseClaims{
|
|
UserID: claims.UserID,
|
|
}))
|
|
if err != nil {
|
|
return nil, errorx.Wrap(err).WithMsg("登录凭证生成失败")
|
|
}
|
|
|
|
return &dto.LoginResponse{Token: token}, nil
|
|
}
|