@plitzi/sdk-server 0.37.2 → 0.37.4
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 +1170 -0
- package/README.md +100 -7
- package/dist/actions.d.ts +6 -2
- package/dist/actions.js +5 -2
- package/dist/adapters/cloudAdapters.js +4 -3
- package/dist/adapters/jsonAdapters.js +28 -6
- package/dist/adapters/jsonAdapters.test.d.ts +1 -0
- 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 +10 -0
- package/dist/core/server/baseServer.js +42 -11
- package/dist/core/server/closeOnSignals.js +4 -3
- 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 +34 -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 +6 -1
- package/dist/index.js +5 -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 +29 -10
- package/dist/modules/actions/index.js +43 -6
- 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 +2 -1
- package/dist/modules/actions/jobs/index.js +5 -3
- package/dist/modules/actions/jobs/memoryQueue.d.ts +3 -0
- package/dist/modules/actions/jobs/memoryQueue.js +17 -1
- package/dist/modules/actions/jobs/scheduler.d.ts +2 -0
- package/dist/modules/actions/jobs/scheduler.js +12 -7
- package/dist/modules/actions/jobs/worker.d.ts +3 -1
- package/dist/modules/actions/jobs/worker.js +9 -4
- package/dist/modules/actions/moduleFor.js +5 -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 +4 -8
- 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/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 +5 -2
- 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 +4 -3
- package/dist/modules/actions/types.d.ts +86 -3
- 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/kv.js +31 -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 +1 -1
- package/dist/modules/mysql/jobs/kv.js +30 -0
- package/dist/modules/mysql/jobs/schema.d.ts +9 -0
- package/dist/modules/mysql/jobs/schema.js +21 -4
- 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 +3 -1
- package/dist/modules/ssr/Component.js +2 -1
- package/dist/modules/ssr/handler.js +10 -2
- package/dist/modules/ssr/loadPluginComponents.js +2 -1
- package/dist/modules/ssr/prepareRender.js +39 -10
- 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/mysql.d.ts +1 -1
- package/dist/mysql.js +2 -2
- 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 +39 -8
- package/skills/plitzi-authoring/SKILL.md +98 -14
- 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 +84 -18
- package/skills/plitzi-authoring/reference/elements-and-styles.md +61 -7
- package/skills/plitzi-authoring/reference/flows.md +108 -17
- 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 +19 -2
- package/skills/plitzi-authoring/reference/templates.md +67 -27
- package/skills/plitzi-authoring/reference/testing.md +72 -5
- package/skills/plitzi-authoring/reference/validation.md +73 -0
|
@@ -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;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/core/http/sse.ts
|
|
2
|
+
/** How often a comment frame goes out. Short enough that a dead peer is noticed in seconds, long enough that an
|
|
3
|
+
* idle stream costs nothing worth counting. */
|
|
4
|
+
var HEARTBEAT_MS = 15e3;
|
|
5
|
+
/**
|
|
6
|
+
* How much unread body may pile up before the stream is dropped.
|
|
7
|
+
*
|
|
8
|
+
* A `write` that answers `false` has been buffered rather than sent, which is normal for a moment and a leak
|
|
9
|
+
* forever: a peer that stopped reading — a phone that slept, a proxy that wedged — otherwise has everything sent to
|
|
10
|
+
* it accumulated in memory. The socket says nothing, so the only signal is this one.
|
|
11
|
+
*/
|
|
12
|
+
var MAX_BUFFERED_BYTES = 1e6;
|
|
13
|
+
/** What the host has written but not yet handed to the socket. Node's own response reports it; the minimum raw
|
|
14
|
+
* response this server accepts does not have to, and a host that cannot say simply never trips the cap. */
|
|
15
|
+
var bufferedBytes = (raw) => {
|
|
16
|
+
const { writableLength } = raw;
|
|
17
|
+
return typeof writableLength === "number" ? writableLength : 0;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Opens a Server-Sent Events response — an action's progress, a realtime channel.
|
|
21
|
+
*
|
|
22
|
+
* Written straight to the RAW response, never through the response helpers: those compress the body, set a
|
|
23
|
+
* Content-Length and end it in one go, which is precisely what a stream cannot do. `no-transform` says the same
|
|
24
|
+
* thing to every proxy in between, and `X-Accel-Buffering: no` says it to nginx, which otherwise holds frames
|
|
25
|
+
* until its buffer fills and turns a live stream into one late burst.
|
|
26
|
+
*/
|
|
27
|
+
var openEventStream = (raw, { onAbort, headers = {}, retryMs }) => {
|
|
28
|
+
let open = true;
|
|
29
|
+
raw.writeHead(200, {
|
|
30
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
31
|
+
"Cache-Control": "no-cache, no-transform",
|
|
32
|
+
Connection: "keep-alive",
|
|
33
|
+
"X-Accel-Buffering": "no",
|
|
34
|
+
...headers
|
|
35
|
+
});
|
|
36
|
+
raw.write(`retry: ${retryMs}\n\n`);
|
|
37
|
+
const drop = () => {
|
|
38
|
+
open = false;
|
|
39
|
+
clearInterval(heartbeat);
|
|
40
|
+
onAbort();
|
|
41
|
+
};
|
|
42
|
+
const heartbeat = setInterval(() => {
|
|
43
|
+
if (!open) return;
|
|
44
|
+
try {
|
|
45
|
+
raw.write(": ping\n\n");
|
|
46
|
+
} catch {
|
|
47
|
+
drop();
|
|
48
|
+
}
|
|
49
|
+
}, HEARTBEAT_MS);
|
|
50
|
+
const close = () => {
|
|
51
|
+
if (!open) return;
|
|
52
|
+
open = false;
|
|
53
|
+
clearInterval(heartbeat);
|
|
54
|
+
try {
|
|
55
|
+
raw.end();
|
|
56
|
+
} catch {}
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
send: (event, data) => {
|
|
60
|
+
if (!open) return;
|
|
61
|
+
try {
|
|
62
|
+
raw.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
63
|
+
if (bufferedBytes(raw) > MAX_BUFFERED_BYTES) {
|
|
64
|
+
drop();
|
|
65
|
+
try {
|
|
66
|
+
raw.end();
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
drop();
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
close,
|
|
74
|
+
isOpen: () => open
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
export { openEventStream };
|
|
@@ -2,6 +2,7 @@ import { readRawBody } from "../../requestParser.js";
|
|
|
2
2
|
import { isNavigation, safeRedirectTarget } from "../navigation.js";
|
|
3
3
|
import { clearSessionCookies, writeSessionCookies } from "../../auth/session.js";
|
|
4
4
|
import { exchangeStage } from "./exchangeRoute.js";
|
|
5
|
+
import { signInStage } from "./signInRoute.js";
|
|
5
6
|
//#region src/core/http/stages/authRoutes.ts
|
|
6
7
|
/**
|
|
7
8
|
* Credentials as they arrive from a page: a posted form, or JSON from a script. Every field is handed to the
|
|
@@ -64,7 +65,8 @@ var logoutStage = async (ctx) => {
|
|
|
64
65
|
var authRoutesStages = [
|
|
65
66
|
loginStage,
|
|
66
67
|
logoutStage,
|
|
67
|
-
exchangeStage
|
|
68
|
+
exchangeStage,
|
|
69
|
+
signInStage
|
|
68
70
|
];
|
|
69
71
|
//#endregion
|
|
70
72
|
export { authRoutesStages, loginStage, logoutStage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { SSRContext, Stage } from '../types';
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const createMiddlewaresStage: () => Stage<SSRContext>;
|
|
@@ -3,15 +3,21 @@ import { authMiddleware } from "../../../middlewares/auth.js";
|
|
|
3
3
|
import { basicAuthMiddleware } from "../../../middlewares/basicAuth.js";
|
|
4
4
|
import { spaceDeploymentMiddleware } from "../../../middlewares/spaceDeployment.js";
|
|
5
5
|
//#region src/core/http/stages/middlewares.ts
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
var createMiddlewaresStage = () => {
|
|
7
|
+
let built;
|
|
8
|
+
return async (ctx) => {
|
|
9
|
+
const { config, req, res } = ctx;
|
|
10
|
+
if (built?.config !== config) built = {
|
|
11
|
+
config,
|
|
12
|
+
chain: [
|
|
13
|
+
spaceDeploymentMiddleware(config.adapters),
|
|
14
|
+
basicAuthMiddleware({ cacheTtlMs: config.cacheTtlMs }),
|
|
15
|
+
authMiddleware(config.adapters),
|
|
16
|
+
...config.middlewares || []
|
|
17
|
+
]
|
|
18
|
+
};
|
|
19
|
+
return await runMiddlewares(built.chain, req, res) || res.status !== 200;
|
|
20
|
+
};
|
|
15
21
|
};
|
|
16
22
|
//#endregion
|
|
17
|
-
export {
|
|
23
|
+
export { createMiddlewaresStage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { readRawBody } from "../../requestParser.js";
|
|
2
|
-
import { authorizationServerMetadata, protectedResourceMetadata } from "../../oauth/metadata.js";
|
|
3
2
|
import { getAccess } from "../../oauth/records.js";
|
|
3
|
+
import { authorizationServerMetadata, protectedResourceMetadata } from "../../oauth/metadata.js";
|
|
4
4
|
import { sendErrorJson, sendJson } from "../../oauth/respond.js";
|
|
5
|
+
import { handleRegister } from "../../oauth/register.js";
|
|
5
6
|
import { handleAuthorizeStart, handleAuthorizeSubmit } from "../../oauth/authorize.js";
|
|
6
7
|
import { bearerOf, sendChallenge } from "../../oauth/challenge.js";
|
|
7
|
-
import { handleRegister } from "../../oauth/register.js";
|
|
8
8
|
import { handleRevoke, handleToken } from "../../oauth/token.js";
|
|
9
9
|
//#region src/core/http/stages/oauth.ts
|
|
10
10
|
var matchesWellKnown = (path, wellKnown) => path === wellKnown || path.startsWith(`${wellKnown}/`);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Auth } from '../../auth/createAuth';
|
|
2
|
+
import { BaseContext, Stage } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Sends a page request from a half-lapsed session to renew first — to `url`, the renewal endpoint — so the server
|
|
5
|
+
* renders the visitor the browser is going to end up with. See `renewForNavigation` for the other half.
|
|
6
|
+
*
|
|
7
|
+
* `url` is a path when this server serves `/auth` itself, and an absolute URL when another host does; the page's
|
|
8
|
+
* own address travels as `redirect` accordingly, and the endpoint only sends the browser back to a host that shares
|
|
9
|
+
* the session's cookies. Only whole-tab `GET` navigations take part: a renewal rotates the session.
|
|
10
|
+
*/
|
|
11
|
+
export declare const createSessionRenewalStage: (url: string) => Stage<BaseContext>;
|
|
12
|
+
/**
|
|
13
|
+
* The renewal endpoint, on a page server that serves `/auth` itself: `GET <basePath>/refresh`. The `POST` flow at the
|
|
14
|
+
* same path is the auth API stage's; this one only ever answers a whole-tab navigation.
|
|
15
|
+
*/
|
|
16
|
+
export declare const createRenewalEndpointStage: (auth: Auth) => Stage<BaseContext>;
|