Skip to content

Commit

Permalink
Merge pull request #12 from imrishuroy/ft/user-session
Browse files Browse the repository at this point in the history
added user sessions
  • Loading branch information
imrishuroy authored Dec 1, 2023
2 parents 7e8cf49 + 918f4cb commit cda75f0
Show file tree
Hide file tree
Showing 19 changed files with 347 additions and 63 deletions.
72 changes: 36 additions & 36 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -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"


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,
) {
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)
Expand Down
1 change: 1 addition & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func (server *Server) setupRouter() {

router.POST("/users", server.createUser)
router.POST("/users/#", server.loginUser)
router.POST("/tokens/renew_access", server.renewAccessToken)

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

Expand Down
82 changes: 82 additions & 0 deletions api/token.go
Original file line number Diff line number Diff line change
@@ -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)
}
43 changes: 38 additions & 5 deletions api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
)
Expand All @@ -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)

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:Prince2024@localhost:5432/simple_bank?sslmode=disable
SERVER_ADDRESS=localhost:8080
TOKEN_SYMMETRIC_KEY=12345678901234567890123456789012
ACCESS_TOKEN_DURATION=15m
ACCESS_TOKEN_DURATION=15m
REFRESH_TOKEN_DURATION=24h
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.

Loading

0 comments on commit cda75f0

Please # to comment.