diff --git a/README.md b/README.md index f7996c8..563ed30 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,11 @@ ## Database Design +## migrate commands +``` +migrate create -ext sql -dir db/migration -seq add_sessions +``` + ## Viper - find, load, unmarshal config file (json, yaml, toml, env, ini) - read config from env variables diff --git a/api/middleware_test.go b/api/middleware_test.go index a4bdee8..e5ce403 100644 --- a/api/middleware_test.go +++ b/api/middleware_test.go @@ -12,8 +12,9 @@ import ( ) func addAuthorization(t *testing.T, r *http.Request, tokenMaker token.Maker, authorizationType string, 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) r.Header.Set(authorizationHeaderKey, authorizationHeader) } diff --git a/api/server.go b/api/server.go index 44e9e06..a0ee873 100644 --- a/api/server.go +++ b/api/server.go @@ -44,6 +44,7 @@ func (server *Server) setUpRouterWithSubUrl() { router := gin.Default() 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)) authRoutes.POST("/accounts", server.createAccount) diff --git a/api/session.go b/api/session.go new file mode 100644 index 0000000..160e13d --- /dev/null +++ b/api/session.go @@ -0,0 +1,71 @@ +package api + +import ( + "database/sql" + "fmt" + "github.com/gin-gonic/gin" + "net/http" + "time" +) + +type renewAccessTokenRequest struct { + RefreshToken string `json:"refresh_token" binding:"required"` +} +type renewAccessTokenResponse struct { + AccessToken string `json:"accessToken"` + AccessTokenExpireAt time.Time `json:"accessTokenExpireAt"` +} + +func (server *Server) renewAccessToken(ctx *gin.Context) { + var req renewAccessTokenRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + ctx.JSON(http.StatusBadRequest, errorResponse(err)) + return + } + fmt.Println("....asdhf", req.RefreshToken) + 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("mismatched session token") + 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 + } + resp := renewAccessTokenResponse{ + AccessToken: accessToken, + AccessTokenExpireAt: accessPayload.ExpiredAt, + } + ctx.JSON(http.StatusOK, resp) +} diff --git a/api/user.go b/api/user.go index 31e2342..a7c28b6 100644 --- a/api/user.go +++ b/api/user.go @@ -5,6 +5,7 @@ import ( db "github.com/emon46/bank-application/db/sqlc" "github.com/emon46/bank-application/util" "github.com/gin-gonic/gin" + "github.com/google/uuid" "github.com/lib/pq" "net/http" "time" @@ -71,8 +72,12 @@ type loginUserRequest struct { Password string `json:"password" binding:"required,min=6"` } type loginUserResponse struct { - AccessToken string `json:"accessToken"` - User userResponse `json:"user"` + SessionID uuid.UUID `json:"sessionID"` + AccessToken string `json:"accessToken"` + AccessTokenExpireAt time.Time `json:"accessTokenExpireAt"` + RefreshToken string `json:"refreshToken"` + RefreshTokenExpireAt time.Time `json:"refreshTokenExpireAt"` + User userResponse `json:"user"` } func (server *Server) loginUser(ctx *gin.Context) { @@ -88,20 +93,46 @@ func (server *Server) loginUser(ctx *gin.Context) { return } ctx.JSON(http.StatusInternalServerError, errorResponse(err)) + return } err = util.CheckPassword(req.Password, user.HashedPassword) if err != nil { ctx.JSON(http.StatusUnauthorized, errorResponse(err)) return } - accessToken, err := server.tokenMaker.CreateToken(user.Username, server.config.AccessTokenDuration) + accessToken, accessPayload, err := server.tokenMaker.CreateToken(user.Username, server.config.AccessTokenDuration) + if err != nil { + ctx.JSON(http.StatusInternalServerError, errorResponse(err)) + return + } + + refreshToken, refreshPayload, err := server.tokenMaker.CreateToken(user.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 } + resp := loginUserResponse{ - AccessToken: accessToken, - User: newUserResponse(user), + SessionID: session.ID, + AccessToken: accessToken, + AccessTokenExpireAt: accessPayload.ExpiredAt, + RefreshToken: refreshToken, + RefreshTokenExpireAt: refreshPayload.ExpiredAt, + User: newUserResponse(user), } ctx.JSON(http.StatusOK, resp) } diff --git a/api/user_test.go b/api/user_test.go index 0e419be..92ebcdb 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -218,6 +218,10 @@ func TestLoginUserAPI(t *testing.T) { GetUser(gomock.Any(), gomock.Eq(user.Username)). Times(1). Return(user, nil) + store.EXPECT(). + CreateSession(gomock.Any(), gomock.Any()). + Times(1) + }, checkResponse: func(recorder *httptest.ResponseRecorder) { require.Equal(t, http.StatusOK, recorder.Code) diff --git a/app.env b/app.env index 0132aaf..72793f6 100644 --- a/app.env +++ b/app.env @@ -3,3 +3,4 @@ DB_SOURCE="postgresql://postgres:secret1234@localhost:5432/simple_bank?sslmode=d SERVER_ADDRESS="0.0.0.0:8080" TOKEN_SYMMETRIC_KEY="12345678123456781234567812345678" ACESS_TOKEN_DURATION=15m +REFRESH_TOKEN_DURATION=24h diff --git a/db/migration/000001_init_schema.up.sql b/db/migration/000001_init_schema.up.sql index 0b13dca..76b57cc 100644 --- a/db/migration/000001_init_schema.up.sql +++ b/db/migration/000001_init_schema.up.sql @@ -1,24 +1,24 @@ CREATE TABLE "account" ( - "id" bigserial PRIMARY KEY, - "owner" varchar NOT NULL, - "balance" bigint NOT NULL, - "currency" varchar NOT NULL, - "created_at" timestamptz NOT NULL DEFAULT (now()) + "id" bigserial PRIMARY KEY, + "owner" varchar NOT NULL, + "balance" bigint NOT NULL, + "currency" varchar NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT (now()) ); CREATE TABLE "entries" ( - "id" bigserial PRIMARY KEY, - "account_id" bigint NOT NULL, - "amount" bigint NOT NULL, - "created_at" timestamptz NOT NULL DEFAULT (now()) + "id" bigserial PRIMARY KEY, + "account_id" bigint NOT NULL, + "amount" bigint NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT (now()) ); CREATE TABLE "transfers" ( - "id" bigserial PRIMARY KEY, - "from_account_id" bigint NOT NULL, - "to_account_id" bigint NOT NULL, - "amount" bigint NOT NULL, - "created_at" timestamptz NOT NULL DEFAULT (now()) + "id" bigserial PRIMARY KEY, + "from_account_id" bigint NOT NULL, + "to_account_id" bigint NOT NULL, + "amount" bigint NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT (now()) ); CREATE INDEX ON "account" ("owner"); diff --git a/db/migration/000002_add_users.up.sql b/db/migration/000002_add_users.up.sql index 79b54d1..4e85af6 100644 --- a/db/migration/000002_add_users.up.sql +++ b/db/migration/000002_add_users.up.sql @@ -1,10 +1,10 @@ CREATE TABLE "users" ( - "username" varchar PRIMARY KEY, - "hashed_password" varchar NOT NULL, - "full_name" varchar NOT NULL, - "email" varchar UNIQUE NOT NULL, - "created_at" timestamptz NOT NULL DEFAULT (now()), - "password_changed_at" timestamptz NOT NULL DEFAULT ('0001-01-01T00:00:00Z') + "username" varchar PRIMARY KEY, + "hashed_password" varchar NOT NULL, + "full_name" varchar NOT NULL, + "email" varchar UNIQUE NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT (now()), + "password_changed_at" timestamptz NOT NULL DEFAULT ('0001-01-01T00:00:00Z') ); ALTER TABLE "account" ADD FOREIGN KEY ("owner") REFERENCES "users" ("username"); 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..b552f65 --- /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, + "created_at" timestamptz NOT NULL DEFAULT (now()), + "expires_at" timestamptz NOT NULL +); + +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 6e7c2e7..0d3984a 100644 --- a/db/mock/store.go +++ b/db/mock/store.go @@ -10,6 +10,7 @@ import ( db "github.com/emon46/bank-application/db/sqlc" gomock "github.com/golang/mock/gomock" + uuid "github.com/google/uuid" ) // MockStore is a mock of Store interface. @@ -80,6 +81,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() @@ -169,6 +185,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..9876ef6 --- /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; diff --git a/db/sqlc/models.go b/db/sqlc/models.go index 4417851..ab359ea 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"` + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_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 740b8e3..7a114f4 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 { AddToAccountBalance(ctx context.Context, arg AddToAccountBalanceParams) (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) DeleteAuthor(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) ListAccount(ctx context.Context, arg ListAccountParams) ([]Account, error) diff --git a/db/sqlc/session.sql.go b/db/sqlc/session.sql.go new file mode 100644 index 0000000..f37467c --- /dev/null +++ b/db/sqlc/session.sql.go @@ -0,0 +1,82 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.15.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, created_at, expires_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.CreatedAt, + &i.ExpiresAt, + ) + return i, err +} + +const getSession = `-- name: GetSession :one +SELECT id, username, refresh_token, user_agent, client_ip, is_blocked, created_at, expires_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.CreatedAt, + &i.ExpiresAt, + ) + return i, err +} diff --git a/eks/db-sts.yaml b/eks/db-sts.yaml new file mode 100644 index 0000000..64b4faf --- /dev/null +++ b/eks/db-sts.yaml @@ -0,0 +1,55 @@ +apiVersion: v1 +kind: Service +metadata: + name: db-svc + namespace: demo +spec: + selector: + app: postgresql-db + ports: + - protocol: TCP + port: 5432 + targetPort: 5432 + type: ClusterIP +--- +# PostgreSQL StatefulSet +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: postgresql-db + namespace: demo +spec: + serviceName: postgresql-db-service + selector: + matchLabels: + app: postgresql-db + replicas: 1 + template: + metadata: + labels: + app: postgresql-db + spec: + containers: + - name: postgresql-db + image: postgres:latest + volumeMounts: + - name: data + mountPath: /data + env: + - name: POSTGRES_USER + value: postgres + - name: POSTGRES_PASSWORD + value: secret1234 + - name: PGDATA + value: /pg/data + - name: POSTGRES_DB + value: simple_bank + # Volume Claim + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/eks/deployment.yaml b/eks/deployment.yaml index 7c8083c..73576a4 100644 --- a/eks/deployment.yaml +++ b/eks/deployment.yaml @@ -17,7 +17,8 @@ spec: spec: containers: - name: bank-server - image: 324813622390.dkr.ecr.us-east-2.amazonaws.com/bank-app:latest +# image: 324813622390.dkr.ecr.us-east-2.amazonaws.com/bank-app:latest + image: hremon331046/bank-app:latest imagePullPolicy: Always ports: - containerPort: 8080 \ No newline at end of file diff --git a/eks/ingress.yaml b/eks/ingress.yaml index caac656..397eca1 100644 --- a/eks/ingress.yaml +++ b/eks/ingress.yaml @@ -29,13 +29,13 @@ spec: name: bank-api-service port: number: 80 -# - host: "*.foo.com" +# - host: "localhost" # http: # paths: # - pathType: Prefix -# path: "/foo" +# path: "/" # backend: # service: -# name: service2 +# name: db-svc # port: -# number: 80 \ No newline at end of file +# number: 5432 \ No newline at end of file diff --git a/token/jwt_maker.go b/token/jwt_maker.go index cfd57bb..5778712 100644 --- a/token/jwt_maker.go +++ b/token/jwt_maker.go @@ -20,13 +20,14 @@ type JWTMaker struct { secretKey string } -func (J JWTMaker) CreateToken(username string, duration time.Duration) (string, error) { +func (J 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(J.secretKey)) + token, err := jwtToken.SignedString([]byte(J.secretKey)) + return token, payload, err } func (J JWTMaker) VerifyToken(token string) (*Payload, error) { diff --git a/token/jwt_maker_test.go b/token/jwt_maker_test.go index bc546d3..3c1785f 100644 --- a/token/jwt_maker_test.go +++ b/token/jwt_maker_test.go @@ -15,11 +15,12 @@ func TestJWTMaker(t *testing.T) { duration := time.Minute 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) + require.NotEmpty(t, payload) - payload, err := maker.VerifyToken(token) + payload, err = maker.VerifyToken(token) require.NoError(t, err) require.NotEmpty(t, payload) require.NotZero(t, payload.ID) @@ -32,11 +33,12 @@ func TestExpiredJWTToken(t *testing.T) { maker, err := NewJWTMaker(util.RandomString(32)) require.NoError(t, err) username := util.RandomName() - token, err := maker.CreateToken(username, -time.Minute) + token, payload, err := maker.CreateToken(username, -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, ErrorExpiredToken.Error()) require.Empty(t, payload) diff --git a/token/maker.go b/token/maker.go index 836bd1d..574eccb 100644 --- a/token/maker.go +++ b/token/maker.go @@ -3,6 +3,6 @@ package token import "time" type Maker interface { - CreateToken(username string, duration time.Duration) (string, error) + CreateToken(username string, duration time.Duration) (string, *Payload, error) VerifyToken(token string) (*Payload, error) } diff --git a/token/paseto_maker.go b/token/paseto_maker.go index 0a46cbd..5705135 100644 --- a/token/paseto_maker.go +++ b/token/paseto_maker.go @@ -12,12 +12,13 @@ type PasetoMaker struct { symmetricKey []byte } -func (p PasetoMaker) CreateToken(username string, duration time.Duration) (string, error) { +func (p PasetoMaker) CreateToken(username string, duration time.Duration) (string, *Payload, error) { payload, err := NewPayload(username, duration) if err != nil { - return "", err + return "", payload, err } - return p.paseto.Encrypt(p.symmetricKey, payload, nil) + token, err := p.paseto.Encrypt(p.symmetricKey, payload, nil) + return token, payload, err } func (p PasetoMaker) VerifyToken(token string) (*Payload, error) { diff --git a/token/paseto_maker_test.go b/token/paseto_maker_test.go index ec45f4a..59b216b 100644 --- a/token/paseto_maker_test.go +++ b/token/paseto_maker_test.go @@ -14,11 +14,12 @@ func TestPasetoMakerToken(t *testing.T) { duration := time.Minute 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) + require.NotEmpty(t, payload) - payload, err := maker.VerifyToken(token) + payload, err = maker.VerifyToken(token) require.NoError(t, err) require.NotEmpty(t, payload) require.NotZero(t, payload.ID) @@ -31,11 +32,12 @@ func TestExpiredPasetoToken(t *testing.T) { maker, err := NewPasetoMaker(util.RandomString(32)) require.NoError(t, err) username := util.RandomName() - token, err := maker.CreateToken(username, -time.Minute) + token, payload, err := maker.CreateToken(username, -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, ErrorExpiredToken.Error()) require.Empty(t, payload) @@ -44,11 +46,12 @@ func TestInvalidPasetoToken(t *testing.T) { maker, err := NewPasetoMaker(util.RandomString(32)) require.NoError(t, err) username := util.RandomName() - token, err := maker.CreateToken(username, time.Minute) + token, payload, err := maker.CreateToken(username, time.Minute) require.NoError(t, err) require.NotEmpty(t, token) + require.NotEmpty(t, payload) - payload, err := maker.VerifyToken(token[2:]) + payload, err = maker.VerifyToken(token[2:]) require.Error(t, err) require.EqualError(t, err, ErrorInvalidToken.Error()) require.Empty(t, payload) diff --git a/util/config.go b/util/config.go index 7178cb2..e2cd81e 100644 --- a/util/config.go +++ b/util/config.go @@ -6,11 +6,12 @@ import ( ) 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:"ACESS_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:"ACESS_TOKEN_DURATION"` + RefreshTokenDuration time.Duration `mapstructure:"REFRESH_TOKEN_DURATION"` } func LoadConfig(path string) (config Config, err error) {