diff --git a/config/config.go b/config/config.go index bc2771f..800b524 100644 --- a/config/config.go +++ b/config/config.go @@ -3,6 +3,7 @@ package config type Config struct { AppConf AppConfig DBConf DatabaseConfig + JWTConf JWTConfig } type AppConfig struct { @@ -23,6 +24,13 @@ type DatabaseConfig struct { LogLevel string } +type JWTConfig struct { + Secret string + Issuer string + AccessExpire string + RefreshExpire string +} + func NewConfig() *Config { return &Config{ AppConf: AppConfig{ @@ -42,5 +50,12 @@ func NewConfig() *Config { DBName: getEnv("DB_NAME", ""), LogLevel: getEnv("DB_LOGLEVEL", ""), }, + + JWTConf: JWTConfig{ + Secret: getEnv("JWT_SECRET", ""), + Issuer: getEnv("JWT_ISSUER", ""), + AccessExpire: getEnv("JWT_ACCESS_EXPIRE", ""), + RefreshExpire: getEnv("JWT_REFRESH_EXPIRE", ""), + }, } } diff --git a/internal/interfaces/jwtProvider.go b/internal/interfaces/jwtProvider.go new file mode 100644 index 0000000..455164c --- /dev/null +++ b/internal/interfaces/jwtProvider.go @@ -0,0 +1,7 @@ +package interfaces + +type JWTProvider interface { + CreateAccessToken(userUuid string) (string, error) + CreateRefreshToken(userUuid, tokenUuid string) (string, int64, error) + Parse(token string) (string, string, error) +} diff --git a/internal/provider/token/service.go b/internal/provider/token/service.go new file mode 100644 index 0000000..3e8f05c --- /dev/null +++ b/internal/provider/token/service.go @@ -0,0 +1,117 @@ +package token + +import ( + "fmt" + "github.com/golang-jwt/jwt/v5" + log "github.com/sirupsen/logrus" + "strconv" + "strings" + "time" +) + +type JWT struct { + SecretKey string + Issuer string + AccessExpire time.Duration + RefreshExpire time.Duration +} + +type Deps struct { + SecretKey string + Issuer string + AccessExpire string + RefreshExpire string +} + +func NewJWT(deps Deps) *JWT { + return &JWT{ + SecretKey: deps.SecretKey, + Issuer: deps.Issuer, + AccessExpire: duration(deps.AccessExpire), + RefreshExpire: duration(deps.RefreshExpire), + } +} + +func (j *JWT) CreateAccessToken(userUuid string) (string, error) { + now := time.Now() + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "exp": now.Add(j.AccessExpire).Unix(), + "iat": now.Unix(), + "iss": j.Issuer, + "nbf": now.Unix(), + "sub": userUuid, + }) + + signedToken, err := token.SignedString([]byte(j.SecretKey)) + if err != nil { + return "", err + } + + return signedToken, nil +} + +func (j *JWT) CreateRefreshToken(userUuid, tokenUuid string) (string, int64, error) { + now := time.Now() + exp := now.Add(j.RefreshExpire).Unix() + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "exp": exp, + "iat": now.Unix(), + "iss": j.Issuer, + "nbf": now.Unix(), + "sub": userUuid, + "tkn": tokenUuid, + }) + + signedToken, err := token.SignedString([]byte(j.SecretKey)) + if err != nil { + return "", 0, err + } + + return signedToken, exp, nil + +} + +func (j *JWT) Parse(token string) (string, string, error) { + if strings.HasPrefix(token, "Bearer ") { + token = strings.TrimPrefix(token, "Bearer ") + } + + parse, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return []byte(j.SecretKey), nil + }) + if err != nil { + log.Error(err) + return "", "", err + } + + if claims, ok := parse.Claims.(jwt.MapClaims); ok && parse.Valid { + userUuid := claims["sub"].(string) + + var refreshUuid string + if tkn, exists := claims["tkn"]; exists { + if tknStr, okay := tkn.(string); okay { + refreshUuid = tknStr + } else { + return "", "", fmt.Errorf("invalid type for 'tkn' claim") + } + } + + return userUuid, refreshUuid, nil + } + + return "", "", fmt.Errorf("invalid token") +} + +func duration(minutes string) time.Duration { + dur, err := strconv.Atoi(minutes) + if err != nil { + return time.Duration(10) * time.Minute + } + + return time.Duration(dur) * time.Minute +}