Skip to content

refactor(api): return resolved Principal from requireAuth/authenticate so handlers don't re-fetch identity #194

@cristim

Description

@cristim

Problem

Handler.requireAuth (added in #84 via the router-layer enforcement for AuthUser, see internal/api/middleware.go:220) returns only error. It internally resolves the caller's identity via Handler.authenticate() (admin API key → user API key → bearer-token session, internal/api/middleware.go:44-57), but throws that resolved identity away.

Most AuthUser handlers then need the session/principal anyway and re-resolve it inside the handler — a redundant pass through the same code path that was just executed.

AuthUser routes that need the resolved identity (non-exhaustive, from internal/api/router.go):

  • /api/auth/logout (POST)
  • /api/auth/me (GET)
  • /api/auth/profile (PUT)
  • /api/auth/change-password (POST)
  • /api/api-keys (GET/POST)
  • /api/api-keys/:id/revoke (POST)
  • /api/api-keys/:id (DELETE)
  • /api/federation/iac (GET)
  • /api/commitment-options (GET)

Proposal

Refactor authenticate() (or add a sibling) to return the resolved principal alongside the auth verdict, and propagate it through requireAuth so handlers can take it from a single source instead of re-resolving.

Sketch:

type Principal struct {
    Kind    PrincipalKind  // admin-api-key | user-api-key | session
    UserID  string         // empty for admin-api-key
    Session *Session       // non-nil for session-bearer; optional for user-api-key
}

func (h *Handler) authenticatePrincipal(ctx context.Context, req *events.LambdaFunctionURLRequest) (*Principal, error)

func (h *Handler) requireAuth(ctx context.Context, req *events.LambdaFunctionURLRequest) (*Principal, error)

Router dispatch keeps its current shape; the handler signatures that need identity gain a *Principal parameter (or read it from a request-scoped context value if a parameter change is too invasive).

Constraints

  • Must not regress credential acceptance. All three principal kinds (admin API key, user API key, bearer session) must remain accepted on AuthUser routes. PR fix(security): enforce AuthUser at the router layer, not just middleware (closes #60) #178 attempted a similar refactor with a narrower helper (requireUser) and silently dropped user API keys — that's the failure mode to avoid.
  • No threat-model change. This is purely an ergonomics refactor; it must produce the same auth decision as requireAuth for every input.
  • Tests: extend internal/api/router_authuser_test.go to assert that the Principal returned by requireAuth is correctly populated for each principal kind, and that handlers wired to use it observe consistent identity.

Out of scope

  • AuthAdmin / AuthInternal enforcement (already covered by their own helpers).
  • Changing what counts as a valid AuthUser credential.

Background

Surfaced while triaging PR #178 (which is being closed as superseded by #84). #178's requireUser returned (*Session, error) and was the seed of this idea, but its narrowing of accepted credentials made it a regression rather than an improvement. This issue captures the ergonomic part of #178's intent without the security regression.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions