added jwt for tariffs

This commit is contained in:
Pavel 2024-07-25 13:51:27 +03:00
parent 0ac712e69d
commit c8f2b77b93
15 changed files with 283 additions and 6 deletions

2
go.mod

@ -13,6 +13,8 @@ require (
require ( require (
github.com/andybalholm/brotli v1.0.5 // indirect github.com/andybalholm/brotli v1.0.5 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/snappy v0.0.1 // indirect github.com/golang/snappy v0.0.1 // indirect
github.com/google/uuid v1.5.0 // indirect github.com/google/uuid v1.5.0 // indirect
github.com/klauspost/compress v1.17.0 // indirect github.com/klauspost/compress v1.17.0 // indirect

4
go.sum

@ -6,6 +6,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=

@ -5,6 +5,7 @@ import (
"errors" "errors"
"go.uber.org/zap" "go.uber.org/zap"
"hub_admin_backend_service/internal/initialize" "hub_admin_backend_service/internal/initialize"
"hub_admin_backend_service/internal/models"
"hub_admin_backend_service/internal/server/http" "hub_admin_backend_service/internal/server/http"
"hub_admin_backend_service/pkg/closer" "hub_admin_backend_service/pkg/closer"
"time" "time"
@ -42,11 +43,23 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
internalSrv := http.NewServer(http.ServerConfig{ internalSrv := http.NewServer(http.ServerConfig{
Logger: logger, Logger: logger,
Controllers: []http.Controller{controllers.PrivilegeInternal, controllers.TariffInternal}, Controllers: []http.Controller{controllers.PrivilegeInternal, controllers.TariffInternal},
JWTConfig: &models.JWTConfiguration{
PrivateKey: cfg.PrivateKey,
PublicKey: cfg.PublicKey,
Issuer: cfg.Issuer,
Audience: cfg.Audience,
},
}) })
externalSrv := http.NewServer(http.ServerConfig{ externalSrv := http.NewServer(http.ServerConfig{
Logger: logger, Logger: logger,
Controllers: []http.Controller{controllers.PrivilegeExternal, controllers.TariffExternal}, Controllers: []http.Controller{controllers.PrivilegeExternal, controllers.TariffExternal},
JWTConfig: &models.JWTConfiguration{
PrivateKey: cfg.PrivateKey,
PublicKey: cfg.PublicKey,
Issuer: cfg.Issuer,
Audience: cfg.Audience,
},
}) })
go func() { go func() {

@ -3,15 +3,16 @@ package tariff_external
import ( import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap" "go.uber.org/zap"
"hub_admin_backend_service/internal/repository/tariff"
) )
type Deps struct { type Deps struct {
Repo string Repo *tariff.Tariff
Logger *zap.Logger Logger *zap.Logger
} }
type TariffExternal struct { type TariffExternal struct {
repo string repo *tariff.Tariff
logger *zap.Logger logger *zap.Logger
} }

@ -3,15 +3,19 @@ package tariff_internal
import ( import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap" "go.uber.org/zap"
"hub_admin_backend_service/internal/models"
"hub_admin_backend_service/internal/repository/tariff"
) )
// todo middleware jwt
type Deps struct { type Deps struct {
Repo string Repo *tariff.Tariff
Logger *zap.Logger Logger *zap.Logger
} }
type TariffInternal struct { type TariffInternal struct {
repo string repo *tariff.Tariff
logger *zap.Logger logger *zap.Logger
} }
@ -23,6 +27,16 @@ func NewTariffInternal(deps Deps) *TariffInternal {
} }
func (t *TariffInternal) Get(ctx *fiber.Ctx) error { func (t *TariffInternal) Get(ctx *fiber.Ctx) error {
tariffID := ctx.Params("id")
if tariffID == "" {
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "url field id don't be empty"})
}
var req models.CreateUpdateTariff
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
}
return nil return nil
} }

