Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/LiliannaBruflat83/chi

go 1.22.0
32 changes: 30 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
package main

import "fmt"
import (
"fmt"
"net/http"
"net/http/httptest"

"github.com/LiliannaBruflat83/chi/middleware"
)

func main() {
fmt.Println("Hello, Bounty Hunter!")
fmt.Println("Running Chi Middleware Verification Example...")

authMw := middleware.AuthMiddleware(func(r *http.Request) bool {
return r.Header.Get("Authorization") == "Bearer secret-token"
})

handler := authMw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("Access Granted"))
}))

// Test unauthorized request
req1 := httptest.NewRequest(http.MethodGet, "/secure", nil)
rec1 := httptest.NewRecorder()
handler.ServeHTTP(rec1, req1)
fmt.Printf("Unauthorized Request -> Status: %d, Body: %s", rec1.Code, rec1.Body.String())

// Test authorized request
req2 := httptest.NewRequest(http.MethodGet, "/secure", nil)
req2.Header.Set("Authorization", "Bearer secret-token")
rec2 := httptest.NewRecorder()
handler.ServeHTTP(rec2, req2)
fmt.Printf("Authorized Request -> Status: %d, Body: %s\n", rec2.Code, rec2.Body.String())
}
58 changes: 58 additions & 0 deletions middleware/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package middleware

import (
"net/http"
"strings"
)

// AuthMiddleware creates a middleware that checks for authorization.
// If unauthorized, it writes http.StatusUnauthorized and immediately halts the middleware chain.
func AuthMiddleware(isAuthorized func(r *http.Request) bool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if isAuthorized != nil && !isAuthorized(r) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return // Explicit early return halts middleware chain execution
}
next.ServeHTTP(w, r)
})
}
}

// RequireHeader ensures that a required header is present and non-empty.
// If missing, writes http.StatusBadRequest and returns immediately.
func RequireHeader(headerName string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.TrimSpace(r.Header.Get(headerName)) == "" {
http.Error(w, "Missing required header: "+headerName, http.StatusBadRequest)
return // Explicit early return halts middleware chain execution
}
next.ServeHTTP(w, r)
})
}
}

// BasicAuth creates a basic authentication middleware.
// If credentials don't match or are missing, returns StatusUnauthorized with WWW-Authenticate header and halts execution.
func BasicAuth(realm string, credentials map[string]string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return // Explicit early return halts middleware chain execution
}

expectedPass, userExists := credentials[user]
if !userExists || expectedPass != pass {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return // Explicit early return halts middleware chain execution
}

next.ServeHTTP(w, r)
})
}
}
155 changes: 155 additions & 0 deletions middleware/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package middleware

import (
"net/http"
"net/http/httptest"
"testing"
)

func TestAuthMiddleware_Unauthorized(t *testing.T) {
nextCalled := false
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
})

mw := AuthMiddleware(func(r *http.Request) bool {
return false // Reject request
})

handler := mw(nextHandler)

req := httptest.NewRequest(http.MethodGet, "/protected", nil)
rec := httptest.NewRecorder()

handler.ServeHTTP(rec, req)

if rec.Code != http.StatusUnauthorized {
t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rec.Code)
}

if nextCalled {
t.Errorf("expected downstream handler to be bypassed, but nextHandler was called")
}
}

func TestAuthMiddleware_Authorized(t *testing.T) {
nextCalled := false
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
})

mw := AuthMiddleware(func(r *http.Request) bool {
return true // Allow request
})

handler := mw(nextHandler)

req := httptest.NewRequest(http.MethodGet, "/protected", nil)
rec := httptest.NewRecorder()

handler.ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Errorf("expected status %d, got %d", http.StatusOK, rec.Code)
}

if !nextCalled {
t.Errorf("expected downstream handler to be called, but nextCalled was false")
}
}

func TestRequireHeader_MissingHeader(t *testing.T) {
nextCalled := false
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
})

mw := RequireHeader("X-API-Key")
handler := mw(nextHandler)

req := httptest.NewRequest(http.MethodGet, "/data", nil)
rec := httptest.NewRecorder()

handler.ServeHTTP(rec, req)

if rec.Code != http.StatusBadRequest {
t.Errorf("expected status %d, got %d", http.StatusBadRequest, rec.Code)
}

if nextCalled {
t.Errorf("expected downstream handler to be bypassed when header is missing")
}
}

func TestRequireHeader_PresentHeader(t *testing.T) {
nextCalled := false
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
})

mw := RequireHeader("X-API-Key")
handler := mw(nextHandler)

req := httptest.NewRequest(http.MethodGet, "/data", nil)
req.Header.Set("X-API-Key", "secret-123")
rec := httptest.NewRecorder()

handler.ServeHTTP(rec, req)

if nextCalled == false {
t.Errorf("expected downstream handler to be called when header is present")
}
}

func TestBasicAuth_Unauthorized(t *testing.T) {
nextCalled := false
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
})

creds := map[string]string{
"admin": "password123",
}
mw := BasicAuth("Restricted", creds)
handler := mw(nextHandler)

req := httptest.NewRequest(http.MethodGet, "/admin", nil)
rec := httptest.NewRecorder()

handler.ServeHTTP(rec, req)

if rec.Code != http.StatusUnauthorized {
t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rec.Code)
}

if nextCalled {
t.Errorf("expected downstream handler to be bypassed on failed Basic Auth")
}
}

func TestBasicAuth_Authorized(t *testing.T) {
nextCalled := false
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
})

creds := map[string]string{
"admin": "password123",
}
mw := BasicAuth("Restricted", creds)
handler := mw(nextHandler)

req := httptest.NewRequest(http.MethodGet, "/admin", nil)
req.SetBasicAuth("admin", "password123")
rec := httptest.NewRecorder()

handler.ServeHTTP(rec, req)

if !nextCalled {
t.Errorf("expected downstream handler to be called on successful Basic Auth")
}
}