Skip to content

Commit

Permalink
feat: basic public and private routes with test (#5)
Browse files Browse the repository at this point in the history
* feat: basic routes with test

* feat: github action for test

* feat: moved to env
  • Loading branch information
kairavkkp authored Sep 18, 2024
1 parent 08553d4 commit b2fb168
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 15 deletions.
23 changes: 23 additions & 0 deletions .air.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# .air.toml

# Configures what directories/files to watch for changes
[dirs]
watch = [
"./cmd",
"./handlers",
"./routes",
"./middleware"
]

# Files to exclude from being watched
[exclude]
watch = [
"db/migrations",
"README.md",
".git/**"
]

# Command to run the app
[build]
cmd = "go build -o ./tmp/main ./cmd/main.go"
bin = "tmp/main"
35 changes: 35 additions & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Run Tests on PR

on:
pull_request:
branches:
- development

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.22.0"

- name: Install dependencies
run: go mod download

- name: Run Go tests
run: go test ./handlers -v

- name: Run Go tests with coverage
run: go test -coverprofile=coverage.out ./handlers

- name: Upload coverage report
if: success()
uses: actions/upload-artifact@v3
with:
name: coverage-report
path: coverage.out
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ go.work.sum

# env file
.env

tmp/*
14 changes: 12 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"net/http"
"os"

"github.com/go-chi/chi/middleware"
"github.com/go-chi/chi/v5"
"github.com/kairavkkp/dedoxify-backend/routes"
_ "github.com/lib/pq"
)
Expand Down Expand Up @@ -44,8 +46,16 @@ func main() {
fmt.Printf("version=%s\n", version)

// Routes
router := routes.SetupRouter()
r := chi.NewRouter()
r.Use(middleware.Logger)

r.Mount("/public", routes.PublicRouter())
r.Mount("/private", routes.PrivateRouter())

chi.Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
log.Printf("%s %s\n", method, route)
return nil
})
log.Println("Starting Server on :", backendPort)
http.ListenAndServe(":"+backendPort, router)
http.ListenAndServe(":"+backendPort, r)
}
15 changes: 15 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,23 @@ require (
github.com/stretchr/testify v1.9.0
)

require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/crypto v0.25.0 // indirect
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-chi/chi v1.5.5
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/lestrrat-go/jwx v1.2.30
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
30 changes: 30 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx v1.2.30 h1:VKIFrmjYn0z2J51iLPadqoHIVLzvWNa1kCsTqNDHYPA=
github.com/lestrrat-go/jwx v1.2.30/go.mod h1:vMxrwFhunGZ3qddmfmEm2+uced8MSI6QFWGTKygjSzQ=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
9 changes: 6 additions & 3 deletions handlers/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import (
"net/http"
)

// RootHandler is a temporary handler that responds with "Hello, World!"
func RootHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Dedoxify Backend!")
func PublicRootHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Public: Dedoxify Backend!")
}

func PrivateRootHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Private: Dedoxify Backend!")
}
29 changes: 23 additions & 6 deletions handlers/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,40 @@ import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"

"github.com/stretchr/testify/assert"
)

func TestRootHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/" , nil)
func TestPublicRootHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()

handler := http.HandlerFunc(PublicRootHandler)
handler.ServeHTTP(rr, req)

assert.Equal(t, http.StatusOK, rr.Code, "Expected Status OK")

expectedResponse := "Public: Dedoxify Backend!"
assert.Equal(t, expectedResponse, rr.Body.String(), "Expected response body to be Dedoxify Backend.")
}

func TestPrivateRootHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()

handler := http.HandlerFunc(RootHandler)
handler := http.HandlerFunc(PrivateRootHandler)
handler.ServeHTTP(rr, req)

assert.Equal(t, http.StatusOK, rr.Code, "Expected Status OK")

expectedResponse := "Dedoxify Backend!"
expectedResponse := "Private: Dedoxify Backend!"
assert.Equal(t, expectedResponse, rr.Body.String(), "Expected response body to be Dedoxify Backend.")
}
}
19 changes: 19 additions & 0 deletions middleware/api_key_middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// middleware/api_key_middleware.go
package middleware

import (
"net/http"
"os"
)

func APIKeyAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
apiKey := r.Header.Get("X-API-Key")
configuredApiKey := os.Getenv("API_KEY")
if apiKey != configuredApiKey {
http.Error(w, "Invalid API key", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
75 changes: 75 additions & 0 deletions middleware/jwt_middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// middleware/jwt_middleware.go
package middleware

import (
"fmt"
"log"
"net/http"
"os"
"strings"

"github.com/golang-jwt/jwt/v5"
"github.com/lestrrat-go/jwx/jwk"
)

// Define the Cognito details
const (
CognitoURL = "https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json"
Issuer = "https://cognito-idp.%s.amazonaws.com/%s"
)

// JWTAuth middleware for AWS Cognito
func JWTAuth(next http.Handler) http.Handler {
Region := os.Getenv("COGNITO_REGION")
UserPoolID := os.Getenv("COGNITO_USER_POOL_ID")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get the token from the Authorization header
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Authorization header is required", http.StatusUnauthorized)
return
}

tokenString := strings.Split(authHeader, "Bearer ")[1]

// Parse the token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Verify that the token's signing algorithm is what you expect
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}

// Fetch the JWKS for this User Pool
jwksURL := fmt.Sprintf(CognitoURL, Region, UserPoolID)
keySet, err := jwk.Fetch(r.Context(), jwksURL)
if err != nil {
log.Println("Failed to fetch JWKs", err)
return nil, err
}

// Extract the kid (key ID) from the JWT header and use it to find the correct public key
kid := token.Header["kid"].(string)
key, found := keySet.LookupKeyID(kid)
if !found {
return nil, fmt.Errorf("unable to find key")
}

// Return the public key for validation
var rawKey interface{}
if err := key.Raw(&rawKey); err != nil {
return nil, err
}
return rawKey, nil
})

// Handle token parsing error or invalid token
if err != nil || !token.Valid {
log.Println("Invalid token:", err)
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}

// If token is valid, continue with the request
next.ServeHTTP(w, r)
})
}
7 changes: 3 additions & 4 deletions routes/router.go → routes/private_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import (
)

// SetupRouter initializes and returns the Chi router
func SetupRouter() http.Handler {
func PrivateRouter() http.Handler {
r := chi.NewRouter()

// Define your routes
r.Get("/", handlers.RootHandler) // Temporary root handler
// r.Use(middleware.APIKeyAuth)
r.Get("/", handlers.PrivateRootHandler)

return r
}
21 changes: 21 additions & 0 deletions routes/public_router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// routes/router.go
package routes

import (
"net/http"

"github.com/go-chi/chi/v5"
"github.com/kairavkkp/dedoxify-backend/handlers"
"github.com/kairavkkp/dedoxify-backend/middleware"
)

// SetupRouter initializes and returns the Chi router
func PublicRouter() http.Handler {
r := chi.NewRouter()

r.Use(middleware.JWTAuth)

r.Get("/", handlers.PublicRootHandler)

return r
}

0 comments on commit b2fb168

Please sign in to comment.