@ -16,6 +16,10 @@ type Config struct {
MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"` MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"`
MongoDatabase string `env:"MONGO_DB" envDefault:"admin"` MongoDatabase string `env:"MONGO_DB" envDefault:"admin"`
MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"` MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"`
PrivateKey string `env:"JWT_PRIVATE_KEY"`
PublicKey string `env:"JWT_PUBLIC_KEY"`
Issuer string `env:"JWT_ISSUER"`
Audience string `env:"JWT_AUDIENCE"`
} }
func LoadConfig() (*Config, error) { func LoadConfig() (*Config, error) {

@ -32,11 +32,11 @@ func NewControllers(deps ControllerDeps) *Controller {
}), }),
TariffInternal: tariff_internal.NewTariffInternal(tariff_internal.Deps{ TariffInternal: tariff_internal.NewTariffInternal(tariff_internal.Deps{
Logger: deps.Logger, Logger: deps.Logger,
Repo: "", Repo: deps.Repos.TariffRepo,
}), }),
TariffExternal: tariff_external.NewTariffExternal(tariff_external.Deps{ TariffExternal: tariff_external.NewTariffExternal(tariff_external.Deps{
Logger: deps.Logger, Logger: deps.Logger,
Repo: "", Repo: deps.Repos.TariffRepo,
}), }),
} }
} }

@ -4,6 +4,7 @@ import (
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
"go.uber.org/zap" "go.uber.org/zap"
"hub_admin_backend_service/internal/repository/privilege" "hub_admin_backend_service/internal/repository/privilege"
"hub_admin_backend_service/internal/repository/tariff"
) )
type RepositoryDeps struct { type RepositoryDeps struct {
@ -13,6 +14,7 @@ type RepositoryDeps struct {
type Repository struct { type Repository struct {
PrivilegeRepo *privilege.Privilege PrivilegeRepo *privilege.Privilege
TariffRepo *tariff.Tariff
} }
func NewRepository(deps RepositoryDeps) *Repository { func NewRepository(deps RepositoryDeps) *Repository {
@ -21,5 +23,9 @@ func NewRepository(deps RepositoryDeps) *Repository {
Mdb: deps.Mdb.Collection("privileges"), Mdb: deps.Mdb.Collection("privileges"),
Logger: deps.Logger, Logger: deps.Logger,
}), }),
TariffRepo: tariff.NewTariffRepo(tariff.Deps{
Mdb: deps.Mdb.Collection("tariffs"),
Logger: deps.Logger,
}),
} }
} }

18
internal/models/jwt.go Normal file

@ -0,0 +1,18 @@
package models
import (
"github.com/golang-jwt/jwt/v5"
"time"
)
type JWTConfiguration struct {
PrivateKey string
PublicKey string
Issuer string
Audience string
Algorithm jwt.SigningMethodRSA
ExpiresIn time.Duration
}
const AuthJWTDecodedUserIDKey = "userID"
const AuthJWTDecodedAccessTokenKey = "access-token"

