feat: add user login

This commit is contained in:
Paul Pan 2022-09-17 11:22:55 +08:00
parent 787fc39c29
commit f163768aae
15 changed files with 167 additions and 48 deletions

View File

@ -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)
}

View File

@ -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
log *zap.Logger
userService user.Service
jwtService global.JwtService
}
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
app := &handler{
log: g.Log,
service: user.NewUserService(g),
log: g.Log,
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)
}

View 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)
}

View 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)
}

View File

@ -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)

View File

@ -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",
}

View File

@ -10,4 +10,5 @@ type Global struct {
Conf *Config
Stat *metrics.Metrics
Db Repo
Jwt JwtService
}

23
internal/global/jwt.go Normal file
View 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
}

View File

@ -8,8 +8,11 @@ import (
)
// @title OJ Server API Documentation
// @version 1.0
// @BasePath /api
// @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)

View File

@ -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
}

View File

@ -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),

View File

@ -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)

View File

@ -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
}

View 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
}

View File

@ -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 {