Added /refresh endpoint and make some refactor again
This commit is contained in:
@@ -5,7 +5,7 @@ go 1.24.0
|
|||||||
toolchain go1.24.1
|
toolchain go1.24.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.ego.freeddns.org/egommerce/api-entities v0.3.4
|
git.ego.freeddns.org/egommerce/api-entities v0.3.8
|
||||||
git.ego.freeddns.org/egommerce/go-api-pkg v0.4.6
|
git.ego.freeddns.org/egommerce/go-api-pkg v0.4.6
|
||||||
github.com/go-pg/migrations/v8 v8.1.0
|
github.com/go-pg/migrations/v8 v8.1.0
|
||||||
github.com/go-pg/pg/v10 v10.15.0
|
github.com/go-pg/pg/v10 v10.15.0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
git.ego.freeddns.org/egommerce/api-entities v0.3.4 h1:yzPpumgtZeANRX3c74Y8/Z3s4vR6BKYceZhPLnLNNVY=
|
git.ego.freeddns.org/egommerce/api-entities v0.3.8 h1:ULuyJfr04E9AIAe8QTxLjDFV81H6HTxMNXtI/b+3Ec0=
|
||||||
git.ego.freeddns.org/egommerce/api-entities v0.3.4/go.mod h1:IqynARw+06GOm4eZGZuepmbi7bUxWBnOB4jd5cI7jf8=
|
git.ego.freeddns.org/egommerce/api-entities v0.3.8/go.mod h1:IqynARw+06GOm4eZGZuepmbi7bUxWBnOB4jd5cI7jf8=
|
||||||
git.ego.freeddns.org/egommerce/go-api-pkg v0.4.6 h1:1iZW+vkbv7fQusv/pMjtIM1QvJ+QQr3nyvuuajgHc80=
|
git.ego.freeddns.org/egommerce/go-api-pkg v0.4.6 h1:1iZW+vkbv7fQusv/pMjtIM1QvJ+QQr3nyvuuajgHc80=
|
||||||
git.ego.freeddns.org/egommerce/go-api-pkg v0.4.6/go.mod h1:5Ft8LCd0UXp5hHpvXRBCv9mCGikogFhL7LP2qit12JM=
|
git.ego.freeddns.org/egommerce/go-api-pkg v0.4.6/go.mod h1:5Ft8LCd0UXp5hHpvXRBCv9mCGikogFhL7LP2qit12JM=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
baseCnf "git.ego.freeddns.org/egommerce/go-api-pkg/config"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
jwt "github.com/gofiber/jwt/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// JWTProtected func for specify routes group with JWT authentication.
|
|
||||||
// See: https://github.com/gofiber/jwt
|
|
||||||
func JWTProtected() func(*fiber.Ctx) error {
|
|
||||||
// Create config for JWT authentication middleware.
|
|
||||||
config := jwt.Config{
|
|
||||||
SigningKey: []byte(baseCnf.GetEnv("JWT_ACCESS_TOKEN_SECRET_KEY", "FallbackAccessTokenSecret")),
|
|
||||||
ContextKey: "jwt", // used in private routes
|
|
||||||
ErrorHandler: jwtError,
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwt.New(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func jwtError(c *fiber.Ctx, err error) error {
|
|
||||||
// Return status 400 Bad Request and failed authentication error.
|
|
||||||
if err.Error() == "Missing or malformed JWT" {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"error": true,
|
|
||||||
"msg": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return status 401 Unauthorized and failed authentication error.
|
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
|
||||||
"error": true,
|
|
||||||
"msg": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
dto "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
|
dto "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
|
||||||
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
|
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// "github.com/gofiber/fiber/v2"
|
|
||||||
// "github.com/gofiber/fiber/v2/middleware/cors"
|
|
||||||
|
|
||||||
func SetupMiddleware(s *Server) {
|
|
||||||
s.Use(LoggingMiddleware())
|
|
||||||
s.Use(XRequestIDMiddleware())
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoggingMiddleware() func(c *fiber.Ctx) error {
|
|
||||||
return func(c *fiber.Ctx) error {
|
|
||||||
log.Printf("Request: %s, remote: %s, via: %s",
|
|
||||||
c.Request().URI().String(),
|
|
||||||
c.Context().RemoteIP().String(),
|
|
||||||
string(c.Context().UserAgent()),
|
|
||||||
)
|
|
||||||
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func XRequestIDMiddleware() func(c *fiber.Ctx) error {
|
|
||||||
return func(c *fiber.Ctx) error {
|
|
||||||
requestID := uuid.New().String()
|
|
||||||
c.Set("X-Request-ID", requestID)
|
|
||||||
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
66
src/internal/server/middlewares.go
Normal file
66
src/internal/server/middlewares.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// "github.com/gofiber/fiber/v2"
|
||||||
|
// "github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
|
|
||||||
|
func SetupMiddleware(s *Server) {
|
||||||
|
s.Use(LoggingMiddleware())
|
||||||
|
s.Use(XRequestIDMiddleware())
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoggingMiddleware() func(c *fiber.Ctx) error {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
log.Printf("Request: %s, remote: %s, via: %s",
|
||||||
|
c.Request().URI().String(),
|
||||||
|
c.Context().RemoteIP().String(),
|
||||||
|
string(c.Context().UserAgent()),
|
||||||
|
)
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func XRequestIDMiddleware() func(c *fiber.Ctx) error {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
requestID := uuid.New().String()
|
||||||
|
c.Set("X-Request-ID", requestID)
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWTProtected func for specify routes group with JWT authentication.
|
||||||
|
// See: https://github.com/gofiber/jwt
|
||||||
|
// func JWTProtected() func(*fiber.Ctx) error {
|
||||||
|
// // Create config for JWT authentication middleware.
|
||||||
|
// config := jwt.Config{
|
||||||
|
// SigningKey: []byte(baseCnf.GetEnv("JWT_ACCESS_TOKEN_SECRET_KEY", "FallbackAccessTokenSecret")),
|
||||||
|
// ContextKey: "jwt", // used in private routes
|
||||||
|
// ErrorHandler: jwtError,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return jwt.New(config)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func jwtError(c *fiber.Ctx, err error) error {
|
||||||
|
// // Return status 400 Bad Request and failed authentication error.
|
||||||
|
// if err.Error() == "Missing or malformed JWT" {
|
||||||
|
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
// "error": true,
|
||||||
|
// "msg": err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Return status 401 Unauthorized and failed authentication error.
|
||||||
|
// return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
// "error": true,
|
||||||
|
// "msg": err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
28
src/internal/server/refresh_handler.go
Normal file
28
src/internal/server/refresh_handler.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
dto "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
|
||||||
|
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) RefreshHandlerFn(c *fiber.Ctx) error {
|
||||||
|
data := new(dto.AuthRefreshTokenRequestDTO)
|
||||||
|
if err := c.BodyParser(data); err != nil {
|
||||||
|
return s.Error(c, fiber.StatusBadRequest, "Error parsing input")
|
||||||
|
}
|
||||||
|
|
||||||
|
authSrv := service.NewAuthService(s.GetDatabase(), s.GetCache())
|
||||||
|
|
||||||
|
token, err := authSrv.RefreshToken(data.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
if err == service.ErrUnableToCacheToken {
|
||||||
|
return s.Error(c, fiber.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Error(c, fiber.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(&dto.AuthLoginResponseDTO{Token: token})
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
dto "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
|
dto "git.ego.freeddns.org/egommerce/api-entities/identity/dto"
|
||||||
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
|
"git.ego.freeddns.org/egommerce/identity-service/internal/service"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -21,5 +21,6 @@ func SetupRouter(s *Server) {
|
|||||||
|
|
||||||
s.Group("/v1").
|
s.Group("/v1").
|
||||||
Post("/login", s.LoginHandlerFn).
|
Post("/login", s.LoginHandlerFn).
|
||||||
|
Post("/refresh", s.RefreshHandlerFn).
|
||||||
Post("/register", s.RegisterHandlerFn)
|
Post("/register", s.RegisterHandlerFn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,17 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
db "git.ego.freeddns.org/egommerce/identity-service/pkg/database"
|
db "git.ego.freeddns.org/egommerce/identity-service/pkg/database"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
AuthService *Auth
|
AuthService *Auth
|
||||||
jwtSrv *JWT
|
|
||||||
|
|
||||||
ErrLoginIncorrect = errors.New("login incorrect")
|
ErrLoginIncorrect = errors.New("login incorrect")
|
||||||
ErrUnableToCacheToken = errors.New("unable to save token in cache")
|
ErrUnableToCacheToken = errors.New("unable to save token in cache")
|
||||||
|
ErrInvalidAccessToken = errors.New("invalid access token")
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -48,13 +49,30 @@ func (a *Auth) Login(login, passwd string) (string, error) {
|
|||||||
|
|
||||||
accessToken, _ := jwtSrv.CreateAccessToken(id)
|
accessToken, _ := jwtSrv.CreateAccessToken(id)
|
||||||
refreshToken, _ := jwtSrv.CreateRefreshToken(id)
|
refreshToken, _ := jwtSrv.CreateRefreshToken(id)
|
||||||
if err = a.saveTokensToCache(accessToken, refreshToken, id); err != nil {
|
if err = a.saveTokensToCache(id, accessToken, refreshToken); err != nil {
|
||||||
return "", ErrUnableToCacheToken
|
return "", ErrUnableToCacheToken
|
||||||
}
|
}
|
||||||
|
|
||||||
return accessToken, nil
|
return accessToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Auth) RefreshToken(accessToken string) (string, error) {
|
||||||
|
token, claims, err := jwtSrv.ValidateAccessToken(accessToken)
|
||||||
|
if err != nil || !token.Valid {
|
||||||
|
return "", ErrInvalidAccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
id := claims["sub"]
|
||||||
|
|
||||||
|
newAccessToken, _ := jwtSrv.CreateAccessToken(id.(string))
|
||||||
|
newRefreshToken, _ := jwtSrv.CreateRefreshToken(id.(string))
|
||||||
|
if err = a.saveTokensToCache(id.(string), newAccessToken, newRefreshToken); err != nil {
|
||||||
|
return "", ErrUnableToCacheToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return newAccessToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Auth) Register(email, login, passwd string) (string, error) {
|
func (a *Auth) Register(email, login, passwd string) (string, error) {
|
||||||
var id string
|
var id string
|
||||||
|
|
||||||
@@ -71,7 +89,7 @@ func (a *Auth) Register(email, login, passwd string) (string, error) {
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) saveTokensToCache(accessToken, refreshToken, id string) error {
|
func (a *Auth) saveTokensToCache(id, accessToken, refreshToken string) error {
|
||||||
res := a.cache.Set(context.Background(), "auth:access_token:"+id, accessToken, accessTokenExpireTime)
|
res := a.cache.Set(context.Background(), "auth:access_token:"+id, accessToken, accessTokenExpireTime)
|
||||||
if err := res.Err(); err != nil {
|
if err := res.Err(); err != nil {
|
||||||
fmt.Println("failed to save access token in redis: ", err.Error())
|
fmt.Println("failed to save access token in redis: ", err.Error())
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -10,10 +11,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
ErrorTokenExpired = errors.New("token has expired")
|
||||||
|
ErrInvalidToken = errors.New("invalid token")
|
||||||
|
|
||||||
accessTokenExpireTime time.Duration
|
accessTokenExpireTime time.Duration
|
||||||
refreshTokenExpireTime time.Duration
|
refreshTokenExpireTime time.Duration
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var jwtSrv *JWT
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
expAccessTokenTime, _ := strconv.Atoi(baseCnf.GetEnv("JWT_ACCESS_TOKEN_EXPIRE_TIME", "5"))
|
expAccessTokenTime, _ := strconv.Atoi(baseCnf.GetEnv("JWT_ACCESS_TOKEN_EXPIRE_TIME", "5"))
|
||||||
accessTokenExpireTime = time.Duration(int(time.Hour) * expAccessTokenTime) // hours
|
accessTokenExpireTime = time.Duration(int(time.Hour) * expAccessTokenTime) // hours
|
||||||
@@ -21,12 +27,16 @@ func init() {
|
|||||||
expRefreshTokenTime, _ := strconv.Atoi(baseCnf.GetEnv("JWT_REFRESH_TOKEN_EXPIRE_TIME", "7"))
|
expRefreshTokenTime, _ := strconv.Atoi(baseCnf.GetEnv("JWT_REFRESH_TOKEN_EXPIRE_TIME", "7"))
|
||||||
refreshTokenExpireTime = time.Duration(int(time.Hour*24) * expRefreshTokenTime) // days
|
refreshTokenExpireTime = time.Duration(int(time.Hour*24) * expRefreshTokenTime) // days
|
||||||
|
|
||||||
jwtSrv = &JWT{
|
jwtSrv = NewJWTService(
|
||||||
accessTokenExpireTime,
|
accessTokenExpireTime,
|
||||||
[]byte(baseCnf.GetEnv("JWT_ACCESS_TOKEN_SECRET_KEY", "FallbackAccessTokenSecret")),
|
[]byte(baseCnf.GetEnv("JWT_ACCESS_TOKEN_SECRET_KEY", "FallbackAccessTokenSecret")),
|
||||||
refreshTokenExpireTime,
|
refreshTokenExpireTime,
|
||||||
[]byte(baseCnf.GetEnv("JWT_REFRESH_TOKEN_SECRET_KEY", "FallbackRefreshTokenSecret")),
|
[]byte(baseCnf.GetEnv("JWT_REFRESH_TOKEN_SECRET_KEY", "FallbackRefreshTokenSecret")),
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJWTService(aTokenExp time.Duration, aTokenSecret []byte, rTokenExp time.Duration, rTokenSecret []byte) *JWT {
|
||||||
|
return &JWT{aTokenExp, aTokenSecret, rTokenExp, rTokenSecret}
|
||||||
}
|
}
|
||||||
|
|
||||||
type JWT struct {
|
type JWT struct {
|
||||||
@@ -61,7 +71,7 @@ func (s *JWT) CreateRefreshToken(id string) (string, error) {
|
|||||||
return token.SignedString(s.accessTokenSecret)
|
return token.SignedString(s.accessTokenSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *JWT) ValidateToken(tokenStr string) error {
|
func (s *JWT) ValidateAccessToken(tokenStr string) (*jwt.Token, jwt.MapClaims, error) {
|
||||||
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
|
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
|
||||||
// Don't forget to validate the alg is what you expect:
|
// Don't forget to validate the alg is what you expect:
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
@@ -71,9 +81,19 @@ func (s *JWT) ValidateToken(tokenStr string) error {
|
|||||||
return s.accessTokenSecret, nil
|
return s.accessTokenSecret, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if _, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
if err != nil {
|
||||||
return nil
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||||
|
if exp, ok := claims["exp"].(float64); ok {
|
||||||
|
if int64(exp) < time.Now().Unix() {
|
||||||
|
return nil, nil, ErrorTokenExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, claims, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user