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 nickname formData string true "nickname"
|
||||||
// @Param password formData string true "password"
|
// @Param password formData string true "password"
|
||||||
// @Response 200 {object} e.Response "random string"
|
// @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) {
|
func (h *handler) Create(c *gin.Context) {
|
||||||
req := new(createRequest)
|
req := new(createRequest)
|
||||||
|
|
||||||
@ -36,11 +37,6 @@ func (h *handler) Create(c *gin.Context) {
|
|||||||
Password: req.Password,
|
Password: req.Password,
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := h.service.Create(createData)
|
id, err := h.userService.Create(createData)
|
||||||
if err != nil {
|
e.Pong(c, err, id)
|
||||||
e.Pong(c, e.DatabaseError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Pong(c, e.Success, id)
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"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/WHUPRJ/woj-server/internal/service/user"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@ -11,20 +12,26 @@ var _ Handler = (*handler)(nil)
|
|||||||
|
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
Create(c *gin.Context)
|
Create(c *gin.Context)
|
||||||
|
Login(c *gin.Context)
|
||||||
// List(c *gin.Context)
|
// List(c *gin.Context)
|
||||||
|
|
||||||
|
tokenNext(c *gin.Context, user *model.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
service user.Service
|
userService user.Service
|
||||||
|
jwtService global.JwtService
|
||||||
}
|
}
|
||||||
|
|
||||||
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
|
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
|
||||||
app := &handler{
|
app := &handler{
|
||||||
log: g.Log,
|
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)
|
// 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/global"
|
||||||
"github.com/WHUPRJ/woj-server/internal/repo/postgresql"
|
"github.com/WHUPRJ/woj-server/internal/repo/postgresql"
|
||||||
"github.com/WHUPRJ/woj-server/internal/router"
|
"github.com/WHUPRJ/woj-server/internal/router"
|
||||||
|
"github.com/WHUPRJ/woj-server/internal/service/jwt"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -19,6 +20,9 @@ func Run(g *global.Global) error {
|
|||||||
g.Db = new(postgresql.PgRepo)
|
g.Db = new(postgresql.PgRepo)
|
||||||
g.Db.Setup(g)
|
g.Db.Setup(g)
|
||||||
|
|
||||||
|
// Setup JWT
|
||||||
|
g.Jwt = jwt.NewJwtService(g)
|
||||||
|
|
||||||
// Prepare Router
|
// Prepare Router
|
||||||
handler := router.InitRouters(g)
|
handler := router.InitRouters(g)
|
||||||
|
|
||||||
|
@ -16,6 +16,10 @@ const (
|
|||||||
TokenInvalid Err = 204
|
TokenInvalid Err = 204
|
||||||
TokenSignError Err = 205
|
TokenSignError Err = 205
|
||||||
TokenRevoked Err = 206
|
TokenRevoked Err = 206
|
||||||
|
|
||||||
|
UserNotFound Err = 300
|
||||||
|
UserWrongPassword Err = 301
|
||||||
|
UserDuplicated Err = 302
|
||||||
)
|
)
|
||||||
|
|
||||||
var msgText = map[Err]string{
|
var msgText = map[Err]string{
|
||||||
@ -34,4 +38,8 @@ var msgText = map[Err]string{
|
|||||||
TokenInvalid: "Token Invalid",
|
TokenInvalid: "Token Invalid",
|
||||||
TokenSignError: "Token Sign Error",
|
TokenSignError: "Token Sign Error",
|
||||||
TokenRevoked: "Token Revoked",
|
TokenRevoked: "Token Revoked",
|
||||||
|
|
||||||
|
UserNotFound: "User Not Found",
|
||||||
|
UserWrongPassword: "User Wrong Password",
|
||||||
|
UserDuplicated: "User Duplicated",
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,5 @@ type Global struct {
|
|||||||
Conf *Config
|
Conf *Config
|
||||||
Stat *metrics.Metrics
|
Stat *metrics.Metrics
|
||||||
Db Repo
|
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
|
||||||
|
}
|
@ -8,8 +8,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// @title OJ Server API Documentation
|
// @title OJ Server API Documentation
|
||||||
// @version 1.0
|
// @version 1.0
|
||||||
// @BasePath /api
|
// @BasePath /api
|
||||||
|
// @securityDefinitions.apikey Authentication
|
||||||
|
// @in header
|
||||||
|
// @name Authorization
|
||||||
func setupApi(g *global.Global, root *gin.RouterGroup) {
|
func setupApi(g *global.Global, root *gin.RouterGroup) {
|
||||||
for _, v := range endpoints {
|
for _, v := range endpoints {
|
||||||
group := root.Group(v.Version).Group(v.Path)
|
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
|
package jwt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"github.com/WHUPRJ/woj-server/internal/global"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Service = (*service)(nil)
|
var _ global.JwtService = (*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
|
|
||||||
}
|
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
@ -23,7 +13,7 @@ type service struct {
|
|||||||
ExpireHour int
|
ExpireHour int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewJwtService(g *global.Global) Service {
|
func NewJwtService(g *global.Global) global.JwtService {
|
||||||
return &service{
|
return &service{
|
||||||
log: g.Log,
|
log: g.Log,
|
||||||
SigningKey: []byte(g.Conf.WebServer.JwtSigningKey),
|
SigningKey: []byte(g.Conf.WebServer.JwtSigningKey),
|
||||||
|
@ -3,20 +3,21 @@ package jwt
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"github.com/WHUPRJ/woj-server/internal/e"
|
||||||
|
"github.com/WHUPRJ/woj-server/internal/global"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
"github.com/WHUPRJ/woj-server/pkg/utils"
|
||||||
"github.com/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *service) ParseToken(tokenText string) (*Claim, e.Err) {
|
func (s *service) ParseToken(tokenText string) (*global.Claim, e.Err) {
|
||||||
if tokenText == "" {
|
if tokenText == "" {
|
||||||
return nil, e.TokenEmpty
|
return nil, e.TokenEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := jwt.ParseWithClaims(
|
token, err := jwt.ParseWithClaims(
|
||||||
tokenText,
|
tokenText,
|
||||||
&Claim{},
|
&global.Claim{},
|
||||||
func(token *jwt.Token) (interface{}, error) {
|
func(token *jwt.Token) (interface{}, error) {
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
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 {
|
if token.Valid {
|
||||||
c := token.Claims.(*Claim)
|
c := token.Claims.(*global.Claim)
|
||||||
return c, e.Success
|
return c, e.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, e.TokenInvalid
|
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()
|
now := time.Now()
|
||||||
|
|
||||||
claim.IssuedAt = jwt.NewNumericDate(now)
|
claim.IssuedAt = jwt.NewNumericDate(now)
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/WHUPRJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/repo/model"
|
"github.com/WHUPRJ/woj-server/internal/repo/model"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateData struct {
|
type CreateData struct {
|
||||||
@ -13,11 +14,11 @@ type CreateData struct {
|
|||||||
Password string
|
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)
|
hashed, err := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Debug("bcrypt error", zap.Error(err), zap.String("password", data.Password))
|
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{
|
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 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))
|
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
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/WHUPRJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"github.com/WHUPRJ/woj-server/internal/global"
|
||||||
|
"github.com/WHUPRJ/woj-server/internal/repo/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Service = (*service)(nil)
|
var _ Service = (*service)(nil)
|
||||||
|
|
||||||
type Service interface {
|
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 {
|
type service struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user