@gjermundgaraba/effect-actions 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -41
- package/dist/Action.d.mts +140 -48
- package/dist/Action.mjs +88 -44
- package/dist/ActionCli.d.mts +70 -35
- package/dist/ActionCli.mjs +53 -34
- package/dist/ActionHttp.d.mts +70 -38
- package/dist/ActionHttp.mjs +76 -25
- package/dist/ActionMcp.d.mts +56 -24
- package/dist/ActionMcp.mjs +104 -61
- package/dist/ActionRpc.d.mts +222 -57
- package/dist/ActionRpc.mjs +301 -49
- package/dist/ActionToolkit.d.mts +22 -7
- package/dist/ActionToolkit.mjs +37 -14
- package/dist/Authentication.d.mts +42 -12
- package/dist/Authentication.mjs +38 -32
- package/dist/Event.d.mts +370 -0
- package/dist/Event.mjs +103 -0
- package/dist/Testing.d.mts +1 -3
- package/dist/Testing.mjs +2 -3
- package/dist/Webhook.d.mts +146 -0
- package/dist/Webhook.mjs +72 -0
- package/dist/authentication/provider.d.mts +35 -12
- package/dist/authentication/provider.mjs +2 -3
- package/dist/authentication/refusal.mjs +3 -4
- package/dist/cli/commands.d.mts +9 -7
- package/dist/cli/commands.mjs +85 -35
- package/dist/contract/ast.mjs +5 -3
- package/dist/contract/authorizer.d.mts +8 -0
- package/dist/contract/authorizer.mjs +20 -0
- package/dist/contract/call.d.mts +9 -8
- package/dist/contract/call.mjs +49 -8
- package/dist/contract/callback.d.mts +7 -0
- package/dist/contract/callback.mjs +27 -0
- package/dist/contract/caller.d.mts +12 -3
- package/dist/contract/caller.mjs +23 -5
- package/dist/contract/canonical.mjs +10 -0
- package/dist/contract/codec.d.mts +9 -0
- package/dist/contract/codec.mjs +9 -0
- package/dist/contract/codes.mjs +81 -0
- package/dist/contract/declared.d.mts +0 -2
- package/dist/contract/declared.mjs +26 -11
- package/dist/contract/digest.mjs +6 -0
- package/dist/contract/errors.d.mts +9 -5
- package/dist/contract/errors.mjs +2 -3
- package/dist/contract/feed.d.mts +127 -0
- package/dist/contract/feed.mjs +249 -0
- package/dist/contract/hints.mjs +1 -2
- package/dist/contract/hub.d.mts +94 -0
- package/dist/contract/hub.mjs +133 -0
- package/dist/contract/idempotency.d.mts +47 -0
- package/dist/contract/idempotency.mjs +99 -0
- package/dist/contract/implementation.d.mts +73 -51
- package/dist/contract/implementation.mjs +94 -60
- package/dist/contract/input-check.d.mts +15 -0
- package/dist/contract/input-check.mjs +16 -0
- package/dist/contract/lease.mjs +28 -0
- package/dist/contract/listed.d.mts +4 -0
- package/dist/contract/listed.mjs +22 -0
- package/dist/contract/log.d.mts +41 -0
- package/dist/contract/log.mjs +144 -0
- package/dist/contract/media.d.mts +1 -3
- package/dist/contract/media.mjs +1 -2
- package/dist/contract/names.mjs +15 -6
- package/dist/contract/occurrence.d.mts +29 -0
- package/dist/contract/occurrence.mjs +45 -0
- package/dist/contract/pages.d.mts +13 -0
- package/dist/contract/pages.mjs +39 -0
- package/dist/contract/progress.d.mts +26 -0
- package/dist/contract/progress.mjs +72 -0
- package/dist/contract/push.d.mts +20 -0
- package/dist/contract/push.mjs +96 -0
- package/dist/contract/receiving.d.mts +29 -0
- package/dist/contract/receiving.mjs +42 -0
- package/dist/contract/recent.mjs +12 -0
- package/dist/contract/reported.mjs +16 -0
- package/dist/contract/serving.mjs +63 -0
- package/dist/contract/status.mjs +15 -7
- package/dist/contract/stream.d.mts +34 -0
- package/dist/contract/stream.mjs +31 -0
- package/dist/contract/subscribe.d.mts +45 -0
- package/dist/contract/subscribe.mjs +50 -0
- package/dist/contract/transient.mjs +28 -0
- package/dist/contract/watch.d.mts +18 -0
- package/dist/contract/watch.mjs +105 -0
- package/dist/contract/webhooks.d.mts +42 -0
- package/dist/contract/webhooks.mjs +53 -0
- package/dist/http/client.d.mts +37 -14
- package/dist/http/client.mjs +52 -14
- package/dist/http/events.d.mts +56 -0
- package/dist/http/events.mjs +102 -0
- package/dist/http/placed.d.mts +36 -0
- package/dist/http/placed.mjs +71 -0
- package/dist/http/schema-errors.mjs +2 -3
- package/dist/http/streamed.d.mts +21 -0
- package/dist/http/streamed.mjs +94 -0
- package/dist/http/watch.mjs +50 -0
- package/dist/mcp/bridge.mjs +283 -0
- package/dist/mcp/client.d.mts +30 -99
- package/dist/mcp/client.mjs +60 -134
- package/dist/mcp/events.mjs +142 -0
- package/dist/mcp/progress.mjs +34 -0
- package/dist/mcp/protocol.mjs +40 -15
- package/dist/mcp/reply.d.mts +79 -0
- package/dist/mcp/reply.mjs +99 -0
- package/dist/mcp/request.d.mts +40 -0
- package/dist/mcp/request.mjs +51 -0
- package/dist/mcp/watch.mjs +115 -0
- package/dist/rpc/watch.mjs +39 -0
- package/dist/stdio/console.mjs +14 -7
- package/dist/testing/memory.d.mts +1 -3
- package/dist/testing/memory.mjs +4 -5
- package/dist/webhook/address.mjs +47 -0
- package/dist/webhook/attempt.mjs +65 -0
- package/dist/webhook/receiver.mjs +67 -0
- package/dist/webhook/signing.mjs +28 -0
- package/dist/webhook/stored.mjs +57 -0
- package/dist/webhook/subscriptions.mjs +342 -0
- package/docs/Action-failures.md +2 -2
- package/docs/Action-streams.md +115 -0
- package/docs/Action.md +58 -124
- package/docs/ActionCli-failures.md +1 -2
- package/docs/ActionCli.md +83 -88
- package/docs/ActionHttp-failures.md +3 -3
- package/docs/ActionHttp.md +90 -70
- package/docs/ActionMcp-failures.md +2 -1
- package/docs/ActionMcp.md +120 -92
- package/docs/ActionRpc-events.md +126 -0
- package/docs/ActionRpc-failures.md +1 -2
- package/docs/ActionRpc.md +108 -79
- package/docs/ActionToolkit-failures.md +1 -2
- package/docs/ActionToolkit.md +33 -90
- package/docs/Authentication-advanced.md +57 -61
- package/docs/Authentication-failures.md +5 -4
- package/docs/Authentication.md +23 -58
- package/docs/Event-failures.md +15 -0
- package/docs/Event.md +281 -0
- package/docs/README.md +51 -82
- package/docs/Testing-failures.md +1 -5
- package/docs/Testing.md +126 -86
- package/docs/Webhook-failures.md +8 -0
- package/docs/Webhook.md +319 -0
- package/docs/setup-failures.md +2 -3
- package/docs/setup.md +32 -53
- package/examples/README.md +12 -37
- package/examples/assistant/README.md +21 -26
- package/examples/assistant/src/agent.ts +66 -24
- package/examples/assistant/src/cli.ts +7 -4
- package/examples/assistant/src/commands.ts +9 -4
- package/examples/assistant/src/identity/Identity.ts +5 -4
- package/examples/assistant/src/identity/authorize.ts +13 -5
- package/examples/assistant/src/identity/handlers.ts +3 -2
- package/examples/assistant/src/identity/permissions.ts +9 -3
- package/examples/assistant/src/platform/tasks-file.ts +15 -11
- package/examples/assistant/src/scripted.ts +7 -4
- package/examples/assistant/src/tasks/Tasks.ts +20 -6
- package/examples/assistant/src/tasks/handlers.ts +9 -5
- package/examples/assistant/src/tasks/store.ts +38 -11
- package/examples/assistant/src/toolkit.ts +11 -7
- package/examples/assistant/test/commands.test.ts +53 -27
- package/examples/assistant/test/identity/identity.test.ts +27 -11
- package/examples/assistant/test/platform/tasks-file.test.ts +39 -23
- package/examples/assistant/test/tasks/tasks.test.ts +62 -34
- package/examples/assistant/test/toolkit.test.ts +104 -75
- package/examples/build-events/README.md +83 -0
- package/examples/build-events/src/Api.ts +27 -0
- package/examples/build-events/src/Rpc.ts +26 -0
- package/examples/build-events/src/builds/Builds.ts +108 -0
- package/examples/build-events/src/builds/events.ts +54 -0
- package/examples/build-events/src/builds/handlers.ts +58 -0
- package/examples/build-events/src/builds/log.ts +30 -0
- package/examples/build-events/src/builds/status.ts +28 -0
- package/examples/build-events/src/builds/store.ts +130 -0
- package/examples/build-events/src/cli.ts +18 -0
- package/examples/build-events/src/commands.ts +32 -0
- package/examples/build-events/src/deploys/Deploys.ts +44 -0
- package/examples/build-events/src/deploys/approvers.ts +55 -0
- package/examples/build-events/src/deploys/events.ts +32 -0
- package/examples/build-events/src/deploys/handlers.ts +38 -0
- package/examples/build-events/src/identity/Identity.ts +29 -0
- package/examples/build-events/src/identity/members.ts +7 -0
- package/examples/build-events/src/identity/verifier.ts +22 -0
- package/examples/build-events/src/notifier/Notices.ts +26 -0
- package/examples/build-events/src/notifier/routes.ts +71 -0
- package/examples/build-events/src/notifier.ts +48 -0
- package/examples/build-events/src/routes.ts +71 -0
- package/examples/build-events/src/server.ts +62 -0
- package/examples/build-events/test/builds/events.test.ts +68 -0
- package/examples/build-events/test/builds/log.test.ts +71 -0
- package/examples/build-events/test/builds/progress.test.ts +57 -0
- package/examples/build-events/test/builds/status.test.ts +42 -0
- package/examples/build-events/test/commands.test.ts +103 -0
- package/examples/build-events/test/deploys/events.test.ts +52 -0
- package/examples/build-events/test/http.test.ts +95 -0
- package/examples/build-events/test/notifier.test.ts +80 -0
- package/examples/build-events/test/routes.test.ts +96 -0
- package/examples/build-events/test/rpc.test.ts +84 -0
- package/examples/build-events/test/support/as.ts +8 -0
- package/examples/build-events/test/support/memory.ts +43 -0
- package/examples/build-events/test/webhooks.test.ts +143 -0
- package/examples/kitchen-sink/README.md +178 -166
- package/examples/kitchen-sink/src/Actions.ts +11 -3
- package/examples/kitchen-sink/src/Api.ts +12 -6
- package/examples/kitchen-sink/src/Rpc.ts +7 -5
- package/examples/kitchen-sink/src/admin/Admin.ts +21 -10
- package/examples/kitchen-sink/src/admin/authenticate.ts +8 -4
- package/examples/kitchen-sink/src/admin/authorize.ts +13 -5
- package/examples/kitchen-sink/src/admin/grants.ts +5 -3
- package/examples/kitchen-sink/src/admin/handlers.ts +9 -5
- package/examples/kitchen-sink/src/agent.ts +14 -6
- package/examples/kitchen-sink/src/assistant/Assistant.ts +21 -15
- package/examples/kitchen-sink/src/assistant/conversations.ts +14 -9
- package/examples/kitchen-sink/src/assistant/demo-model.ts +2 -2
- package/examples/kitchen-sink/src/assistant/handlers.ts +42 -22
- package/examples/kitchen-sink/src/attachments/Attachments.ts +6 -6
- package/examples/kitchen-sink/src/attachments/download.ts +7 -6
- package/examples/kitchen-sink/src/attachments/handlers.ts +9 -4
- package/examples/kitchen-sink/src/attachments/store.ts +25 -18
- package/examples/kitchen-sink/src/browser.ts +10 -4
- package/examples/kitchen-sink/src/cli.ts +28 -12
- package/examples/kitchen-sink/src/config.ts +1 -1
- package/examples/kitchen-sink/src/identity/Identity.ts +24 -10
- package/examples/kitchen-sink/src/identity/authenticate.ts +19 -11
- package/examples/kitchen-sink/src/identity/authorize.ts +14 -4
- package/examples/kitchen-sink/src/identity/directory.ts +16 -7
- package/examples/kitchen-sink/src/identity/handlers.ts +1 -0
- package/examples/kitchen-sink/src/identity/members.ts +7 -2
- package/examples/kitchen-sink/src/identity/quota.ts +23 -20
- package/examples/kitchen-sink/src/issues/Issues.ts +91 -15
- package/examples/kitchen-sink/src/issues/assistant.ts +4 -2
- package/examples/kitchen-sink/src/issues/events.ts +46 -0
- package/examples/kitchen-sink/src/issues/export.ts +35 -0
- package/examples/kitchen-sink/src/issues/guide.ts +4 -3
- package/examples/kitchen-sink/src/issues/handlers.ts +20 -7
- package/examples/kitchen-sink/src/issues/store.ts +156 -67
- package/examples/kitchen-sink/src/issues/write-limit.ts +44 -18
- package/examples/kitchen-sink/src/memory.ts +11 -2
- package/examples/kitchen-sink/src/ops.ts +10 -5
- package/examples/kitchen-sink/src/request-policy.ts +25 -9
- package/examples/kitchen-sink/src/routes.ts +67 -27
- package/examples/kitchen-sink/src/sdk.ts +22 -12
- package/examples/kitchen-sink/src/seed.ts +15 -9
- package/examples/kitchen-sink/src/server.ts +24 -10
- package/examples/kitchen-sink/src/stdio.ts +9 -3
- package/examples/kitchen-sink/src/workspaces/Workspaces.ts +10 -7
- package/examples/kitchen-sink/src/workspaces/handlers.ts +3 -2
- package/examples/kitchen-sink/src/workspaces/registry.ts +16 -10
- package/examples/kitchen-sink/src/workspaces/tenant.ts +12 -5
- package/examples/kitchen-sink/test/admin/admin.test.ts +38 -23
- package/examples/kitchen-sink/test/assistant/assistant.test.ts +471 -299
- package/examples/kitchen-sink/test/attachments/attachments.test.ts +135 -94
- package/examples/kitchen-sink/test/identity/identity.test.ts +110 -82
- package/examples/kitchen-sink/test/identity/session.test.ts +65 -43
- package/examples/kitchen-sink/test/issues/assistant.test.ts +50 -31
- package/examples/kitchen-sink/test/issues/events.test.ts +273 -0
- package/examples/kitchen-sink/test/issues/export.test.ts +120 -0
- package/examples/kitchen-sink/test/issues/issues.test.ts +105 -75
- package/examples/kitchen-sink/test/request-policy.test.ts +130 -33
- package/examples/kitchen-sink/test/routes.test.ts +102 -57
- package/examples/kitchen-sink/test/support/as.ts +9 -0
- package/examples/kitchen-sink/test/support/scripted.ts +6 -5
- package/examples/kitchen-sink/test/support/services.ts +17 -0
- package/examples/live-rpc/README.md +33 -35
- package/examples/live-rpc/src/Rpc.ts +9 -5
- package/examples/live-rpc/src/browser.ts +39 -11
- package/examples/live-rpc/src/chat/Chat.ts +30 -16
- package/examples/live-rpc/src/chat/authorize.ts +2 -1
- package/examples/live-rpc/src/chat/events.ts +74 -0
- package/examples/live-rpc/src/chat/handlers.ts +13 -5
- package/examples/live-rpc/src/chat/room.ts +9 -5
- package/examples/live-rpc/src/chat/store.ts +51 -14
- package/examples/live-rpc/src/client.ts +22 -5
- package/examples/live-rpc/src/identity/Identity.ts +3 -3
- package/examples/live-rpc/src/identity/verifier.ts +8 -4
- package/examples/live-rpc/src/page.ts +58 -12
- package/examples/live-rpc/src/routes.ts +14 -10
- package/examples/live-rpc/src/server.ts +15 -5
- package/examples/live-rpc/test/browser.test.ts +127 -50
- package/examples/live-rpc/test/chat/chat.test.ts +215 -23
- package/examples/live-rpc/test/routes.test.ts +130 -46
- package/examples/live-rpc/test/server.test.ts +94 -33
- package/examples/mcp-tools/README.md +24 -38
- package/examples/mcp-tools/src/platform/fake-farm.ts +63 -24
- package/examples/mcp-tools/src/platform/print-farm.ts +59 -29
- package/examples/mcp-tools/src/printers/Printers.ts +18 -7
- package/examples/mcp-tools/src/printers/farm.ts +15 -5
- package/examples/mcp-tools/src/printers/handlers.ts +12 -6
- package/examples/mcp-tools/src/routes.ts +8 -6
- package/examples/mcp-tools/src/server.ts +12 -5
- package/examples/mcp-tools/src/stdio.ts +3 -2
- package/examples/mcp-tools/test/printers/printers.test.ts +132 -89
- package/examples/mcp-tools/test/routes.test.ts +140 -81
- package/examples/notes-service/README.md +30 -45
- package/examples/notes-service/src/Api.ts +1 -0
- package/examples/notes-service/src/browser.ts +15 -14
- package/examples/notes-service/src/cli.ts +8 -3
- package/examples/notes-service/src/commands.ts +46 -20
- package/examples/notes-service/src/identity/Identity.ts +2 -2
- package/examples/notes-service/src/identity/authorize.ts +6 -2
- package/examples/notes-service/src/identity/handlers.ts +3 -2
- package/examples/notes-service/src/identity/users.ts +2 -1
- package/examples/notes-service/src/identity/verifier.ts +12 -6
- package/examples/notes-service/src/notes/Notes.ts +46 -22
- package/examples/notes-service/src/notes/handlers.ts +18 -9
- package/examples/notes-service/src/notes/store.ts +70 -43
- package/examples/notes-service/src/routes.ts +7 -5
- package/examples/notes-service/src/server.ts +12 -5
- package/examples/notes-service/src/status/Status.ts +1 -1
- package/examples/notes-service/src/status/handlers.ts +5 -3
- package/examples/notes-service/src/stdio.ts +7 -3
- package/examples/notes-service/src/worker.ts +6 -3
- package/examples/notes-service/test/commands.test.ts +164 -91
- package/examples/notes-service/test/notes/as.ts +3 -1
- package/examples/notes-service/test/notes/idempotency.test.ts +97 -67
- package/examples/notes-service/test/notes/in-process.test.ts +26 -15
- package/examples/notes-service/test/notes/notes.test.ts +49 -31
- package/examples/notes-service/test/routes.test.ts +106 -71
- package/examples/notes-service/test/worker.test.ts +21 -13
- package/examples/quickstart/README.md +2 -6
- package/examples/quickstart/src/Api.ts +1 -0
- package/examples/quickstart/src/client.ts +6 -3
- package/examples/quickstart/src/greeting/Greeting.ts +1 -1
- package/examples/quickstart/src/greeting/handlers.ts +5 -2
- package/examples/quickstart/src/routes.ts +3 -2
- package/examples/quickstart/src/server.ts +6 -2
- package/examples/quickstart/test/routes.test.ts +3 -2
- package/package.json +19 -17
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { Codec } from "./contract/codec.mjs";
|
|
1
2
|
import { Refusal } from "./contract/errors.mjs";
|
|
2
3
|
import { Known, OptionalUnless } from "./contract/implementation.mjs";
|
|
3
4
|
import { Errors } from "./Action.mjs";
|
|
4
|
-
import { Any, Descriptor, Provider, Security } from "./authentication/provider.mjs";
|
|
5
5
|
import { ErrorsOf } from "./contract/declared.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { Any, Descriptor, Provider, Security } from "./authentication/provider.mjs";
|
|
7
|
+
import { Array, Context, Effect, Layer, Option, Redacted, Schema, Scope } from "effect";
|
|
7
8
|
import { HttpRouter, HttpServerResponse } from "effect/http";
|
|
8
9
|
import { HttpApiSecurity } from "effect/http-api";
|
|
9
|
-
//#region src/authentication/Authentication.d.ts
|
|
10
10
|
/**
|
|
11
11
|
* The bearer token of an `Authorization` header, `authorization`, or none, read as Effect's
|
|
12
12
|
* `HttpApiSecurity.bearer` reads it for routes and MCP: the scheme matched case-insensitively,
|
|
@@ -14,16 +14,18 @@ import { HttpApiSecurity } from "effect/http-api";
|
|
|
14
14
|
* which holds the header and no request; a route of the host's own is authenticated by
|
|
15
15
|
* `protect`.
|
|
16
16
|
*/
|
|
17
|
-
export declare const bearerTokenOf: (authorization: string | null | undefined) => Option.Option<Redacted.Redacted
|
|
17
|
+
export declare const bearerTokenOf: (authorization: string | null | undefined) => Option.Option<Redacted.Redacted>;
|
|
18
18
|
/** An OAuth protected resource (RFC 9728), as `layer` publishes it. */
|
|
19
19
|
export interface ProtectedResource {
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
* `${url}/mcp
|
|
23
|
-
*
|
|
21
|
+
* OAuth resource identifier: the public URL of the endpoint clients connect to, such as
|
|
22
|
+
* `${url}/mcp`, or its origin, such as `https://api.example.com`, which MCP allows to name one
|
|
23
|
+
* resource for every endpoint it serves. Discovery is served at
|
|
24
|
+
* `/.well-known/oauth-protected-resource` followed by its path and query.
|
|
24
25
|
*
|
|
25
26
|
* **Gotchas**
|
|
26
27
|
*
|
|
28
|
+
* - A client applying RFC 9728 §3.3 strictly finds no authorization server for an origin, since metadata reached from a challenge must name the URL it requested; the official MCP client accepts it, and the endpoint's URL works with both.
|
|
27
29
|
* - Discovery answers 404, and an MCP client finds no authorization server, when a request's path and query differ from this URL's, or when the provider is provided to no served layer.
|
|
28
30
|
* - That it and `authorizationServers` are HTTPS, or loopback HTTP in development, is the deployment's to ensure: nothing checks it.
|
|
29
31
|
*/
|
|
@@ -34,7 +36,7 @@ export interface ProtectedResource {
|
|
|
34
36
|
* Every scope the resource accepts, each an OAuth scope token, which a client requests when a
|
|
35
37
|
* 401 names none.
|
|
36
38
|
*/
|
|
37
|
-
readonly scopesSupported?:
|
|
39
|
+
readonly scopesSupported?: readonly string[];
|
|
38
40
|
/**
|
|
39
41
|
* The scopes every 401 names, each an OAuth scope token: what a client requests when it
|
|
40
42
|
* authenticates, rather than every scope supported.
|
|
@@ -79,8 +81,8 @@ type DeclaredOf<D> = D extends {
|
|
|
79
81
|
* naming scopes. `HttpServerResponse.toWeb` gives it as a web `Response`.
|
|
80
82
|
*/
|
|
81
83
|
export declare const refusalResponse: <D extends Any = never>(error: Refusal | DeclaredOf<D>, options?: RefusalResponseOptions<D>) => HttpServerResponse.HttpServerResponse;
|
|
82
|
-
/** What `make` takes besides the name and the identity
|
|
83
|
-
export interface Options {
|
|
84
|
+
/** What `make` takes besides the name and the identity `A`. */
|
|
85
|
+
export interface Options<A = unknown> {
|
|
84
86
|
/**
|
|
85
87
|
* The one native scheme a caller proves the identity by, Bearer when left out: an API key in a
|
|
86
88
|
* cookie, a header or the query, such as `HttpApiSecurity.apiKey({ in: "cookie", key: "session" })`,
|
|
@@ -101,7 +103,35 @@ export interface Options {
|
|
|
101
103
|
* - A failure that is not the caller's, such as an issuer the verifier cannot reach, belongs here with `{ httpApiStatus: 503 }`: an `Unauthenticated` would have the caller sign in again.
|
|
102
104
|
*/
|
|
103
105
|
readonly error?: Errors | Errors[number];
|
|
106
|
+
/**
|
|
107
|
+
* The stable id of an identity, such as its user id: what tells subscribers apart where a
|
|
108
|
+
* surface keeps a subscription between requests, such as an MCP poll subscription's lease.
|
|
109
|
+
*
|
|
110
|
+
* **Gotchas**
|
|
111
|
+
*
|
|
112
|
+
* - A surface serving a protected event type whose source has a `subscription` hook refuses to start without it, its message naming this option.
|
|
113
|
+
* - Two identities with one principal share those subscriptions, so give each its own: a tenant's id alone merges its users.
|
|
114
|
+
* - Without it no surface offers a protected event by webhook, which a subscription keys by its principal: the listing names no webhook, and a webhook subscription fails with `Event.SubscriptionError`, reason `Unsupported`.
|
|
115
|
+
* - Without it every caller shares one idempotency key space: another caller sending a key with the same input is given the first call's answer, as `Action.Options.idempotencyKey` says.
|
|
116
|
+
*/
|
|
117
|
+
readonly principal?: ((identity: A) => string) | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* The identity's schema, its `Type` the identity: how a surface keeps an identity past a
|
|
120
|
+
* restart, as `Webhook.layer` with `persist` keeps each webhook subscriber's, its JSON in the
|
|
121
|
+
* store.
|
|
122
|
+
*
|
|
123
|
+
* **Gotchas**
|
|
124
|
+
*
|
|
125
|
+
* - `Webhook.layer` with `persist` refuses to serve a protected event by webhook without it, its message naming this option.
|
|
126
|
+
*/
|
|
127
|
+
readonly schema?: Codec | undefined;
|
|
104
128
|
}
|
|
129
|
+
type PrincipalOf<A> = NoInfer<Pick<Options<A>, "principal">>;
|
|
130
|
+
type SchemaOf<O, A> = O extends {
|
|
131
|
+
readonly schema: infer S extends Schema.Top;
|
|
132
|
+
} ? [S["Type"], A] extends [A, S["Type"]] ? unknown : {
|
|
133
|
+
readonly schema: "a schema whose Type is the identity's";
|
|
134
|
+
} : unknown;
|
|
105
135
|
type SecurityOf<O> = O extends {
|
|
106
136
|
readonly security: infer S extends Security;
|
|
107
137
|
} ? S : "security" extends keyof O ? NonNullable<O["security" & keyof O]> | HttpApiSecurity.Http : HttpApiSecurity.Http;
|
|
@@ -112,7 +142,7 @@ type SecurityOf<O> = O extends {
|
|
|
112
142
|
* one name only for one declaration, whose values a server and a client may hold as `layer` says.
|
|
113
143
|
* Guide: `docs/Authentication.md` in this package; in an application: `examples/notes-service/src/identity/`.
|
|
114
144
|
*/
|
|
115
|
-
export declare const make: <const Name extends string, I, A, const O extends Options = {}>(name: Name, identity: Context.Key<I, A>, ...options: OptionalUnless<O, O & NoInfer<Known<O, Options
|
|
145
|
+
export declare const make: <const Name extends string, I, A, const O extends Options<never> = {}>(name: Name, identity: Context.Key<I, A>, ...options: OptionalUnless<O, O & NoInfer<Known<O, Options>> & PrincipalOf<A> & SchemaOf<O, A>>) => Descriptor<I, A, SecurityOf<O>, Name, ErrorsOf<O>>;
|
|
116
146
|
/**
|
|
117
147
|
* A descriptor's verifier: the credential its scheme decodes, as Effect's own decoder gives
|
|
118
148
|
* it, to the identity, or a refusal, or an error the descriptor declares, `E`. Its services
|
|
@@ -155,6 +185,7 @@ type Resource = ProtectedResource | Effect.Effect<ProtectedResource | undefined,
|
|
|
155
185
|
* **Gotchas**
|
|
156
186
|
*
|
|
157
187
|
* - Call it once per descriptor, and provide that value to every layer serving a protected action: nothing else satisfies the provider they require, neither the identity nor another descriptor's provider, which type-checks under the same name and identity, then dies when the layer builds; only a client may hold another descriptor of the name, such as one of a cookie name the server decides at startup.
|
|
188
|
+
* - Over `ActionRpc` a verifier reading `HttpServerRequest` reads one holding the message's headers alone, a socket's upgrade headers before its own, at `/` with no query or body, on every protocol: one judging the URL or query sees none of the client's.
|
|
158
189
|
*/
|
|
159
190
|
export declare function layer<const Name extends string, I, A, const S extends Security, E extends Errors, R, EX = never, RX = never, P extends Resource = undefined>(authentication: Descriptor<I, A, S, Name, E>, verify: Verify<NoInfer<A>, NoInfer<S>, R, NoInfer<E[number]["Type"]>> | Effect.Effect<Verify<NoInfer<A>, NoInfer<S>, R, NoInfer<E[number]["Type"]>>, EX, RX>, options?: {
|
|
160
191
|
/** The OAuth protected resource to publish: its rules are on `LayerOptions.protectedResource`. */
|
|
@@ -190,5 +221,4 @@ export type Protection<I, Name extends string> = HttpRouter.Middleware<{
|
|
|
190
221
|
* - An erased descriptor, `Any`, provides no service the types can name: its identity is `unknown`, which would discharge every request service the route owes.
|
|
191
222
|
*/
|
|
192
223
|
export declare const protect: <I, A, S extends Security, Name extends string, E extends Errors>(authentication: Descriptor<I, A, S, Name, E>) => Protection<I, Name>;
|
|
193
|
-
//#endregion
|
|
194
224
|
export type { Any, Descriptor, Provider };
|
package/dist/Authentication.mjs
CHANGED
|
@@ -2,11 +2,11 @@ import { Unauthenticated, builtIns, scopeToken } from "./contract/errors.mjs";
|
|
|
2
2
|
import { assertDistinctTags, errorList } from "./contract/declared.mjs";
|
|
3
3
|
import { providerOf } from "./authentication/provider.mjs";
|
|
4
4
|
import { answer, answerStepUp, bearer, challenge, declaredResponse, isRefusal, isStepUp, plain } from "./authentication/refusal.mjs";
|
|
5
|
-
import { Context, Effect, Layer, Match, Option, Predicate, Redacted } from "effect";
|
|
5
|
+
import { Context, Effect, Function, Layer, Match, Option, Predicate, Redacted } from "effect";
|
|
6
6
|
import { HttpEffect, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
7
7
|
import { HttpApiBuilder, HttpApiMiddleware, HttpApiSecurity } from "effect/http-api";
|
|
8
|
-
|
|
9
|
-
const bearerAuthorizationHeader = /^[ \t]*Bearer +(
|
|
8
|
+
// src/authentication/Authentication.ts
|
|
9
|
+
const bearerAuthorizationHeader = /^[ \t]*Bearer +(?<token>.*[^ \t])[ \t]*$/iu;
|
|
10
10
|
/**
|
|
11
11
|
* The bearer token of an `Authorization` header, `authorization`, or none, read as Effect's
|
|
12
12
|
* `HttpApiSecurity.bearer` reads it for routes and MCP: the scheme matched case-insensitively,
|
|
@@ -14,7 +14,7 @@ const bearerAuthorizationHeader = /^[ \t]*Bearer +(.*[^ \t])[ \t]*$/i;
|
|
|
14
14
|
* which holds the header and no request; a route of the host's own is authenticated by
|
|
15
15
|
* `protect`.
|
|
16
16
|
*/
|
|
17
|
-
const bearerTokenOf = (authorization) => Option.map(Option.fromNullishOr(bearerAuthorizationHeader.exec(authorization ?? "")?.[
|
|
17
|
+
const bearerTokenOf = (authorization) => Option.map(Option.fromNullishOr(bearerAuthorizationHeader.exec(authorization ?? "")?.groups?.["token"]), (token) => Redacted.make(token));
|
|
18
18
|
const metadataUrlOf = (options) => {
|
|
19
19
|
const resource = new URL(options.resource);
|
|
20
20
|
const url = new URL(resource);
|
|
@@ -43,10 +43,13 @@ const assertPublishableResource = (oauth, options) => {
|
|
|
43
43
|
if (resource === null) throw new Error(`A protected resource is an absolute URL: ${JSON.stringify(options.resource)} is not one; give the public URL of the endpoint clients connect to, such as https://api.example.com/mcp, read from configuration`);
|
|
44
44
|
if (resource.href.includes("#")) throw new Error(`A protected resource has no fragment: ${options.resource}; drop the "#", which no request URL carries, so its discovery would never answer`);
|
|
45
45
|
};
|
|
46
|
-
const withNoStoreAndChallenge = (response,
|
|
46
|
+
const withNoStoreAndChallenge = (response, wwwAuthenticate) => HttpServerResponse.setHeaders(response, {
|
|
47
47
|
...response.headers["cache-control"] === void 0 ? { "cache-control": "no-store" } : {},
|
|
48
|
-
...
|
|
48
|
+
...wwwAuthenticate !== void 0 && response.status === 401 && response.headers["www-authenticate"] === void 0 ? { "www-authenticate": wwwAuthenticate } : {}
|
|
49
49
|
});
|
|
50
|
+
const isBearer = (security) => Predicate.isTagged(security, "Http") && security.scheme.toLowerCase() === "bearer";
|
|
51
|
+
const openApiComponentKey = /^[\w.-]+$/u;
|
|
52
|
+
const nonBearerChallengeOf = (security, name) => Match.value(security).pipe(Match.tag("Http", ({ scheme }) => challenge(scheme, [])), Match.tag("Basic", () => challenge("Basic", [["realm", name]])), Match.tag("ApiKey", Function.constUndefined), Match.exhaustive);
|
|
50
53
|
/**
|
|
51
54
|
* The response `layer` answers a refusal with, for a caller the router never routes, such as a
|
|
52
55
|
* Node `upgrade` handler admitting a socket, which holds a header and no request: read its token
|
|
@@ -60,15 +63,29 @@ const refusalResponse = (error, options) => {
|
|
|
60
63
|
const oauth = authentication === void 0 || isBearer(authentication.security);
|
|
61
64
|
assertPublishableResource(oauth, options?.protectedResource);
|
|
62
65
|
const named = namedOf(options?.protectedResource);
|
|
63
|
-
const
|
|
66
|
+
const wwwAuthenticate = authentication !== void 0 && !oauth ? nonBearerChallengeOf(authentication.security, authentication.name) : bearerChallengeOf(named, presentsBearerToken(options?.authorization));
|
|
64
67
|
if (!isRefusal(error)) {
|
|
65
68
|
const response = declaredResponse(authentication?.error ?? [], error);
|
|
66
69
|
if (response === void 0) throw new Error("Not a refusal, nor an error the authentication declares: pass the descriptor declaring it as `authentication`, or answer the error yourself");
|
|
67
|
-
return withNoStoreAndChallenge(response,
|
|
70
|
+
return withNoStoreAndChallenge(response, wwwAuthenticate);
|
|
68
71
|
}
|
|
69
|
-
return withNoStoreAndChallenge(oauth ? answer(error, named.metadataUrl) : plain(error),
|
|
72
|
+
return withNoStoreAndChallenge(oauth ? answer(error, named.metadataUrl) : plain(error), wwwAuthenticate);
|
|
73
|
+
};
|
|
74
|
+
const mayResolveToDiscoveryPath = /\.well-known|[\t\n\r]/u;
|
|
75
|
+
const corsPreflight = (request) => {
|
|
76
|
+
const requested = request.headers["access-control-request-headers"];
|
|
77
|
+
return HttpServerResponse.empty({
|
|
78
|
+
status: 204,
|
|
79
|
+
headers: {
|
|
80
|
+
"access-control-allow-origin": "*",
|
|
81
|
+
"access-control-allow-methods": "GET, HEAD, OPTIONS",
|
|
82
|
+
...requested === void 0 ? {} : {
|
|
83
|
+
"access-control-allow-headers": requested,
|
|
84
|
+
vary: "Access-Control-Request-Headers"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
70
88
|
};
|
|
71
|
-
const mayResolveToDiscoveryPath = /\.well-known|[\t\n\r]/;
|
|
72
89
|
const discoveryMiddleware = (options) => {
|
|
73
90
|
const discoveryUrl = metadataUrlOf(options);
|
|
74
91
|
const target = discoveryUrl.href.slice(discoveryUrl.origin.length);
|
|
@@ -79,20 +96,6 @@ const discoveryMiddleware = (options) => {
|
|
|
79
96
|
scopes_supported: options.scopesSupported,
|
|
80
97
|
resource_name: options.resourceName
|
|
81
98
|
}, { headers: { "access-control-allow-origin": "*" } });
|
|
82
|
-
const corsPreflight = (request) => {
|
|
83
|
-
const requested = request.headers["access-control-request-headers"];
|
|
84
|
-
return HttpServerResponse.empty({
|
|
85
|
-
status: 204,
|
|
86
|
-
headers: {
|
|
87
|
-
"access-control-allow-origin": "*",
|
|
88
|
-
"access-control-allow-methods": "GET, HEAD, OPTIONS",
|
|
89
|
-
...requested === void 0 ? {} : {
|
|
90
|
-
"access-control-allow-headers": requested,
|
|
91
|
-
vary: "Access-Control-Request-Headers"
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
99
|
return (next) => Effect.flatMap(HttpServerRequest.HttpServerRequest, (request) => {
|
|
97
100
|
const targeted = mayResolveToDiscoveryPath.test(request.url) && URL.canParse(request.url, discoveryUrl.origin) && new URL(request.url, discoveryUrl.origin).href.slice(discoveryUrl.origin.length) === target;
|
|
98
101
|
if (targeted && (request.method === "GET" || request.method === "HEAD")) return Effect.succeed(metadata);
|
|
@@ -109,9 +112,6 @@ const resourceAnswersOf = (options) => {
|
|
|
109
112
|
published: options === void 0 ? void 0 : discoveryMiddleware(options)
|
|
110
113
|
};
|
|
111
114
|
};
|
|
112
|
-
const isBearer = (security) => Predicate.isTagged(security, "Http") && security.scheme.toLowerCase() === "bearer";
|
|
113
|
-
const openApiComponentKey = /^[\w.-]+$/;
|
|
114
|
-
const nonBearerChallengeOf = (security, name) => Match.value(security).pipe(Match.tag("Http", ({ scheme }) => challenge(scheme, [])), Match.tag("Basic", () => challenge("Basic", [["realm", name]])), Match.tag("ApiKey", () => void 0), Match.exhaustive);
|
|
115
115
|
/**
|
|
116
116
|
* Browser-safe declaration shared by clients and remote surfaces: the `identity` it
|
|
117
117
|
* authenticates, and the scheme a caller proves it by, `Options.security`. The literal name
|
|
@@ -122,6 +122,8 @@ const nonBearerChallengeOf = (security, name) => Match.value(security).pipe(Matc
|
|
|
122
122
|
const make = (name, identity, ...options) => {
|
|
123
123
|
const security = options[0]?.security ?? HttpApiSecurity.bearer;
|
|
124
124
|
const error = errorList(options[0]?.error);
|
|
125
|
+
const principal = options[0]?.principal;
|
|
126
|
+
const schema = options[0]?.schema;
|
|
125
127
|
if (!openApiComponentKey.test(name)) throw new Error(`Invalid authentication name: ${JSON.stringify(name)}, not an OpenAPI key: use only letters, digits, "_", "." and "-"`);
|
|
126
128
|
assertDistinctTags(`Authentication "${name}"`, [...builtIns, ...error]);
|
|
127
129
|
if (!Predicate.isTagged(security, "Http") && !Predicate.isTagged(security, "ApiKey") && !Predicate.isTagged(security, "Basic")) throw new Error("Authentication takes one native HttpApiSecurity scheme as `security`: omit it for Bearer, or give one scheme, such as `HttpApiSecurity.basic`, never a record of them");
|
|
@@ -130,6 +132,8 @@ const make = (name, identity, ...options) => {
|
|
|
130
132
|
identity,
|
|
131
133
|
security,
|
|
132
134
|
error,
|
|
135
|
+
...principal === void 0 ? {} : { principal },
|
|
136
|
+
...schema === void 0 ? {} : { schema },
|
|
133
137
|
"~provider": Context.Service(`effect-actions/Authentication/Provider/${name}`),
|
|
134
138
|
"~middleware": HttpApiMiddleware.Service()(`effect-actions/Authentication/Security/${name}`, { security: { [name]: security } }),
|
|
135
139
|
"~verified": Context.Service(`effect-actions/Authentication/Verified/${name}`)
|
|
@@ -139,7 +143,7 @@ const presented = (credential) => Redacted.isRedacted(credential) ? Redacted.val
|
|
|
139
143
|
function layer(authentication, verify, options = {}) {
|
|
140
144
|
const { security } = authentication;
|
|
141
145
|
const oauth = isBearer(security);
|
|
142
|
-
const protectedResource = options
|
|
146
|
+
const { protectedResource } = options;
|
|
143
147
|
assertPublishableResource(oauth, protectedResource);
|
|
144
148
|
const resource = Effect.isEffect(protectedResource) ? Effect.map(protectedResource, (built) => {
|
|
145
149
|
assertPublishableResource(oauth, built);
|
|
@@ -153,17 +157,20 @@ function layer(authentication, verify, options = {}) {
|
|
|
153
157
|
if (!Predicate.isFunction(verifier)) return yield* Effect.die(/* @__PURE__ */ new Error("Missing verify: pass a verify function, or an Effect building one"));
|
|
154
158
|
const authenticate = (credential) => presented(credential) ? verifier(credential) : Effect.fail(missing);
|
|
155
159
|
const noStoreOnFailure = HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setHeader(response, "cache-control", "no-store")));
|
|
156
|
-
const challengeOf = (credential) =>
|
|
160
|
+
const challengeOf = (credential) => {
|
|
161
|
+
if (!oauth) return nonBearerChallengeOf(security, authentication.name);
|
|
162
|
+
return presented(credential) ? invalid : anonymous;
|
|
163
|
+
};
|
|
157
164
|
const refuse = (error) => isRefusal(error) ? Effect.succeed(oauth ? answer(error, metadataUrl) : plain(error)) : Effect.suspend(() => {
|
|
158
165
|
const response = declaredResponse(authentication.error, error);
|
|
159
166
|
return response === void 0 ? Effect.die(error) : Effect.succeed(response);
|
|
160
167
|
});
|
|
161
168
|
const verified = (credential, route, slot) => {
|
|
162
|
-
const
|
|
169
|
+
const wwwAuthenticate = challengeOf(credential);
|
|
163
170
|
return Effect.matchEffect(authenticate(credential), {
|
|
164
171
|
onFailure: refuse,
|
|
165
172
|
onSuccess: (actor) => Effect.provideService(route, slot, actor)
|
|
166
|
-
}).pipe(Effect.onError(() => noStoreOnFailure), HttpEffect.withPreResponseHandler((_request, response) => Effect.succeed(withNoStoreAndChallenge(response,
|
|
173
|
+
}).pipe(Effect.onError(() => noStoreOnFailure), HttpEffect.withPreResponseHandler((_request, response) => Effect.succeed(withNoStoreAndChallenge(response, wwwAuthenticate))));
|
|
167
174
|
};
|
|
168
175
|
return {
|
|
169
176
|
descriptor: authentication,
|
|
@@ -191,5 +198,4 @@ function layer(authentication, verify, options = {}) {
|
|
|
191
198
|
* - An erased descriptor, `Any`, provides no service the types can name: its identity is `unknown`, which would discharge every request service the route owes.
|
|
192
199
|
*/
|
|
193
200
|
const protect = (authentication) => HttpRouter.middleware()(Effect.map(providerOf(authentication), (runtime) => (route) => runtime.route(route)));
|
|
194
|
-
//#endregion
|
|
195
201
|
export { bearerTokenOf, layer, make, protect, refusalResponse };
|