@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/api.js
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
|
+
import { serverLog } from "../../helpers/serverLog.js";
|
|
2
|
+
import { forwardedIp } from "../requestParser.js";
|
|
1
3
|
import { checkPermission } from "./authorize.js";
|
|
2
4
|
import { generateToken, hashPassword, verifyPassword } from "./passwords.js";
|
|
3
|
-
import { createMemoryRateLimit } from "./throttle.js";
|
|
5
|
+
import { createMemoryRateLimit, fleetRateLimit } from "./throttle.js";
|
|
4
6
|
import { authFailureMessage } from "./tokens.js";
|
|
5
|
-
import { generateRecoveryCodes, generateTotpSecret, normalizeRecoveryCode, randomCode,
|
|
7
|
+
import { generateRecoveryCodes, generateTotpSecret, normalizeRecoveryCode, randomCode, totpStep, totpUri } from "./totp.js";
|
|
6
8
|
import { createHash } from "node:crypto";
|
|
7
9
|
//#region src/core/auth/api.ts
|
|
8
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* The user agent and the address, off whatever carried the request — what lets somebody tell their devices apart.
|
|
12
|
+
* Nothing else here reads headers, so it is done once. The address is the host's when it resolved one, the proxies'
|
|
13
|
+
* otherwise; it names a session in a list and decides nothing.
|
|
14
|
+
*/
|
|
9
15
|
var clientOf = (carrier) => {
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
if (!carrier) return;
|
|
17
|
+
const userAgent = carrier.headers["user-agent"];
|
|
18
|
+
const ip = carrier.ip || forwardedIp(carrier.headers);
|
|
19
|
+
const client = {
|
|
20
|
+
...typeof userAgent === "string" && userAgent ? { userAgent } : {},
|
|
21
|
+
...ip ? { ip } : {}
|
|
22
|
+
};
|
|
23
|
+
return client.userAgent || client.ip ? client : void 0;
|
|
12
24
|
};
|
|
13
25
|
var refuse = (status, error, reason) => ({
|
|
14
26
|
ok: false,
|
|
@@ -88,7 +100,7 @@ var profileOf = (account) => ({
|
|
|
88
100
|
* form without either owning it.
|
|
89
101
|
*/
|
|
90
102
|
var createAuthApi = ({ tokens, identity, adapters, config = {} }) => {
|
|
91
|
-
const { verifyPassword: verifyPassword$1 = verifyPassword, hashPassword: hashPassword$1 = hashPassword, generateToken: generateToken$1 = generateToken, verifyOnSignup = false, adminPermission = "userManage", impersonationPermission, password: policy = {}, rateLimit = createMemoryRateLimit(), onMailError, onEvent, mfaIssuer, csrf } = config;
|
|
103
|
+
const { verifyPassword: verifyPassword$1 = verifyPassword, hashPassword: hashPassword$1 = hashPassword, generateToken: generateToken$1 = generateToken, verifyOnSignup = false, adminPermission = "userManage", impersonationPermission, password: policy = {}, rateLimit = fleetRateLimit() ?? createMemoryRateLimit(), onMailError, onEvent, mfaIssuer, csrf } = config;
|
|
92
104
|
/** Never awaited, never able to throw: a logging outage must not become an authentication outage. */
|
|
93
105
|
const record = (event) => {
|
|
94
106
|
if (!onEvent) return;
|
|
@@ -98,7 +110,7 @@ var createAuthApi = ({ tokens, identity, adapters, config = {} }) => {
|
|
|
98
110
|
at: Math.floor(Date.now() / 1e3)
|
|
99
111
|
});
|
|
100
112
|
} catch (error) {
|
|
101
|
-
|
|
113
|
+
serverLog.error("auth", "security event handler threw", error);
|
|
102
114
|
}
|
|
103
115
|
};
|
|
104
116
|
/**
|
|
@@ -132,7 +144,7 @@ var createAuthApi = ({ tokens, identity, adapters, config = {} }) => {
|
|
|
132
144
|
try {
|
|
133
145
|
await adapters.sendMail?.(message);
|
|
134
146
|
} catch (error) {
|
|
135
|
-
(onMailError ?? ((cause) =>
|
|
147
|
+
(onMailError ?? ((cause) => serverLog.error("auth", "could not send mail", cause)))(error, {
|
|
136
148
|
to: message.to,
|
|
137
149
|
template: message.template
|
|
138
150
|
});
|
|
@@ -386,6 +398,11 @@ var createAuthApi = ({ tokens, identity, adapters, config = {} }) => {
|
|
|
386
398
|
return refuse(401, authFailureMessage[reason], reason);
|
|
387
399
|
}
|
|
388
400
|
if ((capabilities.mfa ? await adapters.loadMfa?.(account.id) : void 0)?.confirmedAt) {
|
|
401
|
+
throttleSucceeded({
|
|
402
|
+
action: "login",
|
|
403
|
+
key: username,
|
|
404
|
+
carrier
|
|
405
|
+
});
|
|
389
406
|
record({
|
|
390
407
|
type: "login.mfa-required",
|
|
391
408
|
userId: account.id,
|
|
@@ -444,17 +461,23 @@ var createAuthApi = ({ tokens, identity, adapters, config = {} }) => {
|
|
|
444
461
|
const stored = mfa.recoveryCodes ?? [];
|
|
445
462
|
const supplied = digestRecoveryCode(code);
|
|
446
463
|
const usedRecovery = stored.includes(supplied);
|
|
447
|
-
|
|
464
|
+
const step = usedRecovery ? void 0 : totpStep(mfa.secret, code);
|
|
465
|
+
const replayed = step !== void 0 && mfa.lastUsedStep !== void 0 && step <= mfa.lastUsedStep;
|
|
466
|
+
if (!usedRecovery && (step === void 0 || replayed)) {
|
|
448
467
|
record({
|
|
449
468
|
type: "mfa.failed",
|
|
450
469
|
userId,
|
|
451
|
-
carrier
|
|
470
|
+
carrier,
|
|
471
|
+
...replayed ? { detail: { replayed: true } } : {}
|
|
452
472
|
});
|
|
453
473
|
return refuse(401, "Invalid code");
|
|
454
474
|
}
|
|
455
|
-
|
|
475
|
+
await adapters.saveMfa?.(userId, usedRecovery ? {
|
|
456
476
|
...mfa,
|
|
457
477
|
recoveryCodes: stored.filter((entry) => entry !== supplied)
|
|
478
|
+
} : {
|
|
479
|
+
...mfa,
|
|
480
|
+
lastUsedStep: step
|
|
458
481
|
});
|
|
459
482
|
const session = await issue(userId, { client: clientOf(carrier) });
|
|
460
483
|
throttleSucceeded({
|
|
@@ -631,7 +654,8 @@ var createAuthApi = ({ tokens, identity, adapters, config = {} }) => {
|
|
|
631
654
|
const mfa = await adapters.loadMfa?.(actor.id);
|
|
632
655
|
if (!mfa) return refuse(400, "Start the enrolment first");
|
|
633
656
|
if (mfa.confirmedAt) return refuse(409, "A second factor is already set up");
|
|
634
|
-
|
|
657
|
+
const step = totpStep(mfa.secret, code);
|
|
658
|
+
if (step === void 0) {
|
|
635
659
|
record({
|
|
636
660
|
type: "mfa.failed",
|
|
637
661
|
userId: actor.id
|
|
@@ -642,7 +666,8 @@ var createAuthApi = ({ tokens, identity, adapters, config = {} }) => {
|
|
|
642
666
|
await adapters.saveMfa?.(actor.id, {
|
|
643
667
|
...mfa,
|
|
644
668
|
confirmedAt: Math.floor(Date.now() / 1e3),
|
|
645
|
-
recoveryCodes: plain.map(digestRecoveryCode)
|
|
669
|
+
recoveryCodes: plain.map(digestRecoveryCode),
|
|
670
|
+
lastUsedStep: step
|
|
646
671
|
});
|
|
647
672
|
record({
|
|
648
673
|
type: "mfa.enabled",
|
|
@@ -1070,6 +1095,15 @@ var createAuthApi = ({ tokens, identity, adapters, config = {} }) => {
|
|
|
1070
1095
|
if (account?.passwordHash) {
|
|
1071
1096
|
if (!password || !await verifyPassword$1(password, account.passwordHash)) return refuse(401, "Invalid credentials");
|
|
1072
1097
|
}
|
|
1098
|
+
const blockers = await adapters.deletionBlockers?.(actor.id) ?? [];
|
|
1099
|
+
if (blockers.length > 0) return {
|
|
1100
|
+
ok: false,
|
|
1101
|
+
status: 409,
|
|
1102
|
+
body: {
|
|
1103
|
+
error: "This account still owns things that would be lost with it",
|
|
1104
|
+
blockers
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1073
1107
|
await adapters.deleteAccount(actor.id);
|
|
1074
1108
|
record({
|
|
1075
1109
|
type: "account.deleted",
|
|
@@ -177,6 +177,9 @@ export declare const createAuth: (config: AuthConfig) => {
|
|
|
177
177
|
hostname: string;
|
|
178
178
|
}, res: CookieSink) => void;
|
|
179
179
|
readFlow: (req: import('./session').CookieCarrier) => string | undefined;
|
|
180
|
+
returnTarget: (req: {
|
|
181
|
+
hostname: string;
|
|
182
|
+
}, target: unknown) => string;
|
|
180
183
|
resolveSessionToken: (req: import('./session').CookieCarrier) => string | undefined;
|
|
181
184
|
resolveRefreshToken: (req: import('./session').CookieCarrier, body?: unknown) => string | undefined;
|
|
182
185
|
};
|
|
@@ -202,6 +205,7 @@ export declare const createAuth: (config: AuthConfig) => {
|
|
|
202
205
|
}, res: CookieSink) => void;
|
|
203
206
|
read: (carrier: import('./csrf').CsrfCarrier) => string | undefined;
|
|
204
207
|
required: (carrier: import('./csrf').CsrfCarrier, subject?: import('./csrf').CsrfSubject) => boolean;
|
|
208
|
+
crossSite: (carrier: import('./csrf').CsrfCarrier, alsoAllowed?: readonly string[]) => boolean;
|
|
205
209
|
verify: (carrier: import('./csrf').CsrfCarrier, sessionToken?: string) => import('./csrf').CsrfResult;
|
|
206
210
|
} | undefined;
|
|
207
211
|
/** The `/auth` flows as descriptors, ready for whatever serves HTTP here. */
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createSessionCookies, sessionCookieParams } from "./session.js";
|
|
2
|
+
import { createCsrf } from "./csrf.js";
|
|
3
|
+
import { applySessionOutcome, authPolicyRules, authRoutes } from "./routes.js";
|
|
4
|
+
import { createCarriers, presentedOrigin } from "./credentials.js";
|
|
2
5
|
import { createAuthorizer } from "./authorize.js";
|
|
3
6
|
import { createTokens } from "./tokens.js";
|
|
4
7
|
import { createAuthApi } from "./api.js";
|
|
5
|
-
import { createCarriers, presentedOrigin } from "./credentials.js";
|
|
6
|
-
import { createCsrf } from "./csrf.js";
|
|
7
8
|
import { createIdentity } from "./identity.js";
|
|
8
|
-
import { applySessionOutcome, authPolicyRules, authRoutes } from "./routes.js";
|
|
9
9
|
//#region src/core/auth/createAuth.ts
|
|
10
10
|
/**
|
|
11
11
|
* The whole auth cycle, wired.
|
|
@@ -9,7 +9,10 @@ export interface CredentialCarrier {
|
|
|
9
9
|
query?: Record<string, unknown>;
|
|
10
10
|
/** Populated by cookie-parser on Express; absent elsewhere, where the header is parsed. */
|
|
11
11
|
cookies?: Record<string, string>;
|
|
12
|
+
/** The client's address as the host resolved it (Express's `req.ip`, honouring its `trust proxy`). */
|
|
13
|
+
ip?: string;
|
|
12
14
|
}
|
|
15
|
+
export declare const readCookies: (carrier: CredentialCarrier) => Record<string, string>;
|
|
13
16
|
/**
|
|
14
17
|
* Where a credential of each kind is allowed to ride.
|
|
15
18
|
*
|
package/dist/core/auth/csrf.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface CsrfConfig {
|
|
|
26
26
|
* space declared. Applying the check below on top would refuse every legitimate embed.
|
|
27
27
|
*/
|
|
28
28
|
export type CsrfSubject = 'write' | 'signIn' | 'delegated';
|
|
29
|
-
export type CsrfFailure = 'missing' | 'malformed' | 'expired' | 'mismatch';
|
|
29
|
+
export type CsrfFailure = 'missing' | 'malformed' | 'expired' | 'mismatch' | 'foreign';
|
|
30
30
|
export declare const csrfFailureMessage: Record<CsrfFailure, string>;
|
|
31
31
|
export type CsrfResult = {
|
|
32
32
|
ok: true;
|
|
@@ -42,6 +42,20 @@ export interface CsrfCarrier {
|
|
|
42
42
|
cookies?: Record<string, string>;
|
|
43
43
|
body?: unknown;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* The carrier, read off whatever request a host hands over — named field by field, never spread.
|
|
47
|
+
*
|
|
48
|
+
* A spread copies OWN properties, and on Node's `IncomingMessage` (which an Express request is) `headers` is a getter on
|
|
49
|
+
* the prototype: `{ ...req }` arrives with no headers at all, and the first header read throws. Every guard built on a
|
|
50
|
+
* spread failed that way on every request.
|
|
51
|
+
*/
|
|
52
|
+
export declare const carrierOf: (req: {
|
|
53
|
+
method?: string;
|
|
54
|
+
headers: CsrfCarrier["headers"];
|
|
55
|
+
hostname: string;
|
|
56
|
+
cookies?: unknown;
|
|
57
|
+
body?: unknown;
|
|
58
|
+
}, method?: string | undefined) => CsrfCarrier;
|
|
45
59
|
export declare const createCsrf: (config: CsrfConfig) => {
|
|
46
60
|
issue: (sessionToken?: string) => string;
|
|
47
61
|
/** The name the cookie lands under, for a client that reads it by hand. */
|
|
@@ -79,6 +93,16 @@ export declare const createCsrf: (config: CsrfConfig) => {
|
|
|
79
93
|
* protected by the session cookie's presence, a sign-in by where the request came from. See {@link CsrfSubject}.
|
|
80
94
|
*/
|
|
81
95
|
required: (carrier: CsrfCarrier, subject?: CsrfSubject) => boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Whether a browser sent this from a site this deployment does not recognise — its own origins, and
|
|
98
|
+
* `alsoAllowed` (the origins a SPACE declared, for a request that carries that space's credential).
|
|
99
|
+
*
|
|
100
|
+
* The rule `signIn` flows are guarded by, offered on its own for a router whose writes cannot all carry a token:
|
|
101
|
+
* a cookie-carried write that a foreign page caused is refused by where it came from — see
|
|
102
|
+
* `createOriginGuardMiddleware`. A request with neither Fetch Metadata nor `Origin` is not a browser's, and is
|
|
103
|
+
* never foreign: there is no victim's session in a script.
|
|
104
|
+
*/
|
|
105
|
+
crossSite: (carrier: CsrfCarrier, alsoAllowed?: readonly string[]) => boolean;
|
|
82
106
|
/**
|
|
83
107
|
* The whole check. The echoed token must verify against the session the request carries, and — when the
|
|
84
108
|
* cookie is also present — match it. The second half costs nothing and blocks a token that leaked somewhere
|
package/dist/core/auth/csrf.js
CHANGED
|
@@ -40,8 +40,23 @@ var csrfFailureMessage = {
|
|
|
40
40
|
missing: "A CSRF token is required",
|
|
41
41
|
malformed: "CSRF token malformed",
|
|
42
42
|
expired: "CSRF token expired",
|
|
43
|
-
mismatch: "CSRF token does not match this session"
|
|
43
|
+
mismatch: "CSRF token does not match this session",
|
|
44
|
+
foreign: "A request from another site may not act with this session"
|
|
44
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* The carrier, read off whatever request a host hands over — named field by field, never spread.
|
|
48
|
+
*
|
|
49
|
+
* A spread copies OWN properties, and on Node's `IncomingMessage` (which an Express request is) `headers` is a getter on
|
|
50
|
+
* the prototype: `{ ...req }` arrives with no headers at all, and the first header read throws. Every guard built on a
|
|
51
|
+
* spread failed that way on every request.
|
|
52
|
+
*/
|
|
53
|
+
var carrierOf = (req, method = req.method) => ({
|
|
54
|
+
method,
|
|
55
|
+
headers: req.headers,
|
|
56
|
+
hostname: req.hostname,
|
|
57
|
+
...req.cookies && typeof req.cookies === "object" ? { cookies: req.cookies } : {},
|
|
58
|
+
body: req.body
|
|
59
|
+
});
|
|
45
60
|
var equal = (a, b) => {
|
|
46
61
|
const left = Buffer.from(a);
|
|
47
62
|
const right = Buffer.from(b);
|
|
@@ -94,10 +109,10 @@ var createCsrf = (config) => {
|
|
|
94
109
|
* anything: there is no victim's session sitting in it. That is what keeps every API client, mobile app and
|
|
95
110
|
* script signing in with nothing extra to send.
|
|
96
111
|
*/
|
|
97
|
-
const foreign = (carrier) => {
|
|
112
|
+
const foreign = (carrier, alsoAllowed = []) => {
|
|
98
113
|
const site = header(carrier, "sec-fetch-site");
|
|
99
114
|
const origin = header(carrier, "origin");
|
|
100
|
-
const allowed = origin !== void 0 && permitted(origin);
|
|
115
|
+
const allowed = origin !== void 0 && (permitted(origin) || alsoAllowed.includes(origin));
|
|
101
116
|
if (site !== void 0) return site === "cross-site" && !allowed;
|
|
102
117
|
if (!origin) return false;
|
|
103
118
|
return originHost(origin) !== carrier.hostname && !allowed;
|
|
@@ -212,6 +227,16 @@ var createCsrf = (config) => {
|
|
|
212
227
|
return readCookieHeader(carrier, sessionCookieParams(carrier.hostname, cookie).name) !== void 0;
|
|
213
228
|
},
|
|
214
229
|
/**
|
|
230
|
+
* Whether a browser sent this from a site this deployment does not recognise — its own origins, and
|
|
231
|
+
* `alsoAllowed` (the origins a SPACE declared, for a request that carries that space's credential).
|
|
232
|
+
*
|
|
233
|
+
* The rule `signIn` flows are guarded by, offered on its own for a router whose writes cannot all carry a token:
|
|
234
|
+
* a cookie-carried write that a foreign page caused is refused by where it came from — see
|
|
235
|
+
* `createOriginGuardMiddleware`. A request with neither Fetch Metadata nor `Origin` is not a browser's, and is
|
|
236
|
+
* never foreign: there is no victim's session in a script.
|
|
237
|
+
*/
|
|
238
|
+
crossSite: (carrier, alsoAllowed) => foreign(carrier, alsoAllowed),
|
|
239
|
+
/**
|
|
215
240
|
* The whole check. The echoed token must verify against the session the request carries, and — when the
|
|
216
241
|
* cookie is also present — match it. The second half costs nothing and blocks a token that leaked somewhere
|
|
217
242
|
* the cookie did not.
|
|
@@ -231,4 +256,4 @@ var createCsrf = (config) => {
|
|
|
231
256
|
};
|
|
232
257
|
};
|
|
233
258
|
//#endregion
|
|
234
|
-
export { createCsrf, csrfFailureMessage };
|
|
259
|
+
export { carrierOf, createCsrf, csrfFailureMessage };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AuthApi } from './api';
|
|
2
|
+
import { CredentialCarrier } from './credentials';
|
|
3
|
+
import { Csrf } from './csrf';
|
|
4
|
+
import { CookieSink, SessionCookies } from './session';
|
|
5
|
+
/**
|
|
6
|
+
* A navigation of the whole tab — not a frame, not an image, not a script's fetch. The one kind of request another
|
|
7
|
+
* site cannot make on a visitor's behalf without the visitor watching it happen, which is what lets a renewal (it
|
|
8
|
+
* rotates the session) answer a `GET`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const isDocumentNavigation: (headers: Record<string, string | string[] | undefined>) => boolean;
|
|
11
|
+
export interface RenewalOptions {
|
|
12
|
+
api: AuthApi;
|
|
13
|
+
cookies: SessionCookies;
|
|
14
|
+
/** Re-issued for the renewed session, as every flow that grants one does. */
|
|
15
|
+
csrf?: Csrf;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `GET <basePath>/refresh?redirect=<page>`: renews the session this browser's refresh cookie holds, and answers where
|
|
19
|
+
* to send the browser next — always back to the page, whether or not the renewal worked.
|
|
20
|
+
*
|
|
21
|
+
* It is where a page server sends a browser whose access cookie has died while its refresh cookie lives on, because
|
|
22
|
+
* this is the one path the refresh cookie is sent to. What it writes is what makes the page render right on the way
|
|
23
|
+
* back: the new session, or — for a refresh token that can never work again — no session and no hint, so the page
|
|
24
|
+
* renders a guest and does not send the browser here a second time.
|
|
25
|
+
*/
|
|
26
|
+
export declare const renewForNavigation: (req: CredentialCarrier, res: CookieSink, { api, cookies, csrf }: RenewalOptions) => Promise<string>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { applySessionOutcome } from "./routes.js";
|
|
2
|
+
//#region src/core/auth/renewal.ts
|
|
3
|
+
/**
|
|
4
|
+
* A navigation of the whole tab — not a frame, not an image, not a script's fetch. The one kind of request another
|
|
5
|
+
* site cannot make on a visitor's behalf without the visitor watching it happen, which is what lets a renewal (it
|
|
6
|
+
* rotates the session) answer a `GET`.
|
|
7
|
+
*/
|
|
8
|
+
var isDocumentNavigation = (headers) => headers["sec-fetch-mode"] === "navigate" && headers["sec-fetch-dest"] === "document";
|
|
9
|
+
/**
|
|
10
|
+
* `GET <basePath>/refresh?redirect=<page>`: renews the session this browser's refresh cookie holds, and answers where
|
|
11
|
+
* to send the browser next — always back to the page, whether or not the renewal worked.
|
|
12
|
+
*
|
|
13
|
+
* It is where a page server sends a browser whose access cookie has died while its refresh cookie lives on, because
|
|
14
|
+
* this is the one path the refresh cookie is sent to. What it writes is what makes the page render right on the way
|
|
15
|
+
* back: the new session, or — for a refresh token that can never work again — no session and no hint, so the page
|
|
16
|
+
* renders a guest and does not send the browser here a second time.
|
|
17
|
+
*/
|
|
18
|
+
var renewForNavigation = async (req, res, { api, cookies, csrf }) => {
|
|
19
|
+
const outcome = await api.refresh(cookies.resolveRefreshToken(req), req);
|
|
20
|
+
applySessionOutcome(req, res, outcome, cookies, csrf);
|
|
21
|
+
return cookies.returnTarget(req, req.query?.redirect);
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { isDocumentNavigation, renewForNavigation };
|
package/dist/core/auth/routes.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { csrfFailureMessage } from "./csrf.js";
|
|
1
|
+
import { carrierOf, csrfFailureMessage } from "./csrf.js";
|
|
2
2
|
//#region src/core/auth/routes.ts
|
|
3
3
|
var body = (req) => req.body ?? {};
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* A body field as text. A NUMBER counts — JSON carries an all-digit code or password as one, and a client that sent
|
|
6
|
+
* `123456` has sent a code: reading it as absent answered a right second-factor code with "Invalid code". Anything else
|
|
7
|
+
* (an object, a boolean) is not a value anybody typed, and is absent.
|
|
8
|
+
*/
|
|
5
9
|
var field = (req, name) => {
|
|
6
10
|
const value = body(req)[name];
|
|
11
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
7
12
|
return typeof value === "string" ? value : "";
|
|
8
13
|
};
|
|
9
14
|
/** A body field as a whole number. `NaN` for anything else, which every handler refuses as a missing id. */
|
|
@@ -286,10 +291,7 @@ var authRoutes = ({ api, cookies, csrf }) => FLOWS.map(({ run, csrf: policy, ...
|
|
|
286
291
|
* unbound token satisfies a signed-out caller, which is how signing in is protected when a deployment asks
|
|
287
292
|
* for that.
|
|
288
293
|
*/
|
|
289
|
-
const carrier =
|
|
290
|
-
...req,
|
|
291
|
-
method: route.method
|
|
292
|
-
};
|
|
294
|
+
const carrier = carrierOf(req, route.method);
|
|
293
295
|
if (csrf && csrfNeeded(csrf, policy, carrier)) {
|
|
294
296
|
const result = csrf.verify(carrier, cookies.resolveSessionToken(req));
|
|
295
297
|
if (!result.ok) return {
|
|
@@ -44,6 +44,14 @@ export declare const sessionCookieParams: (hostname: string, config?: SSRAuthCoo
|
|
|
44
44
|
* same question with `credentials: 'include'`.
|
|
45
45
|
*/
|
|
46
46
|
export declare const sessionHintValue: (expiresAt: number, refreshExpiresAt?: number) => string;
|
|
47
|
+
export type SessionHint = {
|
|
48
|
+
/** Unix seconds the access credential dies at. */
|
|
49
|
+
expiresAt: number;
|
|
50
|
+
/** Unix seconds renewal stops being possible at, when the session has a refresh half. */
|
|
51
|
+
refreshExpiresAt?: number;
|
|
52
|
+
};
|
|
53
|
+
/** {@link sessionHintValue} read back — the same reading `@plitzi/sdk-auth` gives it in the browser. */
|
|
54
|
+
export declare const parseSessionHint: (value: string | undefined) => SessionHint | undefined;
|
|
47
55
|
/**
|
|
48
56
|
* Adds to `Set-Cookie` rather than replacing it. A handler may already have written one — clearing an OAuth flow
|
|
49
57
|
* before granting the session it produced is exactly that — and replacing the header would silently drop it.
|
|
@@ -93,6 +101,40 @@ export declare const readRefreshToken: (req: SSRRequest | {
|
|
|
93
101
|
};
|
|
94
102
|
hostname: string;
|
|
95
103
|
}, config?: SSRAuthCookie) => string | undefined;
|
|
104
|
+
/** What the readable companion cookie says about this browser's session, without presenting any credential. */
|
|
105
|
+
export declare const readSessionHint: (req: SSRRequest | {
|
|
106
|
+
headers: {
|
|
107
|
+
cookie?: string;
|
|
108
|
+
};
|
|
109
|
+
hostname: string;
|
|
110
|
+
}, config?: SSRAuthCookie) => SessionHint | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* How long a page server holds off sending a browser to renew again. Long enough to cover the round trip there and
|
|
113
|
+
* back, short enough that a renewal which failed for a passing reason is retried on a later visit.
|
|
114
|
+
*/
|
|
115
|
+
export declare const RENEWAL_GUARD_SECONDS = 30;
|
|
116
|
+
/**
|
|
117
|
+
* Marks a browser as having just been sent to renew its session, so the page it comes back to is rendered rather
|
|
118
|
+
* than bounced again. Every renewal that can fail without ending the session — the refresh cookie not reaching the
|
|
119
|
+
* endpoint, the store being down — would otherwise send the visitor round in a loop.
|
|
120
|
+
*/
|
|
121
|
+
export declare const writeRenewalGuard: (req: {
|
|
122
|
+
hostname: string;
|
|
123
|
+
}, res: CookieSink, config?: SSRAuthCookie) => void;
|
|
124
|
+
/**
|
|
125
|
+
* Where a renewal sends the browser back to: a path on this host, or a page on any host this session's cookies are
|
|
126
|
+
* scoped to — the only hosts that could have sent a browser here to renew, since the hint that triggers it rides on
|
|
127
|
+
* the same cookie domain. Anything else is an open redirect, and goes to `/` instead.
|
|
128
|
+
*/
|
|
129
|
+
export declare const sessionReturnTarget: (req: {
|
|
130
|
+
hostname: string;
|
|
131
|
+
}, target: unknown, config?: SSRAuthCookie) => string;
|
|
132
|
+
export declare const readRenewalGuard: (req: SSRRequest | {
|
|
133
|
+
headers: {
|
|
134
|
+
cookie?: string;
|
|
135
|
+
};
|
|
136
|
+
hostname: string;
|
|
137
|
+
}, config?: SSRAuthCookie) => boolean;
|
|
96
138
|
/** Anything a credential can be read off: this server's own request, an Express one, a bare `node:http` one. */
|
|
97
139
|
export type CookieCarrier = SSRRequest | {
|
|
98
140
|
headers: {
|
|
@@ -124,6 +166,9 @@ export declare const createSessionCookies: (config?: SSRAuthCookie) => {
|
|
|
124
166
|
hostname: string;
|
|
125
167
|
}, res: CookieSink) => void;
|
|
126
168
|
readFlow: (req: CookieCarrier) => string | undefined;
|
|
169
|
+
returnTarget: (req: {
|
|
170
|
+
hostname: string;
|
|
171
|
+
}, target: unknown) => string;
|
|
127
172
|
/**
|
|
128
173
|
* The session credential from `Authorization: Bearer` or the cookie, so one endpoint serves a bearer client and a
|
|
129
174
|
* browser. The header wins: a caller that went to the trouble of presenting one means it.
|
|
@@ -51,6 +51,18 @@ var sessionCookieParams = (hostname, config = {}) => {
|
|
|
51
51
|
* same question with `credentials: 'include'`.
|
|
52
52
|
*/
|
|
53
53
|
var sessionHintValue = (expiresAt, refreshExpiresAt) => `${Math.floor(expiresAt)}.${refreshExpiresAt === void 0 ? "" : Math.floor(refreshExpiresAt)}`;
|
|
54
|
+
/** {@link sessionHintValue} read back — the same reading `@plitzi/sdk-auth` gives it in the browser. */
|
|
55
|
+
var parseSessionHint = (value) => {
|
|
56
|
+
if (!value) return;
|
|
57
|
+
const [expiresAt, refreshExpiresAt] = value.split(".");
|
|
58
|
+
const expiry = Number(expiresAt);
|
|
59
|
+
if (!Number.isFinite(expiry) || expiry <= 0) return;
|
|
60
|
+
const refreshExpiry = Number(refreshExpiresAt);
|
|
61
|
+
return {
|
|
62
|
+
expiresAt: expiry,
|
|
63
|
+
refreshExpiresAt: Number.isFinite(refreshExpiry) && refreshExpiry > 0 ? refreshExpiry : void 0
|
|
64
|
+
};
|
|
65
|
+
};
|
|
54
66
|
var serializeCookie = (name, value, maxAgeSeconds, params, { httpOnly = true, path = "/" } = {}) => {
|
|
55
67
|
const parts = [
|
|
56
68
|
`${name}=${encodeURIComponent(value)}`,
|
|
@@ -127,6 +139,39 @@ var readCookie = (req, name) => {
|
|
|
127
139
|
var readSessionToken = (req, config) => readCookie(req, sessionCookieParams(req.hostname, config).name);
|
|
128
140
|
/** The renewal credential, which rides in its own cookie confined to the refresh path. */
|
|
129
141
|
var readRefreshToken = (req, config) => readCookie(req, `${sessionCookieParams(req.hostname, config).name}_refresh`);
|
|
142
|
+
/** What the readable companion cookie says about this browser's session, without presenting any credential. */
|
|
143
|
+
var readSessionHint = (req, config) => {
|
|
144
|
+
const params = sessionCookieParams(req.hostname, config);
|
|
145
|
+
return parseSessionHint(readCookie(req, `${params.name}${params.hintSuffix}`));
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Marks a browser as having just been sent to renew its session, so the page it comes back to is rendered rather
|
|
149
|
+
* than bounced again. Every renewal that can fail without ending the session — the refresh cookie not reaching the
|
|
150
|
+
* endpoint, the store being down — would otherwise send the visitor round in a loop.
|
|
151
|
+
*/
|
|
152
|
+
var writeRenewalGuard = (req, res, config) => {
|
|
153
|
+
const params = sessionCookieParams(req.hostname, config);
|
|
154
|
+
appendCookies(res, [serializeCookie(`${params.name}_renewing`, "1", 30, params)]);
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Where a renewal sends the browser back to: a path on this host, or a page on any host this session's cookies are
|
|
158
|
+
* scoped to — the only hosts that could have sent a browser here to renew, since the hint that triggers it rides on
|
|
159
|
+
* the same cookie domain. Anything else is an open redirect, and goes to `/` instead.
|
|
160
|
+
*/
|
|
161
|
+
var sessionReturnTarget = (req, target, config) => {
|
|
162
|
+
if (typeof target !== "string" || target === "") return "/";
|
|
163
|
+
if (target.startsWith("/") && !target.startsWith("//") && !target.startsWith("/\\")) return target;
|
|
164
|
+
let url;
|
|
165
|
+
try {
|
|
166
|
+
url = new URL(target);
|
|
167
|
+
} catch {
|
|
168
|
+
return "/";
|
|
169
|
+
}
|
|
170
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return "/";
|
|
171
|
+
const { domain } = sessionCookieParams(req.hostname, config);
|
|
172
|
+
return url.hostname === req.hostname || domain !== void 0 && (url.hostname === domain.slice(1) || url.hostname.endsWith(domain)) ? url.toString() : "/";
|
|
173
|
+
};
|
|
174
|
+
var readRenewalGuard = (req, config) => readCookie(req, `${sessionCookieParams(req.hostname, config).name}_renewing`) !== void 0;
|
|
130
175
|
/**
|
|
131
176
|
* The cookie side of a session with this deployment's naming already bound in.
|
|
132
177
|
*
|
|
@@ -142,6 +187,7 @@ var createSessionCookies = (config) => ({
|
|
|
142
187
|
writeFlow: (req, res, value, ttlSeconds) => writeFlowCookie(req, res, value, ttlSeconds, config),
|
|
143
188
|
clearFlow: (req, res) => clearFlowCookie(req, res, config),
|
|
144
189
|
readFlow: (req) => readFlowCookie(req, config),
|
|
190
|
+
returnTarget: (req, target) => sessionReturnTarget(req, target, config),
|
|
145
191
|
/**
|
|
146
192
|
* The session credential from `Authorization: Bearer` or the cookie, so one endpoint serves a bearer client and a
|
|
147
193
|
* browser. The header wins: a caller that went to the trouble of presenting one means it.
|
|
@@ -162,4 +208,4 @@ var createSessionCookies = (config) => ({
|
|
|
162
208
|
}
|
|
163
209
|
});
|
|
164
210
|
//#endregion
|
|
165
|
-
export { appendCookies, clearFlowCookie, clearSessionCookies, createSessionCookies, isLocalHost, readFlowCookie, readRefreshToken, readSessionToken, registrableDomain, sessionCookieParams, sessionHintValue, writeFlowCookie, writeSessionCookies };
|
|
211
|
+
export { appendCookies, clearFlowCookie, clearSessionCookies, createSessionCookies, isLocalHost, parseSessionHint, readFlowCookie, readRefreshToken, readRenewalGuard, readSessionHint, readSessionToken, registrableDomain, sessionCookieParams, sessionHintValue, sessionReturnTarget, writeFlowCookie, writeRenewalGuard, writeSessionCookies };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When a session was last used, to the resolution a device list needs.
|
|
3
|
+
*
|
|
4
|
+
* "Active 3 minutes ago" against "active last March" is what tells somebody which of their devices is the one they do
|
|
5
|
+
* not recognise — and the time a session was CREATED says neither: a laptop signed in a month ago and used this morning
|
|
6
|
+
* reads as a month-old session. Recording every request would be a write per request on the hottest read in the
|
|
7
|
+
* system, so a store records it at most once per this many seconds, on the lookup it already makes.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SESSION_ACTIVITY_RESOLUTION_SECONDS = 300;
|
|
10
|
+
/** Whether a session last seen at `lastActiveAt` (unix seconds, absent when never) is due a new record at `now`. */
|
|
11
|
+
export declare const activityDue: (lastActiveAt: number | null | undefined, now: number) => boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/core/auth/sessionActivity.ts
|
|
2
|
+
/**
|
|
3
|
+
* When a session was last used, to the resolution a device list needs.
|
|
4
|
+
*
|
|
5
|
+
* "Active 3 minutes ago" against "active last March" is what tells somebody which of their devices is the one they do
|
|
6
|
+
* not recognise — and the time a session was CREATED says neither: a laptop signed in a month ago and used this morning
|
|
7
|
+
* reads as a month-old session. Recording every request would be a write per request on the hottest read in the
|
|
8
|
+
* system, so a store records it at most once per this many seconds, on the lookup it already makes.
|
|
9
|
+
*/
|
|
10
|
+
var SESSION_ACTIVITY_RESOLUTION_SECONDS = 300;
|
|
11
|
+
/** Whether a session last seen at `lastActiveAt` (unix seconds, absent when never) is due a new record at `now`. */
|
|
12
|
+
var activityDue = (lastActiveAt, now) => lastActiveAt === null || lastActiveAt === void 0 || now - lastActiveAt >= 300;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { SESSION_ACTIVITY_RESOLUTION_SECONDS, activityDue };
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { ThrottleAttempt } from './api';
|
|
2
|
+
import { StoreMethods } from '../server/fleet/channel';
|
|
2
3
|
export declare const createMemoryRateLimit: () => ((attempt: ThrottleAttempt) => Promise<{
|
|
3
4
|
allowed: boolean;
|
|
4
5
|
retryAfter?: number;
|
|
5
6
|
}>);
|
|
7
|
+
type RateLimitStore = {
|
|
8
|
+
check: ReturnType<typeof createMemoryRateLimit>;
|
|
9
|
+
};
|
|
10
|
+
/** Every method of the store, for the copy a fleet's workers share (see `fleet/stores.ts`). */
|
|
11
|
+
export declare const RATE_LIMIT_METHODS: StoreMethods<RateLimitStore>;
|
|
12
|
+
/**
|
|
13
|
+
* In a worker of a fleet, the primary's {@link createMemoryRateLimit}, so an attacker spreading attempts over
|
|
14
|
+
* connections meets one counter rather than one per worker. Nothing anywhere else.
|
|
15
|
+
*/
|
|
16
|
+
export declare const fleetRateLimit: () => ((attempt: ThrottleAttempt) => Promise<{
|
|
17
|
+
allowed: boolean;
|
|
18
|
+
retryAfter?: number;
|
|
19
|
+
}>) | undefined;
|
|
20
|
+
export {};
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
import { fleetStore } from "../server/fleet/link.js";
|
|
1
2
|
//#region src/core/auth/throttle.ts
|
|
3
|
+
/**
|
|
4
|
+
* The rate limit a deployment gets without asking for one.
|
|
5
|
+
*
|
|
6
|
+
* This used to be a required-to-be-safe option: no `rateLimit`, no limit, and sign-in was an unmetered password
|
|
7
|
+
* oracle. Nobody sets an option they have not read about, so the effect of "there is no sensible default" was that
|
|
8
|
+
* the common deployment shipped without any — which is not a neutral outcome, it is the bad one.
|
|
9
|
+
*
|
|
10
|
+
* It counts in memory, and that is an honest limitation rather than a hidden one: several replicas each keep
|
|
11
|
+
* their own window, so four replicas tolerate four times the attempts. Four times a small number is still a small
|
|
12
|
+
* number, and it beats infinity. A deployment that wants one counter for all of them supplies `rateLimit` and puts
|
|
13
|
+
* it in Redis. The workers of one server are one replica: they count in the primary (`fleetRateLimit`).
|
|
14
|
+
*/
|
|
2
15
|
/** Attempts allowed per window, per key, per action. */
|
|
3
16
|
var LIMITS = {
|
|
4
17
|
/** Credential stuffing is the thing being priced out. Ten wrong passwords in five minutes is already a lot. */
|
|
@@ -77,5 +90,20 @@ var createMemoryRateLimit = () => {
|
|
|
77
90
|
return Promise.resolve({ allowed: true });
|
|
78
91
|
};
|
|
79
92
|
};
|
|
93
|
+
/** Every method of the store, for the copy a fleet's workers share (see `fleet/stores.ts`). */
|
|
94
|
+
var RATE_LIMIT_METHODS = { check: true };
|
|
95
|
+
/**
|
|
96
|
+
* In a worker of a fleet, the primary's {@link createMemoryRateLimit}, so an attacker spreading attempts over
|
|
97
|
+
* connections meets one counter rather than one per worker. Nothing anywhere else.
|
|
98
|
+
*/
|
|
99
|
+
var fleetRateLimit = () => {
|
|
100
|
+
const shared = fleetStore("auth.rateLimit", RATE_LIMIT_METHODS);
|
|
101
|
+
if (!shared) return;
|
|
102
|
+
return (attempt) => shared.check({
|
|
103
|
+
action: attempt.action,
|
|
104
|
+
key: attempt.key,
|
|
105
|
+
...attempt.succeeded ? { succeeded: true } : {}
|
|
106
|
+
});
|
|
107
|
+
};
|
|
80
108
|
//#endregion
|
|
81
|
-
export { createMemoryRateLimit };
|
|
109
|
+
export { RATE_LIMIT_METHODS, createMemoryRateLimit, fleetRateLimit };
|
package/dist/core/auth/totp.d.ts
CHANGED
|
@@ -3,15 +3,23 @@ export declare const generateTotpSecret: () => string;
|
|
|
3
3
|
/** The code an app would be showing right now. Exported so a test does not have to reimplement the algorithm. */
|
|
4
4
|
export declare const totpCode: (secret: string, at?: number) => string;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Which time step this code belongs to, or `undefined` when it belongs to none near now.
|
|
7
7
|
*
|
|
8
8
|
* `window` steps either side are accepted, one by default — thirty seconds of tolerance for a clock that drifts
|
|
9
9
|
* and for a person who starts typing at second twenty-nine. Wider is friendlier and weaker; this is the usual
|
|
10
10
|
* trade and the same one Google Authenticator's own guidance makes.
|
|
11
11
|
*
|
|
12
|
+
* The step is the answer, not a yes: a code stays valid for its whole window, so a verifier that remembers nothing
|
|
13
|
+
* accepts the same code twice. RFC 6238 §5.2 forbids that — see `MfaRecord.lastUsedStep`.
|
|
14
|
+
*
|
|
12
15
|
* Compared in constant time: a comparison that returns on the first differing digit leaks how much of a guess was
|
|
13
16
|
* right, which turns a million-guess space into ten guesses per position.
|
|
14
17
|
*/
|
|
18
|
+
export declare const totpStep: (secret: string, code: string, options?: {
|
|
19
|
+
window?: number;
|
|
20
|
+
at?: number;
|
|
21
|
+
}) => number | undefined;
|
|
22
|
+
/** Is this the code? `totpStep`, for a caller that does not need to know which step it was. */
|
|
15
23
|
export declare const verifyTotp: (secret: string, code: string, options?: {
|
|
16
24
|
window?: number;
|
|
17
25
|
at?: number;
|