@plitzi/sdk-server 0.37.1 → 0.37.3
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/CHANGELOG.md +1296 -0
- package/README.md +126 -7
- package/dist/actions.d.ts +20 -8
- package/dist/actions.js +11 -4
- package/dist/adapters/cloudAdapters.js +4 -3
- package/dist/adapters/jsonAdapters.js +28 -6
- package/dist/auth.d.ts +7 -4
- package/dist/auth.js +8 -6
- package/dist/core/auth/api.d.ts +29 -1
- package/dist/core/auth/api.js +47 -13
- package/dist/core/auth/createAuth.d.ts +4 -0
- package/dist/core/auth/createAuth.js +3 -3
- package/dist/core/auth/credentials.d.ts +3 -0
- package/dist/core/auth/credentials.js +1 -1
- package/dist/core/auth/csrf.d.ts +25 -1
- package/dist/core/auth/csrf.js +29 -4
- package/dist/core/auth/renewal.d.ts +26 -0
- package/dist/core/auth/renewal.js +24 -0
- package/dist/core/auth/routes.js +8 -6
- package/dist/core/auth/session.d.ts +45 -0
- package/dist/core/auth/session.js +47 -1
- package/dist/core/auth/sessionActivity.d.ts +11 -0
- package/dist/core/auth/sessionActivity.js +14 -0
- package/dist/core/auth/throttle.d.ts +15 -0
- package/dist/core/auth/throttle.js +29 -1
- package/dist/core/auth/totp.d.ts +9 -1
- package/dist/core/auth/totp.js +11 -6
- package/dist/core/createServer.actions.test.d.ts +1 -0
- package/dist/core/createServer.d.ts +14 -1
- package/dist/core/createServer.functions.test.d.ts +1 -0
- package/dist/core/createServer.js +22 -12
- package/dist/core/fontStore.js +1 -1
- package/dist/core/handlers/authRouteHandlers.d.ts +1 -1
- package/dist/core/handlers/authRouteHandlers.js +57 -21
- package/dist/core/handlers/csrfMiddleware.d.ts +23 -0
- package/dist/core/handlers/csrfMiddleware.js +31 -7
- package/dist/core/handlers/csrfMiddleware.test.d.ts +1 -0
- package/dist/core/handlers/oauthRouteHandlers.js +5 -3
- package/dist/core/handlers/socialRouteHandlers.d.ts +1 -1
- package/dist/core/handlers/socialRouteHandlers.js +2 -1
- package/dist/core/http/dispatcher.d.ts +9 -1
- package/dist/core/http/dispatcher.js +38 -11
- package/dist/core/http/navigation.d.ts +5 -2
- package/dist/core/http/navigation.js +7 -4
- package/dist/core/http/socketResponse.d.ts +18 -0
- package/dist/core/http/socketResponse.js +59 -0
- package/dist/core/http/sse.d.ts +26 -0
- package/dist/core/http/sse.js +78 -0
- package/dist/core/http/stages/authRoutes.js +3 -1
- package/dist/core/http/stages/exchangeRoute.test.d.ts +1 -0
- package/dist/core/http/stages/middlewares.d.ts +1 -1
- package/dist/core/http/stages/middlewares.js +16 -10
- package/dist/core/http/stages/middlewares.test.d.ts +1 -0
- package/dist/core/http/stages/oauth.js +2 -2
- package/dist/core/http/stages/sessionRenewal.d.ts +16 -0
- package/dist/core/http/stages/sessionRenewal.js +81 -0
- package/dist/core/http/stages/sessionRenewal.test.d.ts +1 -0
- package/dist/core/http/stages/signInRoute.d.ts +2 -0
- package/dist/core/http/stages/signInRoute.js +139 -0
- package/dist/core/http/stages/signInRoute.test.d.ts +1 -0
- package/dist/core/http/types.d.ts +9 -0
- package/dist/core/http/webExchange.d.ts +18 -0
- package/dist/core/http/webExchange.js +53 -0
- package/dist/core/oauth/authorize.js +37 -13
- package/dist/core/oauth/consentPage.js +11 -0
- package/dist/core/oauth/issueContext.d.ts +14 -0
- package/dist/core/oauth/issueContext.js +32 -0
- package/dist/core/oauth/records.d.ts +7 -0
- package/dist/core/oauth/register.d.ts +2 -0
- package/dist/core/oauth/register.js +32 -11
- package/dist/core/oauth/register.test.d.ts +1 -0
- package/dist/core/oauth/token.js +21 -6
- package/dist/core/oauth/token.test.d.ts +1 -0
- package/dist/core/requestParser.d.ts +4 -0
- package/dist/core/requestParser.js +14 -7
- package/dist/core/sdkAssets.js +1 -1
- package/dist/core/server/baseServer.d.ts +14 -1
- package/dist/core/server/baseServer.js +43 -11
- package/dist/core/server/closeOnSignals.d.ts +28 -0
- package/dist/core/server/closeOnSignals.js +47 -0
- package/dist/core/server/closeOnSignals.test.d.ts +1 -0
- package/dist/core/server/drain.d.ts +18 -0
- package/dist/core/server/drain.js +62 -0
- package/dist/core/server/drain.test.d.ts +1 -0
- package/dist/core/server/fleet/channel.d.ts +103 -0
- package/dist/core/server/fleet/channel.js +191 -0
- package/dist/core/server/fleet/channel.test.d.ts +1 -0
- package/dist/core/server/fleet/fixtures/fleetServer.d.ts +1 -0
- package/dist/core/server/fleet/fleet.integration.test.d.ts +1 -0
- package/dist/core/server/fleet/link.d.ts +23 -0
- package/dist/core/server/fleet/link.js +70 -0
- package/dist/core/server/fleet/relay.d.ts +11 -0
- package/dist/core/server/fleet/relay.js +104 -0
- package/dist/core/server/fleet/relay.test.d.ts +1 -0
- package/dist/core/server/fleet/role.d.ts +46 -0
- package/dist/core/server/fleet/role.js +59 -0
- package/dist/core/server/fleet/role.test.d.ts +1 -0
- package/dist/core/server/fleet/stores.d.ts +7 -0
- package/dist/core/server/fleet/stores.js +19 -0
- package/dist/core/server/fleet/supervisor.d.ts +26 -0
- package/dist/core/server/fleet/supervisor.js +108 -0
- package/dist/core/server/fleet/supervisor.test.d.ts +1 -0
- package/dist/core/server/pageServer.js +36 -5
- package/dist/core/services/action.js +10 -2
- package/dist/core/services/functionRoutes.d.ts +9 -0
- package/dist/core/services/functionRoutes.js +47 -0
- package/dist/core/services/realtime.d.ts +8 -0
- package/dist/core/services/realtime.js +57 -0
- package/dist/core/services/registry.js +6 -2
- package/dist/core/staticFiles.js +12 -4
- package/dist/core/staticFiles.test.d.ts +1 -0
- package/dist/core/transports.d.ts +13 -3
- package/dist/core/transports.js +14 -5
- package/dist/functions-api.d.ts +281 -0
- package/dist/functions.d.ts +19 -0
- package/dist/functions.js +5 -0
- package/dist/functionsRunner.d.ts +15 -0
- package/dist/functionsRunner.js +6 -0
- package/dist/handlers.d.ts +2 -2
- package/dist/handlers.js +2 -2
- package/dist/helpers/atomicFile.d.ts +10 -0
- package/dist/helpers/atomicFile.js +33 -0
- package/dist/helpers/atomicFile.test.d.ts +1 -0
- package/dist/helpers/buildResponseHelpers.js +35 -11
- package/dist/helpers/buildServerInfo.js +4 -2
- package/dist/helpers/buildServerInfo.test.d.ts +1 -0
- package/dist/helpers/cache/CompressedFileCache.d.ts +18 -0
- package/dist/helpers/cache/CompressedFileCache.js +44 -0
- package/dist/helpers/cache/CompressedFileCache.test.d.ts +1 -0
- package/dist/helpers/cache/cacheManager.d.ts +1 -1
- package/dist/helpers/cache/cacheManager.js +2 -1
- package/dist/helpers/cache/cacheManager.test.d.ts +1 -0
- package/dist/helpers/cache/index.d.ts +1 -1
- package/dist/helpers/cache/keys.d.ts +15 -1
- package/dist/helpers/cache/keys.js +47 -5
- package/dist/helpers/cache/serverCaches.d.ts +7 -1
- package/dist/helpers/compress.d.ts +3 -1
- package/dist/helpers/compress.js +11 -3
- package/dist/helpers/hydrationPayload.d.ts +11 -0
- package/dist/helpers/hydrationPayload.js +51 -0
- package/dist/helpers/hydrationPayload.test.d.ts +1 -0
- package/dist/helpers/metrics.d.ts +1 -1
- package/dist/helpers/metrics.js +3 -2
- package/dist/helpers/outboundGuard.d.ts +14 -6
- package/dist/helpers/outboundGuard.js +132 -8
- package/dist/helpers/paintedCookie.d.ts +8 -0
- package/dist/helpers/paintedCookie.js +13 -0
- package/dist/helpers/reportReactBuild.d.ts +9 -0
- package/dist/helpers/reportReactBuild.js +18 -0
- package/dist/helpers/reportReactBuild.test.d.ts +1 -0
- package/dist/helpers/serverLog.d.ts +25 -1
- package/dist/helpers/serverLog.js +68 -3
- package/dist/helpers/serverLog.test.d.ts +1 -0
- package/dist/helpers/wsFrame.d.ts +3 -0
- package/dist/helpers/wsFrame.js +5 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +6 -2
- package/dist/kernel.d.ts +2 -1
- package/dist/kernel.js +3 -2
- package/dist/modules/actions/connectorWrite.js +2 -1
- package/dist/modules/actions/index.d.ts +41 -13
- package/dist/modules/actions/index.js +52 -9
- package/dist/modules/actions/jobs/failureStreak.d.ts +17 -0
- package/dist/modules/actions/jobs/failureStreak.js +47 -0
- package/dist/modules/actions/jobs/failureStreak.test.d.ts +1 -0
- package/dist/modules/actions/jobs/index.d.ts +33 -0
- package/dist/modules/actions/jobs/index.js +59 -0
- package/dist/modules/actions/jobs/memoryQueue.d.ts +33 -0
- package/dist/modules/actions/jobs/memoryQueue.js +194 -0
- package/dist/modules/actions/jobs/memoryStores.test.d.ts +1 -0
- package/dist/modules/actions/jobs/scheduler.d.ts +64 -0
- package/dist/modules/actions/jobs/scheduler.js +130 -0
- package/dist/modules/actions/jobs/scheduler.test.d.ts +1 -0
- package/dist/modules/actions/jobs/schedules.d.ts +23 -0
- package/dist/modules/actions/jobs/schedules.js +47 -0
- package/dist/modules/actions/jobs/storeClock.d.ts +16 -0
- package/dist/modules/actions/jobs/storeClock.js +32 -0
- package/dist/modules/actions/jobs/testing/jobQueueContract.d.ts +25 -0
- package/dist/modules/actions/jobs/worker.d.ts +64 -0
- package/dist/modules/actions/jobs/worker.js +267 -0
- package/dist/modules/actions/jobs/worker.test.d.ts +1 -0
- package/dist/modules/actions/moduleFor.js +6 -2
- package/dist/modules/actions/runtime/errors.d.ts +12 -0
- package/dist/modules/actions/runtime/errors.js +16 -1
- package/dist/modules/actions/runtime/kvChange.d.ts +27 -0
- package/dist/modules/actions/runtime/kvChange.js +23 -0
- package/dist/modules/actions/runtime/kvList.d.ts +51 -0
- package/dist/modules/actions/runtime/kvList.js +52 -0
- package/dist/modules/actions/runtime/kvStore.d.ts +8 -0
- package/dist/modules/actions/runtime/kvStore.js +46 -20
- package/dist/modules/actions/runtime/memoryKv.d.ts +3 -0
- package/dist/modules/actions/runtime/memoryKv.js +19 -1
- package/dist/modules/actions/runtime/namespaceKv.d.ts +4 -6
- package/dist/modules/actions/runtime/namespaceKv.js +15 -3
- package/dist/modules/actions/runtime/namespaceKv.test.d.ts +1 -0
- package/dist/modules/actions/runtime/precheck.js +5 -9
- package/dist/modules/actions/runtime/rateLimit.d.ts +19 -0
- package/dist/modules/actions/runtime/rateLimit.js +19 -0
- package/dist/modules/actions/runtime/redisKv.d.ts +27 -0
- package/dist/modules/actions/runtime/redisKv.integration.test.d.ts +1 -0
- package/dist/modules/actions/runtime/redisKv.js +46 -0
- package/dist/modules/actions/runtime/renderResolver.js +2 -2
- package/dist/modules/actions/runtime/report.js +2 -1
- package/dist/modules/actions/runtime/runAction.d.ts +10 -2
- package/dist/modules/actions/runtime/runAction.js +52 -116
- package/dist/modules/actions/runtime/runLogger.d.ts +5 -0
- package/dist/modules/actions/runtime/runLogger.js +8 -2
- package/dist/modules/actions/runtime/signing.d.ts +20 -0
- package/dist/modules/actions/runtime/signing.js +25 -0
- package/dist/modules/actions/runtime/signing.test.d.ts +1 -0
- package/dist/modules/actions/runtime/taskContext.d.ts +33 -0
- package/dist/modules/actions/runtime/taskContext.js +124 -0
- package/dist/modules/actions/tasks/builtins.js +5 -1
- package/dist/modules/actions/tasks/flow.js +81 -10
- package/dist/modules/actions/tasks/helpers.js +2 -2
- package/dist/modules/actions/tasks/http.js +2 -3
- package/dist/modules/actions/tasks/kv.js +39 -0
- package/dist/modules/actions/tasks/list.d.ts +2 -0
- package/dist/modules/actions/tasks/list.js +145 -0
- package/dist/modules/actions/tasks/realtime.d.ts +2 -0
- package/dist/modules/actions/tasks/realtime.js +104 -0
- package/dist/modules/actions/tasks/registry.d.ts +10 -1
- package/dist/modules/actions/tasks/registry.js +17 -6
- package/dist/modules/actions/transport/callHandler.js +6 -3
- package/dist/modules/actions/transport/catalogHandler.d.ts +5 -2
- package/dist/modules/actions/transport/catalogHandler.js +3 -2
- package/dist/modules/actions/transport/stream.d.ts +4 -5
- package/dist/modules/actions/transport/stream.js +12 -76
- package/dist/modules/actions/transport/webhookHandler.js +5 -4
- package/dist/modules/actions/types.d.ts +162 -4
- package/dist/modules/connectors/engine.js +3 -5
- package/dist/modules/functions/build.d.ts +22 -0
- package/dist/modules/functions/build.js +122 -0
- package/dist/modules/functions/build.test.d.ts +1 -0
- package/dist/modules/functions/capabilities.d.ts +22 -0
- package/dist/modules/functions/capabilities.js +228 -0
- package/dist/modules/functions/config.d.ts +34 -0
- package/dist/modules/functions/config.js +24 -0
- package/dist/modules/functions/context.d.ts +17 -0
- package/dist/modules/functions/context.js +88 -0
- package/dist/modules/functions/contract.d.ts +121 -0
- package/dist/modules/functions/contract.js +9 -0
- package/dist/modules/functions/driver.d.ts +36 -0
- package/dist/modules/functions/driver.js +150 -0
- package/dist/modules/functions/load.d.ts +15 -0
- package/dist/modules/functions/load.js +45 -0
- package/dist/modules/functions/load.test.d.ts +1 -0
- package/dist/modules/functions/local.d.ts +29 -0
- package/dist/modules/functions/local.js +61 -0
- package/dist/modules/functions/local.test.d.ts +1 -0
- package/dist/modules/functions/manifest.d.ts +24 -0
- package/dist/modules/functions/manifest.js +163 -0
- package/dist/modules/functions/manifest.test.d.ts +1 -0
- package/dist/modules/functions/native.d.ts +8 -0
- package/dist/modules/functions/native.js +21 -0
- package/dist/modules/functions/native.test.d.ts +1 -0
- package/dist/modules/functions/protocol.d.ts +216 -0
- package/dist/modules/functions/protocol.js +18 -0
- package/dist/modules/functions/routes.d.ts +36 -0
- package/dist/modules/functions/routes.js +181 -0
- package/dist/modules/functions/routes.test.d.ts +1 -0
- package/dist/modules/functions/runner/messages.d.ts +5 -0
- package/dist/modules/functions/runner/messages.js +188 -0
- package/dist/modules/functions/runner/remote.d.ts +19 -0
- package/dist/modules/functions/runner/remote.js +116 -0
- package/dist/modules/functions/runner/remote.test.d.ts +1 -0
- package/dist/modules/functions/runner/service.d.ts +32 -0
- package/dist/modules/functions/runner/service.js +209 -0
- package/dist/modules/functions/sandbox/guest.d.ts +45 -0
- package/dist/modules/functions/sandbox/guest.js +660 -0
- package/dist/modules/functions/sandbox/isolate.d.ts +37 -0
- package/dist/modules/functions/sandbox/isolate.js +347 -0
- package/dist/modules/functions/sandbox/isolate.test.d.ts +1 -0
- package/dist/modules/functions/sandbox/prelude.d.ts +2 -0
- package/dist/modules/functions/sandbox/prelude.js +49 -0
- package/dist/modules/functions/space.d.ts +40 -0
- package/dist/modules/functions/space.js +162 -0
- package/dist/modules/functions/testing/tasksOf.d.ts +4 -0
- package/dist/modules/functions/tryEntry.d.ts +9 -0
- package/dist/modules/functions/tryEntry.js +49 -0
- package/dist/modules/mongo/index.d.ts +4 -0
- package/dist/modules/mongo/jobQueue.d.ts +38 -0
- package/dist/modules/mongo/jobQueue.js +335 -0
- package/dist/modules/mongo/kv.d.ts +15 -0
- package/dist/modules/mongo/kv.js +129 -0
- package/dist/modules/mongo/stores.integration.test.d.ts +1 -0
- package/dist/modules/mysql/accounts.js +19 -9
- package/dist/modules/mysql/index.d.ts +2 -1
- package/dist/modules/mysql/jobs/index.d.ts +5 -0
- package/dist/modules/mysql/jobs/jobQueue.d.ts +40 -0
- package/dist/modules/mysql/jobs/jobQueue.js +375 -0
- package/dist/modules/mysql/jobs/kv.d.ts +18 -0
- package/dist/modules/mysql/jobs/kv.js +108 -0
- package/dist/modules/mysql/jobs/schema.d.ts +33 -0
- package/dist/modules/mysql/jobs/schema.js +113 -0
- package/dist/modules/mysql/jobs/stores.integration.test.d.ts +1 -0
- package/dist/modules/mysql/schema.js +25 -0
- package/dist/modules/mysql/sessions.d.ts +1 -11
- package/dist/modules/mysql/sessions.js +6 -1
- package/dist/modules/realtime/declarations.d.ts +11 -0
- package/dist/modules/realtime/declarations.js +26 -0
- package/dist/modules/realtime/grants.d.ts +23 -0
- package/dist/modules/realtime/grants.js +61 -0
- package/dist/modules/realtime/handlers.d.ts +74 -0
- package/dist/modules/realtime/handlers.js +224 -0
- package/dist/modules/realtime/hub.d.ts +62 -0
- package/dist/modules/realtime/hub.js +141 -0
- package/dist/modules/realtime/index.d.ts +51 -0
- package/dist/modules/realtime/index.js +62 -0
- package/dist/modules/realtime/memoryPubSub.d.ts +10 -0
- package/dist/modules/realtime/memoryPubSub.js +43 -0
- package/dist/modules/realtime/realtime.test.d.ts +1 -0
- package/dist/modules/realtime/redisPubSub.d.ts +24 -0
- package/dist/modules/realtime/redisPubSub.js +36 -0
- package/dist/modules/realtime/redisPubSub.test.d.ts +1 -0
- package/dist/modules/realtime/socket.d.ts +25 -0
- package/dist/modules/realtime/socket.js +122 -0
- package/dist/modules/realtime/socket.test.d.ts +1 -0
- package/dist/modules/rsc/handler.d.ts +16 -0
- package/dist/modules/rsc/handler.js +17 -10
- package/dist/modules/rsc/handler.test.d.ts +1 -0
- package/dist/modules/rsc/resolveRscData.js +2 -1
- package/dist/modules/runtime/bundle.d.ts +24 -0
- package/dist/modules/runtime/bundle.js +102 -0
- package/dist/modules/runtime/contract.d.ts +37 -0
- package/dist/modules/runtime/contract.js +21 -0
- package/dist/modules/runtime/entry.test.d.ts +1 -0
- package/dist/modules/runtime/host.d.ts +38 -0
- package/dist/modules/runtime/host.js +68 -0
- package/dist/modules/runtime/inside.d.ts +15 -0
- package/dist/modules/runtime/inside.js +47 -0
- package/dist/modules/runtime/inside.test.d.ts +1 -0
- package/dist/modules/runtime/runner.d.ts +11 -0
- package/dist/modules/runtime/runner.js +38 -0
- package/dist/modules/runtime/runtime.test.d.ts +1 -0
- package/dist/modules/runtime/stages.d.ts +50 -0
- package/dist/modules/runtime/stages.js +117 -0
- package/dist/modules/ssr/Component.d.ts +4 -5
- package/dist/modules/ssr/Component.js +2 -2
- package/dist/modules/ssr/handler.js +10 -2
- package/dist/modules/ssr/loadPluginComponents.js +2 -1
- package/dist/modules/ssr/prepareRender.js +39 -13
- package/dist/modules/ssr/preview.d.ts +3 -0
- package/dist/modules/ssr/preview.js +17 -7
- package/dist/modules/ssr/redirect.test.d.ts +1 -0
- package/dist/modules/ssr/registerExternalPlugins.d.ts +6 -1
- package/dist/modules/ssr/registerExternalPlugins.js +19 -16
- package/dist/modules/ssr/registerExternalPlugins.test.d.ts +1 -0
- package/dist/modules/ssr/reportMissingPlugins.d.ts +9 -0
- package/dist/modules/ssr/reportMissingPlugins.js +24 -0
- package/dist/modules/ssr/reportMissingPlugins.test.d.ts +1 -0
- package/dist/modules/ssr/streamBody.d.ts +2 -2
- package/dist/modules/ssr/streamBody.js +4 -1
- package/dist/modules/ssr/template.js +1 -1
- package/dist/modules/ssr/views/template.ejs +8 -5
- package/dist/mongo.d.ts +25 -0
- package/dist/mongo.js +3 -0
- package/dist/mysql.d.ts +11 -0
- package/dist/mysql.js +4 -1
- package/dist/oauth.js +2 -2
- package/dist/plugins/compile.js +9 -4
- package/dist/plugins/copy.js +3 -3
- package/dist/plugins/manager.d.ts +35 -3
- package/dist/plugins/manager.js +109 -38
- package/dist/runtime.d.ts +15 -0
- package/dist/runtime.js +7 -0
- package/package.json +52 -11
- package/skills/plitzi-authoring/SKILL.md +153 -263
- package/skills/plitzi-authoring/reference/accessibility.md +111 -0
- package/skills/plitzi-authoring/reference/authoring-errors.md +70 -0
- package/skills/plitzi-authoring/reference/data-and-visibility.md +180 -0
- package/skills/plitzi-authoring/reference/elements-and-styles.md +161 -0
- package/skills/plitzi-authoring/reference/flows.md +171 -0
- package/skills/plitzi-authoring/reference/layouts.md +140 -0
- package/skills/plitzi-authoring/reference/lists.md +74 -0
- package/skills/plitzi-authoring/reference/performance.md +67 -0
- package/skills/plitzi-authoring/reference/plugins.md +167 -0
- package/skills/plitzi-authoring/reference/review-checklist.md +67 -0
- package/skills/plitzi-authoring/reference/structure.md +83 -0
- package/skills/plitzi-authoring/reference/templates-and-export.md +54 -0
- package/skills/plitzi-authoring/reference/templates.md +109 -0
- package/skills/plitzi-authoring/reference/testing.md +119 -0
- package/skills/plitzi-authoring/reference/validation.md +73 -0
- package/dist/modules/actions/runtime/schedule.d.ts +0 -35
- package/dist/modules/actions/runtime/schedule.js +0 -76
- /package/dist/{modules/actions/runtime/schedule.test.d.ts → adapters/jsonAdapters.test.d.ts} +0 -0
package/dist/core/auth/totp.js
CHANGED
|
@@ -55,28 +55,33 @@ var codeAt = (secret, counter) => {
|
|
|
55
55
|
/** The code an app would be showing right now. Exported so a test does not have to reimplement the algorithm. */
|
|
56
56
|
var totpCode = (secret, at = Date.now()) => codeAt(secret, Math.floor(at / 1e3 / STEP_SECONDS));
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
58
|
+
* Which time step this code belongs to, or `undefined` when it belongs to none near now.
|
|
59
59
|
*
|
|
60
60
|
* `window` steps either side are accepted, one by default — thirty seconds of tolerance for a clock that drifts
|
|
61
61
|
* and for a person who starts typing at second twenty-nine. Wider is friendlier and weaker; this is the usual
|
|
62
62
|
* trade and the same one Google Authenticator's own guidance makes.
|
|
63
63
|
*
|
|
64
|
+
* The step is the answer, not a yes: a code stays valid for its whole window, so a verifier that remembers nothing
|
|
65
|
+
* accepts the same code twice. RFC 6238 §5.2 forbids that — see `MfaRecord.lastUsedStep`.
|
|
66
|
+
*
|
|
64
67
|
* Compared in constant time: a comparison that returns on the first differing digit leaks how much of a guess was
|
|
65
68
|
* right, which turns a million-guess space into ten guesses per position.
|
|
66
69
|
*/
|
|
67
|
-
var
|
|
70
|
+
var totpStep = (secret, code, options = {}) => {
|
|
68
71
|
const { window = 1, at = Date.now() } = options;
|
|
69
72
|
const candidate = code.trim();
|
|
70
|
-
if (!/^\d{6}$/.test(candidate)) return
|
|
73
|
+
if (!/^\d{6}$/.test(candidate)) return;
|
|
71
74
|
const counter = Math.floor(at / 1e3 / STEP_SECONDS);
|
|
72
75
|
const supplied = Buffer.from(candidate);
|
|
73
|
-
let matched
|
|
76
|
+
let matched;
|
|
74
77
|
for (let drift = -window; drift <= window; drift++) {
|
|
75
78
|
const expected = Buffer.from(codeAt(secret, counter + drift));
|
|
76
|
-
|
|
79
|
+
if (timingSafeEqual(expected, supplied)) matched = counter + drift;
|
|
77
80
|
}
|
|
78
81
|
return matched;
|
|
79
82
|
};
|
|
83
|
+
/** Is this the code? `totpStep`, for a caller that does not need to know which step it was. */
|
|
84
|
+
var verifyTotp = (secret, code, options = {}) => totpStep(secret, code, options) !== void 0;
|
|
80
85
|
/**
|
|
81
86
|
* The URI an authenticator app scans. `issuer` appears twice by convention — in the label and as a parameter —
|
|
82
87
|
* because different apps read different ones, and an entry that says only an email address is unidentifiable in a
|
|
@@ -115,4 +120,4 @@ var generateRecoveryCodes = (count = 10) => Array.from({ length: count }, () =>
|
|
|
115
120
|
/** Normalised before hashing so that case and the hyphen a person types or omits do not decide whether it works. */
|
|
116
121
|
var normalizeRecoveryCode = (code) => code.trim().toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
117
122
|
//#endregion
|
|
118
|
-
export { generateRecoveryCodes, generateTotpSecret, normalizeRecoveryCode, randomCode, totpCode, totpUri, verifyTotp };
|
|
123
|
+
export { generateRecoveryCodes, generateTotpSecret, normalizeRecoveryCode, randomCode, totpCode, totpStep, totpUri, verifyTotp };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -19,5 +19,18 @@ export type ServerConfig = Omit<SSRServerConfig, 'adapters'> & {
|
|
|
19
19
|
* `adapters` or `authCookie` still wins, so a deployment can override one piece without giving up the rest.
|
|
20
20
|
*/
|
|
21
21
|
auth?: Auth;
|
|
22
|
+
/**
|
|
23
|
+
* Where a page request whose access cookie has died, while its refresh cookie lives on, is sent to renew before
|
|
24
|
+
* the page is rendered — so the server renders the visitor the browser is about to become, rather than a guest
|
|
25
|
+
* the browser then replaces. The endpoint is `GET <basePath>/refresh`, served by `auth` here or by
|
|
26
|
+
* `mountAuthRoutes` wherever `/auth` lives.
|
|
27
|
+
*
|
|
28
|
+
* On by default with `auth`, at its own `/refresh`. Without it, name the endpoint — an absolute URL when another
|
|
29
|
+
* host serves `/auth` (a host sharing the session's cookie domain, or the hint that triggers this never arrives).
|
|
30
|
+
* `false` turns it off.
|
|
31
|
+
*/
|
|
32
|
+
sessionRenewal?: {
|
|
33
|
+
url: string;
|
|
34
|
+
} | false;
|
|
22
35
|
};
|
|
23
|
-
export declare const createServer: ({ auth, ...config }: ServerConfig, extensions?: PipelineExtensions) => SSRServer;
|
|
36
|
+
export declare const createServer: ({ auth, sessionRenewal, ...config }: ServerConfig, extensions?: PipelineExtensions) => SSRServer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { actionsModuleFor } from "../modules/actions/moduleFor.js";
|
|
2
2
|
import { connectorRscData } from "../modules/rsc/connectorRscData.js";
|
|
3
3
|
import { createAuthApiStage } from "./http/stages/authApi.js";
|
|
4
|
+
import { createRenewalEndpointStage, createSessionRenewalStage } from "./http/stages/sessionRenewal.js";
|
|
4
5
|
import { resolveServices } from "./services/resolve.js";
|
|
5
6
|
import { createPageServer } from "./server/pageServer.js";
|
|
6
7
|
//#region src/core/createServer.ts
|
|
@@ -24,23 +25,27 @@ import { createPageServer } from "./server/pageServer.js";
|
|
|
24
25
|
*/
|
|
25
26
|
var withConnectorRsc = (config) => {
|
|
26
27
|
if (config.adapters.getRscData || !config.connectors && !config.action?.lookups) return config;
|
|
27
|
-
const
|
|
28
|
+
const resolved = {
|
|
29
|
+
...config,
|
|
30
|
+
adapters: { ...config.adapters }
|
|
31
|
+
};
|
|
32
|
+
const module = actionsModuleFor(resolved);
|
|
28
33
|
const actions = module && config.action?.lookups ? {
|
|
29
34
|
lookups: config.action.lookups,
|
|
30
35
|
module
|
|
31
36
|
} : void 0;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
adapters: {
|
|
35
|
-
...config.adapters,
|
|
36
|
-
getRscData: connectorRscData(config.connectors, actions, config.rsc?.elementTimeoutMs)
|
|
37
|
-
}
|
|
38
|
-
};
|
|
37
|
+
resolved.adapters.getRscData = connectorRscData(config.connectors, actions, config.rsc?.elementTimeoutMs);
|
|
38
|
+
return resolved;
|
|
39
39
|
};
|
|
40
|
-
|
|
40
|
+
/** The renewal stage in front of whatever else gates itself, when there is anywhere to renew. */
|
|
41
|
+
var withRenewal = (extensions, url) => ({
|
|
42
|
+
...extensions,
|
|
43
|
+
preAuth: [...extensions?.preAuth ?? [], ...url ? [createSessionRenewalStage(url)] : []]
|
|
44
|
+
});
|
|
45
|
+
var createServer = ({ auth, sessionRenewal, ...config }, extensions) => {
|
|
41
46
|
if (!auth) {
|
|
42
47
|
const resolvedConfig = withConnectorRsc(config);
|
|
43
|
-
return createPageServer(resolvedConfig, resolveServices(resolvedConfig), extensions);
|
|
48
|
+
return createPageServer(resolvedConfig, resolveServices(resolvedConfig), withRenewal(extensions, sessionRenewal ? sessionRenewal.url : void 0));
|
|
44
49
|
}
|
|
45
50
|
const entries = Object.entries(config.adapters);
|
|
46
51
|
const supplied = Object.fromEntries(entries.filter(([, value]) => value !== void 0));
|
|
@@ -57,9 +62,14 @@ var createServer = ({ auth, ...config }, extensions) => {
|
|
|
57
62
|
});
|
|
58
63
|
const withAuthRoutes = {
|
|
59
64
|
...extensions,
|
|
60
|
-
preAuth: [
|
|
65
|
+
preAuth: [
|
|
66
|
+
...extensions?.preAuth ?? [],
|
|
67
|
+
createAuthApiStage(auth, auth.basePath),
|
|
68
|
+
createRenewalEndpointStage(auth)
|
|
69
|
+
]
|
|
61
70
|
};
|
|
62
|
-
|
|
71
|
+
const renewalUrl = sessionRenewal === false ? void 0 : sessionRenewal?.url ?? `${auth.basePath}/refresh`;
|
|
72
|
+
return createPageServer(resolved, resolveServices(resolved), withRenewal(withAuthRoutes, renewalUrl));
|
|
63
73
|
};
|
|
64
74
|
//#endregion
|
|
65
75
|
export { createServer, resolveServices };
|
package/dist/core/fontStore.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
3
|
//#region src/core/fontStore.ts
|
|
4
4
|
/** `<spaceId>/<name>` — the space segment is what keeps one space's files out of another's. */
|
|
5
5
|
var storePathOf = (spaceId, name) => `${spaceId}/${name}`;
|
|
@@ -12,7 +12,7 @@ export interface AuthRouteHandlersOptions {
|
|
|
12
12
|
* deployment that means to go without says so with `csrf: false` there rather than by forgetting here.
|
|
13
13
|
*/
|
|
14
14
|
csrf?: Csrf;
|
|
15
|
-
/** Reports a flow that threw. Without it the failure goes to `
|
|
15
|
+
/** Reports a flow that threw. Without it the failure goes to the server log at `error`; the caller still gets a 500. */
|
|
16
16
|
onError?: (error: unknown, context: {
|
|
17
17
|
method: string;
|
|
18
18
|
path: string;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { serverLog } from "../../helpers/serverLog.js";
|
|
1
2
|
import { applySessionOutcome, authRoutes } from "../auth/routes.js";
|
|
3
|
+
import { isDocumentNavigation, renewForNavigation } from "../auth/renewal.js";
|
|
2
4
|
//#region src/core/handlers/authRouteHandlers.ts
|
|
3
5
|
/**
|
|
4
6
|
* Built field by field rather than spread: on several frameworks `hostname` is a getter on the request prototype,
|
|
@@ -37,28 +39,62 @@ var carrier = (req) => ({
|
|
|
37
39
|
* Which flows actually answer is decided by the adapters, not here — no `createAccount`, no signup, and the route
|
|
38
40
|
* reports 404 rather than failing at runtime. Mounting all of them is correct for a deployment that offers three.
|
|
39
41
|
*/
|
|
40
|
-
var createAuthRouteHandlers = ({ api, cookies, csrf, onError }) =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
var createAuthRouteHandlers = ({ api, cookies, csrf, onError }) => {
|
|
43
|
+
const failed = (error, method, path, res) => {
|
|
44
|
+
if (onError) onError(error, {
|
|
45
|
+
method,
|
|
46
|
+
path
|
|
47
|
+
});
|
|
48
|
+
else serverLog.error("auth", `${method} ${path} failed`, error);
|
|
49
|
+
res.status(500).json({ error: "Internal server error" });
|
|
50
|
+
};
|
|
51
|
+
const flows = authRoutes({
|
|
52
|
+
api,
|
|
53
|
+
cookies,
|
|
54
|
+
csrf
|
|
55
|
+
}).map(({ method, path, handler }) => ({
|
|
56
|
+
method,
|
|
57
|
+
path,
|
|
58
|
+
handle: async (req, res) => {
|
|
59
|
+
try {
|
|
60
|
+
const outcome = await handler(carrier(req));
|
|
61
|
+
applySessionOutcome(req, res, outcome, cookies, csrf);
|
|
62
|
+
res.status(outcome.ok ? outcome.status ?? 200 : outcome.status).json(outcome.body);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
failed(error, method, path, res);
|
|
65
|
+
}
|
|
59
66
|
}
|
|
60
|
-
}
|
|
61
|
-
|
|
67
|
+
}));
|
|
68
|
+
/**
|
|
69
|
+
* Renewal on the way to a page — see `renewForNavigation`. Answered with a status and a `Location` rather than a
|
|
70
|
+
* framework's `redirect`, so the set keeps asking a response for nothing beyond a status and a body; the body says
|
|
71
|
+
* the same thing, for whoever reads it. Anything but a whole-tab navigation is told to renew the usual way.
|
|
72
|
+
*/
|
|
73
|
+
const renewal = {
|
|
74
|
+
method: "GET",
|
|
75
|
+
path: "/refresh",
|
|
76
|
+
handle: async (req, res) => {
|
|
77
|
+
if (!isDocumentNavigation(req.headers)) {
|
|
78
|
+
res.setHeader("Allow", "POST");
|
|
79
|
+
res.status(405).json({ error: "Renew with POST" });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const location = await renewForNavigation(carrier(req), res, {
|
|
84
|
+
api,
|
|
85
|
+
cookies,
|
|
86
|
+
csrf
|
|
87
|
+
});
|
|
88
|
+
res.setHeader("Cache-Control", "no-store");
|
|
89
|
+
res.setHeader("Location", location);
|
|
90
|
+
res.status(303).json({ redirect: location });
|
|
91
|
+
} catch (error) {
|
|
92
|
+
failed(error, "GET", "/refresh", res);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
return [...flows, renewal];
|
|
97
|
+
};
|
|
62
98
|
/**
|
|
63
99
|
* {@link createAuthRouteHandlers}, hung on anything with `get` and `post`.
|
|
64
100
|
*
|
|
@@ -18,3 +18,26 @@ export interface CsrfMiddlewareOptions {
|
|
|
18
18
|
* rather than a cookie, and it will never carry a token. Exempt it by path.
|
|
19
19
|
*/
|
|
20
20
|
export declare const createCsrfMiddleware: (csrf: Csrf, cookies: SessionCookies, { exempt, errorKey }?: CsrfMiddlewareOptions) => (req: AuthedRequest, res: JsonResponse, next: () => void) => void;
|
|
21
|
+
export interface OriginGuardOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Origins this request may also come from, beyond the deployment's own — the ones the space credential it carries
|
|
24
|
+
* declares. `true` for a credential that declares any origin at all.
|
|
25
|
+
*/
|
|
26
|
+
allowedFor?: (req: AuthedRequest) => readonly string[] | true | undefined;
|
|
27
|
+
/** Paths never guarded. A webhook carries no session cookie and is never refused anyway; this is for the rest. */
|
|
28
|
+
exempt?: (path: string) => boolean;
|
|
29
|
+
errorKey?: 'message' | 'error';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Cross-site request forgery refused by ORIGIN, for a router whose cookie-carried writes do not all send a token.
|
|
33
|
+
*
|
|
34
|
+
* A write is refused when a session COOKIE could have authenticated it (an unsafe method, no `Authorization`, the
|
|
35
|
+
* cookie present) and a browser says another site caused it — Fetch Metadata first, `Origin` exactly otherwise (see
|
|
36
|
+
* `Csrf.crossSite`). The deployment's own origins pass, and so do the ones a space credential on the request declares:
|
|
37
|
+
* a published site calling its own space is not forging anything. A client that is not a browser sends neither header
|
|
38
|
+
* and passes, as does anything with a bearer — neither has a victim's cookie to borrow.
|
|
39
|
+
*
|
|
40
|
+
* This is the guard OWASP calls Fetch Metadata / origin verification. It is what a token is for, without a token: the
|
|
41
|
+
* difference is only which clients can satisfy it, and every legitimate one here already does.
|
|
42
|
+
*/
|
|
43
|
+
export declare const createOriginGuardMiddleware: (csrf: Csrf, { allowedFor, exempt, errorKey }?: OriginGuardOptions) => (req: AuthedRequest, res: JsonResponse, next: () => void) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { csrfFailureMessage } from "../auth/csrf.js";
|
|
1
|
+
import { carrierOf, csrfFailureMessage } from "../auth/csrf.js";
|
|
2
2
|
//#region src/core/handlers/csrfMiddleware.ts
|
|
3
3
|
/**
|
|
4
4
|
* The CSRF check as middleware, for a deployment with a router.
|
|
@@ -11,14 +11,11 @@ import { csrfFailureMessage } from "../auth/csrf.js";
|
|
|
11
11
|
* rather than a cookie, and it will never carry a token. Exempt it by path.
|
|
12
12
|
*/
|
|
13
13
|
var createCsrfMiddleware = (csrf, cookies, { exempt, errorKey = "message" } = {}) => (req, res, next) => {
|
|
14
|
-
if (exempt?.(req.path) || !csrf.required({
|
|
15
|
-
...req,
|
|
16
|
-
method: req.method
|
|
17
|
-
})) {
|
|
14
|
+
if (exempt?.(req.path) || !csrf.required(carrierOf(req))) {
|
|
18
15
|
next();
|
|
19
16
|
return;
|
|
20
17
|
}
|
|
21
|
-
const result = csrf.verify(req, cookies.resolveSessionToken(req));
|
|
18
|
+
const result = csrf.verify(carrierOf(req), cookies.resolveSessionToken(req));
|
|
22
19
|
if (result.ok) {
|
|
23
20
|
next();
|
|
24
21
|
return;
|
|
@@ -28,5 +25,32 @@ var createCsrfMiddleware = (csrf, cookies, { exempt, errorKey = "message" } = {}
|
|
|
28
25
|
reason: result.reason
|
|
29
26
|
});
|
|
30
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Cross-site request forgery refused by ORIGIN, for a router whose cookie-carried writes do not all send a token.
|
|
30
|
+
*
|
|
31
|
+
* A write is refused when a session COOKIE could have authenticated it (an unsafe method, no `Authorization`, the
|
|
32
|
+
* cookie present) and a browser says another site caused it — Fetch Metadata first, `Origin` exactly otherwise (see
|
|
33
|
+
* `Csrf.crossSite`). The deployment's own origins pass, and so do the ones a space credential on the request declares:
|
|
34
|
+
* a published site calling its own space is not forging anything. A client that is not a browser sends neither header
|
|
35
|
+
* and passes, as does anything with a bearer — neither has a victim's cookie to borrow.
|
|
36
|
+
*
|
|
37
|
+
* This is the guard OWASP calls Fetch Metadata / origin verification. It is what a token is for, without a token: the
|
|
38
|
+
* difference is only which clients can satisfy it, and every legitimate one here already does.
|
|
39
|
+
*/
|
|
40
|
+
var createOriginGuardMiddleware = (csrf, { allowedFor, exempt, errorKey = "message" } = {}) => (req, res, next) => {
|
|
41
|
+
if (exempt?.(req.path) || !csrf.required(carrierOf(req))) {
|
|
42
|
+
next();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const allowed = allowedFor?.(req);
|
|
46
|
+
if (allowed === true || !csrf.crossSite(carrierOf(req), allowed ?? [])) {
|
|
47
|
+
next();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
res.status(403).json({
|
|
51
|
+
[errorKey]: csrfFailureMessage.foreign,
|
|
52
|
+
reason: "foreign"
|
|
53
|
+
});
|
|
54
|
+
};
|
|
31
55
|
//#endregion
|
|
32
|
-
export { createCsrfMiddleware };
|
|
56
|
+
export { createCsrfMiddleware, createOriginGuardMiddleware };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { serverLog } from "../../helpers/serverLog.js";
|
|
1
2
|
import { authorizationServerMetadata, protectedResourceMetadata } from "../oauth/metadata.js";
|
|
2
3
|
import { sendErrorJson } from "../oauth/respond.js";
|
|
3
|
-
import { handleAuthorizeStart, handleAuthorizeSubmit } from "../oauth/authorize.js";
|
|
4
4
|
import { handleRegister } from "../oauth/register.js";
|
|
5
|
+
import { handleAuthorizeStart, handleAuthorizeSubmit } from "../oauth/authorize.js";
|
|
5
6
|
import { handleRevoke, handleToken } from "../oauth/token.js";
|
|
6
7
|
//#region src/core/handlers/oauthRouteHandlers.ts
|
|
7
8
|
/**
|
|
@@ -51,7 +52,8 @@ var asRequest = (req) => {
|
|
|
51
52
|
headers: req.headers,
|
|
52
53
|
hostname: req.hostname,
|
|
53
54
|
protocol: protocol === "http" ? "http" : "https",
|
|
54
|
-
query: req.query ?? {}
|
|
55
|
+
query: req.query ?? {},
|
|
56
|
+
...req.ip ? { ip: req.ip } : {}
|
|
55
57
|
};
|
|
56
58
|
};
|
|
57
59
|
/** Query first, body second: a field posted by the grant form wins over one the client left in the URL. */
|
|
@@ -79,7 +81,7 @@ var createOAuthRouteHandlers = ({ config, onError }) => {
|
|
|
79
81
|
method,
|
|
80
82
|
path
|
|
81
83
|
});
|
|
82
|
-
else
|
|
84
|
+
else serverLog.error("oauth", `${method} ${path} failed`, error);
|
|
83
85
|
sendErrorJson(asHelpers(res), 500, "server_error", "The authorization server failed to answer.");
|
|
84
86
|
}
|
|
85
87
|
};
|
|
@@ -12,7 +12,7 @@ export interface SocialRouteHandlersOptions {
|
|
|
12
12
|
* proves WHO, and what a session is worth is the kernel's answer, not the provider's.
|
|
13
13
|
*/
|
|
14
14
|
issueSession: (userId: number) => Promise<SSRSession>;
|
|
15
|
-
/** Reports a callback that threw. Without it the failure goes to `
|
|
15
|
+
/** Reports a callback that threw. Without it the failure goes to the server log at `error`; the caller still gets a 500. */
|
|
16
16
|
onError?: (error: unknown, context: {
|
|
17
17
|
method: string;
|
|
18
18
|
path: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serverLog } from "../../helpers/serverLog.js";
|
|
1
2
|
//#region src/core/handlers/socialRouteHandlers.ts
|
|
2
3
|
/**
|
|
3
4
|
* A provider id from the request.
|
|
@@ -35,7 +36,7 @@ var createSocialAuthRouteHandlers = ({ social, cookies, issueSession, onError })
|
|
|
35
36
|
onError(error, context);
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
+
serverLog.error("auth", `${context.method} ${context.path} failed`, error);
|
|
39
40
|
};
|
|
40
41
|
return [
|
|
41
42
|
{
|
|
@@ -4,4 +4,12 @@ import { Handler } from '../transports';
|
|
|
4
4
|
import { SSRRequest, SSRResponseHelpers, SSRServerConfig } from '@plitzi/sdk-shared';
|
|
5
5
|
import { IncomingMessage } from 'node:http';
|
|
6
6
|
export type BuildContext<C extends BaseContext> = (raw: IncomingMessage, rawRes: RawResponse, req: SSRRequest, res: SSRResponseHelpers) => Omit<C, 'signal'>;
|
|
7
|
-
export
|
|
7
|
+
export type HandlerOptions = {
|
|
8
|
+
compression?: SSRServerConfig['compression'];
|
|
9
|
+
/**
|
|
10
|
+
* The paths that may switch protocols. An upgrade anywhere else is refused before the pipeline runs: a WebSocket
|
|
11
|
+
* opened on a page's address would otherwise render the page — and count a visit — to answer a socket.
|
|
12
|
+
*/
|
|
13
|
+
upgrades?: (path: string) => boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare const makeHandler: <C extends BaseContext>(label: string, buildContext: BuildContext<C>, stages: Stage<C>[], { compression: compressionConfig, upgrades }?: HandlerOptions) => Handler;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { isLogged, serverLog } from "../../helpers/serverLog.js";
|
|
1
2
|
import { clientIp, parseRequest } from "../requestParser.js";
|
|
2
3
|
import { applySecurityHeaders } from "./securityHeaders.js";
|
|
4
|
+
import { socketResponse } from "./socketResponse.js";
|
|
3
5
|
import { resolveCompression } from "../../helpers/compress.js";
|
|
4
6
|
import { buildResponseHelpers } from "../../helpers/buildResponseHelpers.js";
|
|
5
7
|
//#region src/core/http/dispatcher.ts
|
|
@@ -11,7 +13,7 @@ var safePath = (url) => {
|
|
|
11
13
|
};
|
|
12
14
|
var errorText = (error) => error instanceof Error ? error.message : String(error);
|
|
13
15
|
var statusOf = (rawRes, res) => rawRes.headersSent ? rawRes.statusCode : res.status;
|
|
14
|
-
var runPipeline = async (raw, rawRes, buildContext, stages, server, compression) => {
|
|
16
|
+
var runPipeline = async (raw, rawRes, buildContext, stages, server, compression, upgrade) => {
|
|
15
17
|
const startedAt = Date.now();
|
|
16
18
|
const req = parseRequest(raw);
|
|
17
19
|
const res = buildResponseHelpers(rawRes, req.headers["accept-encoding"], compression);
|
|
@@ -33,13 +35,16 @@ var runPipeline = async (raw, rawRes, buildContext, stages, server, compression)
|
|
|
33
35
|
});
|
|
34
36
|
const ctx = {
|
|
35
37
|
...buildContext(raw, rawRes, req, res),
|
|
36
|
-
signal: controller.signal
|
|
38
|
+
signal: controller.signal,
|
|
39
|
+
...upgrade ? { upgrade } : {}
|
|
37
40
|
};
|
|
38
41
|
const logger = ctx.config.logger;
|
|
39
42
|
const ip = logger ? clientIp(raw, req) : "";
|
|
40
43
|
const logRequest = (error) => {
|
|
41
44
|
if (!logger) return;
|
|
42
45
|
const status = statusOf(rawRes, res);
|
|
46
|
+
const failed = !!error || status >= 500;
|
|
47
|
+
if (!isLogged(ctx.config.logLevel ?? "error", failed ? "error" : "info")) return;
|
|
43
48
|
logger({
|
|
44
49
|
kind: "request",
|
|
45
50
|
server,
|
|
@@ -72,17 +77,39 @@ var runPipeline = async (raw, rawRes, buildContext, stages, server, compression)
|
|
|
72
77
|
throw error;
|
|
73
78
|
}
|
|
74
79
|
};
|
|
75
|
-
var
|
|
80
|
+
var pathOf = (url) => (url ?? "/").split("?")[0];
|
|
81
|
+
var makeHandler = (label, buildContext, stages, { compression: compressionConfig, upgrades } = {}) => {
|
|
76
82
|
const compression = resolveCompression(compressionConfig);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const fail = (rawRes) => (err) => {
|
|
84
|
+
serverLog.error(label, "Unhandled error", err);
|
|
85
|
+
try {
|
|
86
|
+
if (!rawRes.headersSent) rawRes.writeHead(500, { "Content-Type": "text/plain" });
|
|
87
|
+
rawRes.end("Internal Server Error");
|
|
88
|
+
} catch {}
|
|
89
|
+
};
|
|
90
|
+
const handler = (raw, rawRes) => {
|
|
91
|
+
runPipeline(raw, rawRes, buildContext, stages, label, compression).catch(fail(rawRes));
|
|
92
|
+
};
|
|
93
|
+
if (upgrades) handler.upgrade = (raw, socket, head) => {
|
|
94
|
+
const rawRes = socketResponse(socket);
|
|
95
|
+
if (!upgrades(pathOf(raw.url))) {
|
|
96
|
+
rawRes.writeHead(404, { "Content-Type": "text/plain" });
|
|
97
|
+
rawRes.end("Not Found");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const upgrade = {
|
|
101
|
+
socket,
|
|
102
|
+
head,
|
|
103
|
+
taken: false
|
|
104
|
+
};
|
|
105
|
+
runPipeline(raw, rawRes, buildContext, stages, label, compression, upgrade).then(() => {
|
|
106
|
+
if (!upgrade.taken && !rawRes.writableFinished) {
|
|
107
|
+
rawRes.writeHead(404, { "Content-Type": "text/plain" });
|
|
108
|
+
rawRes.end("Not Found");
|
|
109
|
+
}
|
|
110
|
+
}).catch(fail(rawRes));
|
|
85
111
|
};
|
|
112
|
+
return handler;
|
|
86
113
|
};
|
|
87
114
|
//#endregion
|
|
88
115
|
export { makeHandler };
|
|
@@ -7,5 +7,8 @@ import { SSRRequest } from '@plitzi/sdk-shared';
|
|
|
7
7
|
* thing, so a navigation is answered with a redirect and the view re-renders through a GET. A fetch wants the body.
|
|
8
8
|
*/
|
|
9
9
|
export declare const isNavigation: (req: SSRRequest) => boolean;
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Same-origin only: absolute URLs and protocol-relative `//host` are refused, to avoid an open redirect — and so is
|
|
12
|
+
* `/\host`, which a browser reads as `//host`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const safeRedirectTarget: (req: SSRRequest, param?: string) => string;
|
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
* thing, so a navigation is answered with a redirect and the view re-renders through a GET. A fetch wants the body.
|
|
8
8
|
*/
|
|
9
9
|
var isNavigation = (req) => req.headers["sec-fetch-mode"] === "navigate";
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Same-origin only: absolute URLs and protocol-relative `//host` are refused, to avoid an open redirect — and so is
|
|
12
|
+
* `/\host`, which a browser reads as `//host`.
|
|
13
|
+
*/
|
|
14
|
+
var safeRedirectTarget = (req, param = "redirect") => {
|
|
15
|
+
const target = req.query[param];
|
|
16
|
+
return target && target.startsWith("/") && !/^\/[\\/]/u.test(target) ? target : "/";
|
|
14
17
|
};
|
|
15
18
|
//#endregion
|
|
16
19
|
export { isNavigation, safeRedirectTarget };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RawResponse } from '../../helpers/buildResponseHelpers';
|
|
2
|
+
import { Duplex } from 'node:stream';
|
|
3
|
+
/** A request asking to switch protocols, as the transport handed it over: the raw socket, and what was read past the headers. */
|
|
4
|
+
export type UpgradeRequest = {
|
|
5
|
+
socket: Duplex;
|
|
6
|
+
head: Buffer;
|
|
7
|
+
/** Set by the stage that switched protocols: from then on the socket is its connection, not an HTTP response. */
|
|
8
|
+
taken: boolean;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* An HTTP response written straight to a socket — what an upgrade request gets when it is NOT upgraded.
|
|
12
|
+
*
|
|
13
|
+
* An upgrade arrives with no `ServerResponse`: node hands over the socket and expects whoever listens to speak for
|
|
14
|
+
* it. Every stage of the pipeline writes to a `RawResponse`, so this is one: a refusal (a 403, a 404, a redirect)
|
|
15
|
+
* reaches the client as the stage wrote it, and the socket closes after it — a connection that asked to switch
|
|
16
|
+
* protocols and was answered in HTTP has nothing left to say.
|
|
17
|
+
*/
|
|
18
|
+
export declare const socketResponse: (socket: Duplex) => RawResponse;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { STATUS_CODES } from "node:http";
|
|
2
|
+
//#region src/core/http/socketResponse.ts
|
|
3
|
+
/**
|
|
4
|
+
* An HTTP response written straight to a socket — what an upgrade request gets when it is NOT upgraded.
|
|
5
|
+
*
|
|
6
|
+
* An upgrade arrives with no `ServerResponse`: node hands over the socket and expects whoever listens to speak for
|
|
7
|
+
* it. Every stage of the pipeline writes to a `RawResponse`, so this is one: a refusal (a 403, a 404, a redirect)
|
|
8
|
+
* reaches the client as the stage wrote it, and the socket closes after it — a connection that asked to switch
|
|
9
|
+
* protocols and was answered in HTTP has nothing left to say.
|
|
10
|
+
*/
|
|
11
|
+
var socketResponse = (socket) => {
|
|
12
|
+
const headers = {};
|
|
13
|
+
const body = [];
|
|
14
|
+
let statusCode = 200;
|
|
15
|
+
let headersSent = false;
|
|
16
|
+
let finished = false;
|
|
17
|
+
const flush = () => {
|
|
18
|
+
if (finished) return;
|
|
19
|
+
finished = true;
|
|
20
|
+
headersSent = true;
|
|
21
|
+
const payload = Buffer.concat(body);
|
|
22
|
+
const lines = Object.entries({
|
|
23
|
+
...headers,
|
|
24
|
+
Connection: "close",
|
|
25
|
+
"Content-Length": payload.length
|
|
26
|
+
}).flatMap(([name, value]) => (Array.isArray(value) ? value : [value]).map((entry) => `${name}: ${String(entry)}`));
|
|
27
|
+
const head = `HTTP/1.1 ${statusCode} ${STATUS_CODES[statusCode] ?? ""}\r\n${lines.join("\r\n")}\r\n\r\n`;
|
|
28
|
+
socket.end(Buffer.concat([Buffer.from(head), payload]));
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
get headersSent() {
|
|
32
|
+
return headersSent;
|
|
33
|
+
},
|
|
34
|
+
get statusCode() {
|
|
35
|
+
return statusCode;
|
|
36
|
+
},
|
|
37
|
+
get writableFinished() {
|
|
38
|
+
return finished;
|
|
39
|
+
},
|
|
40
|
+
setHeader: (name, value) => {
|
|
41
|
+
headers[name] = value;
|
|
42
|
+
},
|
|
43
|
+
getHeaders: () => ({ ...headers }),
|
|
44
|
+
writeHead: (code, extra) => {
|
|
45
|
+
statusCode = code;
|
|
46
|
+
Object.assign(headers, extra);
|
|
47
|
+
},
|
|
48
|
+
write: (chunk) => {
|
|
49
|
+
body.push(Buffer.from(chunk));
|
|
50
|
+
},
|
|
51
|
+
end: (chunk) => {
|
|
52
|
+
if (chunk !== void 0) body.push(Buffer.from(chunk));
|
|
53
|
+
flush();
|
|
54
|
+
},
|
|
55
|
+
once: (_event, listener) => socket.once("close", listener)
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
//#endregion
|
|
59
|
+
export { socketResponse };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { RawResponse } from '../../helpers/buildResponseHelpers';
|
|
2
|
+
export type EventStream = {
|
|
3
|
+
/** Sends one event. Silently drops once the socket is gone — writing into a closed peer is not an error. */
|
|
4
|
+
send: (event: string, data: unknown) => void;
|
|
5
|
+
/** Ends the stream and stops the heartbeat. */
|
|
6
|
+
close: () => void;
|
|
7
|
+
/** Whether the peer is still there. */
|
|
8
|
+
isOpen: () => boolean;
|
|
9
|
+
};
|
|
10
|
+
export type EventStreamOptions = {
|
|
11
|
+
/** Called once when the peer is found gone — a failed write, or a peer that stopped reading. */
|
|
12
|
+
onAbort: () => void;
|
|
13
|
+
/** Headers besides the ones every event stream sends. */
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
/** What an `EventSource` should wait before reconnecting, in milliseconds. */
|
|
16
|
+
retryMs: number;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Opens a Server-Sent Events response — an action's progress, a realtime channel.
|
|
20
|
+
*
|
|
21
|
+
* Written straight to the RAW response, never through the response helpers: those compress the body, set a
|
|
22
|
+
* Content-Length and end it in one go, which is precisely what a stream cannot do. `no-transform` says the same
|
|
23
|
+
* thing to every proxy in between, and `X-Accel-Buffering: no` says it to nginx, which otherwise holds frames
|
|
24
|
+
* until its buffer fills and turns a live stream into one late burst.
|
|
25
|
+
*/
|
|
26
|
+
export declare const openEventStream: (raw: RawResponse, { onAbort, headers, retryMs }: EventStreamOptions) => EventStream;
|