feat(auth): Add authentication middleware

This commit is contained in:
Björn Benouarets
2026-02-06 00:08:27 +01:00
parent fb35450880
commit 78da787f43
5 changed files with 672 additions and 121 deletions

View File

@@ -25,10 +25,10 @@ The SecNex API Gateway follows a modular architecture with clear separation of c
│ Route Handler │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Per-Route Middleware Chain │ │
│ │ ┌────────────┐ ┌────────────── │ │
│ │ │ Strip │→ │ Reverse │ │ │
│ │ │ Prefix │ │ Proxy │ │ │
│ │ └────────────┘ └────────────── │ │
│ │ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │ │
│ │ │ Auth │→ │ Strip │→ │ Reverse │ │ │
│ │ │ Middleware │ │ Prefix │ │ Proxy │ │ │
│ │ └────────────┘ └────────────┘ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────┬──────────────────────────────────┘
@@ -54,11 +54,12 @@ app/
│ └── database.go # Database configuration
├── server/ # Core server components
│ ├── gateway.go # Main gateway server
│ ├── routes.go # Route registration
│ ├── routes.go # Route registration & middleware chain
│ ├── api.go # API definitions
│ ├── host.go # Host definitions
│ └── target.go # Target (backend) definitions
├── middlewares/ # HTTP middleware
│ ├── auth.go # Authentication middleware
│ ├── host.go # Host logging middleware
│ └── logger.go # Structured logging middleware
└── utils/ # Utility functions
@@ -79,7 +80,7 @@ The Gateway is the main server component that:
The Routes component handles:
- Creating route handlers from configuration
- Applying strip prefix middleware
- Applying per-route middleware chain (Auth → StripPrefix)
- Registering routes with chi router (method-agnostic)
- Connecting routes to API backends
@@ -101,9 +102,17 @@ Host definitions for:
Target (backend) definitions that:
- Store backend service URLs
- Create `httputil.ReverseProxy` instances
- Create `httputil.NewSingleHostReverseProxy` instances
- Handle proxy configuration
#### Auth Middleware (`middlewares/auth.go`)
Authentication middleware that:
- Validates presence of configured auth header (e.g., `X-Api-Key`, `Authorization`)
- Supports path-based filtering via include/exclude patterns
- Removes auth header before forwarding to backend
- Provides extensive DEBUG logging for troubleshooting
## Middleware Chain
### Global Middleware
@@ -119,19 +128,71 @@ Applied to all requests via chi middleware:
### Per-Route Middleware
Applied to each route handler:
Applied in order to each route handler:
1. **StripPrefix** - Removes specified prefix from request path before proxying
1. **Auth** (if enabled) - Validates authentication header with path filtering
2. **StripPrefix** (if enabled) - Removes specified prefix from request path before proxying
## Request Flow
1. **Client Request** → Gateway receives HTTP request
2. **Global Middleware** → Request ID, Real IP, Host logging, Logger applied
3. **Route Matching** → Chi matches route pattern (e.g., `/api/v1/*`)
4. **Per-Route Middleware** → StripPrefix (if enabled)
4. **Per-Route Middleware** Auth → StripPrefix (if enabled)
5. **Reverse Proxy** → Request forwarded to backend API
6. **Response** → Backend response returned to client
## Authentication Flow
The authentication middleware supports flexible path-based filtering:
```
┌─────────────────────────────────────┐
│ Include and Exclude both empty? │
└──────────────────┬──────────────────┘
│ Yes
┌─────────┴─────────┐
│ Auth required │
│ for ALL paths │
└───────────────────┘
│ No
┌─────────┴─────────┐
▼ │
┌───────────────────┐ │
│ Only Include set? │ │
└─────────┬─────────┘ │
│ Yes │ No │
▼ ▼ │
┌────────┐ ┌────────────────┐│
│ Auth │ │ Exclude set? ││
│ ONLY │ └───────┬────────┘│
│ for │ │ No │
│ Include│ ┌────┴────┐ │
│ paths │ │ Auth │ │
└────────┘ │ for ALL │ │
└────┬────┘ │
│ Yes │
┌─────────┴─────────┐│
│ Auth EXCEPT ││
│ matching Exclude ││
└───────────────────┘│
Check Auth Header
```
**Path Filtering Logic:**
1. **Both include and exclude empty** → Auth required for ALL paths
2. **Only include set** → Auth required ONLY for paths matching include patterns
3. **Only exclude set** → Auth required for ALL paths EXCEPT those matching exclude patterns
4. **Both set** → Include takes precedence (same as #2)
**Wildcard Pattern Matching:**
- `*` matches any path
- `/api/*` matches `/api/` and any subpath
- `/api/v1/public/test/*` matches the prefix and any subpath
## Configuration Flow
1. Load `gateway.yaml` via `config.NewFile()`
@@ -139,10 +200,10 @@ Applied to each route handler:
3. Create Hosts from configuration
4. Create Targets from configuration
5. Create APIs (linking Hosts to Targets)
6. Create Routes (linking Routes to APIs)
6. Create Routes (linking Routes to APIs with Auth config)
7. Initialize Gateway with all components
8. Configure proxy directors
9. Register routes with chi router
9. Register routes with chi router (including Auth middleware)
10. Start HTTP server
## Logging
@@ -151,6 +212,7 @@ The gateway uses structured JSON logging via `masterlog`:
- **HTTP Request Logging** - method, path, status, duration, host, IP
- **Gateway Events** - startup, route registration, proxy configuration
- **Auth Debug Logs** - detailed auth decision logging when DEBUG level enabled
- **Sensitive Field Pseudonymization** - user_id, email, ip fields are pseudonymized
Example log output:
@@ -166,3 +228,15 @@ Example log output:
"ip": "127.0.0.1:52342"
}
```
Auth debug logs (when DEBUG level enabled):
```json
{
"level": "debug",
"msg": "AuthMiddleware: Checking if path requires auth",
"path": "/api/v1/users",
"requires_auth": true,
"include": [],
"exclude": ["/api/v1/public/*"]
}
```

View File

@@ -36,6 +36,14 @@ routes:
strip_prefix:
enabled: true
prefix: "/api/v1"
security:
auth:
enabled: true
type: "api_key"
header: "X-Api-Key"
path:
include: []
exclude: []
```
## Sections
@@ -93,7 +101,7 @@ Links hosts to backend targets.
### Routes
Route definitions with path patterns and prefix stripping.
Route definitions with path patterns, prefix stripping, and authentication.
| Field | Type | Description |
|-------|------|-------------|
@@ -101,6 +109,7 @@ Route definitions with path patterns and prefix stripping.
| `api` | string | API ID to use for this route |
| `path` | string | Chi route pattern (e.g., `/api/v1/*`) |
| `strip_prefix` | object | Prefix stripping configuration |
| `security` | object | Security policies (auth) |
#### Strip Prefix
@@ -109,60 +118,123 @@ Route definitions with path patterns and prefix stripping.
| `enabled` | boolean | Enable prefix stripping |
| `prefix` | string | Prefix to remove from path |
#### Security
##### Authentication
| Field | Type | Description |
|-------|------|-------------|
| `enabled` | boolean | Enable authentication |
| `type` | string | Auth type identifier (for documentation) |
| `header` | string | Header name to validate (e.g., `X-Api-Key`, `Authorization`) |
| `path` | object | Path-based filtering configuration |
##### Auth Path Filtering
| Field | Type | Description |
|-------|------|-------------|
| `include` | array | Paths that require auth (empty = all, if set only these) |
| `exclude` | array | Paths that skip auth (only used if include is empty) |
**Path Filtering Logic:**
| Include | Exclude | Behavior |
|---------|---------|----------|
| Empty | Empty | Auth required for ALL paths |
| Set | Empty | Auth required ONLY for paths matching include |
| Empty | Set | Auth required for ALL EXCEPT paths matching exclude |
| Set | Set | Include takes precedence (same as "Set, Empty") |
**Wildcards in Path Patterns:**
- `*` matches any path
- `/api/*` matches `/api/` and any subpath
- `/api/v1/public/test/*` matches the prefix and any subpath
## Example Configurations
### Simple Proxy (No Prefix Stripping)
### Public API (No Auth)
```yaml
gateway:
host: "0.0.0.0"
port: 8080
features:
- logger
hosts:
- id: "host-001"
name: "localhost"
domain: "localhost:8080"
targets:
- id: "target-001"
name: "backend"
url: "https://api.example.com"
apis:
- id: "api-001"
host: "host-001"
target: "target-001"
routes:
- id: "route-001"
- id: "public-api"
api: "api-001"
path: "/api/*"
path: "/public/*"
strip_prefix:
enabled: true
prefix: "/public"
security:
auth:
enabled: false
```
**Request flow:**
- Client requests: `/api/users/123`
- Backend receives: `/api/users/123`
### Prefix Stripping
### Protected API (All Paths Require Auth)
```yaml
routes:
- id: "route-001"
- id: "protected-api"
api: "api-001"
path: "/api/v1/*"
strip_prefix:
enabled: true
prefix: "/api/v1"
security:
auth:
enabled: true
header: "X-Api-Key"
path:
include: []
exclude: []
```
**Request flow:**
- Client requests: `/api/v1/users/123`
- Gateway strips: `/api/v1`
- Backend receives: `/users/123`
### Public with Protected Sub-paths (Include)
### Multiple Routes
```yaml
routes:
- id: "mixed-api"
api: "api-001"
path: "/api/*"
strip_prefix:
enabled: true
prefix: "/api"
security:
auth:
enabled: true
header: "X-Api-Key"
path:
include: ["/api/admin/*", "/api/users/*/profile"]
exclude: []
```
In this example:
- `/api/admin/users` → Requires auth (matches include)
- `/api/users/123/profile` → Requires auth (matches include)
- `/api/public/data` → No auth (doesn't match include)
- `/api/health` → No auth (doesn't match include)
### Protected with Public Sub-paths (Exclude)
```yaml
routes:
- id: "protected-with-public"
api: "api-001"
path: "/api/*"
security:
auth:
enabled: true
header: "Authorization"
path:
include: []
exclude: ["/api/health", "/api/public/*", "/api/v1/public/test/*"]
```
In this example:
- `/api/health` → No auth (matches exclude)
- `/api/public/data` → No auth (matches exclude)
- `/api/v1/public/test/123` → No auth (matches exclude)
- `/api/users` → Requires auth (doesn't match exclude)
- `/api/admin/settings` → Requires auth (doesn't match exclude)
### Multiple Routes with Different Auth
```yaml
routes:
@@ -172,49 +244,37 @@ routes:
strip_prefix:
enabled: true
prefix: "/public"
security:
auth:
enabled: false
- id: "api-route"
- id: "user-route"
api: "api-001"
path: "/api/v1/*"
path: "/users/*"
strip_prefix:
enabled: true
prefix: "/api/v1"
```
prefix: "/users"
security:
auth:
enabled: true
header: "X-Api-Key"
path:
include: []
exclude: ["/users/login", "/users/register"]
### Multiple Backends
```yaml
hosts:
- id: "host-001"
name: "api-host"
domain: "api.example.com"
- id: "host-002"
name: "admin-host"
domain: "admin.example.com"
targets:
- id: "target-001"
name: "api-backend"
url: "https://api-backend.internal"
- id: "target-002"
name: "admin-backend"
url: "https://admin-backend.internal"
apis:
- id: "api-001"
host: "host-001"
target: "target-001"
- id: "api-002"
host: "host-002"
target: "target-002"
routes:
- id: "route-001"
- id: "admin-route"
api: "api-001"
path: "/api/*"
- id: "route-002"
api: "api-002"
path: "/admin/*"
strip_prefix:
enabled: true
prefix: "/admin"
security:
auth:
enabled: true
header: "Authorization"
path:
include: []
exclude: []
```
## Configuration Loading
@@ -244,3 +304,23 @@ The gateway uses chi/v5 routing patterns. Common patterns:
| `/files/*` | `/files/` and any subpath | `/files/doc.pdf` |
Note: `/*` matches zero or more path segments.
## Debug Logging
To see detailed authentication decisions, enable DEBUG logging in `main.go`:
```go
masterlog.SetLevel(masterlog.LevelDebug)
```
This will output logs like:
```json
{
"level": "debug",
"msg": "AuthMiddleware: Checking if path requires auth",
"path": "/api/v1/users",
"requires_auth": true,
"include": [],
"exclude": ["/api/v1/public/*"]
}
```

View File

@@ -56,6 +56,109 @@ curl -X PUT http://localhost:8080/api/v1/data/123
curl -X DELETE http://localhost:8080/api/v1/data/123
```
## Authentication
### Using API Key Authentication
**Configuration:**
```yaml
security:
auth:
enabled: true
header: "X-Api-Key"
```
**Valid request:**
```bash
curl -H "X-Api-Key: your-secret-key" http://localhost:8080/api/v1/data
```
**Invalid request (missing header):**
```bash
curl http://localhost:8080/api/v1/data
# Returns: 401 Unauthorized
```
### Using Bearer Token Authentication
**Configuration:**
```yaml
security:
auth:
enabled: true
header: "Authorization"
```
**Valid request:**
```bash
curl -H "Authorization: Bearer your-token-here" http://localhost:8080/api/v1/data
```
### Path-Based Authentication Examples
#### All Paths Require Auth
**Configuration:**
```yaml
security:
auth:
enabled: true
header: "X-Api-Key"
path:
include: []
exclude: []
```
All requests to this route require authentication.
#### Only Specific Paths Require Auth (Include)
**Configuration:**
```yaml
security:
auth:
enabled: true
header: "X-Api-Key"
path:
include: ["/api/admin/*", "/api/users/*/profile"]
exclude: []
```
**Request examples:**
```bash
# Requires auth (matches include)
curl -H "X-Api-Key: key" http://localhost:8080/api/admin/users
curl -H "X-Api-Key: key" http://localhost:8080/api/users/123/profile
# No auth required (doesn't match include)
curl http://localhost:8080/api/public/data
curl http://localhost:8080/api/health
```
#### All Paths Except Specific Ones Require Auth (Exclude)
**Configuration:**
```yaml
security:
auth:
enabled: true
header: "X-Api-Key"
path:
include: []
exclude: ["/api/health", "/api/public/*"]
```
**Request examples:**
```bash
# No auth required (matches exclude)
curl http://localhost:8080/api/health
curl http://localhost:8080/api/public/data
# Requires auth (doesn't match exclude)
curl -H "X-Api-Key: key" http://localhost:8080/api/users
curl -H "X-Api-Key: key" http://localhost:8080/api/admin/settings
```
## Route Examples
### Strip Prefix Example
@@ -87,6 +190,9 @@ routes:
strip_prefix:
enabled: true
prefix: "/public"
security:
auth:
enabled: false
- id: "api-route"
api: "api-001"
@@ -94,15 +200,19 @@ routes:
strip_prefix:
enabled: true
prefix: "/api/v1"
security:
auth:
enabled: true
header: "X-Api-Key"
```
**Requests:**
```bash
# Public route - backend receives /status
# Public route - no auth, backend receives /status
curl http://localhost:8080/public/status
# API route - backend receives /users
curl http://localhost:8080/api/v1/users
# API route - requires auth, backend receives /users
curl -H "X-Api-Key: key" http://localhost:8080/api/v1/users
```
## Logging
@@ -130,6 +240,27 @@ The gateway uses structured JSON logging via `masterlog`. All HTTP requests are
}
```
### Debug Logging
To enable detailed authentication logging, set DEBUG level in `main.go`:
```go
masterlog.SetLevel(masterlog.LevelDebug)
```
This will output detailed auth decision logs:
```json
{
"level": "debug",
"msg": "AuthMiddleware: Checking if path requires auth",
"path": "/api/v1/users",
"requires_auth": true,
"include": [],
"exclude": ["/api/v1/public/*"]
}
```
## Troubleshooting
### Gateway fails to start
@@ -146,6 +277,13 @@ cat ../gateway.yaml
3. Check logs for detailed error messages
### 401 Unauthorized
- Verify auth header is included
- Check header name matches configuration (case-sensitive)
- Verify path is not excluded from auth (check exclude patterns)
- Enable DEBUG logging to see auth decision process
### 404 Not Found
- Verify the route path matches your request
@@ -165,6 +303,22 @@ cat ../gateway.yaml
- Remember that `/*` matches zero or more path segments
- Check that multiple routes don't have conflicting patterns
### Auth not working as expected
1. Enable DEBUG logging to see auth decisions:
```go
masterlog.SetLevel(masterlog.LevelDebug)
```
2. Check the logs for:
- Whether the path requires auth
- Whether the auth header is present
- Which patterns are being matched
3. Verify your include/exclude patterns:
- Wildcards end with `*`
- Patterns are prefix-based (e.g., `/api/v1/public/test/*`)
## Performance Considerations
- The gateway uses Go's `httputil.ReverseProxy` for efficient proxying
@@ -175,23 +329,57 @@ cat ../gateway.yaml
## Common Patterns
### API Versioning
### API Versioning with Auth
```yaml
routes:
- id: "v1-api"
- id: "v1-public-api"
api: "api-001"
path: "/api/v1/public/*"
strip_prefix:
enabled: true
prefix: "/api/v1/public"
security:
auth:
enabled: false
- id: "v1-protected-api"
api: "api-001"
path: "/api/v1/*"
strip_prefix:
enabled: true
prefix: "/api/v1"
security:
auth:
enabled: true
header: "X-Api-Key"
```
- id: "v2-api"
### Mixed Authentication
```yaml
routes:
- id: "user-api"
api: "api-001"
path: "/api/v2/*"
strip_prefix:
enabled: true
prefix: "/api/v2"
path: "/users/*"
security:
auth:
enabled: true
header: "X-Api-Key"
path:
include: []
exclude: ["/users/login", "/users/register"]
- id: "admin-api"
api: "api-001"
path: "/admin/*"
security:
auth:
enabled: true
header: "Authorization"
path:
include: []
exclude: []
```
### Microservices Routing
@@ -220,6 +408,10 @@ routes:
strip_prefix:
enabled: true
prefix: "/users"
security:
auth:
enabled: true
header: "X-Api-Key"
- id: "orders-route"
api: "order-api"
@@ -227,28 +419,8 @@ routes:
strip_prefix:
enabled: true
prefix: "/orders"
security:
auth:
enabled: true
header: "Authorization"
```
### External API Proxy
```yaml
targets:
- id: "external-api"
name: "External API"
url: "https://api.external.com"
apis:
- id: "external"
host: "host-001"
target: "external-api"
routes:
- id: "external-proxy"
api: "external"
path: "/proxy/external/*"
strip_prefix:
enabled: true
prefix: "/proxy/external"
```
Request to `/proxy/external/users` becomes `/users` on the external API.

198
app/middlewares/auth.go Normal file
View File

@@ -0,0 +1,198 @@
package middlewares
import (
"net/http"
"strings"
"git.secnex.io/secnex/api-gateway/config"
"git.secnex.io/secnex/masterlog"
)
// AuthMiddleware handles authentication based on header validation and path filtering
type AuthMiddleware struct {
header string
pathConfig config.AuthPath
handler http.Handler
}
// NewAuthMiddleware creates a new authentication middleware
func NewAuthMiddleware(header string, pathConfig config.AuthPath, handler http.Handler) http.Handler {
masterlog.Debug("Creating AuthMiddleware", map[string]interface{}{
"header": header,
"include_paths": pathConfig.Include,
"exclude_paths": pathConfig.Exclude,
})
return &AuthMiddleware{
header: header,
pathConfig: pathConfig,
handler: handler,
}
}
// ServeHTTP handles the authentication logic
func (m *AuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
requestPath := r.URL.Path
// Step 1: Determine if this path requires authentication
requiresAuth := m.requiresAuth(requestPath)
masterlog.Debug("AuthMiddleware: Checking if path requires auth", map[string]interface{}{
"path": requestPath,
"requires_auth": requiresAuth,
"include": m.pathConfig.Include,
"exclude": m.pathConfig.Exclude,
})
if !requiresAuth {
// No auth required, skip to next handler
masterlog.Debug("AuthMiddleware: Skipping auth for path", map[string]interface{}{
"path": requestPath,
"reason": "path_matches_exclude_or_not_include",
})
m.handler.ServeHTTP(w, r)
return
}
// Step 2: Check if auth header is present
authHeader := r.Header.Get(m.header)
masterlog.Debug("AuthMiddleware: Checking auth header", map[string]interface{}{
"path": requestPath,
"header_name": m.header,
"header_present": authHeader != "",
})
if authHeader == "" {
masterlog.Warn("AuthMiddleware: Missing auth header", map[string]interface{}{
"path": requestPath,
"header": m.header,
})
http.Error(w, "Unauthorized: Missing authentication header", http.StatusUnauthorized)
return
}
// Step 3: Auth header present, remove it before forwarding
// (don't send the auth header to the backend)
r.Header.Del(m.header)
masterlog.Debug("AuthMiddleware: Authentication successful", map[string]interface{}{
"path": requestPath,
})
// Step 4: Forward to next handler
m.handler.ServeHTTP(w, r)
}
// requiresAuth determines if a given path requires authentication
//
// Logic:
// 1. If BOTH include and exclude are empty → auth required for ALL paths
// 2. If ONLY include is set (non-empty) → auth required ONLY for paths matching include patterns
// 3. If ONLY exclude is set (non-empty) → auth required for ALL paths EXCEPT those matching exclude patterns
// 4. If BOTH are set → include takes precedence (auth required ONLY for paths matching include patterns)
//
// Wildcard patterns are supported:
// - "*" matches any path
// - "/api/*" matches "/api/" and any subpath like "/api/users", "/api/users/123"
// - "/api/v1/public/test/*" matches "/test", "/test/123", etc.
func (m *AuthMiddleware) requiresAuth(path string) bool {
include := m.pathConfig.Include
exclude := m.pathConfig.Exclude
includeEmpty := len(include) == 0
excludeEmpty := len(exclude) == 0
masterlog.Debug("AuthMiddleware: Evaluating auth requirement", map[string]interface{}{
"path": path,
"include_empty": includeEmpty,
"exclude_empty": excludeEmpty,
"include": include,
"exclude": exclude,
})
// Case 1: Both include and exclude are empty → auth required for ALL
if includeEmpty && excludeEmpty {
masterlog.Debug("AuthMiddleware: Both include/exclude empty, auth required for all", map[string]interface{}{
"path": path,
})
return true
}
// Case 2: Only include is set → auth required ONLY for matching paths
if !includeEmpty {
for _, pattern := range include {
if m.matchPattern(path, pattern) {
masterlog.Debug("AuthMiddleware: Path matches include pattern", map[string]interface{}{
"path": path,
"pattern": pattern,
})
return true
}
}
masterlog.Debug("AuthMiddleware: Path does not match any include pattern", map[string]interface{}{
"path": path,
"patterns": include,
})
return false
}
// Case 3: Only exclude is set (include is empty) → auth required EXCEPT for matching paths
// This is also reached when both are set (include takes precedence above)
for _, pattern := range exclude {
if m.matchPattern(path, pattern) {
masterlog.Debug("AuthMiddleware: Path matches exclude pattern", map[string]interface{}{
"path": path,
"pattern": pattern,
})
return false
}
}
masterlog.Debug("AuthMiddleware: Path does not match any exclude pattern, auth required", map[string]interface{}{
"path": path,
"patterns": exclude,
})
return true
}
// matchPattern checks if a path matches a wildcard pattern
//
// Supported patterns:
// - "*" matches any path
// - "/api/*" matches "/api/" and any subpath
// - "/api/v1/public/test/*" matches the exact prefix and any subpath
//
// The pattern matching is prefix-based. If the pattern ends with "*",
// it matches any path that starts with the pattern (excluding the "*").
func (m *AuthMiddleware) matchPattern(path, pattern string) bool {
// Wildcard: matches everything
if pattern == "*" {
masterlog.Debug("AuthMiddleware: Wildcard pattern matches", map[string]interface{}{
"path": path,
})
return true
}
// Pattern ends with wildcard: prefix matching
if strings.HasSuffix(pattern, "*") {
prefix := strings.TrimSuffix(pattern, "*")
matches := strings.HasPrefix(path, prefix)
masterlog.Debug("AuthMiddleware: Prefix pattern matching", map[string]interface{}{
"path": path,
"pattern": pattern,
"prefix": prefix,
"matches": matches,
})
return matches
}
// Exact match
matches := path == pattern
masterlog.Debug("AuthMiddleware: Exact pattern matching", map[string]interface{}{
"path": path,
"pattern": pattern,
"matches": matches,
})
return matches
}

View File

@@ -5,6 +5,7 @@ import (
"strings"
"git.secnex.io/secnex/api-gateway/config"
"git.secnex.io/secnex/api-gateway/middlewares"
"git.secnex.io/secnex/masterlog"
"github.com/go-chi/chi/v5"
)
@@ -17,6 +18,7 @@ type Route struct {
ID string
Path string
StripPrefix config.StripPrefix
Security config.Security
Api *Api
}
@@ -27,6 +29,7 @@ func NewRoutes(cfg *config.Configuration, apis Apis) *Routes {
ID: route.ID,
Path: route.Path,
StripPrefix: route.StripPrefix,
Security: route.Security,
Api: apis[route.Api],
})
}
@@ -36,9 +39,13 @@ func NewRoutes(cfg *config.Configuration, apis Apis) *Routes {
func (rs *Routes) Register(r *chi.Mux) {
for _, route := range rs.routes {
masterlog.Info("Registering route", map[string]interface{}{
"id": route.ID,
"path": route.Path,
"api": route.Api.ID,
"id": route.ID,
"path": route.Path,
"api": route.Api.ID,
"auth_enabled": route.Security.Auth.Enabled,
"auth_header": route.Security.Auth.Header,
"auth_include": route.Security.Auth.Path.Include,
"auth_exclude": route.Security.Auth.Path.Exclude,
})
handler := route.createHandler()
@@ -47,9 +54,29 @@ func (rs *Routes) Register(r *chi.Mux) {
}
func (r *Route) createHandler() http.Handler {
// Start with the API (proxy) handler
handler := http.Handler(r.Api)
// Apply middlewares in reverse order (last one wraps first)
// 1. Auth middleware (if enabled)
if r.Security.Auth.Enabled {
masterlog.Debug("Route: Applying Auth middleware", map[string]interface{}{
"path": r.Path,
"header": r.Security.Auth.Header,
})
handler = middlewares.NewAuthMiddleware(
r.Security.Auth.Header,
r.Security.Auth.Path,
handler,
)
}
// 2. Strip prefix middleware (if enabled)
if r.StripPrefix.Enabled {
masterlog.Debug("Route: Applying StripPrefix middleware", map[string]interface{}{
"path": r.Path,
"prefix": r.StripPrefix.Prefix,
})
handler = newStripPrefixMiddleware(r.StripPrefix.Prefix, handler)
}