From ba032d33748f606a3553e26f6b5aea43ae84da63 Mon Sep 17 00:00:00 2001 From: dogukanoksuz Date: Mon, 29 Jan 2024 13:32:14 +0000 Subject: [PATCH] feature: JWT authentication --- app/middleware/auth/new.go | 48 +++++++++++++++++++++++ pkg/helpers/aes-decryptor.go | 76 ++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 pkg/helpers/aes-decryptor.go diff --git a/app/middleware/auth/new.go b/app/middleware/auth/new.go index 9e7fb00f..061d280a 100644 --- a/app/middleware/auth/new.go +++ b/app/middleware/auth/new.go @@ -1,10 +1,13 @@ package auth import ( + "net/url" "strings" "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v4" "github.com/limanmys/render-engine/internal/liman" + "github.com/limanmys/render-engine/pkg/helpers" "github.com/limanmys/render-engine/pkg/logger" ) @@ -13,8 +16,33 @@ func New() fiber.Handler { return authorization } +type Cookie struct { + Token string `cookie:"token"` +} + // authorization Middleware auths users before requests func authorization(c *fiber.Ctx) error { + cookie := new(Cookie) + c.CookieParser(cookie) + + if len(cookie.Token) > 0 { + decoded, err := url.QueryUnescape(cookie.Token) + if err != nil { + logger.FiberError(fiber.StatusUnauthorized, "invalid authorization token (cookie), "+err.Error()) + } + + if len(decoded) < 1 { + return logger.FiberError(fiber.StatusUnauthorized, "authorization token is missing") + } + + code, err := helpers.LaravelAesDecrypt("token", decoded) + if err != nil { + return logger.FiberError(fiber.StatusUnauthorized, "invalid authorization token (cookie), "+err.Error()) + } + + return jwtValidation(c, code) + } + if len(c.FormValue("token")) > 0 { user, err := liman.AuthWithToken( strings.Trim(c.FormValue("token"), ""), @@ -56,3 +84,23 @@ func authorization(c *fiber.Ctx) error { return logger.FiberError(fiber.StatusUnauthorized, "authorization token is missing") } + +func jwtValidation(c *fiber.Ctx, code string) error { + token, err := jwt.Parse(code, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, logger.FiberError(fiber.StatusUnauthorized, "invalid authorization token") + } + return []byte(helpers.Env("JWT_SECRET", "")), nil + }) + + if err != nil { + return logger.FiberError(fiber.StatusUnauthorized, "invalid authorization token") + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + c.Locals("user_id", claims["sub"]) + return c.Next() + } else { + return logger.FiberError(fiber.StatusUnauthorized, "invalid authorization token") + } +} diff --git a/pkg/helpers/aes-decryptor.go b/pkg/helpers/aes-decryptor.go new file mode 100644 index 00000000..868a8d8c --- /dev/null +++ b/pkg/helpers/aes-decryptor.go @@ -0,0 +1,76 @@ +package helpers + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/sha1" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "strings" +) + +type laravelAesKey struct { + Iv []byte `json:"iv"` + Value []byte `json:"value"` + Mac string `json:"mac"` + Tag string `json:"tag"` +} + +func LaravelAesDecrypt(name, payload string) (string, error) { + cipherText, err := base64.StdEncoding.DecodeString(payload) + if err != nil { + return "", err + } + + aesKey := laravelAesKey{} + err = json.Unmarshal(cipherText, &aesKey) + if err != nil { + return "", err + } + + dummykey := Env("APP_KEY", "") + dummykey = dummykey[7:] + + key, err := base64.StdEncoding.DecodeString(dummykey) + if err != nil { + return "", err + } + + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + if len(aesKey.Value)%aes.BlockSize != 0 { + return "", errors.New("block size cant be zero") + } + + mode := cipher.NewCBCDecrypter(block, aesKey.Iv) + mode.CryptBlocks(aesKey.Value, aesKey.Value) + plainText := string(unpadding(aesKey.Value)) + + validator := createValidator(name, key) + if strings.HasPrefix(plainText, validator) { + plainText = strings.TrimPrefix(plainText, validator) + } else { + return "", errors.New("token cannot be validated") + } + + return plainText, nil +} + +func unpadding(src []byte) []byte { + length := len(src) + unpadding := int(src[length-1]) + + return src[:(length - unpadding)] +} + +func createValidator(cookieName string, key []byte) string { + h := hmac.New(sha1.New, key) + h.Write([]byte(cookieName + "v2")) + return hex.EncodeToString(h.Sum(nil)) + "|" +}