From 7ea0d08b117788ef872265f8e2b10ed790b895f2 Mon Sep 17 00:00:00 2001 From: celogeek Date: Thu, 26 May 2022 23:58:06 +0200 Subject: [PATCH] use JWT token --- internal/photos/api/account.go | 122 +++++++++++++++++++++++++++------ 1 file changed, 101 insertions(+), 21 deletions(-) diff --git a/internal/photos/api/account.go b/internal/photos/api/account.go index 70b2912..4f8dc69 100644 --- a/internal/photos/api/account.go +++ b/internal/photos/api/account.go @@ -3,17 +3,26 @@ package photosapi import ( "errors" "net/http" + "strings" "time" "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt" "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" ) // Errors var ( - ErrAccountExists = errors.New("account exists") - ErrAccountAuth = errors.New("login or password incorrect") + ErrAccountExists = errors.New("account exists") + ErrAccountAuth = errors.New("login or password incorrect") + ErrAccountTokenMissing = errors.New("token missing") + ErrAccountTokenInvalid = errors.New("token invalid") +) + +// Const +const ( + TOKEN = 1 + REFRESH_TOKEN = 2 ) // Model @@ -45,7 +54,36 @@ type LoginRequest struct { } type LoginResponse struct { - Token string `json:"token"` + Token string `json:"token"` + RefreshToken string `json:"refresh_token"` +} + +// JWT +type JWT struct { + Type int `json:"typ"` + ExpireAt int64 `json:"exp"` + AccountId uint32 `json:"acc"` +} + +func (j *JWT) Valid() error { + return jwt.MapClaims{ + "exp": float64(j.ExpireAt), + }.Valid() +} + +func NewJWTToken(accountId uint32) *JWT { + return &JWT{ + Type: TOKEN, + ExpireAt: time.Now().Add(time.Minute * 15).Unix(), + AccountId: accountId, + } +} +func NewJWTRefreshToken(accountId uint32) *JWT { + return &JWT{ + Type: REFRESH_TOKEN, + ExpireAt: time.Now().Add(time.Hour * 24).Unix(), + AccountId: accountId, + } } func (s *Service) Signup(c *gin.Context) { @@ -73,41 +111,83 @@ func (s *Service) Signup(c *gin.Context) { } func (s *Service) Login(c *gin.Context) { - var account *LoginRequest + var loginRequest *LoginRequest - if c.BindJSON(&account) != nil { + if c.BindJSON(&loginRequest) != nil { return } - session, err := NewSession(s.DB, account.Login, account.Password) + account := &Account{} + if err := s.DB.Where( + "login = ?", + loginRequest.Login, + ).First(account).Error; err != nil { + c.AbortWithError(http.StatusNotFound, ErrAccountAuth) + return + } + if err := bcrypt.CompareHashAndPassword(account.Password, []byte(loginRequest.Password)); err != nil { + c.AbortWithError(http.StatusNotFound, ErrAccountAuth) + return + } + + token, err := jwt.NewWithClaims(&jwt.SigningMethodEd25519{}, NewJWTToken(account.ID)).SignedString(s.SessionKey) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) || errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { - c.AbortWithError(http.StatusNotFound, ErrAccountAuth) - } else { - c.AbortWithError(http.StatusInternalServerError, err) - } + c.AbortWithError(http.StatusInternalServerError, err) return } - c.JSON(http.StatusOK, LoginResponse{session.Token}) + refresh_token, err := jwt.NewWithClaims(&jwt.SigningMethodEd25519{}, NewJWTRefreshToken(account.ID)).SignedString(s.SessionKey) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.JSON(http.StatusOK, LoginResponse{ + Token: token, + RefreshToken: refresh_token, + }) } -func (s *Service) Logout(c *gin.Context) { - res := s.DB.Where("token = ?", c.GetString("token")).Delete(&Session{}) - if res.Error != nil { - c.AbortWithError(http.StatusInternalServerError, res.Error) +func (s *Service) LoginRefresh(c *gin.Context) { + auth := strings.Split(c.GetHeader("Authorization"), " ") + if len(auth) != 2 { + c.AbortWithError(http.StatusForbidden, ErrAccountTokenMissing) return } - if res.RowsAffected == 0 { - c.AbortWithError(http.StatusNotFound, ErrSessionNotFound) + + if auth[0] != "Bearer" { + c.AbortWithError(http.StatusForbidden, ErrAccountTokenMissing) return } - c.Status(http.StatusNoContent) + + var claims JWT + refresh_token, err := jwt.ParseWithClaims(auth[1], &claims, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodEd25519); !ok { + return nil, ErrAccountTokenInvalid + } + return s.SessionKeyValidation, nil + }) + + if err != nil || !refresh_token.Valid || claims.Type != REFRESH_TOKEN { + c.AbortWithError(http.StatusForbidden, ErrAccountTokenInvalid) + return + } + + token, err := jwt.NewWithClaims(&jwt.SigningMethodEd25519{}, NewJWTToken(claims.AccountId)).SignedString(s.SessionKey) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.JSON(http.StatusOK, LoginResponse{ + Token: token, + RefreshToken: refresh_token.Raw, + }) } func (s *Service) AccountInit() { ac := s.Gin.Group("/account") ac.POST("/signup", s.Signup) ac.POST("/login", s.Login) - ac.GET("/logout", s.RequireAuthToken, s.Logout) + ac.GET("/refresh", s.LoginRefresh) }