@nifrajs/core 3.3.0 → 4.0.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 +26 -0
- package/dist/assurance.d.ts +7 -0
- package/dist/assurance.d.ts.map +1 -1
- package/dist/assurance.js +39 -3
- package/dist/assurance.js.map +1 -1
- package/dist/capabilities.d.ts +20 -2
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +47 -15
- package/dist/capabilities.js.map +1 -1
- package/dist/diff.d.ts +6 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +36 -2
- package/dist/diff.js.map +1 -1
- package/dist/durable-adapters.d.ts.map +1 -1
- package/dist/durable-adapters.js +16 -3
- package/dist/durable-adapters.js.map +1 -1
- package/dist/durable-effect.d.ts.map +1 -1
- package/dist/durable-effect.js +6 -3
- package/dist/durable-effect.js.map +1 -1
- package/dist/edge-kit.d.ts +2 -0
- package/dist/edge-kit.d.ts.map +1 -1
- package/dist/edge-kit.js +2 -0
- package/dist/edge-kit.js.map +1 -1
- package/dist/effect-lifecycle.d.ts.map +1 -1
- package/dist/effect-lifecycle.js +2 -1
- package/dist/effect-lifecycle.js.map +1 -1
- package/dist/effect-scope.d.ts.map +1 -1
- package/dist/effect-scope.js +1 -1
- package/dist/effect-scope.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/evidence.d.ts +36 -1
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +219 -13
- package/dist/evidence.js.map +1 -1
- package/dist/idempotency.d.ts +31 -0
- package/dist/idempotency.d.ts.map +1 -1
- package/dist/idempotency.js +48 -9
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/code-unit-order.d.ts +7 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +9 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/durable-saga-protocol.d.ts.map +1 -1
- package/dist/internal/durable-saga-protocol.js +2 -0
- package/dist/internal/durable-saga-protocol.js.map +1 -1
- package/dist/internal/route-catalog.d.ts +6 -6
- package/dist/internal/route-catalog.d.ts.map +1 -1
- package/dist/internal/route-catalog.js +20 -18
- package/dist/internal/route-catalog.js.map +1 -1
- package/dist/internal/route-compiler.d.ts +7 -6
- package/dist/internal/route-compiler.d.ts.map +1 -1
- package/dist/internal/route-compiler.js +36 -4
- package/dist/internal/route-compiler.js.map +1 -1
- package/dist/internal/route-execution.d.ts +7 -3
- package/dist/internal/route-execution.d.ts.map +1 -1
- package/dist/internal/route-execution.js.map +1 -1
- package/dist/internal/route-lanes.d.ts +1 -0
- package/dist/internal/route-lanes.d.ts.map +1 -1
- package/dist/internal/route-lanes.js +34 -29
- package/dist/internal/route-lanes.js.map +1 -1
- package/dist/internal/route-program.d.ts +8 -2
- package/dist/internal/route-program.d.ts.map +1 -1
- package/dist/internal/route-program.js +222 -20
- package/dist/internal/route-program.js.map +1 -1
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +8 -5
- package/dist/ledger.js.map +1 -1
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +52 -46
- package/dist/manifest.js.map +1 -1
- package/dist/mount.d.ts +36 -0
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +32 -0
- package/dist/mount.js.map +1 -1
- package/dist/reflection.d.ts +26 -0
- package/dist/reflection.d.ts.map +1 -1
- package/dist/reflection.js +65 -1
- package/dist/reflection.js.map +1 -1
- package/dist/response-observer.d.ts +1 -1
- package/dist/response-observer.d.ts.map +1 -1
- package/dist/response-observer.js.map +1 -1
- package/dist/router/overlap.d.ts +23 -0
- package/dist/router/overlap.d.ts.map +1 -0
- package/dist/router/overlap.js +224 -0
- package/dist/router/overlap.js.map +1 -0
- package/dist/router/pattern.d.ts +117 -2
- package/dist/router/pattern.d.ts.map +1 -1
- package/dist/router/pattern.js +283 -23
- package/dist/router/pattern.js.map +1 -1
- package/dist/router/router.d.ts +18 -4
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +51 -44
- package/dist/router/router.js.map +1 -1
- package/dist/server/auth.d.ts +53 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +9 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/bare-error-lane.d.ts +12 -7
- package/dist/server/bare-error-lane.d.ts.map +1 -1
- package/dist/server/bare-error-lane.js +13 -11
- package/dist/server/bare-error-lane.js.map +1 -1
- package/dist/server/body-lane.d.ts +20 -2
- package/dist/server/body-lane.d.ts.map +1 -1
- package/dist/server/body-lane.js +26 -7
- package/dist/server/body-lane.js.map +1 -1
- package/dist/server/body-parser.d.ts +68 -0
- package/dist/server/body-parser.d.ts.map +1 -0
- package/dist/server/body-parser.js +172 -0
- package/dist/server/body-parser.js.map +1 -0
- package/dist/server/body.d.ts.map +1 -1
- package/dist/server/body.js +62 -7
- package/dist/server/body.js.map +1 -1
- package/dist/server/client-ip.d.ts +8 -0
- package/dist/server/client-ip.d.ts.map +1 -1
- package/dist/server/client-ip.js +8 -0
- package/dist/server/client-ip.js.map +1 -1
- package/dist/server/context.d.ts +71 -6
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/contract.d.ts +15 -5
- package/dist/server/contract.d.ts.map +1 -1
- package/dist/server/contract.js +25 -4
- package/dist/server/contract.js.map +1 -1
- package/dist/server/cookies.d.ts +4 -1
- package/dist/server/cookies.d.ts.map +1 -1
- package/dist/server/cookies.js +40 -2
- package/dist/server/cookies.js.map +1 -1
- package/dist/server/core-copies.d.ts +2 -0
- package/dist/server/core-copies.d.ts.map +1 -0
- package/dist/server/core-copies.js +20 -0
- package/dist/server/core-copies.js.map +1 -0
- package/dist/server/edge.d.ts.map +1 -1
- package/dist/server/edge.js +2 -0
- package/dist/server/edge.js.map +1 -1
- package/dist/server/http.d.ts +11 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +36 -0
- package/dist/server/http.js.map +1 -1
- package/dist/server/idempotency-lane.d.ts +6 -0
- package/dist/server/idempotency-lane.d.ts.map +1 -1
- package/dist/server/idempotency-lane.js +130 -73
- package/dist/server/idempotency-lane.js.map +1 -1
- package/dist/server/install.d.ts +4 -0
- package/dist/server/install.d.ts.map +1 -1
- package/dist/server/install.js +4 -0
- package/dist/server/install.js.map +1 -1
- package/dist/server/methods.d.ts +45 -0
- package/dist/server/methods.d.ts.map +1 -0
- package/dist/server/methods.js +37 -0
- package/dist/server/methods.js.map +1 -0
- package/dist/server/multipart.d.ts +63 -0
- package/dist/server/multipart.d.ts.map +1 -0
- package/dist/server/multipart.js +374 -0
- package/dist/server/multipart.js.map +1 -0
- package/dist/server/node-outcome-hook.d.ts +20 -0
- package/dist/server/node-outcome-hook.d.ts.map +1 -1
- package/dist/server/node-outcome-hook.js +15 -6
- package/dist/server/node-outcome-hook.js.map +1 -1
- package/dist/server/not-found-answer.d.ts +40 -0
- package/dist/server/not-found-answer.d.ts.map +1 -0
- package/dist/server/not-found-answer.js +56 -0
- package/dist/server/not-found-answer.js.map +1 -0
- package/dist/server/not-found.d.ts +23 -0
- package/dist/server/not-found.d.ts.map +1 -0
- package/dist/server/not-found.js +81 -0
- package/dist/server/not-found.js.map +1 -0
- package/dist/server/plugin.d.ts +26 -1
- package/dist/server/plugin.d.ts.map +1 -1
- package/dist/server/plugin.js.map +1 -1
- package/dist/server/proto-guard.d.ts +18 -5
- package/dist/server/proto-guard.d.ts.map +1 -1
- package/dist/server/proto-guard.js +114 -9
- package/dist/server/proto-guard.js.map +1 -1
- package/dist/server/query.d.ts +7 -0
- package/dist/server/query.d.ts.map +1 -1
- package/dist/server/query.js +47 -0
- package/dist/server/query.js.map +1 -1
- package/dist/server/registry.d.ts +13 -2
- package/dist/server/registry.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +11 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +33 -19
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/request-lineage.d.ts +8 -0
- package/dist/server/request-lineage.d.ts.map +1 -0
- package/dist/server/request-lineage.js +14 -0
- package/dist/server/request-lineage.js.map +1 -0
- package/dist/server/request-target.d.ts +31 -0
- package/dist/server/request-target.d.ts.map +1 -0
- package/dist/server/request-target.js +44 -0
- package/dist/server/request-target.js.map +1 -0
- package/dist/server/respond.d.ts +1 -1
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +16 -4
- package/dist/server/respond.js.map +1 -1
- package/dist/server/response-contract-lane.d.ts +19 -1
- package/dist/server/response-contract-lane.d.ts.map +1 -1
- package/dist/server/response-contract-lane.js +59 -14
- package/dist/server/response-contract-lane.js.map +1 -1
- package/dist/server/response-observer-runtime.d.ts +9 -5
- package/dist/server/response-observer-runtime.d.ts.map +1 -1
- package/dist/server/response-observer-runtime.js +10 -6
- package/dist/server/response-observer-runtime.js.map +1 -1
- package/dist/server/runtime-core.d.ts +2 -0
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +55 -3
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +194 -19
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +1342 -274
- package/dist/server/server.js.map +1 -1
- package/dist/server/sse.d.ts.map +1 -1
- package/dist/server/sse.js +10 -7
- package/dist/server/sse.js.map +1 -1
- package/dist/server/static-headers.d.ts +3 -0
- package/dist/server/static-headers.d.ts.map +1 -1
- package/dist/server/static-headers.js +19 -1
- package/dist/server/static-headers.js.map +1 -1
- package/dist/server/transport-lane.d.ts +8 -0
- package/dist/server/transport-lane.d.ts.map +1 -1
- package/dist/server/transport-lane.js +9 -8
- package/dist/server/transport-lane.js.map +1 -1
- package/dist/server/validation.d.ts.map +1 -1
- package/dist/server/validation.js +5 -1
- package/dist/server/validation.js.map +1 -1
- package/dist/server/websocket.d.ts +10 -3
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +7 -4
- package/dist/server/websocket.js.map +1 -1
- package/dist/server/ws-bun.d.ts +2 -0
- package/dist/server/ws-bun.d.ts.map +1 -1
- package/dist/server/ws-bun.js +6 -3
- package/dist/server/ws-bun.js.map +1 -1
- package/dist/server/ws-hook.d.ts +2 -1
- package/dist/server/ws-hook.d.ts.map +1 -1
- package/dist/server.d.ts +8 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +5 -2
- package/dist/server.js.map +1 -1
- package/dist/single-copy-register.js +5 -0
- package/dist/single-copy-register.js.map +1 -1
- package/dist/single-copy.d.ts +46 -3
- package/dist/single-copy.d.ts.map +1 -1
- package/dist/single-copy.js +125 -12
- package/dist/single-copy.js.map +1 -1
- package/dist/tool-contract.d.ts +23 -0
- package/dist/tool-contract.d.ts.map +1 -1
- package/dist/tool-contract.js +37 -3
- package/dist/tool-contract.js.map +1 -1
- package/dist/transport-codec.d.ts.map +1 -1
- package/dist/transport-codec.js +2 -1
- package/dist/transport-codec.js.map +1 -1
- package/package.json +27 -2
- package/src/assurance.ts +52 -2
- package/src/capabilities.ts +81 -22
- package/src/diff.ts +54 -7
- package/src/durable-adapters.ts +20 -3
- package/src/durable-effect.ts +10 -3
- package/src/edge-kit.ts +7 -0
- package/src/effect-lifecycle.ts +5 -1
- package/src/effect-scope.ts +2 -1
- package/src/errors.ts +3 -0
- package/src/evidence.ts +302 -12
- package/src/idempotency.ts +80 -9
- package/src/index.ts +41 -3
- package/src/internal/code-unit-order.ts +8 -0
- package/src/internal/durable-saga-protocol.ts +2 -0
- package/src/internal/route-catalog.ts +19 -18
- package/src/internal/route-compiler.ts +62 -13
- package/src/internal/route-execution.ts +13 -3
- package/src/internal/route-lanes.ts +37 -28
- package/src/internal/route-program.ts +434 -20
- package/src/ledger.ts +11 -5
- package/src/manifest.ts +52 -46
- package/src/mount.ts +62 -0
- package/src/reflection.ts +84 -1
- package/src/response-observer.ts +1 -0
- package/src/router/overlap.ts +258 -0
- package/src/router/pattern.ts +349 -25
- package/src/router/router.ts +76 -51
- package/src/server/auth.ts +75 -0
- package/src/server/bare-error-lane.ts +12 -11
- package/src/server/body-lane.ts +46 -6
- package/src/server/body-parser.ts +241 -0
- package/src/server/body.ts +82 -9
- package/src/server/client-ip.ts +9 -0
- package/src/server/context.ts +219 -12
- package/src/server/contract.ts +54 -9
- package/src/server/cookies.ts +34 -2
- package/src/server/core-copies.ts +21 -0
- package/src/server/edge.ts +2 -0
- package/src/server/http.ts +34 -0
- package/src/server/idempotency-lane.ts +147 -72
- package/src/server/install.ts +6 -0
- package/src/server/methods.ts +143 -0
- package/src/server/multipart.ts +424 -0
- package/src/server/node-outcome-hook.ts +40 -7
- package/src/server/not-found-answer.ts +109 -0
- package/src/server/not-found.ts +121 -0
- package/src/server/plugin.ts +45 -1
- package/src/server/proto-guard.ts +128 -9
- package/src/server/query.ts +49 -0
- package/src/server/registry.ts +17 -2
- package/src/server/request-context.ts +44 -16
- package/src/server/request-lineage.ts +15 -0
- package/src/server/request-target.ts +46 -0
- package/src/server/respond.ts +14 -3
- package/src/server/response-contract-lane.ts +97 -16
- package/src/server/response-observer-runtime.ts +37 -8
- package/src/server/runtime-core.ts +2 -0
- package/src/server/server-types.ts +61 -2
- package/src/server/server.ts +1798 -355
- package/src/server/sse.ts +10 -8
- package/src/server/static-headers.ts +22 -1
- package/src/server/transport-lane.ts +17 -9
- package/src/server/validation.ts +7 -1
- package/src/server/websocket.ts +23 -9
- package/src/server/ws-bun.ts +8 -3
- package/src/server/ws-hook.ts +2 -1
- package/src/server.ts +39 -2
- package/src/single-copy-register.ts +5 -0
- package/src/single-copy.ts +160 -12
- package/src/tool-contract.ts +66 -3
- package/src/transport-codec.ts +2 -1
|
@@ -8,24 +8,27 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { StandardSchemaV1 } from "../schema/standard.ts"
|
|
11
|
+
import { pathnameOf } from "../server/http.ts"
|
|
11
12
|
import type { Registry } from "../server/registry.ts"
|
|
12
13
|
import {
|
|
13
14
|
CONTEXT_SET,
|
|
14
15
|
EMPTY_RESPONSE_CONTROLS,
|
|
15
16
|
isResponseResult,
|
|
16
17
|
type ResponseResult,
|
|
18
|
+
status,
|
|
17
19
|
} from "../server/runtime-core.ts"
|
|
18
20
|
import type { CtxSet, MaybePromise, RawContext, RequestSource, Server } from "../server/server.ts"
|
|
19
21
|
import type {
|
|
20
22
|
InternalHandler,
|
|
21
23
|
RawAfterHandle,
|
|
24
|
+
RawAuthStage,
|
|
22
25
|
RawBeforeHandle,
|
|
23
26
|
RawDerive,
|
|
24
27
|
RawErrorHandler,
|
|
25
28
|
RouteEntry,
|
|
26
29
|
} from "./route-execution.ts"
|
|
27
30
|
|
|
28
|
-
export type ProgramValidationKind = "headers" | "params" | "body" | "query"
|
|
31
|
+
export type ProgramValidationKind = "headers" | "cookies" | "params" | "body" | "query"
|
|
29
32
|
|
|
30
33
|
export type RouteProgramStage =
|
|
31
34
|
| { readonly kind: ProgramValidationKind; readonly schema: StandardSchemaV1 }
|
|
@@ -37,6 +40,11 @@ export type RouteProgramStage =
|
|
|
37
40
|
/** The complete registration-time lifecycle, in semantic execution order. */
|
|
38
41
|
export interface RouteProgram {
|
|
39
42
|
readonly stages: readonly RouteProgramStage[]
|
|
43
|
+
readonly validationOrder: "validate-before-auth" | "auth-before-validation"
|
|
44
|
+
readonly authBeforeValidation: boolean
|
|
45
|
+
readonly authStages: readonly RawAuthStage[]
|
|
46
|
+
/** Protected route fast lane: auth plus validation/handler, with no general lifecycle hooks. */
|
|
47
|
+
readonly authFastLane: boolean
|
|
40
48
|
readonly validationStages: readonly Extract<RouteProgramStage, { kind: ProgramValidationKind }>[]
|
|
41
49
|
readonly derives: readonly RawDerive[]
|
|
42
50
|
readonly beforeHandle: readonly RawBeforeHandle[]
|
|
@@ -54,6 +62,7 @@ export interface RouteProgramInput {
|
|
|
54
62
|
readonly schema: RouteEntry["schema"]
|
|
55
63
|
readonly handler: InternalHandler
|
|
56
64
|
readonly derives: readonly RawDerive[]
|
|
65
|
+
readonly authStages?: readonly RawAuthStage[]
|
|
57
66
|
readonly beforeHandle: readonly RawBeforeHandle[]
|
|
58
67
|
readonly afterHandle: readonly RawAfterHandle[]
|
|
59
68
|
readonly onError: readonly RawErrorHandler[]
|
|
@@ -69,25 +78,18 @@ export function compileRouteProgram(input: RouteProgramInput): RouteProgram {
|
|
|
69
78
|
const stages: RouteProgramStage[] = []
|
|
70
79
|
const validationStages: Extract<RouteProgramStage, { kind: ProgramValidationKind }>[] = []
|
|
71
80
|
const schema = input.schema
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
stages.push(stage)
|
|
85
|
-
validationStages.push(stage)
|
|
86
|
-
}
|
|
87
|
-
if (schema?.query !== undefined) {
|
|
88
|
-
const stage = { kind: "query", schema: schema.query } as const
|
|
89
|
-
stages.push(stage)
|
|
90
|
-
validationStages.push(stage)
|
|
81
|
+
const authStages = input.authStages ?? []
|
|
82
|
+
const authBeforeValidation =
|
|
83
|
+
authStages.length > 0
|
|
84
|
+
? schema?.validationOrder !== "validate-before-auth"
|
|
85
|
+
: schema?.validationOrder === "auth-before-validation"
|
|
86
|
+
for (const kind of ["headers", "cookies", "params", "body", "query"] as const) {
|
|
87
|
+
const validator = schema?.[kind]
|
|
88
|
+
if (validator !== undefined) {
|
|
89
|
+
const stage = { kind, schema: validator }
|
|
90
|
+
stages.push(stage)
|
|
91
|
+
validationStages.push(stage)
|
|
92
|
+
}
|
|
91
93
|
}
|
|
92
94
|
for (const derive of input.derives) stages.push({ kind: "derive", run: derive })
|
|
93
95
|
for (const before of input.beforeHandle) stages.push({ kind: "before", run: before })
|
|
@@ -96,6 +98,14 @@ export function compileRouteProgram(input: RouteProgramInput): RouteProgram {
|
|
|
96
98
|
|
|
97
99
|
return Object.freeze({
|
|
98
100
|
stages: Object.freeze(stages),
|
|
101
|
+
validationOrder: authBeforeValidation ? "auth-before-validation" : "validate-before-auth",
|
|
102
|
+
authBeforeValidation,
|
|
103
|
+
authStages: Object.freeze([...authStages]),
|
|
104
|
+
authFastLane:
|
|
105
|
+
authStages.length > 0 &&
|
|
106
|
+
input.derives.length === 0 &&
|
|
107
|
+
input.beforeHandle.length === 0 &&
|
|
108
|
+
input.afterHandle.length === 0,
|
|
99
109
|
validationStages: Object.freeze(validationStages),
|
|
100
110
|
derives: Object.freeze([...input.derives]),
|
|
101
111
|
beforeHandle: Object.freeze([...input.beforeHandle]),
|
|
@@ -146,8 +156,395 @@ function isEarly(value: unknown): value is Response | ResponseResult {
|
|
|
146
156
|
return value instanceof Response || isResponseResult(value)
|
|
147
157
|
}
|
|
148
158
|
|
|
159
|
+
function authHeadersOf(source: RequestSource): import("../server/auth.ts").AuthHeaders {
|
|
160
|
+
return Object.freeze({
|
|
161
|
+
get: (name: string) => source.headers.get(name),
|
|
162
|
+
has: (name: string) => source.headers.has(name),
|
|
163
|
+
forEach: (callback: (value: string, name: string) => void) => {
|
|
164
|
+
source.headers.forEach(callback)
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function authenticationInputOf(
|
|
170
|
+
source: RequestSource,
|
|
171
|
+
ctx: RawContext,
|
|
172
|
+
): import("../server/auth.ts").AuthenticationInput<unknown> {
|
|
173
|
+
return Object.freeze({
|
|
174
|
+
method: source.method,
|
|
175
|
+
pathname: pathnameOf(source.url),
|
|
176
|
+
headers: authHeadersOf(source),
|
|
177
|
+
params: Object.freeze({ ...ctx.params }),
|
|
178
|
+
env: ctx.env,
|
|
179
|
+
clientIp: ctx.clientIp,
|
|
180
|
+
signal: ctx.signal,
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function defaultAuthenticationResponse(
|
|
185
|
+
reason: import("../server/auth.ts").AuthenticationFailureReason | undefined,
|
|
186
|
+
): ResponseResult {
|
|
187
|
+
if (reason === "forbidden") return status(403, { ok: false, error: "forbidden" })
|
|
188
|
+
if (reason === "unavailable") return status(503, { ok: false, error: "auth_unavailable" })
|
|
189
|
+
return status(401, { ok: false, error: "unauthorized" })
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function validAuthenticationFailureReason(
|
|
193
|
+
value: unknown,
|
|
194
|
+
): value is import("../server/auth.ts").AuthenticationFailureReason | undefined {
|
|
195
|
+
return (
|
|
196
|
+
value === undefined ||
|
|
197
|
+
value === "unauthenticated" ||
|
|
198
|
+
value === "forbidden" ||
|
|
199
|
+
value === "unavailable"
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function runAuthenticationStages<T>(
|
|
204
|
+
host: RouteProgramRuntime,
|
|
205
|
+
entry: RouteEntry,
|
|
206
|
+
program: RouteProgram,
|
|
207
|
+
source: RequestSource,
|
|
208
|
+
ctx: RawContext,
|
|
209
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
210
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
211
|
+
start = 0,
|
|
212
|
+
validationComplete = false,
|
|
213
|
+
): ProgramResult<T> {
|
|
214
|
+
if (start >= program.authStages.length) {
|
|
215
|
+
if (program.authFastLane) {
|
|
216
|
+
const validationIndex = validationComplete ? program.validationStages.length : 0
|
|
217
|
+
return runFastAuthenticatedProgram(
|
|
218
|
+
host,
|
|
219
|
+
entry,
|
|
220
|
+
program,
|
|
221
|
+
source,
|
|
222
|
+
ctx,
|
|
223
|
+
finalize,
|
|
224
|
+
wrapResponse,
|
|
225
|
+
validationIndex,
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
return validationComplete
|
|
229
|
+
? runSync(
|
|
230
|
+
host,
|
|
231
|
+
entry,
|
|
232
|
+
program,
|
|
233
|
+
source,
|
|
234
|
+
ctx,
|
|
235
|
+
finalize,
|
|
236
|
+
wrapResponse,
|
|
237
|
+
program.validationStages.length,
|
|
238
|
+
undefined,
|
|
239
|
+
)
|
|
240
|
+
: runValidatedProgram(host, entry, program, source, ctx, finalize, wrapResponse)
|
|
241
|
+
}
|
|
242
|
+
const input = authenticationInputOf(source, ctx)
|
|
243
|
+
const stage = program.authStages[start]!
|
|
244
|
+
let outcome: MaybePromise<import("../server/auth.ts").AuthenticationResult<unknown>>
|
|
245
|
+
try {
|
|
246
|
+
outcome = stage.run(input)
|
|
247
|
+
} catch {
|
|
248
|
+
return finalize(defaultAuthenticationResponse("unavailable"), responseSet(ctx))
|
|
249
|
+
}
|
|
250
|
+
if (outcome instanceof Promise) {
|
|
251
|
+
return outcome.then(
|
|
252
|
+
(settled) =>
|
|
253
|
+
finishAuthentication(
|
|
254
|
+
host,
|
|
255
|
+
entry,
|
|
256
|
+
program,
|
|
257
|
+
source,
|
|
258
|
+
ctx,
|
|
259
|
+
finalize,
|
|
260
|
+
wrapResponse,
|
|
261
|
+
settled,
|
|
262
|
+
start + 1,
|
|
263
|
+
validationComplete,
|
|
264
|
+
),
|
|
265
|
+
() => finalize(defaultAuthenticationResponse("unavailable"), responseSet(ctx)),
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
return finishAuthentication(
|
|
269
|
+
host,
|
|
270
|
+
entry,
|
|
271
|
+
program,
|
|
272
|
+
source,
|
|
273
|
+
ctx,
|
|
274
|
+
finalize,
|
|
275
|
+
wrapResponse,
|
|
276
|
+
outcome,
|
|
277
|
+
start + 1,
|
|
278
|
+
validationComplete,
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function finishAuthentication<T>(
|
|
283
|
+
host: RouteProgramRuntime,
|
|
284
|
+
entry: RouteEntry,
|
|
285
|
+
program: RouteProgram,
|
|
286
|
+
source: RequestSource,
|
|
287
|
+
ctx: RawContext,
|
|
288
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
289
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
290
|
+
result: import("../server/auth.ts").AuthenticationResult<unknown>,
|
|
291
|
+
next: number,
|
|
292
|
+
validationComplete = false,
|
|
293
|
+
): ProgramResult<T> {
|
|
294
|
+
if (
|
|
295
|
+
result === null ||
|
|
296
|
+
typeof result !== "object" ||
|
|
297
|
+
(result.kind !== "authenticated" && result.kind !== "rejected")
|
|
298
|
+
) {
|
|
299
|
+
return finalize(defaultAuthenticationResponse("unavailable"), responseSet(ctx))
|
|
300
|
+
}
|
|
301
|
+
if (result.kind === "rejected") {
|
|
302
|
+
if (!validAuthenticationFailureReason(result.reason)) {
|
|
303
|
+
return finalize(defaultAuthenticationResponse("unavailable"), responseSet(ctx))
|
|
304
|
+
}
|
|
305
|
+
if (
|
|
306
|
+
result.response !== undefined &&
|
|
307
|
+
!(result.response instanceof Response) &&
|
|
308
|
+
!isResponseResult(result.response)
|
|
309
|
+
) {
|
|
310
|
+
return finalize(defaultAuthenticationResponse("unavailable"), responseSet(ctx))
|
|
311
|
+
}
|
|
312
|
+
const response = result.response ?? defaultAuthenticationResponse(result.reason)
|
|
313
|
+
return finalize(response, responseSet(ctx))
|
|
314
|
+
}
|
|
315
|
+
if (result.principal === null || result.principal === undefined) {
|
|
316
|
+
return finalize(defaultAuthenticationResponse("unavailable"), responseSet(ctx))
|
|
317
|
+
}
|
|
318
|
+
Object.defineProperty(ctx, "principal", {
|
|
319
|
+
value: result.principal,
|
|
320
|
+
enumerable: true,
|
|
321
|
+
configurable: true,
|
|
322
|
+
writable: false,
|
|
323
|
+
})
|
|
324
|
+
return runAuthenticationStages(
|
|
325
|
+
host,
|
|
326
|
+
entry,
|
|
327
|
+
program,
|
|
328
|
+
source,
|
|
329
|
+
ctx,
|
|
330
|
+
finalize,
|
|
331
|
+
wrapResponse,
|
|
332
|
+
next,
|
|
333
|
+
validationComplete,
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Validate first only when a route explicitly opts out of the protected auth-first default. */
|
|
338
|
+
function runFastAuthenticatedProgram<T>(
|
|
339
|
+
host: RouteProgramRuntime,
|
|
340
|
+
entry: RouteEntry,
|
|
341
|
+
program: RouteProgram,
|
|
342
|
+
source: RequestSource,
|
|
343
|
+
ctx: RawContext,
|
|
344
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
345
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
346
|
+
validationIndex: number,
|
|
347
|
+
): ProgramResult<T> {
|
|
348
|
+
for (let i = validationIndex; i < program.validationStages.length; i++) {
|
|
349
|
+
const stage = program.validationStages[i]!
|
|
350
|
+
const outcome =
|
|
351
|
+
stage.kind === "body"
|
|
352
|
+
? host.readProgramBody(entry, source, ctx)
|
|
353
|
+
: host.validateProgramStage(entry, stage, source, ctx)
|
|
354
|
+
if (outcome instanceof Promise) {
|
|
355
|
+
return continueFastAuthenticatedProgram(
|
|
356
|
+
host,
|
|
357
|
+
entry,
|
|
358
|
+
program,
|
|
359
|
+
source,
|
|
360
|
+
ctx,
|
|
361
|
+
finalize,
|
|
362
|
+
wrapResponse,
|
|
363
|
+
i + 1,
|
|
364
|
+
outcome,
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
if (outcome !== undefined) return wrapResponse(outcome)
|
|
368
|
+
}
|
|
369
|
+
return runFastAuthenticatedHandler(host, entry, program, ctx, finalize, wrapResponse)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function continueFastAuthenticatedProgram<T>(
|
|
373
|
+
host: RouteProgramRuntime,
|
|
374
|
+
entry: RouteEntry,
|
|
375
|
+
program: RouteProgram,
|
|
376
|
+
source: RequestSource,
|
|
377
|
+
ctx: RawContext,
|
|
378
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
379
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
380
|
+
validationIndex: number,
|
|
381
|
+
pending: Promise<unknown>,
|
|
382
|
+
): Promise<T> {
|
|
383
|
+
try {
|
|
384
|
+
const settled = await pending
|
|
385
|
+
if (settled !== undefined) return wrapResponse(settled as Response | ResponseResult)
|
|
386
|
+
return await runFastAuthenticatedProgram(
|
|
387
|
+
host,
|
|
388
|
+
entry,
|
|
389
|
+
program,
|
|
390
|
+
source,
|
|
391
|
+
ctx,
|
|
392
|
+
finalize,
|
|
393
|
+
wrapResponse,
|
|
394
|
+
validationIndex,
|
|
395
|
+
)
|
|
396
|
+
} catch (error) {
|
|
397
|
+
return await host.handleProgramError(entry, error, ctx, finalize, wrapResponse)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function runFastAuthenticatedHandler<T>(
|
|
402
|
+
host: RouteProgramRuntime,
|
|
403
|
+
entry: RouteEntry,
|
|
404
|
+
program: RouteProgram,
|
|
405
|
+
ctx: RawContext,
|
|
406
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
407
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
408
|
+
): ProgramResult<T> {
|
|
409
|
+
let result: unknown
|
|
410
|
+
try {
|
|
411
|
+
result = program.handler(ctx)
|
|
412
|
+
} catch (error) {
|
|
413
|
+
return host.handleProgramError(entry, error, ctx, finalize, wrapResponse)
|
|
414
|
+
}
|
|
415
|
+
if (result instanceof Promise) {
|
|
416
|
+
return result.then(
|
|
417
|
+
(value) => host.finishProgramResult(entry, ctx, value, finalize, wrapResponse),
|
|
418
|
+
(error) => host.handleProgramError(entry, error, ctx, finalize, wrapResponse),
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
return host.finishProgramResult(entry, ctx, result, finalize, wrapResponse)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function runValidationBeforeAuthentication<T>(
|
|
425
|
+
host: RouteProgramRuntime,
|
|
426
|
+
entry: RouteEntry,
|
|
427
|
+
program: RouteProgram,
|
|
428
|
+
source: RequestSource,
|
|
429
|
+
ctx: RawContext,
|
|
430
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
431
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
432
|
+
validationIndex = 0,
|
|
433
|
+
): ProgramResult<T> {
|
|
434
|
+
for (let i = validationIndex; i < program.validationStages.length; i++) {
|
|
435
|
+
const stage = program.validationStages[i]!
|
|
436
|
+
const outcome =
|
|
437
|
+
stage.kind === "body"
|
|
438
|
+
? host.readProgramBody(entry, source, ctx)
|
|
439
|
+
: host.validateProgramStage(entry, stage, source, ctx)
|
|
440
|
+
if (outcome instanceof Promise) {
|
|
441
|
+
return continueValidationBeforeAuthentication(
|
|
442
|
+
host,
|
|
443
|
+
entry,
|
|
444
|
+
program,
|
|
445
|
+
source,
|
|
446
|
+
ctx,
|
|
447
|
+
finalize,
|
|
448
|
+
wrapResponse,
|
|
449
|
+
i + 1,
|
|
450
|
+
outcome,
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
if (outcome !== undefined) return wrapResponse(outcome)
|
|
454
|
+
}
|
|
455
|
+
return runAuthenticationStages(host, entry, program, source, ctx, finalize, wrapResponse, 0, true)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async function continueValidationBeforeAuthentication<T>(
|
|
459
|
+
host: RouteProgramRuntime,
|
|
460
|
+
entry: RouteEntry,
|
|
461
|
+
program: RouteProgram,
|
|
462
|
+
source: RequestSource,
|
|
463
|
+
ctx: RawContext,
|
|
464
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
465
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
466
|
+
validationIndex: number,
|
|
467
|
+
pending: Promise<unknown>,
|
|
468
|
+
): Promise<T> {
|
|
469
|
+
try {
|
|
470
|
+
const settled = await pending
|
|
471
|
+
if (settled !== undefined) return wrapResponse(settled as Response | ResponseResult)
|
|
472
|
+
return await runValidationBeforeAuthentication(
|
|
473
|
+
host,
|
|
474
|
+
entry,
|
|
475
|
+
program,
|
|
476
|
+
source,
|
|
477
|
+
ctx,
|
|
478
|
+
finalize,
|
|
479
|
+
wrapResponse,
|
|
480
|
+
validationIndex,
|
|
481
|
+
)
|
|
482
|
+
} catch (error) {
|
|
483
|
+
return await host.handleProgramError(entry, error, ctx, finalize, wrapResponse)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function runValidatedProgram<T>(
|
|
488
|
+
host: RouteProgramRuntime,
|
|
489
|
+
entry: RouteEntry,
|
|
490
|
+
program: RouteProgram,
|
|
491
|
+
source: RequestSource,
|
|
492
|
+
ctx: RawContext,
|
|
493
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
494
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
495
|
+
): ProgramResult<T> {
|
|
496
|
+
if (program.hasDecorations) Object.assign(ctx, program.decorations)
|
|
497
|
+
return runSync(host, entry, program, source, ctx, finalize, wrapResponse, 0, undefined)
|
|
498
|
+
}
|
|
499
|
+
|
|
149
500
|
type ProgramResult<T> = MaybePromise<T>
|
|
150
501
|
|
|
502
|
+
/** Opt-in sensitive-route lane: run authentication/policy hooks before parsing untrusted input. */
|
|
503
|
+
async function runAuthBeforeValidation<T>(
|
|
504
|
+
host: RouteProgramRuntime,
|
|
505
|
+
entry: RouteEntry,
|
|
506
|
+
program: RouteProgram,
|
|
507
|
+
source: RequestSource,
|
|
508
|
+
ctx: RawContext,
|
|
509
|
+
finalize: (result: unknown, set: CtxSet) => T,
|
|
510
|
+
wrapResponse: (response: Response | ResponseResult) => T,
|
|
511
|
+
): Promise<T> {
|
|
512
|
+
try {
|
|
513
|
+
if (program.hasDecorations) Object.assign(ctx, program.decorations)
|
|
514
|
+
|
|
515
|
+
for (const derive of program.derives) {
|
|
516
|
+
const result = derive(ctx)
|
|
517
|
+
const settled = result instanceof Promise ? await result : result
|
|
518
|
+
if (isEarly(settled)) return finalize(settled, responseSet(ctx))
|
|
519
|
+
Object.assign(ctx, settled)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
for (const before of program.beforeHandle) {
|
|
523
|
+
const result = before(ctx)
|
|
524
|
+
const settled = result instanceof Promise ? await result : result
|
|
525
|
+
if (settled !== undefined) return finalize(settled, responseSet(ctx))
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
for (const stage of program.validationStages) {
|
|
529
|
+
const result =
|
|
530
|
+
stage.kind === "body"
|
|
531
|
+
? await host.readProgramBody(entry, source, ctx)
|
|
532
|
+
: host.validateProgramStage(entry, stage, source, ctx)
|
|
533
|
+
const settled = result instanceof Promise ? await result : result
|
|
534
|
+
if (settled !== undefined) return wrapResponse(settled)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
let result = await program.handler(ctx)
|
|
538
|
+
for (const after of program.afterHandle) {
|
|
539
|
+
const transformed = after(result, ctx)
|
|
540
|
+
result = transformed instanceof Promise ? await transformed : transformed
|
|
541
|
+
}
|
|
542
|
+
return await host.finishProgramResult(entry, ctx, result, finalize, wrapResponse)
|
|
543
|
+
} catch (error) {
|
|
544
|
+
return await host.handleProgramError(entry, error, ctx, finalize, wrapResponse)
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
151
548
|
type PendingPhase =
|
|
152
549
|
| { readonly kind: "validation"; readonly next: number }
|
|
153
550
|
| { readonly kind: "derive"; readonly next: number }
|
|
@@ -167,6 +564,23 @@ export function executeRouteProgram<T, R extends Registry, Ctx>(
|
|
|
167
564
|
): ProgramResult<T> {
|
|
168
565
|
const host = runtime as unknown as RouteProgramRuntime
|
|
169
566
|
try {
|
|
567
|
+
if (program.authStages.length > 0) {
|
|
568
|
+
if (program.hasDecorations) Object.assign(ctx, program.decorations)
|
|
569
|
+
return program.authBeforeValidation
|
|
570
|
+
? runAuthenticationStages(host, entry, program, source, ctx, finalize, wrapResponse)
|
|
571
|
+
: runValidationBeforeAuthentication(
|
|
572
|
+
host,
|
|
573
|
+
entry,
|
|
574
|
+
program,
|
|
575
|
+
source,
|
|
576
|
+
ctx,
|
|
577
|
+
finalize,
|
|
578
|
+
wrapResponse,
|
|
579
|
+
)
|
|
580
|
+
}
|
|
581
|
+
if (program.validationOrder === "auth-before-validation") {
|
|
582
|
+
return runAuthBeforeValidation(host, entry, program, source, ctx, finalize, wrapResponse)
|
|
583
|
+
}
|
|
170
584
|
if (program.hasDecorations) Object.assign(ctx, program.decorations)
|
|
171
585
|
return runSync(host, entry, program, source, ctx, finalize, wrapResponse, 0, undefined)
|
|
172
586
|
} catch (error) {
|
package/src/ledger.ts
CHANGED
|
@@ -145,7 +145,7 @@ const ERROR_CODE = /^[a-z][a-z0-9_.-]{0,63}$/
|
|
|
145
145
|
const DIGEST_HEX = /^[0-9a-f]{64}$/
|
|
146
146
|
|
|
147
147
|
function printableToken(value: string, maxLength: number): boolean {
|
|
148
|
-
if (value.length === 0 || value.length > maxLength) return false
|
|
148
|
+
if (typeof value !== "string" || value.length === 0 || value.length > maxLength) return false
|
|
149
149
|
for (let index = 0; index < value.length; index++) {
|
|
150
150
|
const code = value.charCodeAt(index)
|
|
151
151
|
if (code < 33 || code > 126) return false
|
|
@@ -153,6 +153,9 @@ function printableToken(value: string, maxLength: number): boolean {
|
|
|
153
153
|
return true
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// `RegExp.test` stringifies its argument, so a check on a token's shape must first check it is a string.
|
|
157
|
+
const validDigest = (value: string): boolean => typeof value === "string" && DIGEST_HEX.test(value)
|
|
158
|
+
|
|
156
159
|
function validateCost(cost: EffectCost): EffectCost {
|
|
157
160
|
if (Object(cost) !== cost || Array.isArray(cost)) {
|
|
158
161
|
throw new TypeError("effect ledger: invalid cost")
|
|
@@ -181,7 +184,7 @@ export function normalizeEffectMetadata(input: EffectMetadata): EffectMetadata {
|
|
|
181
184
|
if (input.target !== undefined && !printableToken(input.target, TARGET_MAX_LENGTH)) {
|
|
182
185
|
throw new TypeError("effect ledger: invalid target")
|
|
183
186
|
}
|
|
184
|
-
if (input.digest !== undefined && !
|
|
187
|
+
if (input.digest !== undefined && !validDigest(input.digest)) {
|
|
185
188
|
throw new TypeError("effect ledger: invalid digest")
|
|
186
189
|
}
|
|
187
190
|
return Object.freeze({
|
|
@@ -236,7 +239,7 @@ class BoundedRequestLedger implements RequestLedger {
|
|
|
236
239
|
append(input: EffectEntryInput): EffectEntry {
|
|
237
240
|
if (this.sealResult !== undefined) throw new EffectLedgerSealedError()
|
|
238
241
|
if (this.items.length >= this.maxEntries) throw new EffectLedgerOverflowError(this.maxEntries)
|
|
239
|
-
if (!validCapabilityId(input.capability)) {
|
|
242
|
+
if (typeof input.capability !== "string" || !validCapabilityId(input.capability)) {
|
|
240
243
|
throw new TypeError(`effect ledger: invalid capability ${JSON.stringify(input.capability)}`)
|
|
241
244
|
}
|
|
242
245
|
const phase = input.phase ?? "intent"
|
|
@@ -249,10 +252,13 @@ class BoundedRequestLedger implements RequestLedger {
|
|
|
249
252
|
if (input.target !== undefined && !printableToken(input.target, TARGET_MAX_LENGTH)) {
|
|
250
253
|
throw new TypeError("effect ledger: invalid target")
|
|
251
254
|
}
|
|
252
|
-
if (input.digest !== undefined && !
|
|
255
|
+
if (input.digest !== undefined && !validDigest(input.digest)) {
|
|
253
256
|
throw new TypeError("effect ledger: invalid digest")
|
|
254
257
|
}
|
|
255
|
-
if (
|
|
258
|
+
if (
|
|
259
|
+
input.error !== undefined &&
|
|
260
|
+
(typeof input.error?.code !== "string" || !ERROR_CODE.test(input.error.code))
|
|
261
|
+
) {
|
|
256
262
|
throw new TypeError("effect ledger: error code must be a bounded lowercase token")
|
|
257
263
|
}
|
|
258
264
|
// Only the known token fields are copied - a stray `payload`-like property never survives append.
|