Skip to content

feat: auth middleware for createDurablyHandler#76

Merged
coji merged 6 commits into
mainfrom
feat/auth-middleware
Mar 6, 2026
Merged

feat: auth middleware for createDurablyHandler#76
coji merged 6 commits into
mainfrom
feat/auth-middleware

Conversation

@coji
Copy link
Copy Markdown
Owner

@coji coji commented Mar 6, 2026

Summary

  • Add built-in auth option to createDurablyHandler for multi-tenant authentication and authorization
  • authenticateonTriggeronRunAccessscopeRuns / scopeRunsSubscribe hook chain with typed TContext and TLabels generics
  • Only handle() method exposed (closure-scoped handlers prevent auth bypass)
  • Includes design document, full test coverage (10 new auth tests), and doc updates

Closes #65

Changes

Commit Description
d3970fc Design document (4 rounds of codex review)
2f01532 Implementation: AuthConfig, RunOperation, RunsSubscribeFilter types, closure-scoped handlers
a47cfdb Simplification: extract withErrorHandling, requireRunAccess helpers, pass parsed URL, fix run existence checks
5fbd494 Update llms.md, http-handler.md, index.md, regenerate llms.txt

Test plan

  • All 144 tests pass (pnpm validate)
  • 10 new auth middleware tests covering authenticate, onTrigger, onRunAccess, scopeRuns, handle-only exposure
  • Existing server tests updated to use handle() routing
  • TypeScript type inference verified (TContext from authenticate, TLabels from Durably instance)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Built-in authentication middleware with configurable guards (authenticate, trigger and run access hooks).
    • Multi-tenant scoping hooks to filter visibility of runs per context.
    • Per-request onRequest lifecycle hook for lazy initialization.
  • Documentation
    • Detailed design and migration guidance for the auth-enabled handler model.
  • Breaking Changes
    • HTTP handler now exposes a unified handle(request, basePath) surface; previous per-endpoint handler methods removed.

coji and others added 4 commits March 6, 2026 13:40
Design for issue #65. Adds authenticate/onTrigger/onRunAccess/scopeRuns
hooks to createDurablyHandler with TContext + TLabels generics.

Key decisions:
- auth nested object with required authenticate
- handle() only public method (closure-scoped internals)
- authenticate before onRequest (fail fast)
- validate before auth hooks (no misleading errors)
- RunFilter reused for scopeRuns, separate RunsSubscribeFilter
- operation metadata on onRunAccess for fine-grained control

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Breaking change: DurablyHandler now only exposes handle() method.
Individual methods (trigger, runs, run, etc.) are removed from the
public interface and implemented as closure-scoped helpers.

Auth middleware features:
- auth.authenticate: runs first, returns TContext, fail fast
- auth.onTrigger: guards triggers (after body validation + job lookup)
- auth.onRunAccess: guards run operations with operation metadata
- auth.scopeRuns: transforms RunFilter for list queries
- auth.scopeRunsSubscribe: transforms filter for SSE subscriptions
- TLabels inferred from Durably instance for type-safe labels
- Response throwing pattern for rejection
- Query parameter validation (status, limit, offset)

Closes #65

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract withErrorHandling() to deduplicate try/catch in 7 handlers
- Extract requireRunAccess() to deduplicate run fetch + auth check in 6 handlers
- Pass parsed URL from handle() to handlers instead of re-parsing
- Use Pick<RunFilter, ...> for RunsSubscribeFilter instead of duplicating fields
- Remove dead else branch in handleRunsSubscribe
- Use parseRunsSubscribeFilter in scopeRuns fallback (was parsing unused fields)
- Fix: all run-level endpoints now check run existence even without auth
- Derive VALID_STATUSES from const array with satisfies for type safety

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- llms.md: rewrite createDurablyHandler section with auth middleware,
  remove individual handler methods, add AuthConfig/RunOperation types
- http-handler.md: replace "Individual Handlers" and "Security
  Considerations" sections with built-in auth middleware docs
- index.md: add missing type exports (AuthConfig, RunOperation, etc.)
- Regenerate website/public/llms.txt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
durably-demo Ready Ready Preview Mar 6, 2026 10:25am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

Adds an auth middleware to createDurablyHandler: generic AuthConfig with authenticate, onTrigger, onRunAccess, scopeRuns, scopeRunsSubscribe hooks; refactors handler surface to a single handle(request, basePath) entry; introduces RunOperation and label-generic TriggerRequest and related parsing/authorization flows.

