Skip to content

Commit

Permalink
Auth Token, Access Logs
Browse files Browse the repository at this point in the history
  • Loading branch information
harshsinghvi committed Nov 18, 2023
1 parent 13aa23f commit 4eb0b96
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 47 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@
- ~~ autoscale postgress deployment ~~
- scale down to zero (Coldstart)
- api rate limiting and security
- API Auth
- API Access Logs and Billing
- ~~ API Auth and Access Logs ~~
- access token roles
- API Billing
- API analytics

## practice

Expand Down Expand Up @@ -65,6 +67,7 @@
- golang postgres api <https://medium.com/@cavdy/creating-restful-api-using-golang-and-postgres-part-2-542aac86e2bd> <https://medium.com/@cavdy/creating-restful-api-using-golang-and-postgres-part-1-58fe83c6f1ee>

- <https://github.com/slackapi/slack-github-action> slack webhook gh actions
- <https://sosedoff.com/2014/12/21/gin-middleware.html> go middleware

## AUTOSCALE LOGS HPA

Expand Down
64 changes: 64 additions & 0 deletions controllers/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,67 @@ func DeleteTodo(c *gin.Context) {
"message": "Todo deleted successfully",
})
}

func CreateNewToken(c *gin.Context) {
id := guuid.New().String()
token := utils.GenerateToken(id)
var accessToken models.AccessToken
c.BindJSON(&accessToken)

insertError := database.Connection.Insert(&models.AccessToken{
ID: id,
Token: token,
Email: accessToken.Email, // TODO: validate Email
Expiry: time.Now().AddDate(0, 0, 10),
CreatedAt: time.Now(),
})

if insertError != nil {
utils.InternalServerError(c, "Error while inserting new todo into db, Reason:", insertError)
return
}

c.JSON(http.StatusCreated, gin.H{
"status": http.StatusCreated,
"message": "Token created Successfully",
"token": token,
})
}

// Get tokens by email

func GetTokens(c *gin.Context) {
email := c.Param("email")
var pag models.Pagination
var err error
var accessTokens []models.AccessToken
var pageString = c.Query("page")
pag.ParseString(pageString)

querry := database.Connection.Model(&accessTokens).Order("created_at DESC")

if email != "admin" {
querry = querry.Where("email = ?", email)
}

if pag.TotalRecords, err = querry.Count(); err != nil {
utils.InternalServerError(c, "Error while getting tokens, Reason:", err)
return
}

if pag.CurrentPage != -1 {
querry = querry.Limit(10).Offset(10 * (pag.CurrentPage))
}

if err := querry.Select(); err != nil {
utils.InternalServerError(c, "Error while getting Tokens, Reason:", err)
return
}

c.JSON(http.StatusOK, gin.H{
"status": http.StatusOK,
"message": fmt.Sprintf("All Tokens by %s", email),
"data": accessTokens,
"pagination": pag.Validate(),
})
}
31 changes: 21 additions & 10 deletions database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,35 @@ func Connect() *pg.DB {
return Connection
}

// Create User Table
func CreateTodoTable() error {
func CreateTables() error {
opts := &orm.CreateTableOptions{
IfNotExists: true,
}

createError := Connection.CreateTable(&models.Todo{}, opts)
if createError != nil {
if createError := Connection.CreateTable(&models.Todo{}, opts); createError != nil {
log.Printf("Error while creating todo table, Reason: %v\n", createError)
return createError
}

_, err := Connection.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS index_todo ON todos(id, completed, created_at, updated_at);`)

if err != nil {
if _, err := Connection.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS go_index_todos ON todos(completed, created_at);`); err != nil {
log.Println(err.Error())
return err
}
if createError := Connection.CreateTable(&models.AccessToken{}, opts); createError != nil {
log.Printf("Error while creating access_tokens table, Reason: %v\n", createError)
return createError
}
if _, err := Connection.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS go_index_access_tokens ON access_tokens(created_at, token, email);`); err != nil {
log.Println(err.Error())
return err
}
if createError := Connection.CreateTable(&models.AccessLog{}, opts); createError != nil {
log.Printf("Error while creating access_logs table, Reason: %v\n", createError)
return createError
}
// TODO
if _, err := Connection.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS go_index_access_logs ON access_logs(token, path, method, response_time, status_code, server_hostname, created_at);`); err != nil {
log.Println(err.Error())
return err
}

log.Printf("Todo table and indexes created")
return nil
}
5 changes: 4 additions & 1 deletion db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ SELECT * FROM todos WHERE id=1;

DELETE FROM todos WHERE id=3;

INSERT INTO todos(text,complete) VALUES('task xyz', FALSE) ON CONFLICT DO NOTHING;
INSERT INTO todos(text,complete) VALUES('task xyz', FALSE) ON CONFLICT DO NOTHING;

SELECT created_at, expiry FROM access_tokens WHERE token='fc19728ee6ee29ccd923379577bf34c2';
UPDATE access_tokens SET expiry='2023-11-10 19:51:38.781473+00' where token='fc19728ee6ee29ccd923379577bf34c2';
51 changes: 31 additions & 20 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
version: '3'
services:
app:
container_name: go-todo-api
build: .
ports:
- 8080:8080
restart: on-failure
depends_on:
- postgres
networks:
- fullstack
environment:
- DB_HOST=postgres
- DB_DRIVER=${DB_DRIVER}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- DB_NAME=${DB_NAME}
- DB_PORT=${DB_PORT}
- POSTGRES_URL=${POSTGRES_URL}
# app:
# container_name: go-todo-api
# build: .
# ports:
# - 8080:8080
# restart: on-failure
# depends_on:
# - postgres
# networks:
# - fullstack
# environment:
# - DB_HOST=postgres
# - DB_DRIVER=${DB_DRIVER}
# - DB_USER=${DB_USER}
# - DB_PASSWORD=${DB_PASSWORD}
# - DB_NAME=${DB_NAME}
# - DB_PORT=${DB_PORT}
# - POSTGRES_URL=${POSTGRES_URL}
# - PORT=${PORT}

