@camstack/server 1.2.594 → 1.2.596
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/dist/addon-root-inventory.js +50 -11
- package/dist/agent/addon-scan.js +44 -6
- package/dist/agent/agent-addon-runtime.js +8 -3
- package/dist/agent/agent-addon-updates.js +52 -12
- package/dist/agent/agent-cap-dispatch-service.js +5 -3
- package/dist/agent/agent-config.js +42 -7
- package/dist/agent/agent-deploy-swap.js +41 -5
- package/dist/agent/agent-group-runner.js +47 -8
- package/dist/agent/agent-http-auth.js +22 -10
- package/dist/agent/agent-http.js +113 -64
- package/dist/agent/agent-hub-addon-update.js +16 -10
- package/dist/agent/agent-hub-registration.js +7 -4
- package/dist/agent/agent-infra-capabilities.js +9 -5
- package/dist/agent/agent-json-file.js +42 -5
- package/dist/agent/agent-mesh-gate.js +8 -5
- package/dist/agent/agent-node-certificates.js +28 -25
- package/dist/agent/agent-pairing-local.js +47 -10
- package/dist/agent/agent-pairing-routes.js +20 -16
- package/dist/agent/agent-pairing-store.js +58 -18
- package/dist/agent/agent-reconnect.js +4 -1
- package/dist/agent/agent-service.js +71 -35
- package/dist/agent/agent-sniffing-listener.js +48 -11
- package/dist/agent/agent-undeploy.js +41 -5
- package/dist/agent/agent-update-routes.js +19 -14
- package/dist/agent/agent-update-service.js +80 -38
- package/dist/agent/apply-model-distribution.js +4 -1
- package/dist/agent/apply-stage-dev-uploads.js +9 -6
- package/dist/agent/derive-hub-url.js +8 -3
- package/dist/agent/fetch-bundle-from-hub.js +9 -6
- package/dist/agent/hubs-found.js +43 -6
- package/dist/agent/infra-boot-guard.js +4 -1
- package/dist/agent/infra-boot-plan.js +6 -2
- package/dist/agent/main.js +183 -148
- package/dist/agent/register-agent-cap-dispatch.js +6 -3
- package/dist/agent/serial-queue.js +7 -2
- package/dist/agent-status-page.js +4 -1
- package/dist/api/addon-upload.js +61 -22
- package/dist/api/agent-addon-delivery.js +11 -6
- package/dist/api/auth-whoami.js +4 -1
- package/dist/api/backup-download-pump.js +9 -5
- package/dist/api/backup-download.js +10 -7
- package/dist/api/core/addon-settings.router.js +19 -16
- package/dist/api/core/agent-pairing.router.js +79 -76
- package/dist/api/core/agents.router.js +24 -21
- package/dist/api/core/auth-admin-tokens.procedures.js +52 -48
- package/dist/api/core/auth-device-cert.procedures.js +67 -62
- package/dist/api/core/auth-limits.js +25 -16
- package/dist/api/core/auth-reauth.procedures.js +37 -32
- package/dist/api/core/auth-revoke-credentials.procedures.js +26 -22
- package/dist/api/core/auth-sessions.procedures.js +56 -52
- package/dist/api/core/auth.router.js +304 -300
- package/dist/api/core/cap-providers.js +121 -74
- package/dist/api/core/capabilities.router.js +37 -34
- package/dist/api/core/capability-preference-writes.js +17 -10
- package/dist/api/core/certificates.router.js +109 -106
- package/dist/api/core/cluster-nodes.router.js +18 -15
- package/dist/api/core/collection-preference.js +6 -2
- package/dist/api/core/event-bus-proxy.router.js +37 -34
- package/dist/api/core/failure-contributions.js +8 -4
- package/dist/api/core/hwaccel.router.js +20 -17
- package/dist/api/core/integration-secrets.js +12 -8
- package/dist/api/core/lifecycle-job-runner.js +49 -13
- package/dist/api/core/live-events.router.js +32 -29
- package/dist/api/core/load-contributions.js +8 -4
- package/dist/api/core/logging-settings.js +43 -30
- package/dist/api/core/logs.router.js +43 -40
- package/dist/api/core/notifications.router.js +18 -15
- package/dist/api/core/public-event-filter.js +8 -4
- package/dist/api/core/repl.router.js +21 -18
- package/dist/api/core/request-census-settings.js +16 -11
- package/dist/api/core/server-management.provider.js +4 -1
- package/dist/api/core/server-name.js +17 -9
- package/dist/api/core/site-location.js +24 -16
- package/dist/api/core/stream-probe.router.js +24 -21
- package/dist/api/core/system-events.router.js +26 -23
- package/dist/api/cors/cors-policy.js +29 -20
- package/dist/api/cors/register-cors-policy.js +14 -9
- package/dist/api/deploy-stage-registry.js +10 -6
- package/dist/api/dev-uploads-bundle.js +50 -12
- package/dist/api/health/adaptive-probe.routes.js +13 -9
- package/dist/api/health/health.routes.js +13 -8
- package/dist/api/hub-cluster-url.js +9 -3
- package/dist/api/model-distributor.js +50 -12
- package/dist/api/oauth2/consent-page.js +13 -10
- package/dist/api/oauth2/oauth2-routes.js +25 -17
- package/dist/api/oauth2/private-host.js +4 -1
- package/dist/api/server-upload-propagate.js +51 -15
- package/dist/api/server-upload.js +50 -13
- package/dist/api/sse-no-compression.js +4 -1
- package/dist/api/static/frame-policy.js +9 -4
- package/dist/api/static/precompressed-asset.js +57 -15
- package/dist/api/static/spa-mount.js +47 -8
- package/dist/api/static/spa-static.js +12 -5
- package/dist/api/tarball-manifest.js +44 -7
- package/dist/api/trpc/cap-mount-helpers.js +12 -7
- package/dist/api/trpc/cap-route-error-formatter.js +13 -10
- package/dist/api/trpc/cap-router-runtime.js +51 -40
- package/dist/api/trpc/client-ip.js +20 -9
- package/dist/api/trpc/core-cap-bridge.js +42 -32
- package/dist/api/trpc/device-collection-dispatch.js +27 -23
- package/dist/api/trpc/device-config-secret-redaction.js +40 -24
- package/dist/api/trpc/generated-cap-mounts.js +225 -219
- package/dist/api/trpc/generated-cap-routers.js +5167 -4852
- package/dist/api/trpc/http-request-census-hook.js +11 -8
- package/dist/api/trpc/http-request-census.js +41 -34
- package/dist/api/trpc/http-request-census.singleton.js +8 -4
- package/dist/api/trpc/localized-schema-paths.js +4 -1
- package/dist/api/trpc/principal-visibility.js +22 -14
- package/dist/api/trpc/remote-addon-update-grant.js +4 -1
- package/dist/api/trpc/scope-access.js +15 -10
- package/dist/api/trpc/server-provided-caps.js +7 -3
- package/dist/api/trpc/share-view-access.js +14 -6
- package/dist/api/trpc/trpc-error-device-tags.js +6 -3
- package/dist/api/trpc/trpc-error-principal.js +6 -2
- package/dist/api/trpc/trpc.context.js +25 -20
- package/dist/api/trpc/trpc.middleware.js +46 -38
- package/dist/api/trpc/trpc.router.js +99 -58
- package/dist/api/trpc/user-error.js +8 -5
- package/dist/api/trpc/ws-request-census.js +24 -17
- package/dist/api/trpc/ws-slow-consumer-guard.js +16 -11
- package/dist/api/upload-auth.js +9 -5
- package/dist/auth/addon-route-jwt-gate.js +7 -4
- package/dist/auth/addon-route-share-gate.js +12 -7
- package/dist/auth/app-confirm-page.js +22 -17
- package/dist/auth/app-confirm-routes.js +31 -26
- package/dist/auth/app-confirmations.js +44 -7
- package/dist/auth/app-page-shell.js +30 -17
- package/dist/auth/app-page-strings.js +17 -11
- package/dist/auth/app-passkey-page.js +21 -16
- package/dist/auth/app-passkey-routes.js +41 -37
- package/dist/auth/app-refused-route.js +18 -13
- package/dist/auth/auth-network-policy.js +15 -11
- package/dist/auth/auth-provider-gate.js +9 -4
- package/dist/auth/auth-rate-limit.js +7 -3
- package/dist/auth/client-address.js +9 -6
- package/dist/auth/device-enroll-proofs.js +43 -6
- package/dist/auth/external-link-intents.js +7 -3
- package/dist/auth/hub-sessions.js +12 -6
- package/dist/auth/native-link-completions.js +11 -7
- package/dist/auth/native-return.js +13 -9
- package/dist/auth/passkey-provider.js +32 -26
- package/dist/auth/second-factors.js +10 -6
- package/dist/auth/session-connections.js +12 -6
- package/dist/auth/session-cookie.js +23 -12
- package/dist/auth/session-lifetimes.js +12 -8
- package/dist/auth/session-mint.js +11 -4
- package/dist/auth/sso-finish-app.js +16 -13
- package/dist/auth/sso-finish-deps.js +27 -21
- package/dist/auth/sso-finish.js +38 -28
- package/dist/auth/sso-routes.js +25 -22
- package/dist/auth/used-bridge-ids.js +5 -1
- package/dist/boot/boot-config.js +55 -16
- package/dist/boot/init-sequence.js +4 -1
- package/dist/boot/integration-id-backfill.js +8 -3
- package/dist/boot/platform-keychain-key.js +42 -5
- package/dist/boot/post-boot.service.js +18 -14
- package/dist/boot/reconcile-lifecycle-jobs.js +6 -3
- package/dist/boot/retired-public-url-env.js +8 -4
- package/dist/bootstrap-packages.js +4 -1
- package/dist/closure-node-modules.js +38 -2
- package/dist/core/addon/addon-call-gateway.js +5 -1
- package/dist/core/addon/addon-package.service.js +123 -83
- package/dist/core/addon/addon-process-view.js +6 -2
- package/dist/core/addon/addon-realign-plan.js +4 -1
- package/dist/core/addon/addon-registry.service.js +187 -146
- package/dist/core/addon/addon-row-manifest.js +4 -1
- package/dist/core/addon/addon-search.service.js +7 -3
- package/dist/core/addon/addon-settings-provider.js +3 -1
- package/dist/core/addon/addon-uninstall.js +11 -4
- package/dist/core/addon/addon.tokens.js +5 -2
- package/dist/core/addon/device-meta-mirror.js +13 -8
- package/dist/core/addon/forked-custom-actions.js +11 -6
- package/dist/core/addon/integration-visibility.js +6 -3
- package/dist/core/addon/materialize-route-path.js +4 -1
- package/dist/core/addon/package-dir-utils.js +45 -8
- package/dist/core/addon/prune-misplaced-addons.js +43 -5
- package/dist/core/addon/runner-convergence.js +4 -1
- package/dist/core/addon/runner-serialization.js +4 -1
- package/dist/core/addon/runner-spawn-fanout.js +8 -3
- package/dist/core/addon/settings-door-wiring.js +8 -5
- package/dist/core/addon/single-flight-refresh.js +4 -1
- package/dist/core/addon-bridge/addon-bridge.service.js +44 -7
- package/dist/core/addon-pages/addon-pages.service.js +40 -3
- package/dist/core/addon-widgets/addon-widgets.service.js +42 -4
- package/dist/core/agent/agent-addon-backfill.js +17 -9
- package/dist/core/agent/agent-delivery-ledger.js +7 -3
- package/dist/core/agent/agent-liveness-event.js +6 -2
- package/dist/core/agent/agent-pairing-http.js +45 -41
- package/dist/core/agent/agent-registry.service.js +89 -50
- package/dist/core/agent/cluster-node-history-mirror.js +7 -3
- package/dist/core/agent/cluster-node-history-store.js +35 -31
- package/dist/core/agent/hub-agent-pairing.js +5 -1
- package/dist/core/agent/node-certificates-client.js +6 -3
- package/dist/core/agent/node-directory.js +4 -1
- package/dist/core/app-links/well-known-app-links.js +6 -2
- package/dist/core/auth/auth.service.js +8 -3
- package/dist/core/auth/handoff-code.service.js +49 -11
- package/dist/core/auth/record-kinds.js +19 -16
- package/dist/core/auth/share-token.service.js +79 -42
- package/dist/core/capability/capability.service.js +5 -1
- package/dist/core/config/config.schema.js +6 -1
- package/dist/core/config/config.service.js +6 -2
- package/dist/core/events/event-bus.service.js +7 -3
- package/dist/core/feature/feature.service.js +6 -2
- package/dist/core/lifecycle/lifecycle-runner.singleton.js +10 -5
- package/dist/core/lifecycle/lifecycle-state-machine.js +5 -1
- package/dist/core/logging/log-ring-buffer.js +5 -1
- package/dist/core/logging/logging.service.js +9 -5
- package/dist/core/logging/scoped-logger.js +5 -1
- package/dist/core/moleculer/agent-readiness-pull.js +4 -1
- package/dist/core/moleculer/agent-registration-request.js +7 -3
- package/dist/core/moleculer/cap-call-fn.js +7 -4
- package/dist/core/moleculer/cap-route-authority.js +8 -3
- package/dist/core/moleculer/hub-mesh-guard.js +9 -5
- package/dist/core/moleculer/moleculer.service.js +86 -79
- package/dist/core/network/network-quality.service.js +6 -2
- package/dist/core/notification/notification-wrapper.service.js +7 -3
- package/dist/core/notification/toast-wrapper.service.js +7 -3
- package/dist/core/provider/provider.tokens.js +4 -1
- package/dist/core/repl/repl-engine.service.js +8 -4
- package/dist/core/runner-health/runner-boot-check.js +15 -7
- package/dist/core/runner-health/update-preflight.js +14 -9
- package/dist/core/server-update/server-update.service.js +49 -12
- package/dist/core/server-update/system-apply-closure-patches.js +42 -6
- package/dist/core/server-update/system-ensure-prebuilds.js +6 -3
- package/dist/core/server-update/system-exec-npm.js +5 -2
- package/dist/core/storage/fs-storage-backend.js +5 -1
- package/dist/core/storage/storage-location-manager.js +5 -1
- package/dist/core/storage/storage.service.js +6 -2
- package/dist/core/streaming/ffmpeg-stream-info.js +8 -3
- package/dist/core/streaming/stream-probe.service.js +16 -12
- package/dist/core/streaming/ttl-cache.js +5 -1
- package/dist/core/topology/topology-emitter.service.js +23 -19
- package/dist/core/update-availability-emitter.js +7 -3
- package/dist/core/update-availability-store.js +45 -8
- package/dist/core/updates/agent-installed-packages.js +9 -5
- package/dist/core/updates/restart-marker.js +8 -3
- package/dist/core/updates/update-check-scheduler.js +18 -13
- package/dist/core/updates/wrapper-update-checker.js +7 -3
- package/dist/first-boot-addon-plan.js +6 -2
- package/dist/framework-nodepath.js +38 -2
- package/dist/i18n/request-lang.js +11 -7
- package/dist/launcher.js +65 -30
- package/dist/lifecycle-journal-path.js +38 -2
- package/dist/main.js +324 -286
- package/dist/manual-boot.js +156 -117
- package/dist/node-role.js +4 -1
- package/dist/package-inventory.js +48 -7
- package/dist/pending-restore.js +43 -7
- package/dist/single-copy-cleanup-runner.js +45 -9
- package/dist/single-copy-cleanup.js +15 -7
- package/dist/sqlite-restore.js +48 -9
- package/dist/starter.js +39 -4
- package/dist/ui-serving/lazy-ui-serving.js +39 -3
- package/package.json +7 -7
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.appCancelLink = exports.appHiddenField = exports.appSubmitButton = exports.appErrorLine = exports.appButton = exports.appHint = exports.appParagraph = exports.APP_PAGE_STYLE = exports.APP_PAGE_LOGOS = void 0;
|
|
4
|
+
exports.appPageCsp = appPageCsp;
|
|
5
|
+
exports.escapeHtml = escapeHtml;
|
|
6
|
+
exports.renderAppPage = renderAppPage;
|
|
1
7
|
/**
|
|
2
8
|
* The one shell of every standalone page the hub renders for a native app
|
|
3
9
|
* sign-in (D858): the passkey page, the confirmation page, the refusal pages.
|
|
@@ -17,13 +23,13 @@
|
|
|
17
23
|
* of the very string that is served, computed at module load - there is no
|
|
18
24
|
* hand-pasted hash that can fall out of step with the CSS.
|
|
19
25
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
const node_crypto_1 = require("node:crypto");
|
|
27
|
+
const tokens_1 = require("@camstack/ui-library/theme/tokens");
|
|
22
28
|
/** The brand mark and wordmark, one per colour scheme (the files under `addon-admin-ui/public/brand`). */
|
|
23
29
|
const LOGO_DARK = '<svg class="logo logo-dark" role="img" aria-label="CamStack" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 48" fill="none"><g transform="translate(4, 2) scale(0.68)"><rect x="8" y="28" width="48" height="28" rx="6" fill="#292524"/><rect x="12" y="16" width="40" height="28" rx="6" fill="#44403c"/><rect x="16" y="4" width="32" height="28" rx="6" fill="#f59e42"/><circle cx="32" cy="18" r="8" fill="#0c0a09" opacity="0.3"/><circle cx="32" cy="18" r="5" fill="#0c0a09" opacity="0.2"/><circle cx="32" cy="18" r="2.5" fill="#fafaf9" opacity="0.8"/></g><text x="52" y="31" font-family="system-ui, -apple-system, sans-serif" font-size="26" font-weight="700" letter-spacing="-0.5" fill="#fafaf9">Cam<tspan fill="#f59e42">Stack</tspan></text></svg>';
|
|
24
30
|
const LOGO_LIGHT = '<svg class="logo logo-light" role="img" aria-label="CamStack" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 48" fill="none"><g transform="translate(4, 2) scale(0.68)"><rect x="8" y="28" width="48" height="28" rx="6" fill="#d6d3d1"/><rect x="12" y="16" width="40" height="28" rx="6" fill="#a8a29e"/><rect x="16" y="4" width="32" height="28" rx="6" fill="#e67e22"/><circle cx="32" cy="18" r="8" fill="#fafaf9" opacity="0.3"/><circle cx="32" cy="18" r="5" fill="#fafaf9" opacity="0.2"/><circle cx="32" cy="18" r="2.5" fill="#1c1917" opacity="0.7"/></g><text x="52" y="31" font-family="system-ui, -apple-system, sans-serif" font-size="26" font-weight="700" letter-spacing="-0.5" fill="#1c1917">Cam<tspan fill="#e67e22">Stack</tspan></text></svg>';
|
|
25
31
|
/** The logo markup, exposed so a spec can hold it against the brand files. */
|
|
26
|
-
|
|
32
|
+
exports.APP_PAGE_LOGOS = {
|
|
27
33
|
dark: LOGO_DARK,
|
|
28
34
|
light: LOGO_LIGHT,
|
|
29
35
|
};
|
|
@@ -62,19 +68,19 @@ const COMPONENT_CSS = [
|
|
|
62
68
|
`.actions{display:flex;flex-direction:column;gap:var(--spacing-md)}`,
|
|
63
69
|
].join('');
|
|
64
70
|
/** The one stylesheet, as served: the generated tokens followed by the component rules. */
|
|
65
|
-
|
|
66
|
-
const sha256Source = (text) => `'sha256-${createHash('sha256').update(text).digest('base64')}'`;
|
|
71
|
+
exports.APP_PAGE_STYLE = `${(0, tokens_1.themeToRootCss)(tokens_1.defaultTheme)}${COMPONENT_CSS}`;
|
|
72
|
+
const sha256Source = (text) => `'sha256-${(0, node_crypto_1.createHash)('sha256').update(text).digest('base64')}'`;
|
|
67
73
|
/**
|
|
68
74
|
* The CSP for a page rendered by {@link renderAppPage}: the inline style and
|
|
69
75
|
* (when there is one) the inline script by hash, same-origin requests only, no
|
|
70
76
|
* framing, no base rewrite. The hashes are computed from the strings the page
|
|
71
77
|
* is built from, so they cannot disagree with it.
|
|
72
78
|
*/
|
|
73
|
-
|
|
79
|
+
function appPageCsp(options = {}) {
|
|
74
80
|
return [
|
|
75
81
|
"default-src 'none'",
|
|
76
82
|
...(options.script === undefined ? [] : [`script-src ${sha256Source(options.script)}`]),
|
|
77
|
-
`style-src ${sha256Source(APP_PAGE_STYLE)}`,
|
|
83
|
+
`style-src ${sha256Source(exports.APP_PAGE_STYLE)}`,
|
|
78
84
|
...(options.script === undefined ? [] : ["connect-src 'self'"]),
|
|
79
85
|
"base-uri 'none'",
|
|
80
86
|
`form-action ${options.formToSelf === true ? "'self'" : "'none'"}`,
|
|
@@ -82,29 +88,36 @@ export function appPageCsp(options = {}) {
|
|
|
82
88
|
].join('; ');
|
|
83
89
|
}
|
|
84
90
|
/** Escapes text for an HTML text node or a double-quoted attribute. */
|
|
85
|
-
|
|
91
|
+
function escapeHtml(text) {
|
|
86
92
|
return text.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
87
93
|
}
|
|
88
94
|
/** A full page: doctype, viewport (with safe-area fit), tokens, logo, a card holding `body`. */
|
|
89
|
-
|
|
95
|
+
function renderAppPage(input) {
|
|
90
96
|
const referrer = input.noReferrer === true ? '<meta name="referrer" content="no-referrer">' : '';
|
|
91
97
|
const script = input.script === undefined ? '' : `<script>${input.script}</script>`;
|
|
92
98
|
const strings = Object.entries(input.scriptStrings ?? {})
|
|
93
99
|
.map(([key, value]) => ` data-s-${key}="${escapeHtml(value)}"`)
|
|
94
100
|
.join('');
|
|
95
|
-
return `<!doctype html><html lang="${input.lang}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><meta name="color-scheme" content="light dark">${referrer}<title>${escapeHtml(input.title)}</title><style>${APP_PAGE_STYLE}</style></head><body${strings}><main><div class="brand">${LOGO_DARK}${LOGO_LIGHT}</div><div class="card"><h1>${escapeHtml(input.heading)}</h1>${input.body}</div></main>${script}</body></html>`;
|
|
101
|
+
return `<!doctype html><html lang="${input.lang}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><meta name="color-scheme" content="light dark">${referrer}<title>${escapeHtml(input.title)}</title><style>${exports.APP_PAGE_STYLE}</style></head><body${strings}><main><div class="brand">${LOGO_DARK}${LOGO_LIGHT}</div><div class="card"><h1>${escapeHtml(input.heading)}</h1>${input.body}</div></main>${script}</body></html>`;
|
|
96
102
|
}
|
|
97
103
|
/** A paragraph of the card (`text` is escaped). */
|
|
98
|
-
|
|
104
|
+
const appParagraph = (text) => `<p>${escapeHtml(text)}</p>`;
|
|
105
|
+
exports.appParagraph = appParagraph;
|
|
99
106
|
/** A smaller, muted paragraph (`text` is escaped). */
|
|
100
|
-
|
|
107
|
+
const appHint = (text) => `<p class="hint">${escapeHtml(text)}</p>`;
|
|
108
|
+
exports.appHint = appHint;
|
|
101
109
|
/** The primary button; `attributes` is trusted markup (already escaped), `label` is escaped. */
|
|
102
|
-
|
|
110
|
+
const appButton = (label, attributes) => `<button class="btn" type="button" ${attributes}>${escapeHtml(label)}</button>`;
|
|
111
|
+
exports.appButton = appButton;
|
|
103
112
|
/** The error line a script fills in; empty it renders nothing. */
|
|
104
|
-
|
|
113
|
+
const appErrorLine = (id) => `<p class="error" id="${id}" role="alert"></p>`;
|
|
114
|
+
exports.appErrorLine = appErrorLine;
|
|
105
115
|
/** A submit button of a form (`name`/`value` are escaped); `secondary` is the outlined, non-default choice. */
|
|
106
|
-
|
|
116
|
+
const appSubmitButton = (label, name, value, secondary = false) => `<button class="${secondary ? 'btn secondary' : 'btn'}" type="submit" name="${escapeHtml(name)}" value="${escapeHtml(value)}">${escapeHtml(label)}</button>`;
|
|
117
|
+
exports.appSubmitButton = appSubmitButton;
|
|
107
118
|
/** A hidden form field (`name`/`value` are escaped). */
|
|
108
|
-
|
|
119
|
+
const appHiddenField = (name, value) => `<input type="hidden" name="${escapeHtml(name)}" value="${escapeHtml(value)}">`;
|
|
120
|
+
exports.appHiddenField = appHiddenField;
|
|
109
121
|
/** The quiet link under the actions (Cancel); `href` is escaped here. */
|
|
110
|
-
|
|
122
|
+
const appCancelLink = (href, label) => `<a class="link" href="${escapeHtml(href)}">${escapeHtml(label)}</a>`;
|
|
123
|
+
exports.appCancelLink = appCancelLink;
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APP_PAGE_STRINGS = exports.APP_PAGE_DEFAULT_LANG = void 0;
|
|
4
|
+
exports.appStrings = appStrings;
|
|
5
|
+
exports.langFromAcceptLanguage = langFromAcceptLanguage;
|
|
6
|
+
exports.resolveAppPageLang = resolveAppPageLang;
|
|
1
7
|
/**
|
|
2
8
|
* Every word the hub's app landing pages show (D858), in both languages, and
|
|
3
9
|
* the one rule that picks the language.
|
|
@@ -12,8 +18,8 @@
|
|
|
12
18
|
* (allow-listed) wins; else the first supported tag of `Accept-Language`
|
|
13
19
|
* (`it*` -> `it`); else English. Neither input is ever reflected into a page.
|
|
14
20
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
const types_1 = require("@camstack/types");
|
|
22
|
+
exports.APP_PAGE_DEFAULT_LANG = 'en';
|
|
17
23
|
const EN = {
|
|
18
24
|
confirm: {
|
|
19
25
|
title: 'Sign in the CamStack app',
|
|
@@ -42,7 +48,7 @@ const EN = {
|
|
|
42
48
|
title: 'Sign-in request refused',
|
|
43
49
|
hint: 'Close this window and try again from the app.',
|
|
44
50
|
generic: 'The sign-in request was refused.',
|
|
45
|
-
reasons: APP_START_REFUSAL_MESSAGES,
|
|
51
|
+
reasons: types_1.APP_START_REFUSAL_MESSAGES,
|
|
46
52
|
},
|
|
47
53
|
consent: {
|
|
48
54
|
titlePrefix: 'CamStack · Authorize ',
|
|
@@ -115,12 +121,12 @@ const IT = {
|
|
|
115
121
|
signInFailed: 'Accesso non riuscito. Riprova.',
|
|
116
122
|
},
|
|
117
123
|
};
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return APP_PAGE_STRINGS[lang];
|
|
124
|
+
exports.APP_PAGE_STRINGS = { en: EN, it: IT };
|
|
125
|
+
function appStrings(lang) {
|
|
126
|
+
return exports.APP_PAGE_STRINGS[lang];
|
|
121
127
|
}
|
|
122
128
|
/** The first supported language of an `Accept-Language` value (by q, then order), or `undefined`. */
|
|
123
|
-
|
|
129
|
+
function langFromAcceptLanguage(header) {
|
|
124
130
|
const value = typeof header === 'string' ? header : header?.join(',');
|
|
125
131
|
if (value === undefined || value.length > 512)
|
|
126
132
|
return undefined;
|
|
@@ -137,15 +143,15 @@ export function langFromAcceptLanguage(header) {
|
|
|
137
143
|
.filter((t) => Number.isFinite(t.weight) && t.weight > 0)
|
|
138
144
|
.sort((a, b) => b.weight - a.weight || a.index - b.index);
|
|
139
145
|
for (const { primary } of ranked) {
|
|
140
|
-
const lang = parseAppPageLang(primary);
|
|
146
|
+
const lang = (0, types_1.parseAppPageLang)(primary);
|
|
141
147
|
if (lang !== undefined)
|
|
142
148
|
return lang;
|
|
143
149
|
}
|
|
144
150
|
return undefined;
|
|
145
151
|
}
|
|
146
152
|
/** The page language: the app's `lang` (allow-listed), else `Accept-Language`, else English. */
|
|
147
|
-
|
|
148
|
-
return (parseAppPageLang(input.lang) ??
|
|
153
|
+
function resolveAppPageLang(input) {
|
|
154
|
+
return ((0, types_1.parseAppPageLang)(input.lang) ??
|
|
149
155
|
langFromAcceptLanguage(input.acceptLanguage) ??
|
|
150
|
-
APP_PAGE_DEFAULT_LANG);
|
|
156
|
+
exports.APP_PAGE_DEFAULT_LANG);
|
|
151
157
|
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APP_PASSKEY_REFUSAL_CSP = exports.APP_PASSKEY_CSP = void 0;
|
|
4
|
+
exports.appPasskeyPage = appPasskeyPage;
|
|
5
|
+
exports.appPasskeyRefusalPage = appPasskeyRefusalPage;
|
|
1
6
|
/**
|
|
2
7
|
* The standalone passkey page for a native app sign-in (D858).
|
|
3
8
|
*
|
|
@@ -14,8 +19,8 @@
|
|
|
14
19
|
* specific is ever placed in the script; the only server-rendered value is the
|
|
15
20
|
* (allow-listed, HTML-escaped) cancel link.
|
|
16
21
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
const app_page_shell_js_1 = require("./app-page-shell.js");
|
|
23
|
+
const app_page_strings_js_1 = require("./app-page-strings.js");
|
|
19
24
|
const PAGE_SCRIPT = `(function () {
|
|
20
25
|
var q = new URLSearchParams(location.search);
|
|
21
26
|
var base = location.pathname.replace(/\\/+$/, '');
|
|
@@ -112,11 +117,11 @@ const PAGE_SCRIPT = `(function () {
|
|
|
112
117
|
* The page's CSP: the shell's style and the one script by hash, same-origin
|
|
113
118
|
* requests only, no framing, no base rewrite, no form posts, no plugins.
|
|
114
119
|
*/
|
|
115
|
-
|
|
120
|
+
exports.APP_PASSKEY_CSP = (0, app_page_shell_js_1.appPageCsp)({ script: PAGE_SCRIPT });
|
|
116
121
|
/** The page; `cancelHref` is the app callback carrying `error=cancelled`. */
|
|
117
|
-
|
|
118
|
-
const t = appStrings(lang);
|
|
119
|
-
return renderAppPage({
|
|
122
|
+
function appPasskeyPage(cancelHref, lang) {
|
|
123
|
+
const t = (0, app_page_strings_js_1.appStrings)(lang);
|
|
124
|
+
return (0, app_page_shell_js_1.renderAppPage)({
|
|
120
125
|
lang,
|
|
121
126
|
title: t.passkey.title,
|
|
122
127
|
heading: t.passkey.heading,
|
|
@@ -129,23 +134,23 @@ export function appPasskeyPage(cancelHref, lang) {
|
|
|
129
134
|
'sign-in-failed': t.script.signInFailed,
|
|
130
135
|
},
|
|
131
136
|
body: [
|
|
132
|
-
appParagraph(t.passkey.body),
|
|
133
|
-
appButton(t.passkey.button, 'id="go"'),
|
|
134
|
-
appErrorLine('msg'),
|
|
135
|
-
appCancelLink(cancelHref, t.passkey.cancel),
|
|
136
|
-
`<noscript><p class="error">${escapeHtml(t.passkey.noscript)}</p></noscript>`,
|
|
137
|
+
(0, app_page_shell_js_1.appParagraph)(t.passkey.body),
|
|
138
|
+
(0, app_page_shell_js_1.appButton)(t.passkey.button, 'id="go"'),
|
|
139
|
+
(0, app_page_shell_js_1.appErrorLine)('msg'),
|
|
140
|
+
(0, app_page_shell_js_1.appCancelLink)(cancelHref, t.passkey.cancel),
|
|
141
|
+
`<noscript><p class="error">${(0, app_page_shell_js_1.escapeHtml)(t.passkey.noscript)}</p></noscript>`,
|
|
137
142
|
].join(''),
|
|
138
143
|
});
|
|
139
144
|
}
|
|
140
145
|
/** The page for a request the hub refuses to serve: a sentence, no script, no assets. */
|
|
141
|
-
|
|
142
|
-
const t = appStrings(lang).refused;
|
|
143
|
-
return renderAppPage({
|
|
146
|
+
function appPasskeyRefusalPage(message, lang) {
|
|
147
|
+
const t = (0, app_page_strings_js_1.appStrings)(lang).refused;
|
|
148
|
+
return (0, app_page_shell_js_1.renderAppPage)({
|
|
144
149
|
lang,
|
|
145
150
|
title: t.title,
|
|
146
151
|
heading: t.title,
|
|
147
|
-
body: appParagraph(message) + appHint(t.hint),
|
|
152
|
+
body: (0, app_page_shell_js_1.appParagraph)(message) + (0, app_page_shell_js_1.appHint)(t.hint),
|
|
148
153
|
});
|
|
149
154
|
}
|
|
150
155
|
/** CSP of the refusal page: the shell's style only, nothing runs. */
|
|
151
|
-
|
|
156
|
+
exports.APP_PASSKEY_REFUSAL_CSP = (0, app_page_shell_js_1.appPageCsp)();
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APP_PASSKEY_PATH = void 0;
|
|
4
|
+
exports.registerAppPasskeyRoutes = registerAppPasskeyRoutes;
|
|
1
5
|
/**
|
|
2
6
|
* The passkey app sign-in routes (D858):
|
|
3
7
|
*
|
|
@@ -14,30 +18,30 @@
|
|
|
14
18
|
* `{ error }` for a request that is not an app flow at all), so the page just
|
|
15
19
|
* navigates. The app's parameters are never logged.
|
|
16
20
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const FinishBodySchema = z.object({
|
|
26
|
-
response: z.record(z.string(), z.unknown()),
|
|
27
|
-
code_challenge: z.string().optional(),
|
|
28
|
-
code_challenge_method: z.string().optional(),
|
|
29
|
-
state: z.string().optional(),
|
|
30
|
-
purpose: z.string().optional(),
|
|
31
|
-
redirect: z.string().optional(),
|
|
32
|
-
lang: z.string().optional(),
|
|
21
|
+
const types_1 = require("@camstack/types");
|
|
22
|
+
const zod_1 = require("zod");
|
|
23
|
+
const app_confirm_routes_js_1 = require("./app-confirm-routes.js");
|
|
24
|
+
const app_passkey_page_js_1 = require("./app-passkey-page.js");
|
|
25
|
+
const app_page_strings_js_1 = require("./app-page-strings.js");
|
|
26
|
+
const auth_rate_limit_js_1 = require("./auth-rate-limit.js");
|
|
27
|
+
const session_mint_js_1 = require("./session-mint.js");
|
|
28
|
+
exports.APP_PASSKEY_PATH = '/auth/app/passkey';
|
|
29
|
+
const FinishBodySchema = zod_1.z.object({
|
|
30
|
+
response: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
|
|
31
|
+
code_challenge: zod_1.z.string().optional(),
|
|
32
|
+
code_challenge_method: zod_1.z.string().optional(),
|
|
33
|
+
state: zod_1.z.string().optional(),
|
|
34
|
+
purpose: zod_1.z.string().optional(),
|
|
35
|
+
redirect: zod_1.z.string().optional(),
|
|
36
|
+
lang: zod_1.z.string().optional(),
|
|
33
37
|
});
|
|
34
38
|
function noStore(reply) {
|
|
35
39
|
reply.header('Cache-Control', 'no-store');
|
|
36
40
|
reply.header('Referrer-Policy', 'no-referrer');
|
|
37
41
|
}
|
|
38
|
-
|
|
42
|
+
function registerAppPasskeyRoutes(fastify, deps) {
|
|
39
43
|
const limited = (bucket, request, reply) => {
|
|
40
|
-
const client = clientKeyFromRequest(request, deps.network);
|
|
44
|
+
const client = (0, auth_rate_limit_js_1.clientKeyFromRequest)(request, deps.network);
|
|
41
45
|
if (client === null)
|
|
42
46
|
return false;
|
|
43
47
|
const verdict = deps.limiter.check(`${bucket}:${client}`);
|
|
@@ -47,11 +51,11 @@ export function registerAppPasskeyRoutes(fastify, deps) {
|
|
|
47
51
|
void reply.status(429).send({ error: 'Too many attempts' });
|
|
48
52
|
return true;
|
|
49
53
|
};
|
|
50
|
-
fastify.get(APP_PASSKEY_PATH, async (request, reply) => {
|
|
54
|
+
fastify.get(exports.APP_PASSKEY_PATH, async (request, reply) => {
|
|
51
55
|
noStore(reply);
|
|
52
56
|
reply.type('text/html; charset=utf-8');
|
|
53
|
-
const parsed = parseAppFlowRequest(request.query);
|
|
54
|
-
const lang = resolveAppPageLang({
|
|
57
|
+
const parsed = (0, types_1.parseAppFlowRequest)(request.query);
|
|
58
|
+
const lang = (0, app_page_strings_js_1.resolveAppPageLang)({
|
|
55
59
|
lang: parsed.ok ? parsed.request.lang : request.query['lang'],
|
|
56
60
|
acceptLanguage: request.headers['accept-language'],
|
|
57
61
|
});
|
|
@@ -59,26 +63,26 @@ export function registerAppPasskeyRoutes(fastify, deps) {
|
|
|
59
63
|
deps.logger.warn('Passkey app sign-in refused at the page', {
|
|
60
64
|
meta: { reason: parsed.reason },
|
|
61
65
|
});
|
|
62
|
-
reply.header('Content-Security-Policy', APP_PASSKEY_REFUSAL_CSP);
|
|
66
|
+
reply.header('Content-Security-Policy', app_passkey_page_js_1.APP_PASSKEY_REFUSAL_CSP);
|
|
63
67
|
return reply
|
|
64
68
|
.status(400)
|
|
65
|
-
.send(appPasskeyRefusalPage(appStrings(lang).refused.reasons[parsed.reason], lang));
|
|
69
|
+
.send((0, app_passkey_page_js_1.appPasskeyRefusalPage)((0, app_page_strings_js_1.appStrings)(lang).refused.reasons[parsed.reason], lang));
|
|
66
70
|
}
|
|
67
|
-
reply.header('Content-Security-Policy', APP_PASSKEY_CSP);
|
|
68
|
-
const cancel = appFlowLocation(parsed.request.redirect, { error: 'cancelled' }, parsed.request.state);
|
|
69
|
-
return reply.send(appPasskeyPage(cancel, lang));
|
|
71
|
+
reply.header('Content-Security-Policy', app_passkey_page_js_1.APP_PASSKEY_CSP);
|
|
72
|
+
const cancel = (0, types_1.appFlowLocation)(parsed.request.redirect, { error: 'cancelled' }, parsed.request.state);
|
|
73
|
+
return reply.send((0, app_passkey_page_js_1.appPasskeyPage)(cancel, lang));
|
|
70
74
|
});
|
|
71
|
-
fastify.post(`${APP_PASSKEY_PATH}/begin`, async (request, reply) => {
|
|
75
|
+
fastify.post(`${exports.APP_PASSKEY_PATH}/begin`, async (request, reply) => {
|
|
72
76
|
noStore(reply);
|
|
73
77
|
if (limited('app-passkey-begin', request, reply))
|
|
74
78
|
return reply;
|
|
75
|
-
const body = z.record(z.string(), z.unknown()).safeParse(request.body);
|
|
79
|
+
const body = zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).safeParse(request.body);
|
|
76
80
|
const query = body.success
|
|
77
81
|
? Object.fromEntries(Object.entries(body.data).flatMap(([k, v]) => (typeof v === 'string' ? [[k, v]] : [])))
|
|
78
82
|
: {};
|
|
79
|
-
const parsed = parseAppFlowRequest(query);
|
|
83
|
+
const parsed = (0, types_1.parseAppFlowRequest)(query);
|
|
80
84
|
if (!parsed.ok)
|
|
81
|
-
return reply.status(400).send({ error: APP_FLOW_REFUSAL_MESSAGES[parsed.reason] });
|
|
85
|
+
return reply.status(400).send({ error: types_1.APP_FLOW_REFUSAL_MESSAGES[parsed.reason] });
|
|
82
86
|
const provider = deps.passkeys();
|
|
83
87
|
if (provider === null)
|
|
84
88
|
return reply.status(503).send({ error: 'Passkeys are not available.' });
|
|
@@ -92,14 +96,14 @@ export function registerAppPasskeyRoutes(fastify, deps) {
|
|
|
92
96
|
return reply.status(500).send({ error: 'Passkeys are not available.' });
|
|
93
97
|
}
|
|
94
98
|
});
|
|
95
|
-
fastify.post(`${APP_PASSKEY_PATH}/finish`, async (request, reply) => {
|
|
99
|
+
fastify.post(`${exports.APP_PASSKEY_PATH}/finish`, async (request, reply) => {
|
|
96
100
|
noStore(reply);
|
|
97
101
|
if (limited('app-passkey-finish', request, reply))
|
|
98
102
|
return reply;
|
|
99
103
|
const body = FinishBodySchema.safeParse(request.body);
|
|
100
104
|
if (!body.success)
|
|
101
105
|
return reply.status(400).send({ error: 'Malformed sign-in request.' });
|
|
102
|
-
const parsed = parseAppFlowRequest({
|
|
106
|
+
const parsed = (0, types_1.parseAppFlowRequest)({
|
|
103
107
|
code_challenge: body.data.code_challenge,
|
|
104
108
|
code_challenge_method: body.data.code_challenge_method,
|
|
105
109
|
state: body.data.state,
|
|
@@ -108,11 +112,11 @@ export function registerAppPasskeyRoutes(fastify, deps) {
|
|
|
108
112
|
lang: body.data.lang,
|
|
109
113
|
});
|
|
110
114
|
if (!parsed.ok)
|
|
111
|
-
return reply.status(400).send({ error: APP_FLOW_REFUSAL_MESSAGES[parsed.reason] });
|
|
115
|
+
return reply.status(400).send({ error: types_1.APP_FLOW_REFUSAL_MESSAGES[parsed.reason] });
|
|
112
116
|
const app = parsed.request;
|
|
113
117
|
const deny = (error) => {
|
|
114
118
|
deps.logger.warn('Passkey app sign-in refused', { meta: { error } });
|
|
115
|
-
return reply.send({ location: appFlowLocation(app.redirect, { error }, app.state) });
|
|
119
|
+
return reply.send({ location: (0, types_1.appFlowLocation)(app.redirect, { error }, app.state) });
|
|
116
120
|
};
|
|
117
121
|
const provider = deps.passkeys();
|
|
118
122
|
if (provider === null)
|
|
@@ -138,13 +142,13 @@ export function registerAppPasskeyRoutes(fastify, deps) {
|
|
|
138
142
|
app,
|
|
139
143
|
authMethod: 'passkey',
|
|
140
144
|
});
|
|
141
|
-
reply.header('Set-Cookie', appConfirmCookie(parked.cookie, request.protocol === 'https'));
|
|
145
|
+
reply.header('Set-Cookie', (0, app_confirm_routes_js_1.appConfirmCookie)(parked.cookie, request.protocol === 'https'));
|
|
142
146
|
return reply.send({
|
|
143
|
-
confirm: `confirm?${APP_CONFIRM_PARAM}=${encodeURIComponent(parked.token)}`,
|
|
147
|
+
confirm: `confirm?${app_confirm_routes_js_1.APP_CONFIRM_PARAM}=${encodeURIComponent(parked.token)}`,
|
|
144
148
|
});
|
|
145
149
|
}
|
|
146
150
|
catch (error) {
|
|
147
|
-
if (error instanceof SessionUserGoneError)
|
|
151
|
+
if (error instanceof session_mint_js_1.SessionUserGoneError)
|
|
148
152
|
return deny('user-gone');
|
|
149
153
|
deps.logger.error('Passkey app sign-in failed', {
|
|
150
154
|
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APP_REFUSED_CSP = void 0;
|
|
4
|
+
exports.appRefusedPage = appRefusedPage;
|
|
5
|
+
exports.registerAppRefusedRoute = registerAppRefusedRoute;
|
|
1
6
|
/**
|
|
2
7
|
* `GET /auth/app/refused?reason=<code>` - the page that says why a native app
|
|
3
8
|
* sign-in could not start (D858).
|
|
@@ -7,29 +12,29 @@
|
|
|
7
12
|
* sentence, in the language of `lang`/`Accept-Language`, in the same shell as every other app page. The query is never echoed: an unknown or
|
|
8
13
|
* absent code gets a generic sentence, a known one its fixed words.
|
|
9
14
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
const types_1 = require("@camstack/types");
|
|
16
|
+
const app_page_shell_js_1 = require("./app-page-shell.js");
|
|
17
|
+
const app_page_strings_js_1 = require("./app-page-strings.js");
|
|
13
18
|
/** The refusal page for `reason` (a known code, or anything: unknown renders the generic sentence). */
|
|
14
|
-
|
|
15
|
-
const t = appStrings(lang).refused;
|
|
16
|
-
const message = isAppStartRefusal(reason) ? t.reasons[reason] : t.generic;
|
|
17
|
-
return renderAppPage({
|
|
19
|
+
function appRefusedPage(reason, lang) {
|
|
20
|
+
const t = (0, app_page_strings_js_1.appStrings)(lang).refused;
|
|
21
|
+
const message = (0, types_1.isAppStartRefusal)(reason) ? t.reasons[reason] : t.generic;
|
|
22
|
+
return (0, app_page_shell_js_1.renderAppPage)({
|
|
18
23
|
lang,
|
|
19
24
|
title: t.title,
|
|
20
25
|
heading: t.title,
|
|
21
|
-
body: appParagraph(message) + appHint(t.hint),
|
|
26
|
+
body: (0, app_page_shell_js_1.appParagraph)(message) + (0, app_page_shell_js_1.appHint)(t.hint),
|
|
22
27
|
});
|
|
23
28
|
}
|
|
24
29
|
/** CSP of the refusal page: the shell's style only, nothing runs. */
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
fastify.get(APP_REFUSED_PATH, async (request, reply) => {
|
|
30
|
+
exports.APP_REFUSED_CSP = (0, app_page_shell_js_1.appPageCsp)();
|
|
31
|
+
function registerAppRefusedRoute(fastify) {
|
|
32
|
+
fastify.get(types_1.APP_REFUSED_PATH, async (request, reply) => {
|
|
28
33
|
reply.header('Cache-Control', 'no-store');
|
|
29
34
|
reply.header('Referrer-Policy', 'no-referrer');
|
|
30
|
-
reply.header('Content-Security-Policy', APP_REFUSED_CSP);
|
|
35
|
+
reply.header('Content-Security-Policy', exports.APP_REFUSED_CSP);
|
|
31
36
|
reply.type('text/html; charset=utf-8');
|
|
32
|
-
const lang = resolveAppPageLang({
|
|
37
|
+
const lang = (0, app_page_strings_js_1.resolveAppPageLang)({
|
|
33
38
|
lang: request.query['lang'],
|
|
34
39
|
acceptLanguage: request.headers['accept-language'],
|
|
35
40
|
});
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_AUTH_NETWORK_POLICY = exports.TRUSTED_NETWORKS_KEY = exports.TRUSTED_PROXIES_KEY = void 0;
|
|
4
|
+
exports.createAuthNetworkPolicy = createAuthNetworkPolicy;
|
|
1
5
|
/**
|
|
2
6
|
* The two network facts the auth surface reads from settings:
|
|
3
7
|
*
|
|
@@ -13,10 +17,10 @@
|
|
|
13
17
|
* answer yet is treated as an EMPTY list: no extra proxy is trusted, and no
|
|
14
18
|
* network skips a factor — both fail closed.
|
|
15
19
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
const system_1 = require("@camstack/system");
|
|
21
|
+
const client_address_js_1 = require("./client-address.js");
|
|
22
|
+
exports.TRUSTED_PROXIES_KEY = 'auth.trustedProxies';
|
|
23
|
+
exports.TRUSTED_NETWORKS_KEY = 'auth.trustedNetworks';
|
|
20
24
|
function asRequest(req) {
|
|
21
25
|
if (req === null || typeof req !== 'object')
|
|
22
26
|
return null;
|
|
@@ -32,7 +36,7 @@ function asRequest(req) {
|
|
|
32
36
|
}
|
|
33
37
|
function safeList(read, key) {
|
|
34
38
|
try {
|
|
35
|
-
return readCidrSetting(read(key));
|
|
39
|
+
return (0, system_1.readCidrSetting)(read(key));
|
|
36
40
|
}
|
|
37
41
|
catch {
|
|
38
42
|
return [];
|
|
@@ -41,31 +45,31 @@ function safeList(read, key) {
|
|
|
41
45
|
/** One memo per list: rebuilding a BlockList per request is wasted work. */
|
|
42
46
|
function memoMatcher() {
|
|
43
47
|
let lastKey = null;
|
|
44
|
-
let last = createCidrMatcher([]);
|
|
48
|
+
let last = (0, system_1.createCidrMatcher)([]);
|
|
45
49
|
return (entries) => {
|
|
46
50
|
const key = entries.join('\n');
|
|
47
51
|
if (key !== lastKey) {
|
|
48
52
|
lastKey = key;
|
|
49
|
-
last = createCidrMatcher(entries);
|
|
53
|
+
last = (0, system_1.createCidrMatcher)(entries);
|
|
50
54
|
}
|
|
51
55
|
return last;
|
|
52
56
|
};
|
|
53
57
|
}
|
|
54
|
-
|
|
58
|
+
function createAuthNetworkPolicy(read) {
|
|
55
59
|
const networks = memoMatcher();
|
|
56
60
|
return {
|
|
57
61
|
clientAddress(req) {
|
|
58
62
|
const request = asRequest(req);
|
|
59
63
|
if (request === null)
|
|
60
64
|
return { ip: null, peer: null, viaProxy: false };
|
|
61
|
-
return resolveClientAddress(request, { trustedProxies: safeList(read, TRUSTED_PROXIES_KEY) });
|
|
65
|
+
return (0, client_address_js_1.resolveClientAddress)(request, { trustedProxies: safeList(read, exports.TRUSTED_PROXIES_KEY) });
|
|
62
66
|
},
|
|
63
67
|
isTrustedNetworkLogin(address) {
|
|
64
68
|
if (address.viaProxy || address.ip === null)
|
|
65
69
|
return false;
|
|
66
|
-
return networks(safeList(read, TRUSTED_NETWORKS_KEY)).contains(address.ip);
|
|
70
|
+
return networks(safeList(read, exports.TRUSTED_NETWORKS_KEY)).contains(address.ip);
|
|
67
71
|
},
|
|
68
72
|
};
|
|
69
73
|
}
|
|
70
74
|
/** No settings: loopback is the only trusted proxy, no network skips a factor. */
|
|
71
|
-
|
|
75
|
+
exports.DEFAULT_AUTH_NETWORK_POLICY = createAuthNetworkPolicy(() => undefined);
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AUTH_GOVERNING_CAPS = void 0;
|
|
4
|
+
exports.isAuthAddonDisabled = isAuthAddonDisabled;
|
|
5
|
+
exports.bridgeProviderAddonId = bridgeProviderAddonId;
|
|
1
6
|
/** Capabilities whose per-provider toggle is an auth addon's enable. */
|
|
2
|
-
|
|
7
|
+
exports.AUTH_GOVERNING_CAPS = ['auth-provider', 'user-passkeys'];
|
|
3
8
|
/** Whether the operator disabled `addonId` on Identity → Authentication. */
|
|
4
|
-
|
|
5
|
-
return AUTH_GOVERNING_CAPS.some((cap) => registry.isProviderDisabled(cap, addonId));
|
|
9
|
+
function isAuthAddonDisabled(registry, addonId) {
|
|
10
|
+
return exports.AUTH_GOVERNING_CAPS.some((cap) => registry.isProviderDisabled(cap, addonId));
|
|
6
11
|
}
|
|
7
12
|
/**
|
|
8
13
|
* The addon behind an SSO bridge's `provider` claim: `auth-oidc/<instance>`
|
|
9
14
|
* is signed by `auth-oidc`, anything else names its addon directly.
|
|
10
15
|
*/
|
|
11
|
-
|
|
16
|
+
function bridgeProviderAddonId(provider) {
|
|
12
17
|
const slash = provider.indexOf('/');
|
|
13
18
|
return slash === -1 ? provider : provider.slice(0, slash);
|
|
14
19
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createRateLimiter = createRateLimiter;
|
|
4
|
+
exports.clientKeyFromRequest = clientKeyFromRequest;
|
|
1
5
|
/**
|
|
2
6
|
* Public-auth rate limiting — fixed window per (bucket, client ip).
|
|
3
7
|
*
|
|
@@ -12,8 +16,8 @@
|
|
|
12
16
|
* BOUNDED (`maxKeys`, oldest-window evicted first) so an attacker
|
|
13
17
|
* rotating spoofed source addresses cannot grow it without limit.
|
|
14
18
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
const auth_network_policy_js_1 = require("./auth-network-policy.js");
|
|
20
|
+
function createRateLimiter(options) {
|
|
17
21
|
const maxKeys = options.maxKeys ?? 10_000;
|
|
18
22
|
const now = options.now ?? Date.now;
|
|
19
23
|
const entries = new Map();
|
|
@@ -72,6 +76,6 @@ export function createRateLimiter(options) {
|
|
|
72
76
|
* bucket locked the whole internet out. `null` = no originating request
|
|
73
77
|
* (a mesh-internal `createCaller`) — never limited.
|
|
74
78
|
*/
|
|
75
|
-
|
|
79
|
+
function clientKeyFromRequest(req, policy = auth_network_policy_js_1.DEFAULT_AUTH_NETWORK_POLICY) {
|
|
76
80
|
return policy.clientAddress(req).ip;
|
|
77
81
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveClientAddress = resolveClientAddress;
|
|
1
4
|
/**
|
|
2
5
|
* The real client address of an HTTP / WS request.
|
|
3
6
|
*
|
|
@@ -22,8 +25,8 @@
|
|
|
22
25
|
* second factor from a trusted network) refuses `viaProxy` outright, whatever
|
|
23
26
|
* address the headers name.
|
|
24
27
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
const system_1 = require("@camstack/system");
|
|
29
|
+
const node_net_1 = require("node:net");
|
|
27
30
|
const LOOPBACK = ['127.0.0.0/8', '::1/128'];
|
|
28
31
|
function header(req, name) {
|
|
29
32
|
const raw = req.headers[name];
|
|
@@ -31,15 +34,15 @@ function header(req, name) {
|
|
|
31
34
|
return typeof value === 'string' && value.trim().length > 0 ? value : null;
|
|
32
35
|
}
|
|
33
36
|
function validIp(value) {
|
|
34
|
-
const ip = normalizeIp(value);
|
|
35
|
-
return isIP(ip) === 0 ? null : ip;
|
|
37
|
+
const ip = (0, system_1.normalizeIp)(value);
|
|
38
|
+
return (0, node_net_1.isIP)(ip) === 0 ? null : ip;
|
|
36
39
|
}
|
|
37
|
-
|
|
40
|
+
function resolveClientAddress(req, config) {
|
|
38
41
|
const rawPeer = req.socket?.remoteAddress;
|
|
39
42
|
const peer = typeof rawPeer === 'string' && rawPeer.length > 0 ? validIp(rawPeer) : null;
|
|
40
43
|
if (peer === null)
|
|
41
44
|
return { ip: null, peer: null, viaProxy: false };
|
|
42
|
-
const trusted = createCidrMatcher([...LOOPBACK, ...config.trustedProxies]);
|
|
45
|
+
const trusted = (0, system_1.createCidrMatcher)([...LOOPBACK, ...config.trustedProxies]);
|
|
43
46
|
if (!trusted.contains(peer))
|
|
44
47
|
return { ip: peer, peer, viaProxy: false };
|
|
45
48
|
// Cloudflare stamps the connecting client itself; prefer it when it parses.
|