Skip to content

Commit

Permalink
Merge pull request #2 from cukhoaimon/feat/dbdocs
Browse files Browse the repository at this point in the history
Feat/dbdocs
  • Loading branch information
cukhoaimon authored Feb 4, 2024
2 parents 6acc04f + 67c708a commit 9fdb64e
Show file tree
Hide file tree
Showing 26 changed files with 422 additions and 33 deletions.
2 changes: 2 additions & 0 deletions .idea/sqldialects.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ test:
server:
go run main.go

dbdocs:
dbdocs build doc/database.dbml


mock:
mockgen -package mockdb -destination db/mock/store.go github.com/cukhoaimon/SimpleBank/db/sqlc Store

.PHONY: postgres createdb dropdb migrate-up migrate-down sqlc test server mock
.PHONY: postgres createdb dropdb migrate-up migrate-down sqlc test server mock dbdocs
Binary file modified README.md
Binary file not shown.
3 changes: 2 additions & 1 deletion api/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ func addAuthorization(
username string,
duration time.Duration,
) {
accessToken, err := tokenMaker.CreateToken(username, duration)
accessToken, payload, err := tokenMaker.CreateToken(username, duration)
require.Nil(t, err)
require.NotEmpty(t, payload)
require.NotEmpty(t, accessToken)

authorizationHeader := fmt.Sprintf("%s %s", authorizationType, accessToken)
Expand Down
1 change: 1 addition & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (server *Server) setupRouter() {
router := gin.Default()
router.POST("/api/v1/user", server.createUser)
router.POST("/api/v1/user/login", server.loginUser)
router.POST("/api/v1/user/token/renew_access", server.renewAccessTokenUser)

authRoutes := router.Group("/").Use(authMiddleware(server.tokenMaker))

Expand Down
81 changes: 81 additions & 0 deletions api/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package api

import (
"database/sql"
"errors"
"github.com/gin-gonic/gin"
"net/http"
"time"
)

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) renewAccessTokenUser(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)

session, err := server.store.GetSession(ctx, refreshPayload.Id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
ctx.JSON(http.StatusNotFound, errorResponse(err))
return
}

ctx.JSON(http.StatusInternalServerError, errorResponse(err))
return
}

if session.RefreshToken != req.RefreshToken {
err = errors.New("mismatched session token")
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
return
}

if session.IsBlocked {
err = errors.New("target session is blocked")
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
return
}

if session.Username != refreshPayload.Username {
err = errors.New("incorrect session user")
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
return
}

if time.Now().After(session.ExpiresAt) {
err = errors.New("expired session")
ctx.JSON(http.StatusUnauthorized, errorResponse(err))
return
}

token, accessTokenPayload, err := server.tokenMaker.CreateToken(
refreshPayload.Username,
server.config.TokenDuration,
)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
return
}

res := renewAccessTokenResponse{
AccessToken: token,
AccessTokenExpiresAt: accessTokenPayload.ExpiredAt,
}

ctx.JSON(http.StatusOK, res)
return
}
24 changes: 24 additions & 0 deletions api/token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package api

import (
mockdb "github.com/cukhoaimon/SimpleBank/db/mock"
"github.com/gin-gonic/gin"
"net/http/httptest"
"testing"
)

func TestServer_renewAccessTokenUser(t *testing.T) {
tests := []struct {
name string
body gin.H
buildStubs func(store *mockdb.MockStore)
checkResponse func(t *testing.T, recorder *httptest.ResponseRecorder)
}{
{ /* TODO: add test cases*/ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

})
}
}
50 changes: 45 additions & 5 deletions api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
db "github.com/cukhoaimon/SimpleBank/db/sqlc"
"github.com/cukhoaimon/SimpleBank/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/lib/pq"
"net/http"
"time"
Expand Down Expand Up @@ -73,8 +74,12 @@ type loginRequest struct {
}

type loginResponse 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) {
Expand Down Expand Up @@ -102,15 +107,50 @@ func (server *Server) loginUser(ctx *gin.Context) {
return
}

token, err := server.tokenMaker.CreateToken(req.Username, server.config.TokenDuration)
token, accessTokenPayload, err := server.tokenMaker.CreateToken(req.Username, server.config.TokenDuration)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
return
}

refreshToken, refreshTokenPayload, err := server.tokenMaker.CreateToken(req.Username, server.config.RefreshTokenDuration)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
return
}

arg := db.CreateSessionParams{
ID: accessTokenPayload.Id,
Username: accessTokenPayload.Username,
RefreshToken: refreshToken,
UserAgent: ctx.Request.UserAgent(),
ClientIp: ctx.ClientIP(),
IsBlocked: false,
ExpiresAt: refreshTokenPayload.ExpiredAt,
}

session, err := server.store.CreateSession(ctx, arg)
if err != nil {
var pqErr *pq.Error
if errors.As(err, &pqErr) {
switch pqErr.Code.Name() {
case "unique_violation":
ctx.JSON(http.StatusForbidden, errorResponse(err))
return
}
}

ctx.JSON(http.StatusInternalServerError, errorResponse(err))
return
}

res := loginResponse{
AccessToken: token,
User: makeUserResponse(user),
SessionID: session.ID,
AccessToken: token,
AccessTokenExpiresAt: accessTokenPayload.ExpiredAt,
RefreshToken: refreshToken,
RefreshTokenExpiresAt: refreshTokenPayload.ExpiredAt,
User: makeUserResponse(user),
}

ctx.JSON(http.StatusOK, res)
Expand Down
4 changes: 4 additions & 0 deletions api/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ func TestServer_loginUser(t *testing.T) {
"password": password,
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
CreateSession(gomock.Any(), gomock.Any()).
Times(1)

store.EXPECT().
GetUser(gomock.Any(), gomock.Eq(user.Username)).
Times(1).
Expand Down
3 changes: 2 additions & 1 deletion app.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ DB_DRIVER=postgres
DB_SOURCE=postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable
SERVER_ADDRESS=0.0.0.0:8080
TOKEN_SYMMETRIC_KEY=12345678901234567890123456789012
TOKEN_DURATION=15m
TOKEN_DURATION=15m
REFRESH_TOKEN_DURATION=24h
1 change: 1 addition & 0 deletions db/migration/000001_init_schema.down.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
DROP TABLE IF EXISTS entries;
DROP TABLE IF EXISTS transfers;
DROP TABLE IF EXISTS accounts;
DROP TABLE IF EXISTS users;
1 change: 1 addition & 0 deletions db/migration/000003_add_sessions.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS "sessions";
12 changes: 12 additions & 0 deletions db/migration/000003_add_sessions.up.sql
Original file line number Diff line number Diff line change
@@ -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");
31 changes: 31 additions & 0 deletions db/mock/store.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions db/query/session.sql
Original file line number Diff line number Diff line change
@@ -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;
13 changes: 13 additions & 0 deletions db/sqlc/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions db/sqlc/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9fdb64e

Please sign in to comment.