Changes

Cohort / File(s) Summary
Design & Documentation
docs/design-auth-middleware.md, packages/durably/docs/llms.md, website/api/http-handler.md, website/public/llms.txt, website/api/index.md
Adds a design doc and updates docs to describe the new auth middleware, generics (TContext, TLabels), execution order (authenticate → onRequest → validation → hooks → operation), and public API/type changes.
Public exports & types
packages/durably/src/index.ts, packages/durably/src/server.ts
Introduces and exports AuthConfig<TContext,TLabels>, RunOperation, RunsSubscribeFilter, generic TriggerRequest<TLabels>, and generic CreateDurablyHandlerOptions<TContext,TLabels>. Updates DurablyHandler to expose only handle(request, basePath).
Server implementation
packages/durably/src/server.ts
Refactors createDurablyHandler to be auth-aware and generic; adds auth.authenticate call, onRequest hook sequence, auth hook invocations (onTrigger, onRunAccess, scopeRuns, scopeRunsSubscribe), path-based routing to new internal handlers, parsing/validation helpers, requireRunAccess, SSE stream builder, and unified error handling.
Tests
packages/durably/tests/node/core-extensions.test.ts, packages/durably/tests/shared/server.shared.ts
Updates tests to call handler.handle(request, basePath) and to new endpoint paths; adds auth middleware test suite covering authenticate/onRequest ordering, onTrigger and onRunAccess guards, scopeRuns filtering, and error/Response behavior.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Handler as DurablyHandler
    participant Auth as AuthConfig
    participant Router as Router
    participant Endpoint as EndpointHandler

    Client->>Handler: handle(request, basePath)
    Handler->>Auth: authenticate(request)
    Auth-->>Handler: ctx
    Handler->>Handler: onRequest() [optional]
    Handler->>Router: determine path & method

    alt Trigger
        Router->>Endpoint: handleTrigger(triggerRequest)
        Endpoint->>Auth: onTrigger(ctx, triggerRequest)
        Auth-->>Endpoint: allow / throw Response
        Endpoint-->>Handler: Response
    else Run-level op (read/subscribe/steps/retry/cancel/delete)
        Router->>Endpoint: handleRunOperation(runId,...)
        Endpoint->>Auth: requireRunAccess(ctx, run, {operation})
        Auth-->>Endpoint: allow / throw Response
        Endpoint-->>Handler: Response
    else Runs list / subscribe
        Router->>Endpoint: handleRuns/handleRunsSubscribe(filter)
        Endpoint->>Auth: scopeRuns(scopeRunsSubscribe)(ctx, filter)
        Auth-->>Endpoint: scopedFilter
        Endpoint-->>Handler: Response / SSE
    end

    Handler-->>Client: Response / Stream
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped into middleware, nose all a-twitch,
I wove auth hooks fine, without a single glitch.
One handle to rule them, one flow to share,
Tenants guarded safe with meticulous care.
Hooray — secure hops everywhere! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: auth middleware for createDurablyHandler' clearly and specifically describes the primary change—adding authentication middleware functionality to the handler creation function.
Linked Issues check ✅ Passed The PR implements all core objectives from issue #65: adds AuthConfig with authenticate, onTrigger, onRunAccess, and scope hooks; centralizes auth via handle(); introduces TContext and TLabels generics; provides comprehensive test coverage for all auth scenarios.
Out of Scope Changes check ✅ Passed All changes directly support the auth middleware feature: design documentation, type definitions (AuthConfig, RunOperation, RunsSubscribeFilter, TriggerRequest), implementation with closure-scoped handlers, handler refactoring to use handle(), and comprehensive test coverage. No unrelated changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/auth-middleware

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
docs/design-auth-middleware.md (1)

263-292: Consider adding a language identifier to the execution flow code block.

The ASCII diagram at line 263 uses a fenced code block without a language identifier. While the diagram is readable, adding text or plaintext as the language would satisfy linter expectations.

📝 Suggested fix
-```
+```text
 handle(request, basePath)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design-auth-middleware.md` around lines 263 - 292, The fenced ASCII flow
diagram starting with "handle(request, basePath)" is missing a language
identifier; update the code fence that wraps the diagram to include a plain-text
identifier (e.g., "text" or "plaintext") so the linter recognizes it—locate the
block containing the diagram lines beginning with "handle(request, basePath)"
and replace the opening ``` with ```text (or ```plaintext).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@website/api/http-handler.md`:
- Around line 195-198: The example's TContext is inconsistent: authenticate
returns { orgIds: new Set(...) } but scopeRuns and scopeRunsSubscribe read
ctx.currentOrgId; update the authenticate implementation to return both orgIds
and a currentOrgId (e.g., pick the primary/orgs[0].id or derive from session) so
ctx.currentOrgId exists, or alternatively update scopeRuns and
scopeRunsSubscribe to use ctx.orgIds instead; locate the authenticate function
and the scopeRuns/scopeRunsSubscribe hooks and make the shapes consistent
(ensure TContext includes both orgIds: Set<string> and currentOrgId: string if
you choose the first option).

---

Nitpick comments:
In `@docs/design-auth-middleware.md`:
- Around line 263-292: The fenced ASCII flow diagram starting with
"handle(request, basePath)" is missing a language identifier; update the code
fence that wraps the diagram to include a plain-text identifier (e.g., "text" or
"plaintext") so the linter recognizes it—locate the block containing the diagram
lines beginning with "handle(request, basePath)" and replace the opening ```
with ```text (or ```plaintext).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 98da50b9-f504-4de2-8210-a1db2890ab3f

📥 Commits

Reviewing files that changed from the base of the PR and between 0b36cdb and 5fbd494.

📒 Files selected for processing (9)
  • docs/design-auth-middleware.md
  • packages/durably/docs/llms.md
  • packages/durably/src/index.ts
  • packages/durably/src/server.ts
  • packages/durably/tests/node/core-extensions.test.ts
  • packages/durably/tests/shared/server.shared.ts
  • website/api/http-handler.md
  • website/api/index.md
  • website/public/llms.txt

Comment thread website/api/http-handler.md Outdated
- Fix TContext inconsistency in doc examples: simplify to single orgId
  instead of orgIds Set with mismatched currentOrgId
- Add `text` language identifier to ASCII diagram code block in design doc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design-auth-middleware.md`:
- Around line 367-369: Update the design doc to remove the stale note about
using request.clone().json(); instead describe that handleTrigger parses the
incoming body once via request.json() and passes the parsed object to onTrigger
(mentioning handleTrigger and onTrigger by name), and explain that onTrigger
therefore receives the parsed body rather than re-reading the request stream so
no cloning or second read is performed.
- Around line 310-316: The runtime currently allows an auth object missing
authenticate (e.g., { onRunAccess() {} }) because handle() only checks if
(auth?.authenticate) and silently skips hooks, so add a validation guard inside
handle() where auth is read: if auth is provided but auth.authenticate is falsy,
throw a clear error (e.g., "auth provided but missing required 'authenticate'
hook") so the process fails-fast; locate the auth usage in handle() and validate
before any auth hooks (onTrigger, onRunAccess, scopeRuns, scopeRunsSubscribe)
are invoked to ensure ctx cannot remain undefined.

In `@packages/durably/docs/llms.md`:
- Around line 378-403: Update the documented TypeScript interfaces to match the
source: change the CreateDurablyHandlerOptions declaration to include the
generic defaults and constraints (TContext = undefined, TLabels extends
Record<string, string> = Record<string, string>) and change AuthConfig to
declare TLabels extends Record<string, string> = Record<string, string>; ensure
the interface names CreateDurablyHandlerOptions and AuthConfig are edited so the
docs reflect the exact signatures used in packages/durably/src/server.ts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83778ec9-8758-4d0e-addb-ad5e0ed75d46

📥 Commits

Reviewing files that changed from the base of the PR and between 5fbd494 and 705b5d3.

📒 Files selected for processing (4)
  • docs/design-auth-middleware.md
  • packages/durably/docs/llms.md
  • website/api/http-handler.md
  • website/public/llms.txt

Comment thread docs/design-auth-middleware.md Outdated
Comment thread docs/design-auth-middleware.md Outdated
Comment thread packages/durably/docs/llms.md Outdated
- Add runtime validation: throw if auth provided without authenticate
- Fix stale request.clone().json() note in design doc
- Add generic defaults/constraints to llms.md type signatures
- Update design doc to reflect runtime validation guard
- Add test for auth without authenticate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coji coji merged commit 57ca4af into main Mar 6, 2026
4 checks passed
@coji coji deleted the feat/auth-middleware branch March 6, 2026 10:41
@coji coji mentioned this pull request Mar 6, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add authentication/authorization middleware hooks to createDurablyHandler

1 participant