Skip to content

Commit

Permalink
Merge pull request #35 from DoWithLogic/feat/revoked-token-after-logout
Browse files Browse the repository at this point in the history
✨ feat: revoked token after logout with redis
  • Loading branch information
martinyonatann authored Nov 16, 2024
2 parents f21a3c5 + d89c6af commit 05f1596
Show file tree
Hide file tree
Showing 15 changed files with 245 additions and 78 deletions.
12 changes: 6 additions & 6 deletions config/config-local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ Database:
Authentication:
Key: DoWithLogic!@#

JWT:
Key: DoWithLogic!@#
Expired: 60
Label: XXXXX

Observability:
Enable: false
Mode: "otlp/http"
Mode: "otlp/http"

Redis:
Addr: "localhost:6379"
Password: ""
DB: 0
7 changes: 7 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type (
Authentication AuthenticationConfig
Observability ObservabilityConfig
JWT JWTConfig
Redis RedisConfig
}

// AppConfig holds the configuration related to the application settings.
Expand Down Expand Up @@ -44,6 +45,12 @@ type (
Password string
}

RedisConfig struct {
Addr string // The address of the database.
Password string // The password for connecting to the database.
DB int // The name of the database.
}

AuthenticationConfig struct {
Key string
}
Expand Down
7 changes: 6 additions & 1 deletion config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ Authentication:

Observability:
Enable: false
Mode: "otlp/http"
Mode: "otlp/http"

Redis:
Addr: "localhost:6379"
Password: ""
DB: 0
12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,15 @@ services:
sh -c "
echo 'CREATE DATABASE IF NOT EXISTS users;' > /docker-entrypoint-initdb.d/init.sql;
/usr/local/bin/docker-entrypoint.sh --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci"

redis:
image: redis:latest
container_name: redis-db
restart: unless-stopped
ports:
- 6379:6379
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 0.5s
timeout: 10s
retries: 5
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.21.5
require (
github.com/go-faker/faker/v4 v4.4.1
github.com/go-playground/validator/v10 v10.20.0
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.8.1
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/invopop/validation v0.3.0
Expand Down Expand Up @@ -41,6 +42,7 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-logr/logr v1.4.1 // indirect
Expand All @@ -55,6 +57,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/onsi/gomega v1.25.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
Expand All @@ -38,6 +40,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
Expand Down Expand Up @@ -81,6 +85,12 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
Expand Down Expand Up @@ -204,6 +214,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
28 changes: 23 additions & 5 deletions internal/app/service.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,48 @@
package app

import (
"context"
"net/http"

"github.com/DoWithLogic/golang-clean-architecture/internal/middleware"
userV1 "github.com/DoWithLogic/golang-clean-architecture/internal/users/delivery/http/v1"
userRepository "github.com/DoWithLogic/golang-clean-architecture/internal/users/repository"
userUseCase "github.com/DoWithLogic/golang-clean-architecture/internal/users/usecase"
"github.com/DoWithLogic/golang-clean-architecture/pkg/app_crypto"
"github.com/DoWithLogic/golang-clean-architecture/pkg/app_jwt"
"github.com/DoWithLogic/golang-clean-architecture/pkg/app_redis"
"github.com/go-redis/redis/v8"
"github.com/labstack/echo/v4"
)

func (app *App) startService() error {
domain := app.echo.Group("/api/v1/users")
domain := app.echo.Group("/api/v1")
domain.GET("/ping", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello Word 👋")
})

client := redis.NewClient(&redis.Options{
Addr: app.cfg.Redis.Addr,
Password: app.cfg.Redis.Password,
DB: app.cfg.Redis.DB,
})

if err := client.Ping(context.Background()).Err(); err != nil {
return err
}

var (
crypto = app_crypto.NewCrypto(app.cfg.Authentication.Key)
appJwt = app_jwt.NewJWT(app.cfg.JWT)
redis = app_redis.NewRedis(client)
crypto = app_crypto.NewCrypto(app.cfg.Authentication.Key)
jwt = app_jwt.NewJWT(app.cfg.JWT, redis)
middleware = middleware.NewMiddleware(jwt)

userRepo = userRepository.NewRepository(app.db)
userUC = userUseCase.NewUseCase(userRepo, appJwt, crypto)
userUC = userUseCase.NewUseCase(userRepo, jwt, crypto)
userCTRL = userV1.NewHandlers(userUC)
)

return userCTRL.UserRoutes(domain, app.cfg)
userCTRL.UserRoutes(domain, middleware)

return nil
}
58 changes: 16 additions & 42 deletions internal/middleware/guard.go
Original file line number Diff line number Diff line change
@@ -1,67 +1,41 @@
package middleware

import (
"fmt"

"github.com/DoWithLogic/golang-clean-architecture/config"
"github.com/DoWithLogic/golang-clean-architecture/pkg/app_jwt"
"github.com/DoWithLogic/golang-clean-architecture/pkg/apperror"
"github.com/DoWithLogic/golang-clean-architecture/pkg/constant"
"github.com/DoWithLogic/golang-clean-architecture/pkg/response"
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
)