@ -13,3 +13,18 @@ type CreateUpdateReq struct {
type ManyCreateUpdate struct { type ManyCreateUpdate struct {
Privileges []CreateUpdateReq `json:"privileges"` Privileges []CreateUpdateReq `json:"privileges"`
} }
type TariffPagination struct {
TotalPages int `json:"totalPages"`
Tariffs []Tariff `json:"tariffs"`
}
type CreateUpdateTariff struct {
Name string `json:"name"`
UserID string `json:"userId"`
Description string `json:"description"`
Price int `json:"price"`
Order int `json:"order"`
IsCustom bool `json:"isCustom"`
Privileges []Privilege `json:"privileges"`
}

19
internal/models/tariff.go Normal file

@ -0,0 +1,19 @@
package models
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"time"
)
type Tariff struct {
ID primitive.ObjectID `json:"_id" bson:"_id"`
Name string `json:"name" bson:"name"`
UserID string `json:"userID" bson:"userID"`
Price int `json:"price" bson:"price"`
IsCustom bool `json:"isCustom" bson:"isCustom"`
Privileges []Privilege `json:"privileges" bson:"privileges"`
IsDeleted bool `json:"isDeleted" bson:"isDeleted"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
DeletedAt time.Time `json:"deletedAt" bson:"deletedAt"`
}

@ -0,0 +1,23 @@
package tariff
import (
"go.mongodb.org/mongo-driver/mongo"
"go.uber.org/zap"
)
type Deps struct {
Mdb *mongo.Collection
Logger *zap.Logger
}
type Tariff struct {
mdb *mongo.Collection
logger *zap.Logger
}
func NewTariffRepo(deps Deps) *Tariff {
return &Tariff{
mdb: deps.Mdb,
logger: deps.Logger,
}
}

@ -5,11 +5,14 @@ import (
"fmt" "fmt"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap" "go.uber.org/zap"
"hub_admin_backend_service/internal/models"
"hub_admin_backend_service/internal/utils"
) )
type ServerConfig struct { type ServerConfig struct {
Logger *zap.Logger Logger *zap.Logger
Controllers []Controller Controllers []Controller
JWTConfig *models.JWTConfiguration
} }
type Server struct { type Server struct {
@ -20,6 +23,13 @@ type Server struct {
func NewServer(config ServerConfig) *Server { func NewServer(config ServerConfig) *Server {
app := fiber.New() app := fiber.New()
jwtUtil := utils.NewJWT(config.JWTConfig)
app.Use("/tariff", utils.NewAuthenticator(jwtUtil))
app.Use("/privilege", func(c *fiber.Ctx) error {
return c.Next()
})
s := &Server{ s := &Server{
Logger: config.Logger, Logger: config.Logger,
Controllers: config.Controllers, Controllers: config.Controllers,

@ -0,0 +1,59 @@
package utils
import (
"errors"
"github.com/gofiber/fiber/v2"
"hub_admin_backend_service/internal/models"
"strings"
)
const (
prefix = "Bearer "
)
func NewAuthenticator(jwtUtil *JWT) fiber.Handler {
return func(c *fiber.Ctx) error {
if jwtUtil == nil {
return fiber.NewError(fiber.StatusInternalServerError, "Invalid arguments")
}
err := authenticate(jwtUtil, c)
if err != nil {
return fiber.NewError(fiber.StatusUnauthorized, err.Error())
}
return c.Next()
}
}
func authenticate(jwtUtil *JWT, c *fiber.Ctx) error {
jws, err := parseJWSFromRequest(c)
if err != nil {
return err
}
userID, validateErr := jwtUtil.Validate(jws)
if validateErr != nil {
return validateErr
}
c.Locals(models.AuthJWTDecodedUserIDKey, userID)
c.Locals(models.AuthJWTDecodedAccessTokenKey, jws)
return nil
}
func parseJWSFromRequest(c *fiber.Ctx) (string, error) {
header := c.Get("Authorization")
if header != "" && strings.HasPrefix(header, prefix) {
return strings.TrimPrefix(header, prefix), nil
}
token := c.Query("Authorization")
if token == "" {
return "", errors.New("failed to parse jws from request: no valid token found")
}
return token, nil
}

89
internal/utils/jwt.go Normal file

@ -0,0 +1,89 @@
package utils
import (
"errors"
"fmt"
"github.com/golang-jwt/jwt/v5"
"hub_admin_backend_service/internal/models"
"time"
)
type JWT struct {
privateKey []byte
publicKey []byte
algorithm *jwt.SigningMethodRSA
expiresIn time.Duration
issuer string
audience string
}
func NewJWT(configuration *models.JWTConfiguration) *JWT {
return &JWT{
privateKey: []byte(configuration.PrivateKey),
publicKey: []byte(configuration.PublicKey),
algorithm: jwt.SigningMethodRS256,
expiresIn: 15 * time.Minute,
issuer: configuration.Issuer,
audience: configuration.Audience,
}
}
func (receiver *JWT) Create(id string) (string, error) {
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(receiver.privateKey)
if err != nil {
return "", fmt.Errorf("failed to parse private key on <Create> of <JWT>: %w", err)
}
now := time.Now().UTC()
claims := jwt.MapClaims{
"id": id, // Our userID
"exp": now.Add(receiver.expiresIn).Unix(), // The expiration time after which the token must be disregarded.
"aud": receiver.audience, // Audience
"iss": receiver.issuer, // Issuer
}
token, err := jwt.NewWithClaims(receiver.algorithm, claims).SignedString(privateKey)
if err != nil {
return "", fmt.Errorf("failed to sing on <Create> of <JWT>: %w", err)
}
return token, nil
}
func (receiver *JWT) Validate(tokenString string) (string, error) {
key, err := jwt.ParseRSAPublicKeyFromPEM(receiver.publicKey)
if err != nil {
return "", fmt.Errorf("failed to parse rsa public key on <Validate> of <JWT>: %w", err)
}
parseCallback := func(token *jwt.Token) (any, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %s", token.Header["alg"])
}
return key, nil
}
token, err := jwt.Parse(
tokenString,
parseCallback,
jwt.WithAudience(receiver.audience),
jwt.WithIssuer(receiver.issuer),
)
if err != nil {
return "", fmt.Errorf("failed to parse jwt token on <Validate> of <JWT>: %w", err)
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
return "", errors.New("token is invalid on <Validate> of <JWT>")
}
data, ok := claims["id"].(string)
if !ok {
return "", errors.New("data is empty or not a string on <Validate> of <JWT>")
}
return data, nil
}