postgres:
Expand All @@ -32,8 +32,19 @@ services:
- 5432:5432
volumes:
- ./docker-compose/postgres:/var/lib/postgresql/data
networks:
- fullstack
# networks:
# - fullstack
pgadmin:
image: dpage/pgadmin4
container_name: pgadmin4
restart: unless-stopped
ports:
- "8888:80"
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: strong-password
volumes:
- ./docker-compose/pgadmin-data:/var/lib/pgadmin

# Networks to be created to facilitate communication between containers
networks:
Expand Down
44 changes: 30 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package main

import (
"log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"harshsinghvi/golang-postgres-kubernetes/database"
"harshsinghvi/golang-postgres-kubernetes/controllers"
"harshsinghvi/golang-postgres-kubernetes/controllers_old"
"harshsinghvi/golang-postgres-kubernetes/database"
"harshsinghvi/golang-postgres-kubernetes/middlewares"
"log"
"net/http"
)

func healthHandler(c *gin.Context) {
c.IndentedJSON(http.StatusOK, gin.H{"message": "OK"})
}

func readinessHandler(c *gin.Context) {
if !database.IsDtabaseReady() {
c.IndentedJSON(http.StatusServiceUnavailable, gin.H{"message": "server not ready"})
Expand All @@ -21,16 +23,22 @@ func readinessHandler(c *gin.Context) {
c.IndentedJSON(http.StatusOK, gin.H{"message": "OK"})
}

func main() {
err := godotenv.Load()
if err != nil {
func init() {
if err := godotenv.Load(); err != nil {
log.Printf("Error loading .env file")
}

}
func main() {

database.Connect()
database.CreateTodoTable()
database.CreateTables()

// router := gin.Default()
// TODO: for improved server fault tollerent but dosent log requests
router := gin.New()
router.Use(gin.Logger())
router.Use(gin.Recovery())

router := gin.Default()
api := router.Group("/api")
{
v1 := api.Group("/v1")
Expand All @@ -43,11 +51,19 @@ func main() {

v2 := api.Group("/v2")
{
v2.GET("/todo", controllers.GetAllTodos)
v2.GET("/todo/:id", controllers.GetSingleTodo)
v2.POST("/todo", controllers.CreateTodo)
v2.PUT("/todo/:id", controllers.EditTodo)
v2.DELETE("/todo/:id", controllers.DeleteTodo)
v2.POST("/token", controllers.CreateNewToken)
v2.GET("/token/:email", controllers.GetTokens)

todo := v2.Group("/todo")
{
todo.Use(middlewares.AuthMiddleware())
todo.GET("/", controllers.GetAllTodos)
todo.GET("/:id", controllers.GetSingleTodo)
todo.POST("/", controllers.CreateTodo)
todo.PUT("/:id", controllers.EditTodo)
todo.DELETE("/:id", controllers.DeleteTodo)
}

}
}

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

import (
"github.com/gin-gonic/gin"
guuid "github.com/google/uuid"
"harshsinghvi/golang-postgres-kubernetes/database"
"harshsinghvi/golang-postgres-kubernetes/models"
"harshsinghvi/golang-postgres-kubernetes/utils"
"log"
"os"
"time"
)

func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
reqStart := time.Now()
var accessToken models.AccessToken
var count int
var err error
token := c.GetHeader("token")
reqId := guuid.New().String()
c.Set("requestId", reqId)
c.Writer.Header().Set("X-Request-Id", reqId)

if token == "" {
utils.UnauthorizedResponse(c)
return
}

querry := database.Connection.Model(&accessToken).Where("token = ?", token)

if count, err = querry.Count(); err != nil {
utils.InternalServerError(c, "Error while getting tokens, Reason:", err)
c.Abort()
return
}

if count == 0 {
utils.UnauthorizedResponse(c)
return
}

if err = querry.Select(); err != nil {
utils.InternalServerError(c, "Error while getting all todos, Reason:", err)
c.Abort()
return
}

if time.Until(accessToken.Expiry).Seconds() <= 0 {
utils.UnauthorizedResponse(c)
return
}

c.Next()

var hostname string
if hostname, err = os.Hostname(); err != nil {
log.Printf("Error loading system hostname %v\n", err)
}
insertError := database.Connection.Insert(&models.AccessLog{
ID: reqId,
Token: accessToken.Token,
Path: c.Request.URL.Path,
ServerHostname: hostname,
ResponseSize: c.Writer.Size(),
StatusCode: c.Writer.Status(),
ClientIP: c.ClientIP(),
Method: c.Request.Method,
ResponseTime: time.Since(reqStart).Milliseconds(),
CreatedAt: time.Now(),
})
if insertError != nil {
log.Println("Error loging request in db.")
return
}
}
}
24 changes: 24 additions & 0 deletions models/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package models

import "time"

type AccessToken struct {
ID string `json:"id"`
Email string `json:"email"`
Token string `json:"token"`
Expiry time.Time `json:"expiry"`
CreatedAt time.Time `json:"created_at"`
}

type AccessLog struct {
ID string `json:"id"`
Token string `json:"token"`
Path string `json:"path"`
ClientIP string `json:"client_ip"`
Method string `json:"method"`
ResponseTime int64 `json:"response_time"`
ResponseSize int `json:"response_size"`
StatusCode int `json:"status_code"`
ServerHostname string `json:"server_hostname"`
CreatedAt time.Time `json:"created_at"`
}
Loading

0 comments on commit 4eb0b96

Please sign in to comment.