type PayloadToken struct {
Data *Data `json:"data"`
jwt.StandardClaims
var (
errMissingJwtToken = errors.New("Missing JWT token")
errInvalidJwtToken = errors.New("Invalid JWT token")
)

type Middleware struct {
jwt *app_jwt.JWT
}

type Data struct {
UserID int64 `json:"user_id"`
Email string `json:"email"`
func NewMiddleware(jwt *app_jwt.JWT) *Middleware {
return &Middleware{jwt: jwt}
}

// Middleware function to validate JWT token
func JWTMiddleware(cfg config.Config) echo.MiddlewareFunc {
func (m *Middleware) JWTMiddleware() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
tokenString := c.Request().Header.Get(constant.AuthorizationHeaderKey)
if tokenString == "" {
return response.ErrorBuilder(apperror.Unauthorized(errors.New("Missing JWT token"))).Send(c)
token := c.Request().Header.Get(constant.AuthorizationHeaderKey)
if token == "" {
return response.ErrorBuilder(apperror.Unauthorized(errMissingJwtToken)).Send(c)
}

// Remove "Bearer " prefix from token string
tokenString = tokenString[len("Bearer "):]

token, err := jwt.ParseWithClaims(tokenString, &PayloadToken{}, 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(cfg.JWT.Key), nil
})

if err != nil {
return response.ErrorBuilder(apperror.Unauthorized(errors.New("Invalid JWT token"))).Send(c)
}
if !token.Valid {
return response.ErrorBuilder(apperror.Unauthorized(errors.New("JWT token is not valid"))).Send(c)
if err := m.jwt.ValidateToken(c, token); err != nil {
return err
}

// Store the token claims in the request context for later use
claims := token.Claims.(*PayloadToken)
c.Set(constant.AuthCredentialKey, claims)

return next(c)
}
}
}

func NewTokenInformation(ctx echo.Context) (*PayloadToken, error) {
tokenInformation, ok := ctx.Get(constant.AuthCredentialKey).(*PayloadToken)
if !ok {
return tokenInformation, apperror.Unauthorized(apperror.ErrFailedGetTokenInformation)
}

return tokenInformation, nil
}
8 changes: 4 additions & 4 deletions internal/users/delivery/http/v1/handlers.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package v1

import (
"github.com/DoWithLogic/golang-clean-architecture/internal/middleware"
"github.com/DoWithLogic/golang-clean-architecture/internal/users"
"github.com/DoWithLogic/golang-clean-architecture/internal/users/dtos"
"github.com/DoWithLogic/golang-clean-architecture/pkg/app_jwt"
"github.com/DoWithLogic/golang-clean-architecture/pkg/apperror"
"github.com/DoWithLogic/golang-clean-architecture/pkg/observability/instrumentation"
"github.com/DoWithLogic/golang-clean-architecture/pkg/response"
Expand Down Expand Up @@ -64,7 +64,7 @@ func (h *handlers) UserDetail(c echo.Context) error {
ctx, span := instrumentation.NewTraceSpan(c.Request().Context(), "UserDetailHandler")
defer span.End()

userData, err := middleware.NewTokenInformation(c)
userData, err := app_jwt.NewTokenInformation(c)
if err != nil {
return response.ErrorBuilder(err).Send(c)
}
Expand All @@ -90,7 +90,7 @@ func (h *handlers) UpdateUser(c echo.Context) error {
return response.ErrorBuilder(apperror.BadRequest(err)).Send(c)
}

userData, err := middleware.NewTokenInformation(c)
userData, err := app_jwt.NewTokenInformation(c)
if err != nil {
return response.ErrorBuilder(err).Send(c)
}
Expand Down Expand Up @@ -120,7 +120,7 @@ func (h *handlers) UpdateUserStatus(c echo.Context) error {
return response.ErrorBuilder(apperror.BadRequest(err)).Send(c)
}

userData, err := middleware.NewTokenInformation(c)
userData, err := app_jwt.NewTokenInformation(c)
if err != nil {
return response.ErrorBuilder(err).Send(c)
}
Expand Down
19 changes: 11 additions & 8 deletions internal/users/delivery/http/v1/routes.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package v1

import (
"github.com/DoWithLogic/golang-clean-architecture/config"
"github.com/DoWithLogic/golang-clean-architecture/internal/middleware"
"github.com/labstack/echo/v4"
)

func (h *handlers) UserRoutes(domain *echo.Group, cfg config.Config) error {
domain.POST("", h.CreateUser)
domain.POST("/login", h.Login)
domain.GET("/detail", h.UserDetail, middleware.JWTMiddleware(cfg))
domain.PATCH("/update", h.UpdateUser, middleware.JWTMiddleware(cfg))
domain.PUT("/update/status", h.UpdateUserStatus, middleware.JWTMiddleware(cfg))
func (h *handlers) UserRoutes(domain *echo.Group, mw *middleware.Middleware) {
// privates
{
private := domain.Group("/user", mw.JWTMiddleware())
private.GET("/detail", h.UserDetail)
private.PATCH("/update", h.UpdateUser)
private.PUT("/update/status", h.UpdateUserStatus)
}

return nil
// publics
domain.POST("/user", h.CreateUser)
domain.POST("/user/login", h.Login)
}
5 changes: 2 additions & 3 deletions internal/users/usecase/usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"strings"
"time"

"github.com/DoWithLogic/golang-clean-architecture/internal/middleware"
"github.com/DoWithLogic/golang-clean-architecture/internal/users"
"github.com/DoWithLogic/golang-clean-architecture/internal/users/dtos"
"github.com/DoWithLogic/golang-clean-architecture/internal/users/entities"
Expand Down Expand Up @@ -42,8 +41,8 @@ func (uc *usecase) Login(ctx context.Context, request dtos.UserLoginRequest) (re
return response, apperror.Unauthorized(apperror.ErrInvalidPassword)
}

claims := middleware.PayloadToken{
Data: &middleware.Data{
claims := app_jwt.PayloadToken{
Data: &app_jwt.Data{
UserID: dataLogin.UserID,
Email: dataLogin.Email,
},
Expand Down
Loading

0 comments on commit 05f1596

Please sign in to comment.