Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ return libHTTP.OK(c, pagination)
```
```


### Agent 2: Error Framework Auditor

```prompt
Expand Down Expand Up @@ -275,9 +276,9 @@ func RegisterRoutes(protected func(resource, action string) fiber.Router, handle
if handler == nil {
return errors.New("handler is nil")
}
protected("resource", "create").Post("/v1/resources", handler.Create)
protected("resource", "read").Get("/v1/resources", handler.List)
protected("resource", "read").Get("/v1/resources/:id", handler.Get)
protected("resources", "create").Post("/v1/resources", handler.Create)
protected("resources", "read").Get("/v1/resources", handler.List)
protected("resources", "read").Get("/v1/resources/:id", handler.Get)
return nil
}

Expand All @@ -295,13 +296,16 @@ func NewHandler(deps ...interface{}) (*Handler, error) {
2. (HARD GATE) Centralized route registration per module
3. Handler constructors validate all dependencies
4. Consistent URL patterns (v1, kebab-case, plural resources) per Ring conventions
5. All routes use protected() wrapper (no public endpoints without explicit exemption)
6. Clear separation: routes.go vs handlers.go per Ring directory structure
5. Authorization resource names are plural and match the route collection (for example, `/v1/resources` -> `protected("resources", ...)`)
6. Singular authorization resource names only appear for protected singleton/capability endpoints, with a local comment explaining the exception
7. All routes use protected() wrapper (no public endpoints without explicit exemption)
8. Clear separation: routes.go vs handlers.go per Ring directory structure

**Severity Ratings:**
- CRITICAL: Unprotected routes (missing auth middleware)
- CRITICAL: HARD GATE violation — project does not follow hexagonal architecture per Ring standards
- HIGH: Scattered route definitions
- HIGH: Authorization resource name is singular for a collection route
- MEDIUM: Handler accepts nil dependencies
- LOW: Inconsistent URL naming conventions

Expand Down Expand Up @@ -1453,4 +1457,3 @@ async function fetchData(url: string): Promise<Response> {
1. ...
```
```

12 changes: 7 additions & 5 deletions dev-team/docs/standards/golang/multi-tenant.md
Original file line number Diff line number Diff line change
Expand Up @@ -1670,7 +1670,7 @@ MULTI_TENANT_ENABLED=true MULTI_TENANT_URL=http://dispatch layer:4003 go test ./
```go
// ❌ WRONG: Tenant middleware runs before auth on ALL routes
app.Use(tenantMid.WithTenantDB) // Runs first — calls TM API before auth validates JWT
app.Post("/v1/resources", auth.Authorize("app", "resource", "post"), handler.Create)
app.Post("/v1/resources", auth.Authorize("app", "resources", "post"), handler.Create)
```

In this pattern, `WithTenantDB` executes for **every request** before `auth.Authorize` validates the JWT. A request with a forged JWT containing `tenantId: "victim-tenant"` triggers a full Tenant Manager resolution — fetching credentials, opening connections — before auth rejects it.
Expand Down Expand Up @@ -1700,11 +1700,13 @@ func WhenEnabled(middleware fiber.Handler) fiber.Handler {
```go
// ✅ CORRECT: Auth validates JWT FIRST, then tenant resolves DB
// ttHandler is nil when MULTI_TENANT_ENABLED=false (single-tenant passthrough)
f.Post("/v1/resources", auth.Authorize("app", "resource", "post"), WhenEnabled(ttHandler), handler.Create)
f.Get("/v1/resources", auth.Authorize("app", "resource", "get"), WhenEnabled(ttHandler), handler.GetAll)
f.Get("/v1/resources/:id", auth.Authorize("app", "resource", "get"), WhenEnabled(ttHandler), handler.GetByID)
f.Post("/v1/resources", auth.Authorize("app", "resources", "post"), WhenEnabled(ttHandler), handler.Create)
f.Get("/v1/resources", auth.Authorize("app", "resources", "get"), WhenEnabled(ttHandler), handler.GetAll)
f.Get("/v1/resources/:id", auth.Authorize("app", "resources", "get"), WhenEnabled(ttHandler), handler.GetByID)
```

`auth.Authorize(..., resource, ...)` MUST use the plural resource name that matches the collection route. Singular resources are allowed only for protected singleton/capability endpoints that are not collection routes, and the exception must be documented next to the route.

**How it works:**
1. `auth.Authorize(...)` is the first handler — validates JWT before anything else
2. `WhenEnabled(ttHandler)` runs second — if `ttHandler` is nil (single-tenant mode), it calls `c.Next()` immediately; if non-nil, it executes the tenant middleware
Expand Down Expand Up @@ -2675,4 +2677,4 @@ The service catalog enforces a maximum of 2 active keys per environment, so both
|-----------------|----------------|-----------------|
| "We don't need API key auth for internal services" | The `/settings` endpoint returns database credentials. Unauthenticated access is a security risk. | **MUST configure `WithServiceAPIKey`** |
| "We'll add the API key later" | Without authentication, the Tenant Manager rejects `/settings` requests. The service cannot resolve tenant connections. | **MUST configure before enabling multi-tenant** |
| "We can use a shared API key across services" | Each service MUST have its own API key for audit trail and independent revocation. | **MUST generate per-service keys via service catalog** |
| "We can use a shared API key across services" | Each service MUST have its own API key for audit trail and independent revocation. | **MUST generate per-service keys via service catalog** |
13 changes: 10 additions & 3 deletions dev-team/docs/standards/golang/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This module covers authentication, licensing, and secret protection.

---


## Table of Contents

| # | Section | Description |
Expand Down Expand Up @@ -171,6 +172,10 @@ auth.Authorize(applicationName, resource, action)
| `resource` | string | Resource being accessed | `"ledgers"`, `"transactions"`, `"packages"` |
| `action` | string | HTTP method (lowercase) | `"get"`, `"post"`, `"patch"`, `"delete"` |

**Resource naming rule:** `resource` MUST be plural and MUST match the REST collection name used by the route. For example, `POST /v1/payments` authorizes `"payments"`, not `"payment"`; `GET /v1/boletos/:id` authorizes `"boletos"`, not `"boleto"`.

**Allowed exception:** use a singular resource only when the endpoint protects a singleton or capability that is not modeled as a collection route (for example, `/version` if it ever becomes protected). Document the exception next to the route registration and in the PR description.

### Middleware Behavior

| Scenario | HTTP Response |
Expand Down Expand Up @@ -272,13 +277,16 @@ req.Header.Set("Authorization", "Bearer hardcoded-token-here") // never
f.Post("/v1/sensitive-data", handler.Create) // Missing auth.Authorize

// FORBIDDEN: Using wrong application name
auth.Authorize("wrong-app-name", "resource", "post") // Must match identity registration
auth.Authorize("wrong-app-name", "resources", "post") // Must match identity registration

// FORBIDDEN: Singular resource for a collection route
auth.Authorize(applicationName, "payment", "post") // Route is /v1/payments, resource must be "payments"

// FORBIDDEN: Direct calls to plugin-auth API
http.Post("http://plugin-auth:4000/v1/authorize", ...) // Use lib-auth instead

// CORRECT: Always use lib-auth for auth operations
auth.Authorize(applicationName, "resource", "post")
auth.Authorize(applicationName, "resources", "post")
token, _ := auth.GetApplicationToken(ctx, clientID, clientSecret)
```

Expand Down Expand Up @@ -1952,4 +1960,3 @@ If any checkbox is unchecked → FIX before submitting.
```

---