Skip to content

feat(auth): RequireRoles middleware — fail closed, no permissive fallback #145

Closed

Description

@EricAndrechek

Problem

The README and AGENTS.md (design decision #11) advertise "Hasura-style access control" with per-table, per-role column- and row-level permissions. The data plane for this is in place — internal/policy/ evaluates JWT-claim-templated policies against WAVEHOUSE_POLICY KV — but there is no RequireRoles middleware that gates routes on a role list.

What exists today in internal/api/middleware.go:

  • JWTMiddleware parses the token and validates the signature.
  • extractClaim() reads the role string out of the configured claim path (default role).
  • The role is placed in r.Context() under ContextKeyRole.

What does not exist: any per-route check that says "this endpoint requires role X / Y / Z." Handlers can pull RoleFromContext(r) and gate themselves, but there's no enforcement primitive, no central policy, and no fail-closed default for routes that forget to gate. Shipping alpha with the public docs claiming RBAC enforcement, while the only enforcement is whatever the policy evaluator (or individual handlers) happen to do, is a credibility gap.

Proposed Solution

Build the middleware fail-closed from day one:

  • New RequireRoles(roles ...string) func(http.Handler) http.Handler in internal/api/middleware.go.
  • On entry: extract role from context (must already have been populated by JWTMiddleware).
  • If the role is missing/empty, or doesn't match any of the allowed roles, return 403 with writeJSONError.
  • If auth.dev_mode: true, bypass entirely (explicit configured intent, not a fallback).
  • If auth.enabled: false, also bypass (consistent with the rest of the auth stack).
  • Log a structured WARN on every denial (reason, role_observed, roles_allowed, route) so operators see misconfiguration immediately.

Then apply it to the routes that need it. Suggested initial scope:

  • /v1/query (raw SQL) — admin role only.
  • /v1/dlq/* — admin role only.
  • /v1/policy/* — admin role only.
  • /v1/pipes/* (mutations) — admin role only; reads can stay open or use the named-pipe's own role gate.
  • Ingest, structured query, SSE/WS — continue to rely on policy evaluator for per-table/per-row checks (this middleware is a coarser outer ring, not a replacement).

Tests

For each route: token-with-allowed-role → 200, token-with-wrong-role → 403, no-token (auth on) → 401, no-token (auth off) → 200, no-token (dev mode) → 200. Plus a fuzz-y "every role list permutation" table-driven test for the middleware itself.

Breaking change implications

For deployments that have auth.enabled: true today but no auth.role_claim configured (or a claim path that doesn't resolve), routes that newly require a role will start returning 403. Document under "Breaking changes" in CHANGELOG with the exact config remediation. Confirm against current deployments before tagging alpha.

Sequencing

Blocks v0.1.0-alpha.1 (#149). The README RBAC pitch and the actual enforcement need to match before public launch.

Related

Activity

  1. added
    enhancementNew feature or request
    area/apiHTTP handlers, routing, middleware
    securitySecurity-sensitive issue or fix
    breaking-changeBreaking change to public API, CLI, or config
    on May 15, 2026
  2. coderabbitai commented on May 15, 2026

    @coderabbitai
    ⚠️ Possible Duplicate Issue(s)
    🔗 Related PRs

    #123 - fix(api): drop CORS credentials + skip same-origin decoration [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  3. moved this from Backlog to Ready in WaveHouse Task Boardon May 18, 2026
  4. moved this from Ready to Backlog in WaveHouse Task Boardon May 20, 2026
  5. moved this from Backlog to Ready in WaveHouse Task Boardon May 20, 2026
  6. moved this from Ready to In progress in WaveHouse Task Boardon May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area/apiHTTP handlers, routing, middlewarearea/docsDocumentation, site/, READMEarea/policyAccess control policies (Hasura-style)breaking-changeBreaking change to public API, CLI, or configdocumentationImprovements or additions to documentationenhancementNew feature or requestsecuritySecurity-sensitive issue or fix

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions