From 918f4cb9f3ad37d31b4801e9c40c370e14551f52 Mon Sep 17 00:00:00 2001 From: imrishuroy Date: Fri, 1 Dec 2023 17:57:19 +0530 Subject: [PATCH] added user sessions --- .github/workflows/deploy.yml | 72 ++++++++++---------- api/middleware_test.go | 3 +- api/server.go | 1 + api/token.go | 82 +++++++++++++++++++++++ api/user.go | 43 ++++++++++-- app.env | 3 +- db/migration/000003_add_sessions.down.sql | 1 + db/migration/000003_add_sessions.up.sql | 12 ++++ db/mock/store.go | 31 +++++++++ db/query/session.sql | 16 +++++ db/sqlc/models.go | 13 ++++ db/sqlc/querier.go | 4 ++ db/sqlc/session.sql.go | 82 +++++++++++++++++++++++ token/jwt_maker.go | 7 +- token/jwt_maker_test.go | 10 +-- token/maker.go | 2 +- token/paseto_maker.go | 7 +- token/paseto_maker_test.go | 10 +-- util/confgi.go | 11 +-- 19 files changed, 347 insertions(+), 63 deletions(-) create mode 100644 api/token.go create mode 100644 db/migration/000003_add_sessions.down.sql create mode 100644 db/migration/000003_add_sessions.up.sql create mode 100644 db/query/session.sql create mode 100644 db/sqlc/session.sql.go diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 078bfc9..fee6e99 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,46 +1,46 @@ -# name: Deploy to production +name: Deploy to production -# on: -# push: -# branches: [ main ] -# # pull_request: -# # branches: [ main ] +on: + push: + branches: [ prod ] + # pull_request: + # branches: [ main ] -# jobs: -# deploy: -# name: Deploy -# runs-on: ubuntu-latest +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest -# steps: -# - name: Checkout code -# uses: actions/checkout@v2 + steps: + - name: Checkout code + uses: actions/checkout@v2 -# - name: Configure AWS credentials -# uses: aws-actions/configure-aws-credentials@v1 -# with: -# aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} -# aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} -# aws-region: ap-south-1 + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-south-1 -# - name: Login to Amazon ECR -# id: login-ecr -# uses: aws-actions/amazon-ecr-login@v1 + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 -# - name: Load secrets and save to app.env -# run: aws secretsmanager get-secret-value --secret-id simple_bank --query SecretString --output text | jq -r 'to_entries|map("\(.key)=\(.value)")|.[]' > app.env + - name: Load secrets and save to app.env + run: aws secretsmanager get-secret-value --secret-id simple_bank --query SecretString --output text | jq -r 'to_entries|map("\(.key)=\(.value)")|.[]' > app.env -# - name: Build, tag, and push the image to Amazon ECR -# id: build-image -# env: -# ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} -# ECR_REPOSITORY: simplebank -# IMAGE_TAG: latest -# run: | -# # Build a docker container and push it to ECR -# docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . -# echo "Pushing image to ECR..." -# docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -# echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" + - name: Build, tag, and push the image to Amazon ECR + id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: simplebank + IMAGE_TAG: latest + run: | + # Build a docker container and push it to ECR + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + echo "Pushing image to ECR..." + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" diff --git a/api/middleware_test.go b/api/middleware_test.go index 7ef2b38..ecc8380 100644 --- a/api/middleware_test.go +++ b/api/middleware_test.go @@ -20,8 +20,9 @@ func addAuthorization( username string, duration time.Duration, ) { - token, err := tokenMaker.CreateToken(username, duration) + token, payload, err := tokenMaker.CreateToken(username, duration) require.NoError(t, err) + require.NotEmpty(t, payload) authorizationHeader := fmt.Sprintf("%s %s", authorizationType, token) request.Header.Set(authorizationHeaderKey, authorizationHeader) diff --git a/api/server.go b/api/server.go index c44dff7..7e29f57 100644 --- a/api/server.go +++ b/api/server.go @@ -43,6 +43,7 @@ func (server *Server) setupRouter() { router.POST("/users", server.createUser) router.POST("/users/login", server.loginUser) + router.POST("/tokens/renew_access", server.renewAccessToken) authRoutes := router.Group("/").Use(authMiddleware(server.tokenMaker)) diff --git a/api/token.go b/api/token.go new file mode 100644 index 0000000..04a7d98 --- /dev/null +++ b/api/token.go @@ -0,0 +1,82 @@ +package api + +import ( + "database/sql" + "fmt" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +type renewAccessTokenRequest struct { + RefreshToken string `json:"refresh_token" binding:"required"` +} + +type renewAccessTokenResponse struct { + AccessToken string `json:"access_token"` + AccessTokenExpiresAt time.Time `json:"access_token_expires_at"` +} + +func (server *Server) renewAccessToken(ctx *gin.Context) { + var req renewAccessTokenRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + ctx.JSON(http.StatusBadRequest, errorResponse(err)) + return + } + + refreshPayload, err := server.tokenMaker.VerifyToken(req.RefreshToken) + if err != nil { + ctx.JSON(http.StatusUnauthorized, errorResponse(err)) + return + } + + session, err := server.store.GetSession(ctx, refreshPayload.ID) + if err != nil { + if err == sql.ErrNoRows { + ctx.JSON(http.StatusNotFound, errorResponse(err)) + return + } + ctx.JSON(http.StatusInternalServerError, errorResponse(err)) + return + } + + if session.IsBlocked { + err := fmt.Errorf("blocked session") + ctx.JSON(http.StatusUnauthorized, errorResponse(err)) + return + } + + if session.Username != refreshPayload.Username { + err := fmt.Errorf("incorrect session user") + ctx.JSON(http.StatusUnauthorized, errorResponse(err)) + return + } + + if session.RefreshToken != req.RefreshToken { + err := fmt.Errorf("mismatched session token") + ctx.JSON(http.StatusUnauthorized, errorResponse(err)) + return + } + + if time.Now().After(session.ExpiresAt) { + err := fmt.Errorf("expired session") + ctx.JSON(http.StatusUnauthorized, errorResponse(err)) + return + } + + accessToken, accessPayload, err := server.tokenMaker.CreateToken( + refreshPayload.Username, + server.config.AccessTokenDuration, + ) + if err != nil { + ctx.JSON(http.StatusInternalServerError, errorResponse(err)) + return + } + + rsp := renewAccessTokenResponse{ + AccessToken: accessToken, + AccessTokenExpiresAt: accessPayload.ExpiredAt, + } + ctx.JSON(http.StatusOK, rsp) +} diff --git a/api/user.go b/api/user.go index 18bde8f..ba85ce3 100644 --- a/api/user.go +++ b/api/user.go @@ -6,6 +6,7 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/google/uuid" db "github.com/imrishuroy/simplebank/db/sqlc" "github.com/imrishuroy/simplebank/util" "github.com/lib/pq" @@ -80,8 +81,12 @@ type loginUserRequest struct { } type loginUserResponse struct { - AccessToken string `json:"access_token"` - User userResponse `json:"user"` + SessionID uuid.UUID `json:"session_id"` + AccessToken string `json:"access_token"` + AccessTokenExpiresAt time.Time `json:"access_token_expires_at"` + RefreshToken string `json:"refresh_token"` + RefreshTokenExpiresAt time.Time `json:"refresh_token_expires_at"` + User userResponse `json:"user"` } func (server *Server) loginUser(ctx *gin.Context) { @@ -105,7 +110,7 @@ func (server *Server) loginUser(ctx *gin.Context) { return } - accessToken, err := server.tokenMaker.CreateToken( + accessToken, accessPayload, err := server.tokenMaker.CreateToken( user.Username, server.config.AccessTokenDuration, ) @@ -115,9 +120,37 @@ func (server *Server) loginUser(ctx *gin.Context) { return } + refreshToken, refreshPayload, err := server.tokenMaker.CreateToken( + req.Username, server.config.RefreshTokenDuration, + ) + + if err != nil { + ctx.JSON(http.StatusInternalServerError, errorResponse(err)) + return + } + + session, err := server.store.CreateSession(ctx, db.CreateSessionParams{ + ID: refreshPayload.ID, + Username: user.Username, + RefreshToken: refreshToken, + UserAgent: ctx.Request.UserAgent(), + ClientIp: ctx.ClientIP(), + IsBlocked: false, + ExpiresAt: refreshPayload.ExpiredAt, + }) + + if err != nil { + ctx.JSON(http.StatusInternalServerError, errorResponse(err)) + return + } + rsp := loginUserResponse{ - AccessToken: accessToken, - User: newUserResponse(user), + SessionID: session.ID, + AccessToken: accessToken, + AccessTokenExpiresAt: accessPayload.ExpiredAt, + RefreshToken: refreshToken, + RefreshTokenExpiresAt: refreshPayload.ExpiredAt, + User: newUserResponse(user), } ctx.JSON(http.StatusOK, rsp) diff --git a/app.env b/app.env index 13db1ae..cb2249c 100644 --- a/app.env +++ b/app.env @@ -2,4 +2,5 @@ DB_DRIVER=postgres DB_SOURCE=postgresql://root:Prince2024@localhost:5432/simple_bank?sslmode=disable SERVER_ADDRESS=localhost:8080 TOKEN_SYMMETRIC_KEY=12345678901234567890123456789012 -ACCESS_TOKEN_DURATION=15m \ No newline at end of file +ACCESS_TOKEN_DURATION=15m +REFRESH_TOKEN_DURATION=24h \ No newline at end of file diff --git a/db/migration/000003_add_sessions.down.sql b/db/migration/000003_add_sessions.down.sql new file mode 100644 index 0000000..9a8955b --- /dev/null +++ b/db/migration/000003_add_sessions.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS "sessions"; \ No newline at end of file diff --git a/db/migration/000003_add_sessions.up.sql b/db/migration/000003_add_sessions.up.sql new file mode 100644 index 0000000..86035af --- /dev/null +++ b/db/migration/000003_add_sessions.up.sql @@ -0,0 +1,12 @@ +CREATE TABLE "sessions" ( + "id" uuid PRIMARY KEY, + "username" varchar NOT NULL, + "refresh_token" varchar NOT NULL, + "user_agent" varchar NOT NULL, + "client_ip" varchar NOT NULL, + "is_blocked" boolean NOT NULL DEFAULT false, + "expires_at" timestamptz NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT (now()) +); + +ALTER TABLE "sessions" ADD FOREIGN KEY ("username") REFERENCES "users" ("username"); \ No newline at end of file diff --git a/db/mock/store.go b/db/mock/store.go index 43d9180..1b285ee 100644 --- a/db/mock/store.go +++ b/db/mock/store.go @@ -7,6 +7,7 @@ package mockdb import ( context "context" gomock "github.com/golang/mock/gomock" + uuid "github.com/google/uuid" db "github.com/imrishuroy/simplebank/db/sqlc" reflect "reflect" ) @@ -79,6 +80,21 @@ func (mr *MockStoreMockRecorder) CreateEntry(arg0, arg1 interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEntry", reflect.TypeOf((*MockStore)(nil).CreateEntry), arg0, arg1) } +// CreateSession mocks base method +func (m *MockStore) CreateSession(arg0 context.Context, arg1 db.CreateSessionParams) (db.Session, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateSession", arg0, arg1) + ret0, _ := ret[0].(db.Session) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateSession indicates an expected call of CreateSession +func (mr *MockStoreMockRecorder) CreateSession(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSession", reflect.TypeOf((*MockStore)(nil).CreateSession), arg0, arg1) +} + // CreateTransfer mocks base method func (m *MockStore) CreateTransfer(arg0 context.Context, arg1 db.CreateTransferParams) (db.Transfer, error) { m.ctrl.T.Helper() @@ -168,6 +184,21 @@ func (mr *MockStoreMockRecorder) GetEntry(arg0, arg1 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntry", reflect.TypeOf((*MockStore)(nil).GetEntry), arg0, arg1) } +// GetSession mocks base method +func (m *MockStore) GetSession(arg0 context.Context, arg1 uuid.UUID) (db.Session, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSession", arg0, arg1) + ret0, _ := ret[0].(db.Session) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSession indicates an expected call of GetSession +func (mr *MockStoreMockRecorder) GetSession(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSession", reflect.TypeOf((*MockStore)(nil).GetSession), arg0, arg1) +} + // GetTransfer mocks base method func (m *MockStore) GetTransfer(arg0 context.Context, arg1 int64) (db.Transfer, error) { m.ctrl.T.Helper() diff --git a/db/query/session.sql b/db/query/session.sql new file mode 100644 index 0000000..7f2028b --- /dev/null +++ b/db/query/session.sql @@ -0,0 +1,16 @@ +-- name: CreateSession :one +INSERT INTO sessions ( + id, + username, + refresh_token, + user_agent, + client_ip, + is_blocked, + expires_at +) VALUES ( + $1, $2, $3, $4, $5, $6, $7 +) RETURNING *; + +-- name: GetSession :one +SELECT * FROM sessions +WHERE id = $1 LIMIT 1; \ No newline at end of file diff --git a/db/sqlc/models.go b/db/sqlc/models.go index ed5fc19..50aefdb 100644 --- a/db/sqlc/models.go +++ b/db/sqlc/models.go @@ -6,6 +6,8 @@ package db import ( "time" + + "github.com/google/uuid" ) type Account struct { @@ -24,6 +26,17 @@ type Entry struct { CreatedAt time.Time `json:"created_at"` } +type Session struct { + ID uuid.UUID `json:"id"` + Username string `json:"username"` + RefreshToken string `json:"refresh_token"` + UserAgent string `json:"user_agent"` + ClientIp string `json:"client_ip"` + IsBlocked bool `json:"is_blocked"` + ExpiresAt time.Time `json:"expires_at"` + CreatedAt time.Time `json:"created_at"` +} + type Transfer struct { ID int64 `json:"id"` FromAccountID int64 `json:"from_account_id"` diff --git a/db/sqlc/querier.go b/db/sqlc/querier.go index ef6148c..c32ca01 100644 --- a/db/sqlc/querier.go +++ b/db/sqlc/querier.go @@ -6,18 +6,22 @@ package db import ( "context" + + "github.com/google/uuid" ) type Querier interface { AddAccountBalance(ctx context.Context, arg AddAccountBalanceParams) (Account, error) CreateAccount(ctx context.Context, arg CreateAccountParams) (Account, error) CreateEntry(ctx context.Context, arg CreateEntryParams) (Entry, error) + CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) CreateTransfer(ctx context.Context, arg CreateTransferParams) (Transfer, error) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) DeleteAccount(ctx context.Context, id int64) error GetAccount(ctx context.Context, id int64) (Account, error) GetAccountForUpdate(ctx context.Context, id int64) (Account, error) GetEntry(ctx context.Context, id int64) (Entry, error) + GetSession(ctx context.Context, id uuid.UUID) (Session, error) GetTransfer(ctx context.Context, id int64) (Transfer, error) GetUser(ctx context.Context, username string) (User, error) ListAccounts(ctx context.Context, arg ListAccountsParams) ([]Account, error) diff --git a/db/sqlc/session.sql.go b/db/sqlc/session.sql.go new file mode 100644 index 0000000..ec8146b --- /dev/null +++ b/db/sqlc/session.sql.go @@ -0,0 +1,82 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.0 +// source: session.sql + +package db + +import ( + "context" + "time" + + "github.com/google/uuid" +) + +const createSession = `-- name: CreateSession :one +INSERT INTO sessions ( + id, + username, + refresh_token, + user_agent, + client_ip, + is_blocked, + expires_at +) VALUES ( + $1, $2, $3, $4, $5, $6, $7 +) RETURNING id, username, refresh_token, user_agent, client_ip, is_blocked, expires_at, created_at +` + +type CreateSessionParams struct { + ID uuid.UUID `json:"id"` + Username string `json:"username"` + RefreshToken string `json:"refresh_token"` + UserAgent string `json:"user_agent"` + ClientIp string `json:"client_ip"` + IsBlocked bool `json:"is_blocked"` + ExpiresAt time.Time `json:"expires_at"` +} + +func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) { + row := q.db.QueryRowContext(ctx, createSession, + arg.ID, + arg.Username, + arg.RefreshToken, + arg.UserAgent, + arg.ClientIp, + arg.IsBlocked, + arg.ExpiresAt, + ) + var i Session + err := row.Scan( + &i.ID, + &i.Username, + &i.RefreshToken, + &i.UserAgent, + &i.ClientIp, + &i.IsBlocked, + &i.ExpiresAt, + &i.CreatedAt, + ) + return i, err +} + +const getSession = `-- name: GetSession :one +SELECT id, username, refresh_token, user_agent, client_ip, is_blocked, expires_at, created_at FROM sessions +WHERE id = $1 LIMIT 1 +` + +func (q *Queries) GetSession(ctx context.Context, id uuid.UUID) (Session, error) { + row := q.db.QueryRowContext(ctx, getSession, id) + var i Session + err := row.Scan( + &i.ID, + &i.Username, + &i.RefreshToken, + &i.UserAgent, + &i.ClientIp, + &i.IsBlocked, + &i.ExpiresAt, + &i.CreatedAt, + ) + return i, err +} diff --git a/token/jwt_maker.go b/token/jwt_maker.go index a5faee9..9e86cc0 100644 --- a/token/jwt_maker.go +++ b/token/jwt_maker.go @@ -27,14 +27,15 @@ func NewJWTMaker(secretKey string) (Maker, error) { } // CreateToken creates a new token for a specific username and duration -func (maker *JWTMaker) CreateToken(username string, duration time.Duration) (string, error) { +func (maker *JWTMaker) CreateToken(username string, duration time.Duration) (string, *Payload, error) { payload, err := NewPayload(username, duration) if err != nil { - return "", err + return "", payload, err } jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, payload) - return jwtToken.SignedString([]byte(maker.secretKey)) + token, err := jwtToken.SignedString([]byte(maker.secretKey)) + return token, payload, err } // VerifyToken checks if the token is valid or not diff --git a/token/jwt_maker_test.go b/token/jwt_maker_test.go index 9404640..d8970b3 100644 --- a/token/jwt_maker_test.go +++ b/token/jwt_maker_test.go @@ -19,13 +19,14 @@ func TestJWTMaker(t *testing.T) { issuedAt := time.Now() expiredAt := issuedAt.Add(duration) - token, err := maker.CreateToken(username, duration) + token, payload, err := maker.CreateToken(username, duration) require.NoError(t, err) require.NotEmpty(t, token) - payload, err := maker.VerifyToken(token) + payload, err = maker.VerifyToken(token) require.NoError(t, err) require.NotEmpty(t, token) + require.NotEmpty(t, payload) require.NotZero(t, payload.ID) require.Equal(t, username, payload.Username) @@ -37,11 +38,12 @@ func TestExpiredJWTToken(t *testing.T) { maker, err := NewJWTMaker(util.RandomString(32)) require.NoError(t, err) - token, err := maker.CreateToken(util.RandomOwner(), -time.Minute) + token, payload, err := maker.CreateToken(util.RandomOwner(), -time.Minute) require.NoError(t, err) require.NotEmpty(t, token) + require.NotEmpty(t, payload) - payload, err := maker.VerifyToken(token) + payload, err = maker.VerifyToken(token) require.Error(t, err) require.EqualError(t, err, ErrExpiredToken.Error()) require.Nil(t, payload) diff --git a/token/maker.go b/token/maker.go index e530796..9454d7a 100644 --- a/token/maker.go +++ b/token/maker.go @@ -6,7 +6,7 @@ import "time" type Maker interface { //CreateToken creates a new token for a specific username and duration - CreateToken(username string, duration time.Duration) (string, error) + CreateToken(username string, duration time.Duration) (string, *Payload, error) // VerifyToken checks if the token is valid or not VerifyToken(token string) (*Payload, error) diff --git a/token/paseto_maker.go b/token/paseto_maker.go index d99bd5c..ba8f6ef 100644 --- a/token/paseto_maker.go +++ b/token/paseto_maker.go @@ -27,14 +27,15 @@ func NewPasetoMaker(symmetricKey string) (Maker, error) { } // CreateToken creates a new token for a specific username and duration -func (maker *PasetoMaker) CreateToken(username string, duration time.Duration) (string, error) { +func (maker *PasetoMaker) CreateToken(username string, duration time.Duration) (string, *Payload, error) { payload, err := NewPayload(username, duration) if err != nil { - return "", err + return "", payload, err } - return maker.paseto.Encrypt(maker.symmetricKey, payload, nil) + token, err := maker.paseto.Encrypt(maker.symmetricKey, payload, nil) + return token, payload, err } // VerifyToken checks if the token is valid or not diff --git a/token/paseto_maker_test.go b/token/paseto_maker_test.go index 193a1f5..6de5ce0 100644 --- a/token/paseto_maker_test.go +++ b/token/paseto_maker_test.go @@ -18,13 +18,14 @@ func TestPasetoMaker(t *testing.T) { issuedAt := time.Now() expiredAt := issuedAt.Add(duration) - token, err := maker.CreateToken(username, duration) + token, payload, err := maker.CreateToken(username, duration) require.NoError(t, err) require.NotEmpty(t, token) - payload, err := maker.VerifyToken(token) + payload, err = maker.VerifyToken(token) require.NoError(t, err) require.NotEmpty(t, token) + require.NotEmpty(t, payload) require.NotZero(t, payload.ID) require.Equal(t, username, payload.Username) @@ -36,11 +37,12 @@ func TestExpiredPasetoToken(t *testing.T) { maker, err := NewPasetoMaker(util.RandomString(32)) require.NoError(t, err) - token, err := maker.CreateToken(util.RandomOwner(), -time.Minute) + token, payload, err := maker.CreateToken(util.RandomOwner(), -time.Minute) require.NoError(t, err) require.NotEmpty(t, token) + require.NotEmpty(t, payload) - payload, err := maker.VerifyToken(token) + payload, err = maker.VerifyToken(token) require.Error(t, err) require.EqualError(t, err, ErrExpiredToken.Error()) require.Nil(t, payload) diff --git a/util/confgi.go b/util/confgi.go index a567e85..6d50a0c 100644 --- a/util/confgi.go +++ b/util/confgi.go @@ -9,11 +9,12 @@ import ( // Config stores all the configuration used by the application // The values are read by viper from a config file or environment variables type Config struct { - DBDriver string `mapstructure:"DB_DRIVER"` - DBSource string `mapstructure:"DB_SOURCE"` - ServerAddress string `mapstructure:"SERVER_ADDRESS"` - TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"` - AccessTokenDuration time.Duration `mapstructure:"ACCESS_TOKEN_DURATION"` + DBDriver string `mapstructure:"DB_DRIVER"` + DBSource string `mapstructure:"DB_SOURCE"` + ServerAddress string `mapstructure:"SERVER_ADDRESS"` + TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"` + AccessTokenDuration time.Duration `mapstructure:"ACCESS_TOKEN_DURATION"` + RefreshTokenDuration time.Duration `mapstructure:"REFRESH_TOKEN_DURATION"` } // LoadConfig loads configuration from file or environment variables