Add tenant-aware HTTP middleware and router
internal/httpapi/ provides:
- Tenant{UserID, Email} carried on context.Context, with
WithTenant / TenantFrom helpers and ErrNoTenant for the
programming-error case (route reached without middleware).
- AuthMiddleware verifies an Authorization: Bearer <jwt> on every
request via auth.Signer.Verify (which already enforces HS256
and rejects alg=none). On failure: 401, with the underlying
reason logged server-side but not exposed to the client.
- RequireTenantOwnership(ownerID) compares the request's tenant
against the resource owner; returns 403 on mismatch. Handlers
that touch tenant-owned resources call this guard.
- Server.Routes() mounts /auth/* unauthenticated and wraps
/api/* with the middleware. /api/whoami is included as the
canonical example of a tenant-scoped endpoint.
Tests cover: valid JWT pass-through, missing/empty Authorization,
wrong scheme, malformed JWT, tampered signature, JWT signed with
a different secret (cross-tenant key confusion), and the 200/403
matrix for RequireTenantOwnership.
Closes #11.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
// Package httpapi provides the HTTP-facing layer for the multi-tenant
|
||||
// backend: auth middleware, tenant context propagation, and route
|
||||
// wiring for the auth and note endpoints.
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Tenant carries the per-request tenant identity extracted from a
|
||||
// validated JWT. It is the only thing handlers need to know about
|
||||
// "who is this request for".
|
||||
type Tenant struct {
|
||||
UserID string
|
||||
Email string
|
||||
}
|
||||
|
||||
type ctxKey struct{}
|
||||
|
||||
// ErrNoTenant indicates that handler code expected a tenant on the
|
||||
// request context but found none. This is always a programming error
|
||||
// (the route was reached without going through AuthMiddleware).
|
||||
var ErrNoTenant = errors.New("httpapi: no tenant in context")
|
||||
|
||||
// WithTenant returns a derived context carrying t.
|
||||
func WithTenant(ctx context.Context, t Tenant) context.Context {
|
||||
return context.WithValue(ctx, ctxKey{}, t)
|
||||
}
|
||||
|
||||
// TenantFrom retrieves the tenant from ctx. Panics are avoided by
|
||||
// returning ErrNoTenant when the value is missing.
|
||||
func TenantFrom(ctx context.Context) (Tenant, error) {
|
||||
v, ok := ctx.Value(ctxKey{}).(Tenant)
|
||||
if !ok {
|
||||
return Tenant{}, ErrNoTenant
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
Reference in New Issue
Block a user