@aglyn/tenant-data-admin 1.0.0-beta.222 → 1.0.0-beta.223
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/package.json +7 -7
- package/src/lib/server/ga4-measurement-protocol.js +41 -28
- package/src/lib/server/ga4-measurement-protocol.js.map +1 -1
- package/src/lib/server/host-visitor-records.d.ts +13 -0
- package/src/lib/server/host-visitor-records.js +22 -4
- package/src/lib/server/host-visitor-records.js.map +1 -1
- package/src/lib/server/hosted-page-review.js +6 -3
- package/src/lib/server/hosted-page-review.js.map +1 -1
- package/src/lib/server/outbound-send-review.js +6 -3
- package/src/lib/server/outbound-send-review.js.map +1 -1
- package/src/lib/server/payment-fraud-signal.d.ts +49 -9
- package/src/lib/server/payment-fraud-signal.js +192 -48
- package/src/lib/server/payment-fraud-signal.js.map +1 -1
- package/src/lib/server/render-monitor.d.ts +166 -0
- package/src/lib/server/render-monitor.js +412 -0
- package/src/lib/server/render-monitor.js.map +1 -0
- package/src/lib/server/risk-notice.d.ts +7 -0
- package/src/lib/server/risk-notice.js +6 -0
- package/src/lib/server/risk-notice.js.map +1 -1
- package/src/lib/server/web-risk.d.ts +2 -0
- package/src/lib/server/web-risk.js +18 -21
- package/src/lib/server/web-risk.js.map +1 -1
- package/src/tenancy.d.ts +0 -2
- package/src/tenancy.js +0 -2
- package/src/tenancy.js.map +1 -1
- package/src/lib/server/crm-email-activity.d.ts +0 -118
- package/src/lib/server/crm-email-activity.js +0 -148
- package/src/lib/server/crm-email-activity.js.map +0 -1
- package/src/lib/server/crm-next-activity.d.ts +0 -74
- package/src/lib/server/crm-next-activity.js +0 -194
- package/src/lib/server/crm-next-activity.js.map +0 -1
|
@@ -86,6 +86,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
86
86
|
*=========================================*/ import { createHash } from "crypto";
|
|
87
87
|
import { TENANT_APEX } from "@aglyn/aglyn/app-utils/tenant-apex";
|
|
88
88
|
import { LINK_THREAT_TYPES, MAX_REPUTATION_HOSTS_PER_LOOKUP, MAX_REPUTATION_URLS_PER_LOOKUP, normalizeReputationHost, normalizeReputationLink, pickReputationUrls, setLinkReputationLookup } from "@aglyn/shared-util-email/link-reputation";
|
|
89
|
+
import { boundedAwait } from "@aglyn/shared-util-http/bounded-await";
|
|
89
90
|
import { getApp } from "firebase-admin/app";
|
|
90
91
|
import firebaseAdmin from "./firebase-admin.js";
|
|
91
92
|
export const WEB_RISK_SEARCH_ENDPOINT = 'https://webrisk.googleapis.com/v1/uris:search';
|
|
@@ -98,6 +99,7 @@ export const WEB_RISK_SEARCH_ENDPOINT = 'https://webrisk.googleapis.com/v1/uris:
|
|
|
98
99
|
export const WEB_RISK_SETTINGS_DOC = 'webRisk';
|
|
99
100
|
/** One call's budget. */ export const WEB_RISK_TIMEOUT_MS = 1000;
|
|
100
101
|
/** One lookup's budget, however many hosts and addresses it asks about. */ export const WEB_RISK_DEADLINE_MS = 1200;
|
|
102
|
+
/** The service account's token exchange; a cached token answers at once. */ export const WEB_RISK_TOKEN_DEADLINE_MS = 3000;
|
|
101
103
|
/** How long a clean answer is kept in the store. */ export const WEB_RISK_CLEAN_TTL_MS = 12 * 60 * 60000;
|
|
102
104
|
/** How long a clean answer is trusted from this process's memory. */ export const WEB_RISK_MEMORY_CLEAN_TTL_MS = 10 * 60000;
|
|
103
105
|
/** A listing whose answer named no `expireTime` is kept this long. */ export const WEB_RISK_LISTED_FALLBACK_TTL_MS = 5 * 60000;
|
|
@@ -137,8 +139,7 @@ export const WEB_RISK_SETTINGS_DOC = 'webRisk';
|
|
|
137
139
|
return `https://${host}/`;
|
|
138
140
|
}
|
|
139
141
|
/** The default client: `uris.search` over HTTPS with a bearer token. */ export function createWebRiskHttpClient(options) {
|
|
140
|
-
var
|
|
141
|
-
const request = (_options_fetch = options.fetch) != null ? _options_fetch : fetch;
|
|
142
|
+
var _options_timeoutMs;
|
|
142
143
|
const timeoutMs = (_options_timeoutMs = options.timeoutMs) != null ? _options_timeoutMs : WEB_RISK_TIMEOUT_MS;
|
|
143
144
|
return {
|
|
144
145
|
async searchUri (uri) {
|
|
@@ -148,24 +149,26 @@ export const WEB_RISK_SETTINGS_DOC = 'webRisk';
|
|
|
148
149
|
const url = new URL((_options_endpoint = options.endpoint) != null ? _options_endpoint : WEB_RISK_SEARCH_ENDPOINT);
|
|
149
150
|
for (const type of LINK_THREAT_TYPES)url.searchParams.append('threatTypes', type);
|
|
150
151
|
url.searchParams.set('uri', uri);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
// A real deadline over the request AND its body: the signal alone did
|
|
153
|
+
// not bound Next's patched fetch (AGL-3565). It still cancels the
|
|
154
|
+
// request where it can.
|
|
155
|
+
const answer = await boundedAwait(async (signal)=>{
|
|
156
|
+
var _options_fetch;
|
|
157
|
+
const response = await ((_options_fetch = options.fetch) != null ? _options_fetch : fetch)(url.toString(), {
|
|
155
158
|
method: 'GET',
|
|
156
159
|
headers: {
|
|
157
160
|
Authorization: `Bearer ${token}`,
|
|
158
161
|
Accept: 'application/json'
|
|
159
162
|
},
|
|
160
|
-
signal
|
|
163
|
+
signal
|
|
161
164
|
});
|
|
162
165
|
if (!response.ok) {
|
|
163
166
|
throw new WebRiskHttpError(response.status, await response.text().catch(()=>''));
|
|
164
167
|
}
|
|
165
168
|
return parseWebRiskSearch(await response.json());
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
+
}, timeoutMs, null, 'web-risk.search');
|
|
170
|
+
if (!answer) throw new Error(`Web Risk did not answer within ${timeoutMs} ms`);
|
|
171
|
+
return answer;
|
|
169
172
|
}
|
|
170
173
|
};
|
|
171
174
|
}
|
|
@@ -175,10 +178,11 @@ export const WEB_RISK_SETTINGS_DOC = 'webRisk';
|
|
|
175
178
|
*/ async function serviceAccountAccessToken() {
|
|
176
179
|
try {
|
|
177
180
|
var _ref;
|
|
178
|
-
var _this;
|
|
179
181
|
const credential = getApp().options.credential;
|
|
180
182
|
if (!credential) return null;
|
|
181
|
-
|
|
183
|
+
// A token exchange with Google, on a page render's path (AGL-3565).
|
|
184
|
+
const token = await boundedAwait(credential.getAccessToken(), WEB_RISK_TOKEN_DEADLINE_MS, null, 'web-risk.access-token');
|
|
185
|
+
return (_ref = token == null ? void 0 : token.access_token) != null ? _ref : null;
|
|
182
186
|
} catch (unused) {
|
|
183
187
|
return null;
|
|
184
188
|
}
|
|
@@ -387,6 +391,7 @@ async function lookupLinkReputationOrThrow(hosts, options) {
|
|
|
387
391
|
const answered = new Map();
|
|
388
392
|
const client = activeClient();
|
|
389
393
|
if (mayAsk && client && nowMs >= backoffUntilMs && (missingHosts.length || missingUrls.length)) {
|
|
394
|
+
var _options_deadlineMs;
|
|
390
395
|
let looked = 0;
|
|
391
396
|
const ask = async (key)=>{
|
|
392
397
|
if (Date.now() < backoffUntilMs) return null;
|
|
@@ -440,15 +445,7 @@ async function lookupLinkReputationOrThrow(hosts, options) {
|
|
|
440
445
|
if (hostVerdict == null ? void 0 : hostVerdict.threats.length) return;
|
|
441
446
|
await Promise.all(missingUrls.filter((url)=>hostOf.get(url) === host).map((url)=>ask(url)));
|
|
442
447
|
});
|
|
443
|
-
|
|
444
|
-
await Promise.race([
|
|
445
|
-
Promise.allSettled(pipelines),
|
|
446
|
-
new Promise((resolve)=>{
|
|
447
|
-
var _options_deadlineMs;
|
|
448
|
-
timer = setTimeout(resolve, (_options_deadlineMs = options.deadlineMs) != null ? _options_deadlineMs : WEB_RISK_DEADLINE_MS);
|
|
449
|
-
})
|
|
450
|
-
]);
|
|
451
|
-
if (timer) clearTimeout(timer);
|
|
448
|
+
await boundedAwait(Promise.allSettled(pipelines), (_options_deadlineMs = options.deadlineMs) != null ? _options_deadlineMs : WEB_RISK_DEADLINE_MS, undefined, 'web-risk.lookup');
|
|
452
449
|
result.looked = looked;
|
|
453
450
|
const late = [
|
|
454
451
|
...missingHosts,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/web-risk.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n/*==========================================\n * GOOGLE WEB RISK (AGL-3451): the lookup behind the link reputation screen.\n *\n * `@aglyn/shared-util-email/link-reputation` decides which hosts are foreign\n * and turns a listing into a strong `web-risk-link` signal. This is the store\n * half: the `uris.search` client, the cache, the switches.\n *\n * ## The client\n *\n * {@link WebRiskClient} is the contract: one URI in, the threat types it is\n * listed for and how long that answer may be kept. The default\n * implementation calls `webrisk.googleapis.com/v1/uris:search` for\n * SOCIAL_ENGINEERING, MALWARE and UNWANTED_SOFTWARE as the platform's own\n * service account — the firebase-admin credential every other server-side\n * Google call here already uses (`client-error-report.ts`), so there is no\n * key of its own. What is sent depends on the lookup mode, below.\n *\n * ## The lookup mode (AGL-3459)\n *\n * `platformSettings/webRisk.lookupMode`:\n *\n * - `'host'` — the default, and anything that is not `'url'`. Only\n * `https://<host>/` is ever sent.\n * - `'url'` — the host first, from the cache or a call; then, for a host\n * that is not itself listed, each of its links' addresses as\n * `normalizeReputationLink` reduced them: scheme, host and path, never a\n * query string, a fragment or a `user:password@`. A listed address is a\n * hit on a clean host, so a kit at a path on a compromised site holds the\n * page. At most `MAX_REPUTATION_URLS_PER_LOOKUP` addresses per lookup,\n * inside the same deadline as the hosts.\n *\n * `'url'` sends more than a host to Google, so it stays off until the\n * Subprocessors page names that purpose — see the `webrisk.googleapis.com`\n * entry in `apps/console/constants/subprocessor-inventory.ts`.\n *\n * ## Not evidence when it fails\n *\n * A call is given {@link WEB_RISK_TIMEOUT_MS}, and a whole lookup\n * {@link WEB_RISK_DEADLINE_MS} — hosts and addresses together. A host or an\n * address whose answer did not arrive, or whose call failed, is UNKNOWN: it\n * holds nothing and logs a warning. A credential the API refuses (the API is\n * not enabled on a self-hosted project, say) backs the lookup off for a\n * quarter of an hour, so a misconfiguration is one log line rather than one\n * per page.\n *\n * ## The cache\n *\n * Per host and per address, in two layers, with the same rules for both:\n *\n * - this process's memory — a listing until the answer's `expireTime`,\n * which Web Risk sets and which may not be exceeded; a clean answer for\n * {@link WEB_RISK_MEMORY_CLEAN_TTL_MS}, short so a listing the daily\n * re-check writes reaches every warm render within minutes. Capped at\n * {@link WEB_RISK_MEMORY_MAX_ENTRIES}, then started again;\n * - the store, server-only, a clean answer kept {@link WEB_RISK_CLEAN_TTL_MS}:\n * `webRiskVerdicts/{host}`, bounded by the number of distinct foreign\n * hosts and rewritten in place; and `webRiskUrlVerdicts/{sha256(address)}`,\n * whose answers the daily re-check deletes a day after they expire\n * ({@link reapExpiredWebRiskUrlVerdicts}), because addresses are many more\n * than hosts and a path is worth keeping no longer than it is used.\n *\n * Read in one `getAll` per lookup, so a page's hosts and addresses cost one\n * round trip, and nothing at all once they are in memory.\n *\n * ## The kill switch\n *\n * `platformSettings/webRisk` `{ enabled: false }` stops every lookup within a\n * minute — every host reads unknown, so nothing holds on it — without a\n * deploy. Absent or anything else means on.\n *=========================================*/\n\nimport { createHash } from 'crypto'\nimport { TENANT_APEX } from '@aglyn/aglyn/app-utils/tenant-apex'\nimport {\n LINK_THREAT_TYPES,\n type LinkReputationAnswer,\n type LinkThreatType,\n MAX_REPUTATION_HOSTS_PER_LOOKUP,\n MAX_REPUTATION_URLS_PER_LOOKUP,\n normalizeReputationHost,\n normalizeReputationLink,\n pickReputationUrls,\n setLinkReputationLookup,\n} from '@aglyn/shared-util-email/link-reputation'\nimport { getApp } from 'firebase-admin/app'\nimport firebaseAdmin from './firebase-admin'\n\nexport const WEB_RISK_SEARCH_ENDPOINT = 'https://webrisk.googleapis.com/v1/uris:search'\n/** One answer per host. */\nexport const WEB_RISK_CACHE_COLLECTION = 'webRiskVerdicts'\n/** One answer per address, in `'url'` mode (AGL-3459), keyed by the address's SHA-256. */\nexport const WEB_RISK_URL_CACHE_COLLECTION = 'webRiskUrlVerdicts'\n/**\n * The switches: `{ enabled: false }` turns every lookup off;\n * `lookupMode: 'url'` looks up links' addresses as well as their hosts.\n */\nexport const WEB_RISK_SETTINGS_COLLECTION = 'platformSettings'\nexport const WEB_RISK_SETTINGS_DOC = 'webRisk'\n\n/** What a lookup sends: the host only (the default), or the address too. */\nexport type WebRiskLookupMode = 'host' | 'url'\n\n/** One call's budget. */\nexport const WEB_RISK_TIMEOUT_MS = 1_000\n/** One lookup's budget, however many hosts and addresses it asks about. */\nexport const WEB_RISK_DEADLINE_MS = 1_200\n/** How long a clean answer is kept in the store. */\nexport const WEB_RISK_CLEAN_TTL_MS = 12 * 60 * 60_000\n/** How long a clean answer is trusted from this process's memory. */\nexport const WEB_RISK_MEMORY_CLEAN_TTL_MS = 10 * 60_000\n/** A listing whose answer named no `expireTime` is kept this long. */\nexport const WEB_RISK_LISTED_FALLBACK_TTL_MS = 5 * 60_000\n/** The most answers this process remembers before it starts again. */\nexport const WEB_RISK_MEMORY_MAX_ENTRIES = 20_000\n/** How long past its expiry an address's stored answer is deleted. */\nexport const WEB_RISK_URL_VERDICT_GRACE_MS = 24 * 60 * 60_000\n/** How long the switches' answer is trusted. */\nconst SWITCH_TTL_MS = 60_000\n/** How long a refused credential stops the lookup. */\nconst REFUSED_BACKOFF_MS = 15 * 60_000\n/** How long a rate limit stops the lookup. */\nconst THROTTLED_BACKOFF_MS = 60_000\n/** At most one warning per this long, so an outage is not a log flood. */\nconst WARN_EVERY_MS = 60_000\n\n/** What `uris.search` answered for one URI. */\nexport interface WebRiskSearchResult {\n /** The lists it is on; empty when it is on none. */\n threats: LinkThreatType[]\n /** Until when the answer may be kept, when the API said. */\n expireTimeMs: number | null\n}\n\n/** The contract: one URI, its listing. Throws on anything but an answer. */\nexport interface WebRiskClient {\n searchUri(uri: string): Promise<WebRiskSearchResult>\n}\n\n/** The API answered with a status that is not an answer. */\nexport class WebRiskHttpError extends Error {\n constructor(\n readonly status: number,\n detail: string,\n ) {\n super(`Web Risk answered ${status}: ${detail.slice(0, 200)}`)\n this.name = 'WebRiskHttpError'\n }\n}\n\n/** There is no credential to call with. */\nexport class WebRiskUnavailableError extends Error {\n constructor(reason: string) {\n super(reason)\n this.name = 'WebRiskUnavailableError'\n }\n}\n\n/** `uris.search`'s body, read defensively: an empty object is \"not listed\". */\nexport function parseWebRiskSearch(body: unknown): WebRiskSearchResult {\n const threat = (body as { threat?: { threatTypes?: unknown; expireTime?: unknown } } | null)\n ?.threat\n if (!threat || typeof threat !== 'object') return { threats: [], expireTimeMs: null }\n const listed = Array.isArray(threat.threatTypes) ? threat.threatTypes : []\n const threats = LINK_THREAT_TYPES.filter((type) => listed.includes(type))\n const expireTimeMs =\n typeof threat.expireTime === 'string' ? Date.parse(threat.expireTime) : Number.NaN\n return { threats, expireTimeMs: Number.isFinite(expireTimeMs) ? expireTimeMs : null }\n}\n\n/** The URI a host is looked up as. Only the host: never a path or a query. */\nexport function webRiskUriForHost(host: string): string {\n return `https://${host}/`\n}\n\n/** The default client: `uris.search` over HTTPS with a bearer token. */\nexport function createWebRiskHttpClient(options: {\n accessToken: () => Promise<string | null>\n fetch?: typeof fetch\n timeoutMs?: number\n endpoint?: string\n}): WebRiskClient {\n const request = options.fetch ?? fetch\n const timeoutMs = options.timeoutMs ?? WEB_RISK_TIMEOUT_MS\n return {\n async searchUri(uri: string): Promise<WebRiskSearchResult> {\n const token = await options.accessToken()\n if (!token) throw new WebRiskUnavailableError('no service-account credential')\n const url = new URL(options.endpoint ?? WEB_RISK_SEARCH_ENDPOINT)\n for (const type of LINK_THREAT_TYPES) url.searchParams.append('threatTypes', type)\n url.searchParams.set('uri', uri)\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const response = await request(url.toString(), {\n method: 'GET',\n headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },\n signal: controller.signal,\n })\n if (!response.ok) {\n throw new WebRiskHttpError(response.status, await response.text().catch(() => ''))\n }\n return parseWebRiskSearch(await response.json())\n } finally {\n clearTimeout(timer)\n }\n },\n }\n}\n\n/**\n * The platform service account's access token, or null when this deployment\n * has none — the same credential `client-error-report.ts` writes logs with.\n */\nasync function serviceAccountAccessToken(): Promise<string | null> {\n try {\n const credential = getApp().options.credential\n if (!credential) return null\n return (await credential.getAccessToken())?.access_token ?? null\n } catch {\n return null\n }\n}\n\nlet defaultClient: WebRiskClient | null = null\nlet clientOverride: WebRiskClient | null | undefined\n\nfunction activeClient(): WebRiskClient | null {\n if (clientOverride !== undefined) return clientOverride\n defaultClient ??= createWebRiskHttpClient({ accessToken: serviceAccountAccessToken })\n return defaultClient\n}\n\ninterface Verdict {\n threats: LinkThreatType[]\n checkedAtMs: number\n /** When the answer stops being good, in the store. */\n expiresAtMs: number\n}\n\n/** Hosts and addresses share one memory: an address always holds `://`, a host never does. */\nconst memory = new Map<string, Verdict & { memoryUntilMs: number }>()\nlet settingsMemo: { off: boolean; mode: WebRiskLookupMode; atMs: number } | null = null\nlet backoffUntilMs = 0\nlet lastWarnAtMs = 0\n\n/** Test seam: forget the caches, the switches, the back-off; optionally stand in a client. */\nexport function resetWebRiskForTests(client?: WebRiskClient | null): void {\n memory.clear()\n settingsMemo = null\n backoffUntilMs = 0\n lastWarnAtMs = 0\n clientOverride = client\n defaultClient = null\n}\n\nfunction warn(nowMs: number, message: string, error?: unknown): void {\n if (nowMs - lastWarnAtMs < WARN_EVERY_MS) return\n lastWarnAtMs = nowMs\n console.warn(`[web-risk] ${message} — it reads unknown, which is not evidence`, error ?? '')\n}\n\n/**\n * The switches, read at most once a minute. An unreadable document is on,\n * in `'host'` mode; a mode that is not exactly `'url'` is `'host'`.\n */\nasync function webRiskSettings(nowMs: number): Promise<{ off: boolean; mode: WebRiskLookupMode }> {\n if (settingsMemo && nowMs - settingsMemo.atMs < SWITCH_TTL_MS) return settingsMemo\n const settings = await firebaseAdmin\n .app()\n .firestore()\n .collection(WEB_RISK_SETTINGS_COLLECTION)\n .doc(WEB_RISK_SETTINGS_DOC)\n .get()\n .then(\n (snapshot) => ({\n off: snapshot.get('enabled') === false,\n mode: snapshot.get('lookupMode') === 'url' ? ('url' as const) : ('host' as const),\n }),\n () => ({ off: false, mode: 'host' as const }),\n )\n settingsMemo = { ...settings, atMs: nowMs }\n return settings\n}\n\n/**\n * The lookup mode in force (AGL-3459): `'url'` only while the lookup is on\n * and `platformSettings/webRisk.lookupMode` is exactly `'url'`.\n */\nexport async function webRiskLookupMode(nowMs: number = Date.now()): Promise<WebRiskLookupMode> {\n const settings = await webRiskSettings(nowMs)\n return settings.off ? 'host' : settings.mode\n}\n\n/**\n * Hosts that are the platform's own: every site's subdomain on the tenant\n * apex. Never looked up — a neighbour's site is screened on its own page.\n */\nexport function platformReputationExclusions(): string[] {\n return [TENANT_APEX].filter(Boolean)\n}\n\nfunction isPlatformHost(host: string): boolean {\n return platformReputationExclusions().some(\n (domain) => host === domain || host.endsWith(`.${domain}`),\n )\n}\n\n/** An address's document id in {@link WEB_RISK_URL_CACHE_COLLECTION}: an address holds `/`, a hash does not. */\nexport function webRiskUrlVerdictId(url: string): string {\n return createHash('sha256').update(url).digest('hex')\n}\n\nfunction isAddress(key: string): boolean {\n return key.includes('://')\n}\n\n/** A stored answer, or null. An address's must name the address it was asked for. */\nfunction verdictFrom(data: Record<string, unknown> | undefined, url?: string): Verdict | null {\n if (!data) return null\n if (url !== undefined && data['url'] !== url) return null\n const threats = Array.isArray(data['threats'])\n ? LINK_THREAT_TYPES.filter((type) => (data['threats'] as unknown[]).includes(type))\n : []\n const checkedAtMs = Number(data['checkedAtMs'])\n const expiresAtMs = Number(data['expiresAtMs'])\n if (!Number.isFinite(checkedAtMs) || !Number.isFinite(expiresAtMs)) return null\n return { threats, checkedAtMs, expiresAtMs }\n}\n\nfunction remember(key: string, verdict: Verdict, nowMs: number): void {\n if (memory.size >= WEB_RISK_MEMORY_MAX_ENTRIES && !memory.has(key)) memory.clear()\n memory.set(key, {\n ...verdict,\n memoryUntilMs: verdict.threats.length\n ? verdict.expiresAtMs\n : Math.min(verdict.expiresAtMs, nowMs + WEB_RISK_MEMORY_CLEAN_TTL_MS),\n })\n}\n\nfunction fromMemory(key: string, nowMs: number): Verdict | null {\n const known = memory.get(key)\n return known && nowMs < known.memoryUntilMs ? known : null\n}\n\nexport interface HostReputationLookupOptions {\n /** How long the caller will wait, overall. */\n deadlineMs?: number\n /** The most hosts asked about; the rest read unknown. */\n maxHosts?: number\n /**\n * The links' addresses, looked up on the hosts asked about in `'url'`\n * mode (AGL-3459) and ignored in `'host'` mode. Normalized again here, so\n * a query string handed in is still never sent.\n */\n urls?: readonly string[]\n /** The most addresses asked about ({@link MAX_REPUTATION_URLS_PER_LOOKUP}); the rest are not asked about. */\n maxUrls?: number\n nowMs?: number\n}\n\n/** What a lookup answered, and how many calls it made to the API. */\nexport interface HostReputationLookupResult extends LinkReputationAnswer {\n looked: number\n}\n\n/**\n * The reputation of each host — and, in `'url'` mode, of each address on a\n * host that is not itself listed: listed, clean, or unknown. Platform hosts\n * are clean without asking. Never throws, and never waits on the API longer\n * than the deadline: a call still running then finishes in the background\n * and fills the caches for the next asker.\n *\n * \"Never throws\" covers the store as well as the API: a missing app, a\n * settings read that fails before it is a promise, or a cache reference\n * that cannot be built all read every host as unknown. The page review\n * calls this before its own fail-closed `try`, so a throw here would take\n * down every page with a link off the site.\n */\nexport async function lookupLinkReputation(\n hosts: readonly string[],\n options: HostReputationLookupOptions = {},\n): Promise<HostReputationLookupResult> {\n try {\n return await lookupLinkReputationOrThrow(hosts, options)\n } catch (error) {\n warn(options.nowMs ?? Date.now(), 'a lookup failed before it could answer', error)\n const unknown = [\n ...new Set(\n hosts\n .map((raw) => normalizeReputationHost(raw))\n .filter((host): host is string => Boolean(host)),\n ),\n ]\n return { hits: [], clean: [], unknown, looked: 0 }\n }\n}\n\nasync function lookupLinkReputationOrThrow(\n hosts: readonly string[],\n options: HostReputationLookupOptions,\n): Promise<HostReputationLookupResult> {\n const nowMs = options.nowMs ?? Date.now()\n const result: HostReputationLookupResult = { hits: [], clean: [], unknown: [], looked: 0 }\n const wanted: string[] = []\n for (const raw of hosts) {\n const host = normalizeReputationHost(raw)\n if (!host || wanted.includes(host) || result.clean.includes(host)) continue\n if (isPlatformHost(host)) result.clean.push(host)\n else if (wanted.length < (options.maxHosts ?? MAX_REPUTATION_HOSTS_PER_LOOKUP)) wanted.push(host)\n else result.unknown.push(host)\n }\n if (!wanted.length) return result\n let urls = options.urls?.length\n ? pickReputationUrls(options.urls, wanted, options.maxUrls ?? MAX_REPUTATION_URLS_PER_LOOKUP)\n : []\n const hostOf = new Map(urls.map((url) => [url, normalizeReputationLink(url)?.host ?? '']))\n\n // What is known before the API is asked, host or address.\n const known = new Map<string, Verdict>()\n const unknownTo = (keys: readonly string[]) =>\n keys.filter((key) => {\n const verdict = fromMemory(key, nowMs)\n if (verdict) known.set(key, verdict)\n return !verdict\n })\n\n // 1. This process's memory, for the hosts.\n let missingHosts = unknownTo(wanted)\n let missingUrls: string[] = []\n let mayAsk = true\n\n // 2. The switches — read only when a host is left to ask about, or\n // addresses wait on the mode. Off reads everything left as unknown.\n if (missingHosts.length || urls.length) {\n const settings = await webRiskSettings(nowMs)\n if (settings.off || settings.mode !== 'url') urls = []\n if (settings.off) mayAsk = false\n // An address on a host already known to be listed adds nothing.\n urls = urls.filter((url) => !known.get(hostOf.get(url) ?? '')?.threats.length)\n // 1b. This process's memory, for the addresses.\n missingUrls = unknownTo(urls)\n }\n\n // 3. The store, hosts and addresses in one round trip.\n const firestore = firebaseAdmin.app().firestore()\n const hostCollection = firestore.collection(WEB_RISK_CACHE_COLLECTION)\n const urlCollection = firestore.collection(WEB_RISK_URL_CACHE_COLLECTION)\n if (mayAsk && (missingHosts.length || missingUrls.length)) {\n const keys = [...missingHosts, ...missingUrls]\n try {\n const snapshots = await firestore.getAll(\n ...keys.map((key) =>\n isAddress(key) ? urlCollection.doc(webRiskUrlVerdictId(key)) : hostCollection.doc(key),\n ),\n )\n snapshots.forEach((snapshot, index) => {\n const key = keys[index]\n const data = snapshot.exists ? (snapshot.data() as Record<string, unknown>) : undefined\n const verdict = verdictFrom(data, isAddress(key) ? key : undefined)\n if (!verdict || nowMs >= verdict.expiresAtMs) return\n remember(key, verdict, nowMs)\n known.set(key, verdict)\n })\n missingHosts = missingHosts.filter((host) => !known.has(host))\n missingUrls = missingUrls.filter((url) => !known.has(url))\n } catch (error) {\n warn(nowMs, 'the verdict cache could not be read', error)\n }\n }\n\n // 4. The API, everything left at once, within the deadline: each host\n // first, then — unless the host itself is listed — its addresses.\n const answered = new Map<string, Verdict>()\n const client = activeClient()\n if (mayAsk && client && nowMs >= backoffUntilMs && (missingHosts.length || missingUrls.length)) {\n let looked = 0\n const ask = async (key: string): Promise<Verdict | null> => {\n if (Date.now() < backoffUntilMs) return null\n looked += 1\n try {\n const answer = await client.searchUri(isAddress(key) ? key : webRiskUriForHost(key))\n const checkedAtMs = Date.now()\n const verdict: Verdict = {\n threats: answer.threats,\n checkedAtMs,\n expiresAtMs: answer.threats.length\n ? (answer.expireTimeMs ?? checkedAtMs + WEB_RISK_LISTED_FALLBACK_TTL_MS)\n : checkedAtMs + WEB_RISK_CLEAN_TTL_MS,\n }\n answered.set(key, verdict)\n remember(key, verdict, checkedAtMs)\n const document = isAddress(key)\n ? urlCollection\n .doc(webRiskUrlVerdictId(key))\n .set({ host: hostOf.get(key) ?? '', url: key, ...verdict, listed: verdict.threats.length > 0 })\n : hostCollection.doc(key).set({ host: key, ...verdict, listed: verdict.threats.length > 0 })\n await document.catch((error: unknown) => warn(checkedAtMs, 'a verdict could not be cached', error))\n return verdict\n } catch (error) {\n if (error instanceof WebRiskHttpError && [401, 403, 404].includes(error.status)) {\n backoffUntilMs = Date.now() + REFUSED_BACKOFF_MS\n console.error('[web-risk] the API refused the credential — lookups paused for 15 minutes', error)\n } else if (error instanceof WebRiskHttpError && error.status === 429) {\n backoffUntilMs = Date.now() + THROTTLED_BACKOFF_MS\n warn(Date.now(), 'the API is rate limiting', error)\n } else if (error instanceof WebRiskUnavailableError) {\n backoffUntilMs = Date.now() + REFUSED_BACKOFF_MS\n warn(Date.now(), 'no credential to call the API with', error)\n } else {\n warn(Date.now(), `the lookup of ${key} failed or timed out`, error)\n }\n return null\n }\n }\n const pipelines = wanted.map(async (host) => {\n const hostVerdict = known.get(host) ?? (missingHosts.includes(host) ? await ask(host) : null)\n if (hostVerdict?.threats.length) return\n await Promise.all(missingUrls.filter((url) => hostOf.get(url) === host).map((url) => ask(url)))\n })\n let timer: ReturnType<typeof setTimeout> | undefined\n await Promise.race([\n Promise.allSettled(pipelines),\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, options.deadlineMs ?? WEB_RISK_DEADLINE_MS)\n }),\n ])\n if (timer) clearTimeout(timer)\n result.looked = looked\n const late = [...missingHosts, ...missingUrls].filter((key) => !answered.has(key)).length\n if (late) warn(Date.now(), `${late} host(s) or address(es) had no answer within the deadline`)\n }\n\n // 5. The answer. An address on a listed host is not reported: the host's\n // listing already names it.\n const verdictOf = (key: string) => known.get(key) ?? answered.get(key)\n for (const host of wanted) {\n const verdict = verdictOf(host)\n if (!verdict) result.unknown.push(host)\n else if (verdict.threats.length) result.hits.push({ host, threats: verdict.threats })\n else result.clean.push(host)\n }\n for (const url of urls) {\n const host = hostOf.get(url) ?? ''\n if (verdictOf(host)?.threats.length) continue\n const verdict = verdictOf(url)\n if (!verdict) result.unknown.push(url)\n else if (verdict.threats.length) result.hits.push({ host, url, threats: verdict.threats })\n else result.clean.push(url)\n }\n return result\n}\n\n/**\n * Delete the addresses' stored answers that expired more than\n * {@link WEB_RISK_URL_VERDICT_GRACE_MS} ago (AGL-3459), at most `limit` at a\n * time. The daily re-check calls it once per walk. A host's answers are not\n * reaped: they are few and rewritten in place. Never throws.\n */\nexport async function reapExpiredWebRiskUrlVerdicts(\n options: { nowMs?: number; limit?: number; firestore?: FirebaseFirestore.Firestore } = {},\n): Promise<number> {\n const nowMs = options.nowMs ?? Date.now()\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n try {\n const stale = await firestore\n .collection(WEB_RISK_URL_CACHE_COLLECTION)\n .where('expiresAtMs', '<', nowMs - WEB_RISK_URL_VERDICT_GRACE_MS)\n .limit(options.limit ?? 500)\n .get()\n await Promise.all(stale.docs.map((snapshot) => snapshot.ref.delete()))\n return stale.docs.length\n } catch (error) {\n console.warn('[web-risk] expired address verdicts could not be deleted', error)\n return 0\n }\n}\n\n/**\n * Puts this lookup behind the shared screen's reputation seam, so the send\n * seam (`screenTenantMessage`) asks it. Called by\n * `installOutboundScreenGate`, at that module's load.\n */\nexport function installLinkReputationLookup(): void {\n if (typeof setLinkReputationLookup !== 'function') return\n setLinkReputationLookup((hosts, options) =>\n lookupLinkReputation(hosts, { deadlineMs: options?.deadlineMs, urls: options?.urls }),\n )\n}\n"],"names":["createHash","TENANT_APEX","LINK_THREAT_TYPES","MAX_REPUTATION_HOSTS_PER_LOOKUP","MAX_REPUTATION_URLS_PER_LOOKUP","normalizeReputationHost","normalizeReputationLink","pickReputationUrls","setLinkReputationLookup","getApp","firebaseAdmin","WEB_RISK_SEARCH_ENDPOINT","WEB_RISK_CACHE_COLLECTION","WEB_RISK_URL_CACHE_COLLECTION","WEB_RISK_SETTINGS_COLLECTION","WEB_RISK_SETTINGS_DOC","WEB_RISK_TIMEOUT_MS","WEB_RISK_DEADLINE_MS","WEB_RISK_CLEAN_TTL_MS","WEB_RISK_MEMORY_CLEAN_TTL_MS","WEB_RISK_LISTED_FALLBACK_TTL_MS","WEB_RISK_MEMORY_MAX_ENTRIES","WEB_RISK_URL_VERDICT_GRACE_MS","SWITCH_TTL_MS","REFUSED_BACKOFF_MS","THROTTLED_BACKOFF_MS","WARN_EVERY_MS","WebRiskHttpError","Error","status","detail","slice","name","WebRiskUnavailableError","reason","parseWebRiskSearch","body","threat","threats","expireTimeMs","listed","Array","isArray","threatTypes","filter","type","includes","expireTime","Date","parse","Number","NaN","isFinite","webRiskUriForHost","host","createWebRiskHttpClient","options","request","fetch","timeoutMs","searchUri","uri","token","accessToken","url","URL","endpoint","searchParams","append","set","controller","AbortController","timer","setTimeout","abort","response","toString","method","headers","Authorization","Accept","signal","ok","text","catch","json","clearTimeout","serviceAccountAccessToken","credential","getAccessToken","access_token","defaultClient","clientOverride","activeClient","undefined","memory","Map","settingsMemo","backoffUntilMs","lastWarnAtMs","resetWebRiskForTests","client","clear","warn","nowMs","message","error","console","webRiskSettings","atMs","settings","app","firestore","collection","doc","get","then","snapshot","off","mode","webRiskLookupMode","now","platformReputationExclusions","Boolean","isPlatformHost","some","domain","endsWith","webRiskUrlVerdictId","update","digest","isAddress","key","verdictFrom","data","checkedAtMs","expiresAtMs","remember","verdict","size","has","memoryUntilMs","length","Math","min","fromMemory","known","lookupLinkReputation","hosts","lookupLinkReputationOrThrow","unknown","Set","map","raw","hits","clean","looked","result","wanted","push","maxHosts","urls","maxUrls","hostOf","unknownTo","keys","missingHosts","missingUrls","mayAsk","hostCollection","urlCollection","snapshots","getAll","forEach","index","exists","answered","ask","answer","document","pipelines","hostVerdict","Promise","all","race","allSettled","resolve","deadlineMs","late","verdictOf","reapExpiredWebRiskUrlVerdicts","stale","where","limit","docs","ref","delete","installLinkReputationLookup"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/web-risk.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n/*==========================================\n * GOOGLE WEB RISK (AGL-3451): the lookup behind the link reputation screen.\n *\n * `@aglyn/shared-util-email/link-reputation` decides which hosts are foreign\n * and turns a listing into a strong `web-risk-link` signal. This is the store\n * half: the `uris.search` client, the cache, the switches.\n *\n * ## The client\n *\n * {@link WebRiskClient} is the contract: one URI in, the threat types it is\n * listed for and how long that answer may be kept. The default\n * implementation calls `webrisk.googleapis.com/v1/uris:search` for\n * SOCIAL_ENGINEERING, MALWARE and UNWANTED_SOFTWARE as the platform's own\n * service account — the firebase-admin credential every other server-side\n * Google call here already uses (`client-error-report.ts`), so there is no\n * key of its own. What is sent depends on the lookup mode, below.\n *\n * ## The lookup mode (AGL-3459)\n *\n * `platformSettings/webRisk.lookupMode`:\n *\n * - `'host'` — the default, and anything that is not `'url'`. Only\n * `https://<host>/` is ever sent.\n * - `'url'` — the host first, from the cache or a call; then, for a host\n * that is not itself listed, each of its links' addresses as\n * `normalizeReputationLink` reduced them: scheme, host and path, never a\n * query string, a fragment or a `user:password@`. A listed address is a\n * hit on a clean host, so a kit at a path on a compromised site holds the\n * page. At most `MAX_REPUTATION_URLS_PER_LOOKUP` addresses per lookup,\n * inside the same deadline as the hosts.\n *\n * `'url'` sends more than a host to Google, so it stays off until the\n * Subprocessors page names that purpose — see the `webrisk.googleapis.com`\n * entry in `apps/console/constants/subprocessor-inventory.ts`.\n *\n * ## Not evidence when it fails\n *\n * A call is given {@link WEB_RISK_TIMEOUT_MS}, and a whole lookup\n * {@link WEB_RISK_DEADLINE_MS} — hosts and addresses together. A host or an\n * address whose answer did not arrive, or whose call failed, is UNKNOWN: it\n * holds nothing and logs a warning. A credential the API refuses (the API is\n * not enabled on a self-hosted project, say) backs the lookup off for a\n * quarter of an hour, so a misconfiguration is one log line rather than one\n * per page.\n *\n * ## The cache\n *\n * Per host and per address, in two layers, with the same rules for both:\n *\n * - this process's memory — a listing until the answer's `expireTime`,\n * which Web Risk sets and which may not be exceeded; a clean answer for\n * {@link WEB_RISK_MEMORY_CLEAN_TTL_MS}, short so a listing the daily\n * re-check writes reaches every warm render within minutes. Capped at\n * {@link WEB_RISK_MEMORY_MAX_ENTRIES}, then started again;\n * - the store, server-only, a clean answer kept {@link WEB_RISK_CLEAN_TTL_MS}:\n * `webRiskVerdicts/{host}`, bounded by the number of distinct foreign\n * hosts and rewritten in place; and `webRiskUrlVerdicts/{sha256(address)}`,\n * whose answers the daily re-check deletes a day after they expire\n * ({@link reapExpiredWebRiskUrlVerdicts}), because addresses are many more\n * than hosts and a path is worth keeping no longer than it is used.\n *\n * Read in one `getAll` per lookup, so a page's hosts and addresses cost one\n * round trip, and nothing at all once they are in memory.\n *\n * ## The kill switch\n *\n * `platformSettings/webRisk` `{ enabled: false }` stops every lookup within a\n * minute — every host reads unknown, so nothing holds on it — without a\n * deploy. Absent or anything else means on.\n *=========================================*/\n\nimport { createHash } from 'crypto'\nimport { TENANT_APEX } from '@aglyn/aglyn/app-utils/tenant-apex'\nimport {\n LINK_THREAT_TYPES,\n type LinkReputationAnswer,\n type LinkThreatType,\n MAX_REPUTATION_HOSTS_PER_LOOKUP,\n MAX_REPUTATION_URLS_PER_LOOKUP,\n normalizeReputationHost,\n normalizeReputationLink,\n pickReputationUrls,\n setLinkReputationLookup,\n} from '@aglyn/shared-util-email/link-reputation'\nimport { boundedAwait } from '@aglyn/shared-util-http/bounded-await'\nimport { getApp } from 'firebase-admin/app'\nimport firebaseAdmin from './firebase-admin'\n\nexport const WEB_RISK_SEARCH_ENDPOINT = 'https://webrisk.googleapis.com/v1/uris:search'\n/** One answer per host. */\nexport const WEB_RISK_CACHE_COLLECTION = 'webRiskVerdicts'\n/** One answer per address, in `'url'` mode (AGL-3459), keyed by the address's SHA-256. */\nexport const WEB_RISK_URL_CACHE_COLLECTION = 'webRiskUrlVerdicts'\n/**\n * The switches: `{ enabled: false }` turns every lookup off;\n * `lookupMode: 'url'` looks up links' addresses as well as their hosts.\n */\nexport const WEB_RISK_SETTINGS_COLLECTION = 'platformSettings'\nexport const WEB_RISK_SETTINGS_DOC = 'webRisk'\n\n/** What a lookup sends: the host only (the default), or the address too. */\nexport type WebRiskLookupMode = 'host' | 'url'\n\n/** One call's budget. */\nexport const WEB_RISK_TIMEOUT_MS = 1_000\n/** One lookup's budget, however many hosts and addresses it asks about. */\nexport const WEB_RISK_DEADLINE_MS = 1_200\n/** The service account's token exchange; a cached token answers at once. */\nexport const WEB_RISK_TOKEN_DEADLINE_MS = 3_000\n/** How long a clean answer is kept in the store. */\nexport const WEB_RISK_CLEAN_TTL_MS = 12 * 60 * 60_000\n/** How long a clean answer is trusted from this process's memory. */\nexport const WEB_RISK_MEMORY_CLEAN_TTL_MS = 10 * 60_000\n/** A listing whose answer named no `expireTime` is kept this long. */\nexport const WEB_RISK_LISTED_FALLBACK_TTL_MS = 5 * 60_000\n/** The most answers this process remembers before it starts again. */\nexport const WEB_RISK_MEMORY_MAX_ENTRIES = 20_000\n/** How long past its expiry an address's stored answer is deleted. */\nexport const WEB_RISK_URL_VERDICT_GRACE_MS = 24 * 60 * 60_000\n/** How long the switches' answer is trusted. */\nconst SWITCH_TTL_MS = 60_000\n/** How long a refused credential stops the lookup. */\nconst REFUSED_BACKOFF_MS = 15 * 60_000\n/** How long a rate limit stops the lookup. */\nconst THROTTLED_BACKOFF_MS = 60_000\n/** At most one warning per this long, so an outage is not a log flood. */\nconst WARN_EVERY_MS = 60_000\n\n/** What `uris.search` answered for one URI. */\nexport interface WebRiskSearchResult {\n /** The lists it is on; empty when it is on none. */\n threats: LinkThreatType[]\n /** Until when the answer may be kept, when the API said. */\n expireTimeMs: number | null\n}\n\n/** The contract: one URI, its listing. Throws on anything but an answer. */\nexport interface WebRiskClient {\n searchUri(uri: string): Promise<WebRiskSearchResult>\n}\n\n/** The API answered with a status that is not an answer. */\nexport class WebRiskHttpError extends Error {\n constructor(\n readonly status: number,\n detail: string,\n ) {\n super(`Web Risk answered ${status}: ${detail.slice(0, 200)}`)\n this.name = 'WebRiskHttpError'\n }\n}\n\n/** There is no credential to call with. */\nexport class WebRiskUnavailableError extends Error {\n constructor(reason: string) {\n super(reason)\n this.name = 'WebRiskUnavailableError'\n }\n}\n\n/** `uris.search`'s body, read defensively: an empty object is \"not listed\". */\nexport function parseWebRiskSearch(body: unknown): WebRiskSearchResult {\n const threat = (body as { threat?: { threatTypes?: unknown; expireTime?: unknown } } | null)\n ?.threat\n if (!threat || typeof threat !== 'object') return { threats: [], expireTimeMs: null }\n const listed = Array.isArray(threat.threatTypes) ? threat.threatTypes : []\n const threats = LINK_THREAT_TYPES.filter((type) => listed.includes(type))\n const expireTimeMs =\n typeof threat.expireTime === 'string' ? Date.parse(threat.expireTime) : Number.NaN\n return { threats, expireTimeMs: Number.isFinite(expireTimeMs) ? expireTimeMs : null }\n}\n\n/** The URI a host is looked up as. Only the host: never a path or a query. */\nexport function webRiskUriForHost(host: string): string {\n return `https://${host}/`\n}\n\n/** The default client: `uris.search` over HTTPS with a bearer token. */\nexport function createWebRiskHttpClient(options: {\n accessToken: () => Promise<string | null>\n fetch?: typeof fetch\n timeoutMs?: number\n endpoint?: string\n}): WebRiskClient {\n const timeoutMs = options.timeoutMs ?? WEB_RISK_TIMEOUT_MS\n return {\n async searchUri(uri: string): Promise<WebRiskSearchResult> {\n const token = await options.accessToken()\n if (!token) throw new WebRiskUnavailableError('no service-account credential')\n const url = new URL(options.endpoint ?? WEB_RISK_SEARCH_ENDPOINT)\n for (const type of LINK_THREAT_TYPES) url.searchParams.append('threatTypes', type)\n url.searchParams.set('uri', uri)\n // A real deadline over the request AND its body: the signal alone did\n // not bound Next's patched fetch (AGL-3565). It still cancels the\n // request where it can.\n const answer = await boundedAwait(\n async (signal) => {\n const response = await (options.fetch ?? fetch)(url.toString(), {\n method: 'GET',\n headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },\n signal,\n })\n if (!response.ok) {\n throw new WebRiskHttpError(response.status, await response.text().catch(() => ''))\n }\n return parseWebRiskSearch(await response.json())\n },\n timeoutMs,\n null,\n 'web-risk.search',\n )\n if (!answer) throw new Error(`Web Risk did not answer within ${timeoutMs} ms`)\n return answer\n },\n }\n}\n\n/**\n * The platform service account's access token, or null when this deployment\n * has none — the same credential `client-error-report.ts` writes logs with.\n */\nasync function serviceAccountAccessToken(): Promise<string | null> {\n try {\n const credential = getApp().options.credential\n if (!credential) return null\n // A token exchange with Google, on a page render's path (AGL-3565).\n const token = await boundedAwait(\n credential.getAccessToken(),\n WEB_RISK_TOKEN_DEADLINE_MS,\n null,\n 'web-risk.access-token',\n )\n return token?.access_token ?? null\n } catch {\n return null\n }\n}\n\nlet defaultClient: WebRiskClient | null = null\nlet clientOverride: WebRiskClient | null | undefined\n\nfunction activeClient(): WebRiskClient | null {\n if (clientOverride !== undefined) return clientOverride\n defaultClient ??= createWebRiskHttpClient({ accessToken: serviceAccountAccessToken })\n return defaultClient\n}\n\ninterface Verdict {\n threats: LinkThreatType[]\n checkedAtMs: number\n /** When the answer stops being good, in the store. */\n expiresAtMs: number\n}\n\n/** Hosts and addresses share one memory: an address always holds `://`, a host never does. */\nconst memory = new Map<string, Verdict & { memoryUntilMs: number }>()\nlet settingsMemo: { off: boolean; mode: WebRiskLookupMode; atMs: number } | null = null\nlet backoffUntilMs = 0\nlet lastWarnAtMs = 0\n\n/** Test seam: forget the caches, the switches, the back-off; optionally stand in a client. */\nexport function resetWebRiskForTests(client?: WebRiskClient | null): void {\n memory.clear()\n settingsMemo = null\n backoffUntilMs = 0\n lastWarnAtMs = 0\n clientOverride = client\n defaultClient = null\n}\n\nfunction warn(nowMs: number, message: string, error?: unknown): void {\n if (nowMs - lastWarnAtMs < WARN_EVERY_MS) return\n lastWarnAtMs = nowMs\n console.warn(`[web-risk] ${message} — it reads unknown, which is not evidence`, error ?? '')\n}\n\n/**\n * The switches, read at most once a minute. An unreadable document is on,\n * in `'host'` mode; a mode that is not exactly `'url'` is `'host'`.\n */\nasync function webRiskSettings(nowMs: number): Promise<{ off: boolean; mode: WebRiskLookupMode }> {\n if (settingsMemo && nowMs - settingsMemo.atMs < SWITCH_TTL_MS) return settingsMemo\n const settings = await firebaseAdmin\n .app()\n .firestore()\n .collection(WEB_RISK_SETTINGS_COLLECTION)\n .doc(WEB_RISK_SETTINGS_DOC)\n .get()\n .then(\n (snapshot) => ({\n off: snapshot.get('enabled') === false,\n mode: snapshot.get('lookupMode') === 'url' ? ('url' as const) : ('host' as const),\n }),\n () => ({ off: false, mode: 'host' as const }),\n )\n settingsMemo = { ...settings, atMs: nowMs }\n return settings\n}\n\n/**\n * The lookup mode in force (AGL-3459): `'url'` only while the lookup is on\n * and `platformSettings/webRisk.lookupMode` is exactly `'url'`.\n */\nexport async function webRiskLookupMode(nowMs: number = Date.now()): Promise<WebRiskLookupMode> {\n const settings = await webRiskSettings(nowMs)\n return settings.off ? 'host' : settings.mode\n}\n\n/**\n * Hosts that are the platform's own: every site's subdomain on the tenant\n * apex. Never looked up — a neighbour's site is screened on its own page.\n */\nexport function platformReputationExclusions(): string[] {\n return [TENANT_APEX].filter(Boolean)\n}\n\nfunction isPlatformHost(host: string): boolean {\n return platformReputationExclusions().some(\n (domain) => host === domain || host.endsWith(`.${domain}`),\n )\n}\n\n/** An address's document id in {@link WEB_RISK_URL_CACHE_COLLECTION}: an address holds `/`, a hash does not. */\nexport function webRiskUrlVerdictId(url: string): string {\n return createHash('sha256').update(url).digest('hex')\n}\n\nfunction isAddress(key: string): boolean {\n return key.includes('://')\n}\n\n/** A stored answer, or null. An address's must name the address it was asked for. */\nfunction verdictFrom(data: Record<string, unknown> | undefined, url?: string): Verdict | null {\n if (!data) return null\n if (url !== undefined && data['url'] !== url) return null\n const threats = Array.isArray(data['threats'])\n ? LINK_THREAT_TYPES.filter((type) => (data['threats'] as unknown[]).includes(type))\n : []\n const checkedAtMs = Number(data['checkedAtMs'])\n const expiresAtMs = Number(data['expiresAtMs'])\n if (!Number.isFinite(checkedAtMs) || !Number.isFinite(expiresAtMs)) return null\n return { threats, checkedAtMs, expiresAtMs }\n}\n\nfunction remember(key: string, verdict: Verdict, nowMs: number): void {\n if (memory.size >= WEB_RISK_MEMORY_MAX_ENTRIES && !memory.has(key)) memory.clear()\n memory.set(key, {\n ...verdict,\n memoryUntilMs: verdict.threats.length\n ? verdict.expiresAtMs\n : Math.min(verdict.expiresAtMs, nowMs + WEB_RISK_MEMORY_CLEAN_TTL_MS),\n })\n}\n\nfunction fromMemory(key: string, nowMs: number): Verdict | null {\n const known = memory.get(key)\n return known && nowMs < known.memoryUntilMs ? known : null\n}\n\nexport interface HostReputationLookupOptions {\n /** How long the caller will wait, overall. */\n deadlineMs?: number\n /** The most hosts asked about; the rest read unknown. */\n maxHosts?: number\n /**\n * The links' addresses, looked up on the hosts asked about in `'url'`\n * mode (AGL-3459) and ignored in `'host'` mode. Normalized again here, so\n * a query string handed in is still never sent.\n */\n urls?: readonly string[]\n /** The most addresses asked about ({@link MAX_REPUTATION_URLS_PER_LOOKUP}); the rest are not asked about. */\n maxUrls?: number\n nowMs?: number\n}\n\n/** What a lookup answered, and how many calls it made to the API. */\nexport interface HostReputationLookupResult extends LinkReputationAnswer {\n looked: number\n}\n\n/**\n * The reputation of each host — and, in `'url'` mode, of each address on a\n * host that is not itself listed: listed, clean, or unknown. Platform hosts\n * are clean without asking. Never throws, and never waits on the API longer\n * than the deadline: a call still running then finishes in the background\n * and fills the caches for the next asker.\n *\n * \"Never throws\" covers the store as well as the API: a missing app, a\n * settings read that fails before it is a promise, or a cache reference\n * that cannot be built all read every host as unknown. The page review\n * calls this before its own fail-closed `try`, so a throw here would take\n * down every page with a link off the site.\n */\nexport async function lookupLinkReputation(\n hosts: readonly string[],\n options: HostReputationLookupOptions = {},\n): Promise<HostReputationLookupResult> {\n try {\n return await lookupLinkReputationOrThrow(hosts, options)\n } catch (error) {\n warn(options.nowMs ?? Date.now(), 'a lookup failed before it could answer', error)\n const unknown = [\n ...new Set(\n hosts\n .map((raw) => normalizeReputationHost(raw))\n .filter((host): host is string => Boolean(host)),\n ),\n ]\n return { hits: [], clean: [], unknown, looked: 0 }\n }\n}\n\nasync function lookupLinkReputationOrThrow(\n hosts: readonly string[],\n options: HostReputationLookupOptions,\n): Promise<HostReputationLookupResult> {\n const nowMs = options.nowMs ?? Date.now()\n const result: HostReputationLookupResult = { hits: [], clean: [], unknown: [], looked: 0 }\n const wanted: string[] = []\n for (const raw of hosts) {\n const host = normalizeReputationHost(raw)\n if (!host || wanted.includes(host) || result.clean.includes(host)) continue\n if (isPlatformHost(host)) result.clean.push(host)\n else if (wanted.length < (options.maxHosts ?? MAX_REPUTATION_HOSTS_PER_LOOKUP)) wanted.push(host)\n else result.unknown.push(host)\n }\n if (!wanted.length) return result\n let urls = options.urls?.length\n ? pickReputationUrls(options.urls, wanted, options.maxUrls ?? MAX_REPUTATION_URLS_PER_LOOKUP)\n : []\n const hostOf = new Map(urls.map((url) => [url, normalizeReputationLink(url)?.host ?? '']))\n\n // What is known before the API is asked, host or address.\n const known = new Map<string, Verdict>()\n const unknownTo = (keys: readonly string[]) =>\n keys.filter((key) => {\n const verdict = fromMemory(key, nowMs)\n if (verdict) known.set(key, verdict)\n return !verdict\n })\n\n // 1. This process's memory, for the hosts.\n let missingHosts = unknownTo(wanted)\n let missingUrls: string[] = []\n let mayAsk = true\n\n // 2. The switches — read only when a host is left to ask about, or\n // addresses wait on the mode. Off reads everything left as unknown.\n if (missingHosts.length || urls.length) {\n const settings = await webRiskSettings(nowMs)\n if (settings.off || settings.mode !== 'url') urls = []\n if (settings.off) mayAsk = false\n // An address on a host already known to be listed adds nothing.\n urls = urls.filter((url) => !known.get(hostOf.get(url) ?? '')?.threats.length)\n // 1b. This process's memory, for the addresses.\n missingUrls = unknownTo(urls)\n }\n\n // 3. The store, hosts and addresses in one round trip.\n const firestore = firebaseAdmin.app().firestore()\n const hostCollection = firestore.collection(WEB_RISK_CACHE_COLLECTION)\n const urlCollection = firestore.collection(WEB_RISK_URL_CACHE_COLLECTION)\n if (mayAsk && (missingHosts.length || missingUrls.length)) {\n const keys = [...missingHosts, ...missingUrls]\n try {\n const snapshots = await firestore.getAll(\n ...keys.map((key) =>\n isAddress(key) ? urlCollection.doc(webRiskUrlVerdictId(key)) : hostCollection.doc(key),\n ),\n )\n snapshots.forEach((snapshot, index) => {\n const key = keys[index]\n const data = snapshot.exists ? (snapshot.data() as Record<string, unknown>) : undefined\n const verdict = verdictFrom(data, isAddress(key) ? key : undefined)\n if (!verdict || nowMs >= verdict.expiresAtMs) return\n remember(key, verdict, nowMs)\n known.set(key, verdict)\n })\n missingHosts = missingHosts.filter((host) => !known.has(host))\n missingUrls = missingUrls.filter((url) => !known.has(url))\n } catch (error) {\n warn(nowMs, 'the verdict cache could not be read', error)\n }\n }\n\n // 4. The API, everything left at once, within the deadline: each host\n // first, then — unless the host itself is listed — its addresses.\n const answered = new Map<string, Verdict>()\n const client = activeClient()\n if (mayAsk && client && nowMs >= backoffUntilMs && (missingHosts.length || missingUrls.length)) {\n let looked = 0\n const ask = async (key: string): Promise<Verdict | null> => {\n if (Date.now() < backoffUntilMs) return null\n looked += 1\n try {\n const answer = await client.searchUri(isAddress(key) ? key : webRiskUriForHost(key))\n const checkedAtMs = Date.now()\n const verdict: Verdict = {\n threats: answer.threats,\n checkedAtMs,\n expiresAtMs: answer.threats.length\n ? (answer.expireTimeMs ?? checkedAtMs + WEB_RISK_LISTED_FALLBACK_TTL_MS)\n : checkedAtMs + WEB_RISK_CLEAN_TTL_MS,\n }\n answered.set(key, verdict)\n remember(key, verdict, checkedAtMs)\n const document = isAddress(key)\n ? urlCollection\n .doc(webRiskUrlVerdictId(key))\n .set({ host: hostOf.get(key) ?? '', url: key, ...verdict, listed: verdict.threats.length > 0 })\n : hostCollection.doc(key).set({ host: key, ...verdict, listed: verdict.threats.length > 0 })\n await document.catch((error: unknown) => warn(checkedAtMs, 'a verdict could not be cached', error))\n return verdict\n } catch (error) {\n if (error instanceof WebRiskHttpError && [401, 403, 404].includes(error.status)) {\n backoffUntilMs = Date.now() + REFUSED_BACKOFF_MS\n console.error('[web-risk] the API refused the credential — lookups paused for 15 minutes', error)\n } else if (error instanceof WebRiskHttpError && error.status === 429) {\n backoffUntilMs = Date.now() + THROTTLED_BACKOFF_MS\n warn(Date.now(), 'the API is rate limiting', error)\n } else if (error instanceof WebRiskUnavailableError) {\n backoffUntilMs = Date.now() + REFUSED_BACKOFF_MS\n warn(Date.now(), 'no credential to call the API with', error)\n } else {\n warn(Date.now(), `the lookup of ${key} failed or timed out`, error)\n }\n return null\n }\n }\n const pipelines = wanted.map(async (host) => {\n const hostVerdict = known.get(host) ?? (missingHosts.includes(host) ? await ask(host) : null)\n if (hostVerdict?.threats.length) return\n await Promise.all(missingUrls.filter((url) => hostOf.get(url) === host).map((url) => ask(url)))\n })\n await boundedAwait(\n Promise.allSettled(pipelines),\n options.deadlineMs ?? WEB_RISK_DEADLINE_MS,\n undefined,\n 'web-risk.lookup',\n )\n result.looked = looked\n const late = [...missingHosts, ...missingUrls].filter((key) => !answered.has(key)).length\n if (late) warn(Date.now(), `${late} host(s) or address(es) had no answer within the deadline`)\n }\n\n // 5. The answer. An address on a listed host is not reported: the host's\n // listing already names it.\n const verdictOf = (key: string) => known.get(key) ?? answered.get(key)\n for (const host of wanted) {\n const verdict = verdictOf(host)\n if (!verdict) result.unknown.push(host)\n else if (verdict.threats.length) result.hits.push({ host, threats: verdict.threats })\n else result.clean.push(host)\n }\n for (const url of urls) {\n const host = hostOf.get(url) ?? ''\n if (verdictOf(host)?.threats.length) continue\n const verdict = verdictOf(url)\n if (!verdict) result.unknown.push(url)\n else if (verdict.threats.length) result.hits.push({ host, url, threats: verdict.threats })\n else result.clean.push(url)\n }\n return result\n}\n\n/**\n * Delete the addresses' stored answers that expired more than\n * {@link WEB_RISK_URL_VERDICT_GRACE_MS} ago (AGL-3459), at most `limit` at a\n * time. The daily re-check calls it once per walk. A host's answers are not\n * reaped: they are few and rewritten in place. Never throws.\n */\nexport async function reapExpiredWebRiskUrlVerdicts(\n options: { nowMs?: number; limit?: number; firestore?: FirebaseFirestore.Firestore } = {},\n): Promise<number> {\n const nowMs = options.nowMs ?? Date.now()\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n try {\n const stale = await firestore\n .collection(WEB_RISK_URL_CACHE_COLLECTION)\n .where('expiresAtMs', '<', nowMs - WEB_RISK_URL_VERDICT_GRACE_MS)\n .limit(options.limit ?? 500)\n .get()\n await Promise.all(stale.docs.map((snapshot) => snapshot.ref.delete()))\n return stale.docs.length\n } catch (error) {\n console.warn('[web-risk] expired address verdicts could not be deleted', error)\n return 0\n }\n}\n\n/**\n * Puts this lookup behind the shared screen's reputation seam, so the send\n * seam (`screenTenantMessage`) asks it. Called by\n * `installOutboundScreenGate`, at that module's load.\n */\nexport function installLinkReputationLookup(): void {\n if (typeof setLinkReputationLookup !== 'function') return\n setLinkReputationLookup((hosts, options) =>\n lookupLinkReputation(hosts, { deadlineMs: options?.deadlineMs, urls: options?.urls }),\n )\n}\n"],"names":["createHash","TENANT_APEX","LINK_THREAT_TYPES","MAX_REPUTATION_HOSTS_PER_LOOKUP","MAX_REPUTATION_URLS_PER_LOOKUP","normalizeReputationHost","normalizeReputationLink","pickReputationUrls","setLinkReputationLookup","boundedAwait","getApp","firebaseAdmin","WEB_RISK_SEARCH_ENDPOINT","WEB_RISK_CACHE_COLLECTION","WEB_RISK_URL_CACHE_COLLECTION","WEB_RISK_SETTINGS_COLLECTION","WEB_RISK_SETTINGS_DOC","WEB_RISK_TIMEOUT_MS","WEB_RISK_DEADLINE_MS","WEB_RISK_TOKEN_DEADLINE_MS","WEB_RISK_CLEAN_TTL_MS","WEB_RISK_MEMORY_CLEAN_TTL_MS","WEB_RISK_LISTED_FALLBACK_TTL_MS","WEB_RISK_MEMORY_MAX_ENTRIES","WEB_RISK_URL_VERDICT_GRACE_MS","SWITCH_TTL_MS","REFUSED_BACKOFF_MS","THROTTLED_BACKOFF_MS","WARN_EVERY_MS","WebRiskHttpError","Error","status","detail","slice","name","WebRiskUnavailableError","reason","parseWebRiskSearch","body","threat","threats","expireTimeMs","listed","Array","isArray","threatTypes","filter","type","includes","expireTime","Date","parse","Number","NaN","isFinite","webRiskUriForHost","host","createWebRiskHttpClient","options","timeoutMs","searchUri","uri","token","accessToken","url","URL","endpoint","searchParams","append","set","answer","signal","response","fetch","toString","method","headers","Authorization","Accept","ok","text","catch","json","serviceAccountAccessToken","credential","getAccessToken","access_token","defaultClient","clientOverride","activeClient","undefined","memory","Map","settingsMemo","backoffUntilMs","lastWarnAtMs","resetWebRiskForTests","client","clear","warn","nowMs","message","error","console","webRiskSettings","atMs","settings","app","firestore","collection","doc","get","then","snapshot","off","mode","webRiskLookupMode","now","platformReputationExclusions","Boolean","isPlatformHost","some","domain","endsWith","webRiskUrlVerdictId","update","digest","isAddress","key","verdictFrom","data","checkedAtMs","expiresAtMs","remember","verdict","size","has","memoryUntilMs","length","Math","min","fromMemory","known","lookupLinkReputation","hosts","lookupLinkReputationOrThrow","unknown","Set","map","raw","hits","clean","looked","result","wanted","push","maxHosts","urls","maxUrls","hostOf","unknownTo","keys","missingHosts","missingUrls","mayAsk","hostCollection","urlCollection","snapshots","getAll","forEach","index","exists","answered","ask","document","pipelines","hostVerdict","Promise","all","allSettled","deadlineMs","late","verdictOf","reapExpiredWebRiskUrlVerdicts","stale","where","limit","docs","ref","delete","installLinkReputationLookup"],"mappings":"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"}
|
package/src/tenancy.d.ts
CHANGED
|
@@ -117,8 +117,6 @@ export * from './lib/server/contact-company-link';
|
|
|
117
117
|
export * from './lib/server/contact-email-index';
|
|
118
118
|
export * from './lib/server/upsert-contact';
|
|
119
119
|
export * from './lib/server/crm-records';
|
|
120
|
-
export * from './lib/server/crm-next-activity';
|
|
121
|
-
export * from './lib/server/crm-email-activity';
|
|
122
120
|
export * from './lib/server/member-email-aliases';
|
|
123
121
|
export * from './lib/server/user-profiles';
|
|
124
122
|
export * from './lib/server/new-account-announcement';
|
package/src/tenancy.js
CHANGED
|
@@ -148,10 +148,8 @@ export * from "./lib/server/upsert-contact.js";
|
|
|
148
148
|
export * from "./lib/server/crm-records.js";
|
|
149
149
|
// The `nextTaskAtMs` a contact, a company and a deal carry, recomputed from
|
|
150
150
|
// their open tasks by every server-side task writer (AGL-2661).
|
|
151
|
-
export * from "./lib/server/crm-next-activity.js";
|
|
152
151
|
// Where a one-to-one email's activity row lives and how the delivery
|
|
153
152
|
// webhook advances it (AGL-2615).
|
|
154
|
-
export * from "./lib/server/crm-email-activity.js";
|
|
155
153
|
// A member's own addresses in a workspace, and the signed link that
|
|
156
154
|
// confirms one (AGL-2975).
|
|
157
155
|
export * from "./lib/server/member-email-aliases.js";
|
package/src/tenancy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/tenant/data/admin/src/tenancy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport * from './lib/server/analytics-retention'\nexport * from './lib/server/api-http'\nexport * from './lib/server/api-keys'\nexport * from './lib/server/account-emails'\nexport * from './lib/server/auth-pools'\nexport * from './lib/server/client-error-report'\n// The merchant's payment account, as a contract. Its adapter is not exported:\n// a plugin asks the contract, and the doors that only need the readiness rule\n// import `@aglyn/tenant-data-admin/server/payment-provider` directly, which\n// loads no Firebase.\nexport {\n merchantAccountIsReady,\n merchantAccountReadiness,\n paymentProvider,\n} from './lib/server/payment-provider'\nexport type {\n MerchantAccountReadiness,\n MerchantAccountReadinessInput,\n PaymentMode,\n PaymentProvider,\n PaymentProviderEvent,\n} from './lib/server/payment-provider'\nexport * from './lib/server/auth-handoff'\nexport * from './lib/server/console-domains'\nexport * from './lib/server/dns-probe'\nexport * from './lib/server/sending-domains'\nexport * from './lib/server/sender-readiness'\nexport * from './lib/server/host-sending-domain'\nexport * from './lib/server/sending-domain-debt'\nexport * from './lib/server/sending-domain-recheck'\n// The hosting seam itself, so a door can ask whether this deployment is able\n// to register a name at all before it promises the caller one.\n// `workspace-domains` re-exports the pieces its own callers share with it; the\n// provider behind it is not among them.\nexport { domainProvider } from './lib/server/domain-provider'\nexport type {\n DomainProvider,\n DomainProviderId,\n DomainScope,\n} from './lib/server/domain-provider'\nexport * from './lib/server/platform-domain-names'\nexport * from './lib/server/consume-once'\nexport * from './lib/server/csp-aggregate'\nexport * from './lib/server/data-storage-gate'\nexport * from './lib/server/contact-suppression'\nexport * from './lib/server/email-suppression'\n// The double opt-in quarantine, after the module whose keying it shares and\n// whose `filterTopicSendable` is the reader that makes it a quarantine rather\n// than a stored fact.\nexport * from './lib/server/topic-subscriptions'\nexport * from './lib/server/email-unsubscribe-link'\nexport * from './lib/server/email-flow-gate'\n// Side-effecting on purpose, for the reason `email-send-rate` below is:\n// evaluating this module INSTALLS the marketing gate on `sendEmail`, so the\n// unsubscribe header, the suppression check and the frequency ceiling reach\n// every bulk sender without a call at each server entrypoint.\nexport * from './lib/server/email-marketing-gate'\n// A consent group change (AGL-3320): the carries of the three stores above,\n// and the executor that runs a change around them.\nexport * from './lib/server/consent-group-carry'\nexport * from './lib/server/consent-group-change'\n// A person's refusal kept after the record it was written on is deleted\n// (AGL-3338): the delete's write, the list gate's read, and the change's carry.\nexport * from './lib/server/retained-refusals'\nexport * from './lib/server/sms-keywords'\nexport * from './lib/server/document-id'\nexport * from './lib/server/collection-preview-token'\nexport * from './lib/server/edit-access-authz'\nexport * from './lib/server/edit-access-token'\nexport * from './lib/server/edit-hint-token'\nexport * from './lib/server/email-metering'\n// Side-effecting on purpose (AGL-2409): evaluating this module INSTALLS the\n// platform send-rate governor on `sendEmail`. Every server surface already\n// imports this barrel, so nothing has to remember to call an installer.\nexport * from './lib/server/email-send-rate'\n// Side-effecting on purpose, for the same reason (AGL-3328): evaluating this\n// module INSTALLS the deliverability preflight on `sendEmail`, so a recipient\n// whose domain takes no mail is refused, and bulk mail behind a gateway that\n// refused its sending domain twice is held, on every send path at once.\nexport * from './lib/server/email-deliverability'\nexport * from './lib/server/email-sender-reputation'\nexport * from './lib/server/erase'\nexport * from './lib/server/erase-person'\nexport * from './lib/server/dynamic-list-materialize'\nexport * from './lib/server/firebase-admin'\nexport * from './lib/server/free-workspace-cap'\nexport * from './lib/server/ga4-measurement-protocol'\nexport * from './lib/server/host-memberships'\nexport * from './lib/server/host-visitor-records'\nexport * from './lib/server/list-members'\nexport * from './lib/server/lockdown'\nexport * from './lib/server/token-revocation'\nexport * from './lib/server/tenant-write-lockdown'\nexport * from './lib/server/outbound-send-review'\n// The same review for a published page, read where it is composed (AGL-3356).\nexport * from './lib/server/hosted-page-review'\n// Every hold, flag, lock and pause tells owners and staff through one seam (AGL-3368).\nexport * from './lib/server/risk-notice'\nexport * from './lib/server/host-email-tokens'\nexport * from './lib/server/notifications'\n// Every operator alert raises through one entry point, and every health check\n// remembers its state (AGL-3377).\nexport * from './lib/server/operator-alerts'\nexport * from './lib/server/operator-health'\nexport * from './lib/server/org-billing'\nexport * from './lib/server/organizations'\n// Platform staff take no customer seat, and a workspace is asked to upgrade\n// as a separate step after it is handed over (AGL-3466).\nexport * from './lib/server/staff-seat'\nexport * from './lib/server/upgrade-proposal'\nexport * from './lib/server/duplicate-activity'\nexport * from './lib/server/duplicate-resource'\nexport * from './lib/server/workspace-domains'\nexport * from './lib/server/membership-recover-throttle'\nexport * from './lib/server/password-reset-throttle'\nexport * from './lib/server/rate-limit-store'\nexport * from './lib/server/verify-email-cooldown'\nexport * from './lib/server/vercel-drain-signature'\nexport * from './lib/server/vercel-log-drain'\nexport * from './lib/server/visitor-write-rate-limit'\nexport * from './lib/server/card-payment-velocity'\nexport * from './lib/server/console-api-rate-limit'\nexport * from './lib/server/realm-plugins'\nexport * from './lib/server/release-flags'\nexport * from './lib/server/resolve-people'\nexport * from './lib/server/member-photo'\nexport * from './lib/server/personal-data-export'\nexport * from './lib/server/media-delivery'\nexport * from './lib/server/media-download-tokens'\nexport * from './lib/server/media-quarantine'\nexport * from './lib/server/media-signing'\nexport * from './lib/server/media-storage-path'\nexport * from './lib/server/safe-equal'\nexport * from './lib/server/media-strong-digest'\nexport * from './lib/server/media-tombstone'\nexport * from './lib/server/media-delivery-regeneration'\nexport * from './lib/server/media-variants'\nexport * from './lib/server/serve-media-cdn'\nexport * from './lib/server/serve-plugin-fetch'\nexport * from './lib/server/configured-url-fetch'\nexport * from './lib/server/sso-break-glass-owners'\nexport * from './lib/server/sso-domain-policy'\nexport * from './lib/server/sso-enforcement'\nexport * from './lib/server/sso-provisioning'\nexport * from './lib/server/update-existing'\nexport * from './lib/server/contact-company-link'\nexport * from './lib/server/contact-email-index'\nexport * from './lib/server/upsert-contact'\nexport * from './lib/server/crm-records'\n// The `nextTaskAtMs` a contact, a company and a deal carry, recomputed from\n// their open tasks by every server-side task writer (AGL-2661).\nexport * from './lib/server/crm-next-activity'\n// Where a one-to-one email's activity row lives and how the delivery\n// webhook advances it (AGL-2615).\nexport * from './lib/server/crm-email-activity'\n// A member's own addresses in a workspace, and the signed link that\n// confirms one (AGL-2975).\nexport * from './lib/server/member-email-aliases'\nexport * from './lib/server/user-profiles'\n// The one \"New account\" staff notice per account, keyed on the auth\n// record's creation (AGL-3225).\nexport * from './lib/server/new-account-announcement'\nexport * from './lib/server/legal-acceptance'\n// The person's decision about the platform's own product email, on their\n// document and on the operator's marketing contact (AGL-3185).\nexport * from './lib/server/platform-marketing-consent'\n"],"names":["merchantAccountIsReady","merchantAccountReadiness","paymentProvider","domainProvider"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/tenant/data/admin/src/tenancy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport * from './lib/server/analytics-retention'\nexport * from './lib/server/api-http'\nexport * from './lib/server/api-keys'\nexport * from './lib/server/account-emails'\nexport * from './lib/server/auth-pools'\nexport * from './lib/server/client-error-report'\n// The merchant's payment account, as a contract. Its adapter is not exported:\n// a plugin asks the contract, and the doors that only need the readiness rule\n// import `@aglyn/tenant-data-admin/server/payment-provider` directly, which\n// loads no Firebase.\nexport {\n merchantAccountIsReady,\n merchantAccountReadiness,\n paymentProvider,\n} from './lib/server/payment-provider'\nexport type {\n MerchantAccountReadiness,\n MerchantAccountReadinessInput,\n PaymentMode,\n PaymentProvider,\n PaymentProviderEvent,\n} from './lib/server/payment-provider'\nexport * from './lib/server/auth-handoff'\nexport * from './lib/server/console-domains'\nexport * from './lib/server/dns-probe'\nexport * from './lib/server/sending-domains'\nexport * from './lib/server/sender-readiness'\nexport * from './lib/server/host-sending-domain'\nexport * from './lib/server/sending-domain-debt'\nexport * from './lib/server/sending-domain-recheck'\n// The hosting seam itself, so a door can ask whether this deployment is able\n// to register a name at all before it promises the caller one.\n// `workspace-domains` re-exports the pieces its own callers share with it; the\n// provider behind it is not among them.\nexport { domainProvider } from './lib/server/domain-provider'\nexport type {\n DomainProvider,\n DomainProviderId,\n DomainScope,\n} from './lib/server/domain-provider'\nexport * from './lib/server/platform-domain-names'\nexport * from './lib/server/consume-once'\nexport * from './lib/server/csp-aggregate'\nexport * from './lib/server/data-storage-gate'\nexport * from './lib/server/contact-suppression'\nexport * from './lib/server/email-suppression'\n// The double opt-in quarantine, after the module whose keying it shares and\n// whose `filterTopicSendable` is the reader that makes it a quarantine rather\n// than a stored fact.\nexport * from './lib/server/topic-subscriptions'\nexport * from './lib/server/email-unsubscribe-link'\nexport * from './lib/server/email-flow-gate'\n// Side-effecting on purpose, for the reason `email-send-rate` below is:\n// evaluating this module INSTALLS the marketing gate on `sendEmail`, so the\n// unsubscribe header, the suppression check and the frequency ceiling reach\n// every bulk sender without a call at each server entrypoint.\nexport * from './lib/server/email-marketing-gate'\n// A consent group change (AGL-3320): the carries of the three stores above,\n// and the executor that runs a change around them.\nexport * from './lib/server/consent-group-carry'\nexport * from './lib/server/consent-group-change'\n// A person's refusal kept after the record it was written on is deleted\n// (AGL-3338): the delete's write, the list gate's read, and the change's carry.\nexport * from './lib/server/retained-refusals'\nexport * from './lib/server/sms-keywords'\nexport * from './lib/server/document-id'\nexport * from './lib/server/collection-preview-token'\nexport * from './lib/server/edit-access-authz'\nexport * from './lib/server/edit-access-token'\nexport * from './lib/server/edit-hint-token'\nexport * from './lib/server/email-metering'\n// Side-effecting on purpose (AGL-2409): evaluating this module INSTALLS the\n// platform send-rate governor on `sendEmail`. Every server surface already\n// imports this barrel, so nothing has to remember to call an installer.\nexport * from './lib/server/email-send-rate'\n// Side-effecting on purpose, for the same reason (AGL-3328): evaluating this\n// module INSTALLS the deliverability preflight on `sendEmail`, so a recipient\n// whose domain takes no mail is refused, and bulk mail behind a gateway that\n// refused its sending domain twice is held, on every send path at once.\nexport * from './lib/server/email-deliverability'\nexport * from './lib/server/email-sender-reputation'\nexport * from './lib/server/erase'\nexport * from './lib/server/erase-person'\nexport * from './lib/server/dynamic-list-materialize'\nexport * from './lib/server/firebase-admin'\nexport * from './lib/server/free-workspace-cap'\nexport * from './lib/server/ga4-measurement-protocol'\nexport * from './lib/server/host-memberships'\nexport * from './lib/server/host-visitor-records'\nexport * from './lib/server/list-members'\nexport * from './lib/server/lockdown'\nexport * from './lib/server/token-revocation'\nexport * from './lib/server/tenant-write-lockdown'\nexport * from './lib/server/outbound-send-review'\n// The same review for a published page, read where it is composed (AGL-3356).\nexport * from './lib/server/hosted-page-review'\n// Every hold, flag, lock and pause tells owners and staff through one seam (AGL-3368).\nexport * from './lib/server/risk-notice'\nexport * from './lib/server/host-email-tokens'\nexport * from './lib/server/notifications'\n// Every operator alert raises through one entry point, and every health check\n// remembers its state (AGL-3377).\nexport * from './lib/server/operator-alerts'\nexport * from './lib/server/operator-health'\nexport * from './lib/server/org-billing'\nexport * from './lib/server/organizations'\n// Platform staff take no customer seat, and a workspace is asked to upgrade\n// as a separate step after it is handed over (AGL-3466).\nexport * from './lib/server/staff-seat'\nexport * from './lib/server/upgrade-proposal'\nexport * from './lib/server/duplicate-activity'\nexport * from './lib/server/duplicate-resource'\nexport * from './lib/server/workspace-domains'\nexport * from './lib/server/membership-recover-throttle'\nexport * from './lib/server/password-reset-throttle'\nexport * from './lib/server/rate-limit-store'\nexport * from './lib/server/verify-email-cooldown'\nexport * from './lib/server/vercel-drain-signature'\nexport * from './lib/server/vercel-log-drain'\nexport * from './lib/server/visitor-write-rate-limit'\nexport * from './lib/server/card-payment-velocity'\nexport * from './lib/server/console-api-rate-limit'\nexport * from './lib/server/realm-plugins'\nexport * from './lib/server/release-flags'\nexport * from './lib/server/resolve-people'\nexport * from './lib/server/member-photo'\nexport * from './lib/server/personal-data-export'\nexport * from './lib/server/media-delivery'\nexport * from './lib/server/media-download-tokens'\nexport * from './lib/server/media-quarantine'\nexport * from './lib/server/media-signing'\nexport * from './lib/server/media-storage-path'\nexport * from './lib/server/safe-equal'\nexport * from './lib/server/media-strong-digest'\nexport * from './lib/server/media-tombstone'\nexport * from './lib/server/media-delivery-regeneration'\nexport * from './lib/server/media-variants'\nexport * from './lib/server/serve-media-cdn'\nexport * from './lib/server/serve-plugin-fetch'\nexport * from './lib/server/configured-url-fetch'\nexport * from './lib/server/sso-break-glass-owners'\nexport * from './lib/server/sso-domain-policy'\nexport * from './lib/server/sso-enforcement'\nexport * from './lib/server/sso-provisioning'\nexport * from './lib/server/update-existing'\nexport * from './lib/server/contact-company-link'\nexport * from './lib/server/contact-email-index'\nexport * from './lib/server/upsert-contact'\nexport * from './lib/server/crm-records'\n// The `nextTaskAtMs` a contact, a company and a deal carry, recomputed from\n// their open tasks by every server-side task writer (AGL-2661).\n// Where a one-to-one email's activity row lives and how the delivery\n// webhook advances it (AGL-2615).\n// A member's own addresses in a workspace, and the signed link that\n// confirms one (AGL-2975).\nexport * from './lib/server/member-email-aliases'\nexport * from './lib/server/user-profiles'\n// The one \"New account\" staff notice per account, keyed on the auth\n// record's creation (AGL-3225).\nexport * from './lib/server/new-account-announcement'\nexport * from './lib/server/legal-acceptance'\n// The person's decision about the platform's own product email, on their\n// document and on the operator's marketing contact (AGL-3185).\nexport * from './lib/server/platform-marketing-consent'\n"],"names":["merchantAccountIsReady","merchantAccountReadiness","paymentProvider","domainProvider"],"mappings":"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"}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
import { type CrmActivity, type CrmEmailDeliveryState } from '@aglyn/aglyn/server';
|
|
18
|
-
/**
|
|
19
|
-
* The activity row a one-to-one email is logged as, on the server side
|
|
20
|
-
* (AGL-2615): where it lives, how it is written, and how the delivery
|
|
21
|
-
* webhook moves its state.
|
|
22
|
-
*
|
|
23
|
-
* Two writers log a sent email — the console's `crm/email-send` route and
|
|
24
|
-
* the `sendEmail` automation step — and one reader advances it, the Resend
|
|
25
|
-
* event webhook. The reader has to find the row from nothing but the tags
|
|
26
|
-
* on the message, so the id is minted BEFORE the send and stamped on it,
|
|
27
|
-
* and the row is written only after the provider accepted the message. A
|
|
28
|
-
* row written first would be an "email" on the timeline for a message that
|
|
29
|
-
* never left.
|
|
30
|
-
*
|
|
31
|
-
* Firestore is a parameter throughout, as in `crm-records.ts`: the callers
|
|
32
|
-
* hold one, and a module that reached for the Admin app itself would drag
|
|
33
|
-
* it into every spec that only wants the arithmetic.
|
|
34
|
-
*/
|
|
35
|
-
/** `orgs/{orgId}/crmActivities/{activityId}`. */
|
|
36
|
-
export declare function crmActivityRef(firestore: FirebaseFirestore.Firestore, orgId: string, activityId: string): FirebaseFirestore.DocumentReference;
|
|
37
|
-
/**
|
|
38
|
-
* A fresh reference for the row a send is about to earn — `doc()` with no
|
|
39
|
-
* id mints one locally and reads nothing, so the id can ride the message
|
|
40
|
-
* as a tag before anything is written.
|
|
41
|
-
*/
|
|
42
|
-
export declare function newCrmActivityRef(firestore: FirebaseFirestore.Firestore, orgId: string): FirebaseFirestore.DocumentReference;
|
|
43
|
-
/**
|
|
44
|
-
* Writes the row a sent email is logged as, stamped with the server clock.
|
|
45
|
-
*
|
|
46
|
-
* `set` rather than `add`, because the id was minted ahead of the send and
|
|
47
|
-
* is already on the message; a second id here would be the one the webhook
|
|
48
|
-
* could not find.
|
|
49
|
-
*/
|
|
50
|
-
export declare function writeCrmEmailActivity(ref: FirebaseFirestore.DocumentReference, activity: CrmActivity): Promise<void>;
|
|
51
|
-
/**
|
|
52
|
-
* Which delivery state one normalized delivery event moves a row to, or
|
|
53
|
-
* `null` for an event that moves nothing.
|
|
54
|
-
*
|
|
55
|
-
* Takes the log's own vocabulary (`EmailDeliveryEventType`) rather than the
|
|
56
|
-
* provider's wire strings: the mail provider's `deliveryEvents` is the one place
|
|
57
|
-
* that reads those, and this module must not become a second. The five
|
|
58
|
-
* states the timeline shows share the log's names exactly, so the mapping
|
|
59
|
-
* is membership; `delayed` and `failed` are the log's own and the row keeps
|
|
60
|
-
* whatever it held.
|
|
61
|
-
*/
|
|
62
|
-
export declare function crmEmailDeliveryStateForEvent(type: string | null | undefined): CrmEmailDeliveryState | null;
|
|
63
|
-
export type CrmEmailDeliveryOutcome =
|
|
64
|
-
/** The row moved to the incoming state. */
|
|
65
|
-
'advanced'
|
|
66
|
-
/** The row already held this state or a later one; nothing written. */
|
|
67
|
-
| 'unchanged'
|
|
68
|
-
/** No email activity by that id in that org; nothing written. */
|
|
69
|
-
| 'missing'
|
|
70
|
-
/** The read or the write threw; logged, and nothing is promised. */
|
|
71
|
-
| 'failed';
|
|
72
|
-
/**
|
|
73
|
-
* One delivery event, onto the activity row it names (AGL-2615).
|
|
74
|
-
*
|
|
75
|
-
* A TRANSACTION, because the rule is "keep the later state" and two events
|
|
76
|
-
* for one message routinely arrive together — `delivered` and `opened`
|
|
77
|
-
* within the same second from a mail client that fetches images on
|
|
78
|
-
* receipt. Two plain read-then-writes interleaved would let the earlier
|
|
79
|
-
* event land last and print "Delivered" over an open that already
|
|
80
|
-
* happened; inside a transaction the second read sees the first write and
|
|
81
|
-
* `nextCrmEmailDeliveryState` answers "unchanged".
|
|
82
|
-
*
|
|
83
|
-
* A row that is not an email activity is left alone: the tag named it, but
|
|
84
|
-
* a delivery state on a call log would be a chip the timeline has no way to
|
|
85
|
-
* explain. **Never throws** — the webhook answers the provider 200 whatever
|
|
86
|
-
* happens here, so a failure is logged and reported rather than raised.
|
|
87
|
-
*/
|
|
88
|
-
export declare function recordCrmEmailDelivery(firestore: FirebaseFirestore.Firestore, input: {
|
|
89
|
-
orgId: string;
|
|
90
|
-
activityId: string;
|
|
91
|
-
state: CrmEmailDeliveryState;
|
|
92
|
-
/** When the provider says it happened, epoch ms. */
|
|
93
|
-
atMs: number;
|
|
94
|
-
/**
|
|
95
|
-
* What the receiving server said (AGL-3245), for a failure: the bounce's
|
|
96
|
-
* diagnostic, scrubbed of the address. Kept beside the state as
|
|
97
|
-
* `deliveryDetail`, so the timeline can say why the message did not land.
|
|
98
|
-
*/
|
|
99
|
-
detail?: string | null;
|
|
100
|
-
}): Promise<CrmEmailDeliveryOutcome>;
|
|
101
|
-
/**
|
|
102
|
-
* `orgs/{orgId}/crmActivities/{id}` for a CAPTURED message (AGL-2657), with
|
|
103
|
-
* the id derived from the message's key — its `Message-ID`, else the
|
|
104
|
-
* provider's id — so a second delivery of one message addresses the row
|
|
105
|
-
* the first delivery wrote. A hash rather than the key itself, because a
|
|
106
|
-
* `Message-ID` carries `<`, `@` and `/`, which a document id may not.
|
|
107
|
-
*/
|
|
108
|
-
export declare function crmCapturedEmailActivityRef(firestore: FirebaseFirestore.Firestore, orgId: string, key: string): FirebaseFirestore.DocumentReference;
|
|
109
|
-
/** What a create answered: the row is new, or it was already there. */
|
|
110
|
-
export type CrmEmailActivityCreateOutcome = 'created' | 'duplicate';
|
|
111
|
-
/**
|
|
112
|
-
* Writes a captured message's row ONCE: `create()`, which refuses a
|
|
113
|
-
* document that already exists, is what makes two deliveries of one
|
|
114
|
-
* message one row even when they arrive together — the second create is
|
|
115
|
-
* the one Firestore refuses, and this answers `duplicate` for it. Any
|
|
116
|
-
* other failure is the caller's.
|
|
117
|
-
*/
|
|
118
|
-
export declare function createCrmEmailActivity(ref: FirebaseFirestore.DocumentReference, activity: CrmActivity): Promise<CrmEmailActivityCreateOutcome>;
|