@camstack/types 1.2.430 → 1.2.432
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 +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +436 -4
- package/dist/index.mjs +431 -5
- package/dist/interfaces/config-ui.d.ts +7 -0
- package/dist/vendor-login-proxy/index.d.ts +3 -0
- package/dist/vendor-login-proxy/login-proxy.d.ts +53 -0
- package/dist/vendor-login-proxy/prefix.d.ts +11 -0
- package/dist/vendor-login-proxy/url-rewrite.d.ts +68 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.432] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- The vendor login proxy is served on one shared vendor-login prefix that cannot shadow an addon's own routes
|
|
15
|
+
|
|
16
|
+
## [1.2.431] - 2026-10-27
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Addons can now offer a vendor sign-in that opens the vendor's login page through the hub and finishes on the CamStack result page.
|
|
21
|
+
|
|
10
22
|
## [1.2.430] - 2026-10-27
|
|
11
23
|
|
|
12
24
|
### Added
|
package/dist/index.d.ts
CHANGED
|
@@ -353,6 +353,7 @@ export { errMsg } from './utils/err-msg.js';
|
|
|
353
353
|
export { hfModelUrl } from './utils/hf-url.js';
|
|
354
354
|
export { asBoolean, asJsonArray, asJsonObject, asNumber, asString, parseJsonArray, parseJsonObject, parseJsonUnknown, } from './utils/json-safe.js';
|
|
355
355
|
export { maskUrlCredentials } from './utils/mask-url.js';
|
|
356
|
+
export { VendorLoginProxy, rewriteBody as rewriteLoginProxyBody, rewriteLocation as rewriteLoginProxyLocation, rewriteReferer as rewriteLoginProxyReferer, rewriteSetCookie as rewriteLoginProxySetCookie, type LoginProxyAttemptSpec, type LoginProxyResponseView, type LoginProxyRoute, type LoginProxyScheme, type LoginProxyUpstream, type LoginProxyVerdict, type VendorLoginProxyOptions, VENDOR_LOGIN_PROXY_PREFIX, } from './vendor-login-proxy/index.js';
|
|
356
357
|
export { OAUTH_LANDING_PATH, OAUTH_LANDING_REASONS, isOauthLandingReason, oauthLandingUrl, parseOauthLandingFragment, type OauthLandingOutcome, type OauthLandingReason, } from './utils/oauth-landing.js';
|
|
357
358
|
export { commitWatchdogRestart, DEFAULT_POOL_MEMORY_POLICY, evaluatePoolMemory, initialPoolMemoryState, type PoolMemoryAction, type PoolMemoryPolicy, type PoolMemoryState, type PoolMemoryTelemetry, type PoolMemoryVerdict, PoolMemoryWatchdog, type PoolMemoryWatchdogOptions, type ProcMemory, parseProcStatus, pickRestartCandidate, poolMemoryThreshold, type RestartCandidate, resetPoolBaseline, resolvePoolMemoryPolicy, } from './utils/pool-memory-watchdog.js';
|
|
358
359
|
export { cellsToRects, type NormRect, rectsToCells } from './utils/privacy-grid-raster.js';
|
package/dist/index.js
CHANGED
|
@@ -43694,7 +43694,7 @@ function autoRankFor(role, endpoint) {
|
|
|
43694
43694
|
if (isPlausibleLan(endpoint)) return 3;
|
|
43695
43695
|
return null;
|
|
43696
43696
|
}
|
|
43697
|
-
var LOOPBACK_HOSTS = new Set([
|
|
43697
|
+
var LOOPBACK_HOSTS$1 = new Set([
|
|
43698
43698
|
"localhost",
|
|
43699
43699
|
"0.0.0.0",
|
|
43700
43700
|
"::1",
|
|
@@ -43702,9 +43702,9 @@ var LOOPBACK_HOSTS = new Set([
|
|
|
43702
43702
|
"::",
|
|
43703
43703
|
"[::]"
|
|
43704
43704
|
]);
|
|
43705
|
-
function isLoopbackHost(hostname) {
|
|
43705
|
+
function isLoopbackHost$1(hostname) {
|
|
43706
43706
|
const h = hostname.toLowerCase();
|
|
43707
|
-
if (LOOPBACK_HOSTS.has(h) || h.endsWith(".localhost")) return true;
|
|
43707
|
+
if (LOOPBACK_HOSTS$1.has(h) || h.endsWith(".localhost")) return true;
|
|
43708
43708
|
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
|
|
43709
43709
|
}
|
|
43710
43710
|
/**
|
|
@@ -43739,7 +43739,7 @@ function validateCustomAddress(raw) {
|
|
|
43739
43739
|
ok: false,
|
|
43740
43740
|
reason: "The address has no host."
|
|
43741
43741
|
};
|
|
43742
|
-
if (isLoopbackHost(parsed.hostname)) return {
|
|
43742
|
+
if (isLoopbackHost$1(parsed.hostname)) return {
|
|
43743
43743
|
ok: false,
|
|
43744
43744
|
reason: "A loopback address names whoever dials it, never this hub — use an address other machines reach."
|
|
43745
43745
|
};
|
|
@@ -117778,6 +117778,19 @@ function setNestedValue(obj, path, value) {
|
|
|
117778
117778
|
current[parts[parts.length - 1]] = value;
|
|
117779
117779
|
}
|
|
117780
117780
|
//#endregion
|
|
117781
|
+
//#region src/vendor-login-proxy/prefix.ts
|
|
117782
|
+
/**
|
|
117783
|
+
* The data-plane prefix every `VendorLoginProxy` is served under
|
|
117784
|
+
* (`/addon/<addonId>/vendor-login/<attemptId>/<vendorHost>/…`).
|
|
117785
|
+
*
|
|
117786
|
+
* The hub consults an addon's data-plane prefixes BEFORE its HTTP routes, so a
|
|
117787
|
+
* prefix equal to the first segment of a route the addon serves swallows that
|
|
117788
|
+
* route. A sign-in addon serves `/login/start|status|commit|cancel`; the proxy
|
|
117789
|
+
* therefore lives on a segment no addon route uses. One constant, so the entry
|
|
117790
|
+
* path, the mount and the docs cannot drift apart.
|
|
117791
|
+
*/
|
|
117792
|
+
var VENDOR_LOGIN_PROXY_PREFIX = "vendor-login";
|
|
117793
|
+
//#endregion
|
|
117781
117794
|
//#region src/utils/oauth-landing.ts
|
|
117782
117795
|
/**
|
|
117783
117796
|
* Where an OAuth round-trip ends: the admin-ui completion route
|
|
@@ -117845,6 +117858,419 @@ function parseOauthLandingFragment(hash) {
|
|
|
117845
117858
|
};
|
|
117846
117859
|
}
|
|
117847
117860
|
//#endregion
|
|
117861
|
+
//#region src/vendor-login-proxy/url-rewrite.ts
|
|
117862
|
+
var LOOPBACK_HOSTS = new Set([
|
|
117863
|
+
"127.0.0.1",
|
|
117864
|
+
"localhost",
|
|
117865
|
+
"[::1]"
|
|
117866
|
+
]);
|
|
117867
|
+
function hostWithoutPort(host) {
|
|
117868
|
+
if (host.startsWith("[")) return host.slice(0, host.indexOf("]") + 1);
|
|
117869
|
+
const colon = host.indexOf(":");
|
|
117870
|
+
return colon === -1 ? host : host.slice(0, colon);
|
|
117871
|
+
}
|
|
117872
|
+
function isLoopbackHost(host) {
|
|
117873
|
+
return LOOPBACK_HOSTS.has(hostWithoutPort(host.toLowerCase()));
|
|
117874
|
+
}
|
|
117875
|
+
/** An allow-list entry is valid when it is a bare host[:port] and `http` only targets loopback. */
|
|
117876
|
+
function isValidUpstream(upstream) {
|
|
117877
|
+
if (upstream.host.length === 0 || !/^[a-z0-9.[\]:-]+$/.test(upstream.host)) return false;
|
|
117878
|
+
return upstream.scheme === "https" || isLoopbackHost(upstream.host);
|
|
117879
|
+
}
|
|
117880
|
+
function findUpstream(route, host) {
|
|
117881
|
+
const wanted = host.toLowerCase();
|
|
117882
|
+
return route.upstreams.find((upstream) => upstream.host === wanted);
|
|
117883
|
+
}
|
|
117884
|
+
/** `<basePath>/<host><pathname><search>` — what the browser is sent to. */
|
|
117885
|
+
function proxyPathFor(route, host, pathname, search = "") {
|
|
117886
|
+
return `${route.basePath}/${host}${pathname.startsWith("/") ? pathname : `/${pathname}`}${search}`;
|
|
117887
|
+
}
|
|
117888
|
+
/** Split `/<attemptId>/<host>/<rest…>[?query]` (the data-plane view of a request). */
|
|
117889
|
+
function parseProxyPath(rawUrl) {
|
|
117890
|
+
const queryAt = rawUrl.indexOf("?");
|
|
117891
|
+
const path = queryAt === -1 ? rawUrl : rawUrl.slice(0, queryAt);
|
|
117892
|
+
const search = queryAt === -1 ? "" : rawUrl.slice(queryAt);
|
|
117893
|
+
const match = /^\/([A-Za-z0-9_-]{8,128})\/([^/]+)(\/.*)?$/.exec(path);
|
|
117894
|
+
if (match === null) return null;
|
|
117895
|
+
const attemptId = match[1];
|
|
117896
|
+
const host = match[2];
|
|
117897
|
+
if (attemptId === void 0 || host === void 0) return null;
|
|
117898
|
+
return {
|
|
117899
|
+
attemptId,
|
|
117900
|
+
host: host.toLowerCase(),
|
|
117901
|
+
pathname: match[3] ?? "/",
|
|
117902
|
+
search
|
|
117903
|
+
};
|
|
117904
|
+
}
|
|
117905
|
+
/** The vendor URL a proxied request targets, or null when the host would not stay `host`. */
|
|
117906
|
+
function upstreamUrlFor(upstream, parsed) {
|
|
117907
|
+
try {
|
|
117908
|
+
const url = new URL(`${upstream.scheme}://${upstream.host}${parsed.pathname}${parsed.search}`);
|
|
117909
|
+
return url.host.toLowerCase() === upstream.host ? url : null;
|
|
117910
|
+
} catch {
|
|
117911
|
+
return null;
|
|
117912
|
+
}
|
|
117913
|
+
}
|
|
117914
|
+
function escapeRegex(value) {
|
|
117915
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
117916
|
+
}
|
|
117917
|
+
/** A Location (or any URL value) to a vendor host becomes a proxy path; anything else is unchanged. */
|
|
117918
|
+
function rewriteLocation(location, currentUpstream, route) {
|
|
117919
|
+
let resolved;
|
|
117920
|
+
try {
|
|
117921
|
+
resolved = new URL(location, `${currentUpstream.scheme}://${currentUpstream.host}/`);
|
|
117922
|
+
} catch {
|
|
117923
|
+
return location;
|
|
117924
|
+
}
|
|
117925
|
+
if (resolved.protocol !== "https:" && resolved.protocol !== "http:") return location;
|
|
117926
|
+
const target = findUpstream(route, resolved.host);
|
|
117927
|
+
if (target === void 0) return location;
|
|
117928
|
+
return `${proxyPathFor(route, target.host, resolved.pathname, resolved.search)}${resolved.hash}`;
|
|
117929
|
+
}
|
|
117930
|
+
/** Characters that may precede a URL that STARTS a value (never `=`, `?`, `&`, `%3D`). */
|
|
117931
|
+
var VALUE_START = String.raw`(?<=^|["'(\s>,\[{])`;
|
|
117932
|
+
/**
|
|
117933
|
+
* Rewrite vendor URLs in a text body: absolute (`https://host/…`), protocol-relative
|
|
117934
|
+
* (`//host/…`) and JSON-escaped (`https:\/\/host\/…`) when they start a value; and, in
|
|
117935
|
+
* HTML/CSS, root-relative references (`href="/x"`, `url(/x)`) which would otherwise
|
|
117936
|
+
* escape the proxy mount and land on the hub.
|
|
117937
|
+
*/
|
|
117938
|
+
function rewriteBody(body, kind, currentUpstream, route) {
|
|
117939
|
+
let out = body;
|
|
117940
|
+
for (const upstream of route.upstreams) {
|
|
117941
|
+
const host = escapeRegex(upstream.host);
|
|
117942
|
+
const plain = new RegExp(`${VALUE_START}(?:https?:)?//${host}(?=[/"'?#\\s)\\]},<]|$)`, "g");
|
|
117943
|
+
out = out.replace(plain, `${route.basePath}/${upstream.host}`);
|
|
117944
|
+
const escapedBase = `${route.basePath}/${upstream.host}`.replace(/\//g, "\\/");
|
|
117945
|
+
const escaped = new RegExp(`${VALUE_START}(?:https?:)?\\\\/\\\\/${host}(?=\\\\/|["'?#\\s)\\]},<]|$)`, "g");
|
|
117946
|
+
out = out.replace(escaped, escapedBase);
|
|
117947
|
+
}
|
|
117948
|
+
const current = `${route.basePath}/${currentUpstream.host}/`;
|
|
117949
|
+
const notMounted = `(?!/)(?!${escapeRegex(route.basePath.slice(1))}/)`;
|
|
117950
|
+
if (kind === "html") {
|
|
117951
|
+
const attribute = String.raw`(\b(?:href|src|action|formaction|poster|data-href|data-src)\s*=\s*["'])/`;
|
|
117952
|
+
out = out.replace(new RegExp(`${attribute}${notMounted}`, "gi"), `$1${current}`);
|
|
117953
|
+
}
|
|
117954
|
+
if (kind === "html" || kind === "css") {
|
|
117955
|
+
const cssUrl = String.raw`(url\(\s*["']?)/`;
|
|
117956
|
+
out = out.replace(new RegExp(`${cssUrl}${notMounted}`, "gi"), `$1${current}`);
|
|
117957
|
+
}
|
|
117958
|
+
return out;
|
|
117959
|
+
}
|
|
117960
|
+
/**
|
|
117961
|
+
* A vendor `Set-Cookie` for the browser: no `Domain` (the cookie belongs to the hub
|
|
117962
|
+
* origin), `Path` narrowed to this attempt's prefix, `Secure` kept (the hub is HTTPS).
|
|
117963
|
+
*/
|
|
117964
|
+
function rewriteSetCookie(cookie, route) {
|
|
117965
|
+
const kept = cookie.split(";").map((part) => part.trim()).filter((part, index) => {
|
|
117966
|
+
if (index === 0) return true;
|
|
117967
|
+
const name = part.split("=")[0]?.trim().toLowerCase() ?? "";
|
|
117968
|
+
return name !== "domain" && name !== "path";
|
|
117969
|
+
});
|
|
117970
|
+
kept.push(`Path=${route.basePath}/`);
|
|
117971
|
+
return kept.join("; ");
|
|
117972
|
+
}
|
|
117973
|
+
/**
|
|
117974
|
+
* A browser `Referer` that points into this proxy, turned back into the vendor URL it
|
|
117975
|
+
* stands for. A Referer that is not ours is dropped (null): it names the hub.
|
|
117976
|
+
*/
|
|
117977
|
+
function rewriteReferer(referer, route) {
|
|
117978
|
+
let url;
|
|
117979
|
+
try {
|
|
117980
|
+
url = new URL(referer, "http://hub.invalid");
|
|
117981
|
+
} catch {
|
|
117982
|
+
return null;
|
|
117983
|
+
}
|
|
117984
|
+
if (!url.pathname.startsWith(`${route.basePath}/`)) return null;
|
|
117985
|
+
const rest = /^\/([^/]+)(\/.*)?$/.exec(url.pathname.slice(route.basePath.length));
|
|
117986
|
+
const host = rest?.[1]?.toLowerCase();
|
|
117987
|
+
if (host === void 0) return null;
|
|
117988
|
+
const upstream = findUpstream(route, host);
|
|
117989
|
+
if (upstream === void 0) return null;
|
|
117990
|
+
return `${upstream.scheme}://${upstream.host}${rest?.[2] ?? "/"}${url.search}`;
|
|
117991
|
+
}
|
|
117992
|
+
/** Cookies the browser holds for the hub itself must never reach a vendor. */
|
|
117993
|
+
function filterForwardedCookies(cookieHeader) {
|
|
117994
|
+
return cookieHeader.split(";").map((part) => part.trim()).filter((part) => part.length > 0 && !part.toLowerCase().startsWith("camstack_")).join("; ");
|
|
117995
|
+
}
|
|
117996
|
+
//#endregion
|
|
117997
|
+
//#region src/vendor-login-proxy/login-proxy.ts
|
|
117998
|
+
var MAX_REQUEST_BODY_BYTES = 2 * 1024 * 1024;
|
|
117999
|
+
var MAX_TEXT_BODY_BYTES = 4 * 1024 * 1024;
|
|
118000
|
+
/** Request headers that are never forwarded to the vendor. */
|
|
118001
|
+
var DROP_REQUEST_HEADERS = new Set([
|
|
118002
|
+
"host",
|
|
118003
|
+
"connection",
|
|
118004
|
+
"keep-alive",
|
|
118005
|
+
"proxy-authorization",
|
|
118006
|
+
"proxy-connection",
|
|
118007
|
+
"te",
|
|
118008
|
+
"trailer",
|
|
118009
|
+
"transfer-encoding",
|
|
118010
|
+
"upgrade",
|
|
118011
|
+
"content-length",
|
|
118012
|
+
"accept-encoding",
|
|
118013
|
+
"authorization",
|
|
118014
|
+
"cookie",
|
|
118015
|
+
"origin",
|
|
118016
|
+
"referer",
|
|
118017
|
+
"x-camstack-dataplane-secret",
|
|
118018
|
+
"x-forwarded-for",
|
|
118019
|
+
"x-forwarded-host",
|
|
118020
|
+
"x-forwarded-proto",
|
|
118021
|
+
"x-forwarded-prefix",
|
|
118022
|
+
"x-real-ip",
|
|
118023
|
+
"forwarded"
|
|
118024
|
+
]);
|
|
118025
|
+
/**
|
|
118026
|
+
* Response headers dropped: framing/CSP/HSTS that break a page under another origin, and
|
|
118027
|
+
* the ones that no longer describe the (decoded, possibly rewritten) body.
|
|
118028
|
+
*/
|
|
118029
|
+
var DROP_RESPONSE_HEADERS = new Set([
|
|
118030
|
+
"content-security-policy",
|
|
118031
|
+
"content-security-policy-report-only",
|
|
118032
|
+
"x-frame-options",
|
|
118033
|
+
"strict-transport-security",
|
|
118034
|
+
"public-key-pins",
|
|
118035
|
+
"report-to",
|
|
118036
|
+
"nel",
|
|
118037
|
+
"content-encoding",
|
|
118038
|
+
"content-length",
|
|
118039
|
+
"transfer-encoding",
|
|
118040
|
+
"connection",
|
|
118041
|
+
"keep-alive",
|
|
118042
|
+
"set-cookie",
|
|
118043
|
+
"location"
|
|
118044
|
+
]);
|
|
118045
|
+
function bodyKindOf(contentType) {
|
|
118046
|
+
const type = contentType.toLowerCase();
|
|
118047
|
+
if (type.includes("text/html") || type.includes("application/xhtml")) return "html";
|
|
118048
|
+
if (type.includes("text/css")) return "css";
|
|
118049
|
+
if (type.startsWith("text/") || type.includes("javascript") || type.includes("json") || type.includes("xml")) return "other";
|
|
118050
|
+
return null;
|
|
118051
|
+
}
|
|
118052
|
+
function firstHeader(value) {
|
|
118053
|
+
return Array.isArray(value) ? value[0] : value;
|
|
118054
|
+
}
|
|
118055
|
+
async function readRequestBody(req) {
|
|
118056
|
+
const chunks = [];
|
|
118057
|
+
let size = 0;
|
|
118058
|
+
for await (const chunk of req) {
|
|
118059
|
+
const buf = typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk);
|
|
118060
|
+
size += buf.length;
|
|
118061
|
+
if (size > MAX_REQUEST_BODY_BYTES) return "too-large";
|
|
118062
|
+
chunks.push(buf);
|
|
118063
|
+
}
|
|
118064
|
+
return Buffer.concat(chunks);
|
|
118065
|
+
}
|
|
118066
|
+
function sendPlain(res, status, text) {
|
|
118067
|
+
res.writeHead(status, {
|
|
118068
|
+
"content-type": "text/plain; charset=utf-8",
|
|
118069
|
+
"cache-control": "no-store"
|
|
118070
|
+
});
|
|
118071
|
+
res.end(text);
|
|
118072
|
+
}
|
|
118073
|
+
var VendorLoginProxy = class {
|
|
118074
|
+
#addonId;
|
|
118075
|
+
#logger;
|
|
118076
|
+
#now;
|
|
118077
|
+
#fetch;
|
|
118078
|
+
#attempts = /* @__PURE__ */ new Map();
|
|
118079
|
+
constructor(options) {
|
|
118080
|
+
this.#addonId = options.addonId;
|
|
118081
|
+
this.#logger = options.logger;
|
|
118082
|
+
this.#now = options.now ?? (() => Date.now());
|
|
118083
|
+
this.#fetch = options.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
118084
|
+
}
|
|
118085
|
+
/** The `DataPlaneHandler` to pass to `ctx.dataPlane.serve({ prefix: VENDOR_LOGIN_PROXY_PREFIX, access: 'admin', … })`. */
|
|
118086
|
+
handler = (req, res) => this.#handle(req, res);
|
|
118087
|
+
/** Register an attempt. A one-shot capability: refused hosts are rejected up front. */
|
|
118088
|
+
open(spec) {
|
|
118089
|
+
for (const upstream of spec.upstreams) if (!isValidUpstream(upstream)) throw new Error(`vendor login proxy: upstream ${upstream.host} is not allowed`);
|
|
118090
|
+
this.#attempts.set(spec.attemptId, {
|
|
118091
|
+
expiresAtMs: spec.expiresAtMs,
|
|
118092
|
+
upstreams: spec.upstreams,
|
|
118093
|
+
evaluate: async (view) => {
|
|
118094
|
+
const verdict = await spec.terminal(view);
|
|
118095
|
+
if (verdict.kind === "continue") return verdict;
|
|
118096
|
+
return {
|
|
118097
|
+
kind: "done",
|
|
118098
|
+
landing: await spec.onDone(verdict.payload)
|
|
118099
|
+
};
|
|
118100
|
+
}
|
|
118101
|
+
});
|
|
118102
|
+
}
|
|
118103
|
+
close(attemptId) {
|
|
118104
|
+
this.#attempts.delete(attemptId);
|
|
118105
|
+
}
|
|
118106
|
+
isOpen(attemptId) {
|
|
118107
|
+
const attempt = this.#attempts.get(attemptId);
|
|
118108
|
+
return attempt !== void 0 && this.#now() <= attempt.expiresAtMs;
|
|
118109
|
+
}
|
|
118110
|
+
/**
|
|
118111
|
+
* The hub-relative path that opens `vendorUrl` through the proxy for this attempt:
|
|
118112
|
+
* the URL the browser is sent to. Null when the host is not in the attempt's list.
|
|
118113
|
+
*/
|
|
118114
|
+
entryPath(attemptId, vendorUrl) {
|
|
118115
|
+
const attempt = this.#attempts.get(attemptId);
|
|
118116
|
+
if (attempt === void 0) return null;
|
|
118117
|
+
const url = new URL(vendorUrl);
|
|
118118
|
+
if (findUpstream({
|
|
118119
|
+
basePath: "",
|
|
118120
|
+
upstreams: attempt.upstreams
|
|
118121
|
+
}, url.host) === void 0) return null;
|
|
118122
|
+
return `/addon/${this.#addonId}/${VENDOR_LOGIN_PROXY_PREFIX}/${attemptId}/${url.host}${url.pathname}${url.search}`;
|
|
118123
|
+
}
|
|
118124
|
+
#routeFor(attemptId, attempt, forwardedPrefix) {
|
|
118125
|
+
return {
|
|
118126
|
+
basePath: `${forwardedPrefix}/addon/${this.#addonId}/${VENDOR_LOGIN_PROXY_PREFIX}/${attemptId}`,
|
|
118127
|
+
upstreams: attempt.upstreams
|
|
118128
|
+
};
|
|
118129
|
+
}
|
|
118130
|
+
async #handle(req, res) {
|
|
118131
|
+
try {
|
|
118132
|
+
await this.#serve(req, res);
|
|
118133
|
+
} catch (err) {
|
|
118134
|
+
this.#logger.warn("vendor login proxy: request failed", { meta: {
|
|
118135
|
+
addonId: this.#addonId,
|
|
118136
|
+
error: err instanceof Error ? err.name : "unknown"
|
|
118137
|
+
} });
|
|
118138
|
+
if (!res.headersSent) sendPlain(res, 502, "The sign-in page could not be reached.");
|
|
118139
|
+
else res.end();
|
|
118140
|
+
}
|
|
118141
|
+
}
|
|
118142
|
+
async #serve(req, res) {
|
|
118143
|
+
const parsed = parseProxyPath(req.url ?? "/");
|
|
118144
|
+
if (parsed === null) {
|
|
118145
|
+
sendPlain(res, 404, "Not found");
|
|
118146
|
+
return;
|
|
118147
|
+
}
|
|
118148
|
+
const forwardedPrefix = (firstHeader(req.headers["x-forwarded-prefix"]) ?? "").replace(/\/+$/, "");
|
|
118149
|
+
const attempt = this.#attempts.get(parsed.attemptId);
|
|
118150
|
+
if (attempt === void 0 || this.#now() > attempt.expiresAtMs) {
|
|
118151
|
+
if (attempt !== void 0) this.#attempts.delete(parsed.attemptId);
|
|
118152
|
+
this.#logger.info("vendor login proxy: attempt refused", { meta: {
|
|
118153
|
+
addonId: this.#addonId,
|
|
118154
|
+
reason: attempt === void 0 ? "missing" : "expired"
|
|
118155
|
+
} });
|
|
118156
|
+
this.#redirect(res, forwardedPrefix, {
|
|
118157
|
+
status: "error",
|
|
118158
|
+
addonId: this.#addonId,
|
|
118159
|
+
reason: "invalid_state"
|
|
118160
|
+
});
|
|
118161
|
+
return;
|
|
118162
|
+
}
|
|
118163
|
+
const route = this.#routeFor(parsed.attemptId, attempt, forwardedPrefix);
|
|
118164
|
+
const upstream = findUpstream(route, parsed.host);
|
|
118165
|
+
const target = upstream === void 0 ? null : upstreamUrlFor(upstream, parsed);
|
|
118166
|
+
if (upstream === void 0 || target === null) {
|
|
118167
|
+
this.#logger.warn("vendor login proxy: host refused", { meta: {
|
|
118168
|
+
addonId: this.#addonId,
|
|
118169
|
+
host: parsed.host
|
|
118170
|
+
} });
|
|
118171
|
+
sendPlain(res, 404, "Not found");
|
|
118172
|
+
return;
|
|
118173
|
+
}
|
|
118174
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
118175
|
+
let body;
|
|
118176
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
118177
|
+
const read = await readRequestBody(req);
|
|
118178
|
+
if (read === "too-large") {
|
|
118179
|
+
sendPlain(res, 413, "Request too large");
|
|
118180
|
+
return;
|
|
118181
|
+
}
|
|
118182
|
+
body = read;
|
|
118183
|
+
}
|
|
118184
|
+
const headers = this.#requestHeaders(req, route, upstream);
|
|
118185
|
+
const upstreamRes = await this.#fetch(target, {
|
|
118186
|
+
method,
|
|
118187
|
+
headers,
|
|
118188
|
+
redirect: "manual",
|
|
118189
|
+
...body !== void 0 && body.length > 0 ? { body: new Uint8Array(body) } : {}
|
|
118190
|
+
});
|
|
118191
|
+
const location = upstreamRes.headers.get("location");
|
|
118192
|
+
const setCookies = upstreamRes.headers.getSetCookie();
|
|
118193
|
+
const kind = bodyKindOf(upstreamRes.headers.get("content-type") ?? "");
|
|
118194
|
+
const declaredLength = Number(upstreamRes.headers.get("content-length") ?? "0");
|
|
118195
|
+
const text = kind !== null && !(declaredLength > MAX_TEXT_BODY_BYTES) && method !== "HEAD" ? await upstreamRes.text() : null;
|
|
118196
|
+
this.#logger.debug("vendor login proxy: upstream answered", { meta: {
|
|
118197
|
+
addonId: this.#addonId,
|
|
118198
|
+
host: upstream.host,
|
|
118199
|
+
status: upstreamRes.status,
|
|
118200
|
+
method
|
|
118201
|
+
} });
|
|
118202
|
+
const settled = await attempt.evaluate({
|
|
118203
|
+
status: upstreamRes.status,
|
|
118204
|
+
url: target.toString(),
|
|
118205
|
+
location,
|
|
118206
|
+
setCookies,
|
|
118207
|
+
bodyText: text
|
|
118208
|
+
});
|
|
118209
|
+
if (settled.kind === "done") {
|
|
118210
|
+
this.#attempts.delete(parsed.attemptId);
|
|
118211
|
+
this.#logger.info("vendor login proxy: sign-in finished", { meta: {
|
|
118212
|
+
addonId: this.#addonId,
|
|
118213
|
+
status: settled.landing.status
|
|
118214
|
+
} });
|
|
118215
|
+
this.#redirect(res, forwardedPrefix, settled.landing);
|
|
118216
|
+
return;
|
|
118217
|
+
}
|
|
118218
|
+
const out = {};
|
|
118219
|
+
upstreamRes.headers.forEach((value, name) => {
|
|
118220
|
+
if (!DROP_RESPONSE_HEADERS.has(name.toLowerCase())) out[name] = value;
|
|
118221
|
+
});
|
|
118222
|
+
if (setCookies.length > 0) out["set-cookie"] = setCookies.map((c) => rewriteSetCookie(c, route));
|
|
118223
|
+
if (location !== null) out["location"] = rewriteLocation(location, upstream, route);
|
|
118224
|
+
out["cache-control"] = "no-store";
|
|
118225
|
+
if (text !== null && kind !== null) {
|
|
118226
|
+
const rewritten = rewriteBody(text, kind, upstream, route);
|
|
118227
|
+
res.writeHead(upstreamRes.status, out);
|
|
118228
|
+
res.end(rewritten);
|
|
118229
|
+
return;
|
|
118230
|
+
}
|
|
118231
|
+
res.writeHead(upstreamRes.status, out);
|
|
118232
|
+
await this.#pipeBody(upstreamRes, res);
|
|
118233
|
+
}
|
|
118234
|
+
#requestHeaders(req, route, upstream) {
|
|
118235
|
+
const headers = {};
|
|
118236
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
118237
|
+
const lower = name.toLowerCase();
|
|
118238
|
+
if (DROP_REQUEST_HEADERS.has(lower) || value === void 0) continue;
|
|
118239
|
+
headers[lower] = Array.isArray(value) ? value.join(", ") : value;
|
|
118240
|
+
}
|
|
118241
|
+
const cookie = filterForwardedCookies(firstHeader(req.headers["cookie"]) ?? "");
|
|
118242
|
+
if (cookie.length > 0) headers["cookie"] = cookie;
|
|
118243
|
+
if (req.headers["origin"] !== void 0) headers["origin"] = `${upstream.scheme}://${upstream.host}`;
|
|
118244
|
+
const referer = firstHeader(req.headers["referer"]);
|
|
118245
|
+
if (referer !== void 0) {
|
|
118246
|
+
const back = rewriteReferer(referer, route);
|
|
118247
|
+
if (back !== null) headers["referer"] = back;
|
|
118248
|
+
}
|
|
118249
|
+
return headers;
|
|
118250
|
+
}
|
|
118251
|
+
async #pipeBody(upstreamRes, res) {
|
|
118252
|
+
if (upstreamRes.body === null) {
|
|
118253
|
+
res.end();
|
|
118254
|
+
return;
|
|
118255
|
+
}
|
|
118256
|
+
for await (const chunk of upstreamRes.body) {
|
|
118257
|
+
if (!res.write(chunk)) await new Promise((resolve) => {
|
|
118258
|
+
res.once("drain", resolve);
|
|
118259
|
+
res.once("close", resolve);
|
|
118260
|
+
});
|
|
118261
|
+
if (res.destroyed) return;
|
|
118262
|
+
}
|
|
118263
|
+
res.end();
|
|
118264
|
+
}
|
|
118265
|
+
#redirect(res, forwardedPrefix, outcome) {
|
|
118266
|
+
res.writeHead(302, {
|
|
118267
|
+
location: `${forwardedPrefix}${oauthLandingUrl(null, outcome)}`,
|
|
118268
|
+
"cache-control": "no-store"
|
|
118269
|
+
});
|
|
118270
|
+
res.end();
|
|
118271
|
+
}
|
|
118272
|
+
};
|
|
118273
|
+
//#endregion
|
|
117848
118274
|
//#region src/utils/pool-memory-watchdog.ts
|
|
117849
118275
|
var MB = 1024 * 1024;
|
|
117850
118276
|
var DEFAULT_POOL_MEMORY_POLICY = {
|
|
@@ -120925,6 +121351,7 @@ exports.UserRecordSchema = UserRecordSchema;
|
|
|
120925
121351
|
exports.UserScopeSchema = UserScopeSchema;
|
|
120926
121352
|
exports.UserSummarySchema = UserSummarySchema;
|
|
120927
121353
|
exports.VECTOR_INDEX_DIM_CONFLICT = VECTOR_INDEX_DIM_CONFLICT;
|
|
121354
|
+
exports.VENDOR_LOGIN_PROXY_PREFIX = VENDOR_LOGIN_PROXY_PREFIX;
|
|
120928
121355
|
exports.VIDEOCLIPS_MAX_OFFER_BYTES = VIDEOCLIPS_MAX_OFFER_BYTES;
|
|
120929
121356
|
exports.VIDEOCLIPS_MAX_READ_BYTES = VIDEOCLIPS_MAX_READ_BYTES;
|
|
120930
121357
|
exports.VIEWER_FEATURES = VIEWER_FEATURES;
|
|
@@ -120952,6 +121379,7 @@ exports.VectorStatsInputSchema = VectorStatsInputSchema;
|
|
|
120952
121379
|
exports.VectorStatsResultSchema = VectorStatsResultSchema;
|
|
120953
121380
|
exports.VectorUpsertInputSchema = VectorUpsertInputSchema;
|
|
120954
121381
|
exports.VectorUpsertResultSchema = VectorUpsertResultSchema;
|
|
121382
|
+
exports.VendorLoginProxy = VendorLoginProxy;
|
|
120955
121383
|
exports.VibrationStatusSchema = VibrationStatusSchema;
|
|
120956
121384
|
exports.VideoEncodeSchema = VideoEncodeSchema;
|
|
120957
121385
|
exports.ViewerDeviceAllowSchema = ViewerDeviceAllowSchema;
|
|
@@ -121849,6 +122277,10 @@ exports.resultCellKey = resultCellKey;
|
|
|
121849
122277
|
exports.resultStandings = resultStandings;
|
|
121850
122278
|
exports.retrainBoxIou = retrainBoxIou;
|
|
121851
122279
|
exports.retrainRevisionConflictMessage = retrainRevisionConflictMessage;
|
|
122280
|
+
exports.rewriteLoginProxyBody = rewriteBody;
|
|
122281
|
+
exports.rewriteLoginProxyLocation = rewriteLocation;
|
|
122282
|
+
exports.rewriteLoginProxyReferer = rewriteReferer;
|
|
122283
|
+
exports.rewriteLoginProxySetCookie = rewriteSetCookie;
|
|
121852
122284
|
exports.roleSpec = roleSpec;
|
|
121853
122285
|
exports.rollingDurationMs = require_composition.rollingDurationMs;
|
|
121854
122286
|
exports.roomCompositionExample = roomCompositionExample;
|