feat: add user login
This commit is contained in:
parent
787fc39c29
commit
f163768aae
@ -21,7 +21,8 @@ type createRequest struct {
|
||||
// @Param nickname formData string true "nickname"
|
||||
// @Param password formData string true "password"
|
||||
// @Response 200 {object} e.Response "random string"
|
||||
// @Router /v1/user [post]
|
||||
// @Security Authentication
|
||||
// @Router /v1/user/create [post]
|
||||
func (h *handler) Create(c *gin.Context) {
|
||||
req := new(createRequest)
|
||||
|
||||
@ -36,11 +37,6 @@ func (h *handler) Create(c *gin.Context) {
|
||||
Password: req.Password,
|
||||
}
|
||||
|
||||
id, err := h.service.Create(createData)
|
||||
if err != nil {
|
||||
e.Pong(c, e.DatabaseError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
e.Pong(c, e.Success, id)
|
||||
id, err := h.userService.Create(createData)
|
||||
e.Pong(c, err, id)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package user
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/WHUPRJ/woj-server/internal/repo/model"
|
||||
"github.com/WHUPRJ/woj-server/internal/service/user"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
@ -11,20 +12,26 @@ var _ Handler = (*handler)(nil)
|
||||
|
||||
type Handler interface {
|
||||
Create(c *gin.Context)
|
||||
Login(c *gin.Context)
|
||||
// List(c *gin.Context)
|
||||
|
||||
tokenNext(c *gin.Context, user *model.User)
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
log *zap.Logger
|
||||
service user.Service
|
||||
userService user.Service
|
||||
jwtService global.JwtService
|
||||
}
|
||||
|
||||
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
|
||||
app := &handler{
|
||||
log: g.Log,
|
||||
service: user.NewUserService(g),
|
||||
userService: user.NewUserService(g),
|
||||
jwtService: g.Jwt,
|
||||
}
|
||||
|
||||
group.POST("/", app.Create)
|
||||
group.POST("/login", app.Login)
|
||||
group.POST("/create", app.jwtService.Handler(), app.Create)
|
||||
// group.GET("/", app.List)
|
||||
}
|
||||
|
44
internal/api/user/login.go
Normal file
44
internal/api/user/login.go
Normal file
@ -0,0 +1,44 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/repo/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type loginRequest struct {
|
||||
Username string `form:"username" binding:"required"`
|
||||
Password string `form:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// Login
|
||||
// @Summary login
|
||||
// @Description login and return token
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param username formData string true "username"
|
||||
// @Param password formData string true "password"
|
||||
// @Response 200 {object} e.Response "random string"
|
||||
// @Router /v1/user/login [post]
|
||||
func (h *handler) Login(c *gin.Context) {
|
||||
req := new(loginRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// check password
|
||||
userData := &model.User{
|
||||
UserName: req.Username,
|
||||
Password: []byte(req.Password),
|
||||
}
|
||||
user, err := h.userService.Login(userData)
|
||||
if err != e.Success {
|
||||
e.Pong(c, err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// sign and return token
|
||||
h.tokenNext(c, user)
|
||||
}
|
18
internal/api/user/token.go
Normal file
18
internal/api/user/token.go
Normal file
@ -0,0 +1,18 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/WHUPRJ/woj-server/internal/repo/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (h *handler) tokenNext(c *gin.Context, user *model.User) {
|
||||
claim := &global.Claim{
|
||||
UID: user.ID,
|
||||
UserName: user.UserName,
|
||||
NickName: user.NickName,
|
||||
}
|
||||
token, err := h.jwtService.SignClaim(claim)
|
||||
e.Pong(c, err, token)
|
||||
}
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/WHUPRJ/woj-server/internal/repo/postgresql"
|
||||
"github.com/WHUPRJ/woj-server/internal/router"
|
||||
"github.com/WHUPRJ/woj-server/internal/service/jwt"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -19,6 +20,9 @@ func Run(g *global.Global) error {
|
||||
g.Db = new(postgresql.PgRepo)
|
||||
g.Db.Setup(g)
|
||||
|
||||
// Setup JWT
|
||||
g.Jwt = jwt.NewJwtService(g)
|
||||
|
||||
// Prepare Router
|
||||
handler := router.InitRouters(g)
|
||||
|
||||
|
@ -16,6 +16,10 @@ const (
|
||||
TokenInvalid Err = 204
|
||||
TokenSignError Err = 205
|
||||
TokenRevoked Err = 206
|
||||
|
||||
UserNotFound Err = 300
|
||||
UserWrongPassword Err = 301
|
||||
UserDuplicated Err = 302
|
||||
)
|
||||
|
||||
var msgText = map[Err]string{
|
||||
@ -34,4 +38,8 @@ var msgText = map[Err]string{
|
||||
TokenInvalid: "Token Invalid",
|
||||
TokenSignError: "Token Sign Error",
|
||||
TokenRevoked: "Token Revoked",
|
||||
|
||||
UserNotFound: "User Not Found",
|
||||
UserWrongPassword: "User Wrong Password",
|
||||
UserDuplicated: "User Duplicated",
|
||||
}
|
||||
|
@ -10,4 +10,5 @@ type Global struct {
|
||||
Conf *Config
|
||||
Stat *metrics.Metrics
|
||||
Db Repo
|
||||
Jwt JwtService
|
||||
}
|
||||
|
23
internal/global/jwt.go
Normal file
23
internal/global/jwt.go
Normal file
@ -0,0 +1,23 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
type Claim struct {
|
||||
UID uint `json:"id"`
|
||||
UserName string `json:"user_name"`
|
||||
NickName string `json:"nick_name"`
|
||||
Version int `json:"version"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type JwtService interface {
|
||||
ParseToken(tokenText string) (*Claim, e.Err)
|
||||
SignClaim(claim *Claim) (string, e.Err)
|
||||
// TODO: Validate(claim *Claim) bool
|
||||
|
||||
Handler() gin.HandlerFunc
|
||||
}
|
@ -10,6 +10,9 @@ import (
|
||||
// @title OJ Server API Documentation
|
||||
// @version 1.0
|
||||
// @BasePath /api
|
||||
// @securityDefinitions.apikey Authentication
|
||||
// @in header
|
||||
// @name Authorization
|
||||
func setupApi(g *global.Global, root *gin.RouterGroup) {
|
||||
for _, v := range endpoints {
|
||||
group := root.Group(v.Version).Group(v.Path)
|
||||
|
@ -1,11 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import "github.com/golang-jwt/jwt/v4"
|
||||
|
||||
type Claim struct {
|
||||
UID uint `json:"id"`
|
||||
UserName string `json:"user_name"`
|
||||
NickName string `json:"nick_name"`
|
||||
Version int `json:"version"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
@ -1,21 +1,11 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var _ Service = (*service)(nil)
|
||||
|
||||
type Service interface {
|
||||
ParseToken(tokenText string) (*Claim, e.Err)
|
||||
SignClaim(claim *Claim) (string, e.Err)
|
||||
// TODO: Validate(claim *Claim) bool
|
||||
|
||||
Handler() gin.HandlerFunc
|
||||
}
|
||||
var _ global.JwtService = (*service)(nil)
|
||||
|
||||
type service struct {
|
||||
log *zap.Logger
|
||||
@ -23,7 +13,7 @@ type service struct {
|
||||
ExpireHour int
|
||||
}
|
||||
|
||||
func NewJwtService(g *global.Global) Service {
|
||||
func NewJwtService(g *global.Global) global.JwtService {
|
||||
return &service{
|
||||
log: g.Log,
|
||||
SigningKey: []byte(g.Conf.WebServer.JwtSigningKey),
|
||||
|
@ -3,20 +3,21 @@ package jwt
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *service) ParseToken(tokenText string) (*Claim, e.Err) {
|
||||
func (s *service) ParseToken(tokenText string) (*global.Claim, e.Err) {
|
||||
if tokenText == "" {
|
||||
return nil, e.TokenEmpty
|
||||
}
|
||||
|
||||
token, err := jwt.ParseWithClaims(
|
||||
tokenText,
|
||||
&Claim{},
|
||||
&global.Claim{},
|
||||
func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
@ -39,14 +40,14 @@ func (s *service) ParseToken(tokenText string) (*Claim, e.Err) {
|
||||
}
|
||||
|
||||
if token.Valid {
|
||||
c := token.Claims.(*Claim)
|
||||
c := token.Claims.(*global.Claim)
|
||||
return c, e.Success
|
||||
}
|
||||
|
||||
return nil, e.TokenInvalid
|
||||
}
|
||||
|
||||
func (s *service) SignClaim(claim *Claim) (string, e.Err) {
|
||||
func (s *service) SignClaim(claim *global.Claim) (string, e.Err) {
|
||||
now := time.Now()
|
||||
|
||||
claim.IssuedAt = jwt.NewNumericDate(now)
|
||||
|
@ -1,10 +1,11 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/repo/model"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CreateData struct {
|
||||
@ -13,11 +14,11 @@ type CreateData struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
func (s *service) Create(data *CreateData) (id uint, err error) {
|
||||
func (s *service) Create(data *CreateData) (uint, e.Err) {
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
s.log.Debug("bcrypt error", zap.Error(err), zap.String("password", data.Password))
|
||||
return 0, errors.Wrap(err, "bcrypt error")
|
||||
return 0, e.InternalError
|
||||
}
|
||||
|
||||
user := &model.User{
|
||||
@ -28,8 +29,11 @@ func (s *service) Create(data *CreateData) (id uint, err error) {
|
||||
}
|
||||
|
||||
if err := s.db.Get().Create(user).Error; err != nil {
|
||||
if strings.Contains(err.Error(), "duplicate key") {
|
||||
return 0, e.UserDuplicated
|
||||
}
|
||||
s.log.Debug("create user error", zap.Error(err), zap.Any("data", data))
|
||||
return 0, errors.Wrap(err, "create error")
|
||||
return 0, e.DatabaseError
|
||||
}
|
||||
return user.ID, nil
|
||||
return user.ID, e.Success
|
||||
}
|
||||
|
28
internal/service/user/login.go
Normal file
28
internal/service/user/login.go
Normal file
@ -0,0 +1,28 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/repo/model"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (s *service) Login(data *model.User) (*model.User, e.Err) {
|
||||
user := &model.User{UserName: data.UserName}
|
||||
|
||||
err := s.db.Get().Where(user).First(&user).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return user, e.UserNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return user, e.DatabaseError
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword(user.Password, data.Password)
|
||||
if err != nil {
|
||||
return user, e.UserWrongPassword
|
||||
}
|
||||
|
||||
return user, e.Success
|
||||
}
|
@ -1,14 +1,17 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/WHUPRJ/woj-server/internal/repo/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var _ Service = (*service)(nil)
|
||||
|
||||
type Service interface {
|
||||
Create(data *CreateData) (id uint, err error)
|
||||
Create(data *CreateData) (uint, e.Err)
|
||||
Login(data *model.User) (*model.User, e.Err)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
|
Loading…
Reference in New Issue
Block a user