@end-close/relay 0.12.0
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/COMPATIBILITY.md +39 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/config/schema.d.ts +993 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +156 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/crypto/at-rest.d.ts +6 -0
- package/dist/crypto/at-rest.d.ts.map +1 -0
- package/dist/crypto/at-rest.js +19 -0
- package/dist/crypto/at-rest.js.map +1 -0
- package/dist/crypto/keys.d.ts +2 -0
- package/dist/crypto/keys.d.ts.map +1 -0
- package/dist/crypto/keys.js +12 -0
- package/dist/crypto/keys.js.map +1 -0
- package/dist/engine/codec.d.ts +12 -0
- package/dist/engine/codec.d.ts.map +1 -0
- package/dist/engine/codec.js +21 -0
- package/dist/engine/codec.js.map +1 -0
- package/dist/engine/hooks.d.ts +84 -0
- package/dist/engine/hooks.d.ts.map +1 -0
- package/dist/engine/hooks.js +34 -0
- package/dist/engine/hooks.js.map +1 -0
- package/dist/engine/ingest.d.ts +39 -0
- package/dist/engine/ingest.d.ts.map +1 -0
- package/dist/engine/ingest.js +131 -0
- package/dist/engine/ingest.js.map +1 -0
- package/dist/engine/memory-store.d.ts +35 -0
- package/dist/engine/memory-store.d.ts.map +1 -0
- package/dist/engine/memory-store.js +205 -0
- package/dist/engine/memory-store.js.map +1 -0
- package/dist/engine/relay.d.ts +116 -0
- package/dist/engine/relay.d.ts.map +1 -0
- package/dist/engine/relay.js +180 -0
- package/dist/engine/relay.js.map +1 -0
- package/dist/engine/secrets.d.ts +18 -0
- package/dist/engine/secrets.d.ts.map +1 -0
- package/dist/engine/secrets.js +42 -0
- package/dist/engine/secrets.js.map +1 -0
- package/dist/engine/settings.d.ts +29 -0
- package/dist/engine/settings.d.ts.map +1 -0
- package/dist/engine/settings.js +13 -0
- package/dist/engine/settings.js.map +1 -0
- package/dist/engine/store.d.ts +141 -0
- package/dist/engine/store.d.ts.map +1 -0
- package/dist/engine/store.js +52 -0
- package/dist/engine/store.js.map +1 -0
- package/dist/forward/backoff.d.ts +3 -0
- package/dist/forward/backoff.d.ts.map +1 -0
- package/dist/forward/backoff.js +10 -0
- package/dist/forward/backoff.js.map +1 -0
- package/dist/forward/dispatcher.d.ts +98 -0
- package/dist/forward/dispatcher.d.ts.map +1 -0
- package/dist/forward/dispatcher.js +444 -0
- package/dist/forward/dispatcher.js.map +1 -0
- package/dist/forward/endclose-client.d.ts +42 -0
- package/dist/forward/endclose-client.d.ts.map +1 -0
- package/dist/forward/endclose-client.js +76 -0
- package/dist/forward/endclose-client.js.map +1 -0
- package/dist/forward/enrich.d.ts +39 -0
- package/dist/forward/enrich.d.ts.map +1 -0
- package/dist/forward/enrich.js +77 -0
- package/dist/forward/enrich.js.map +1 -0
- package/dist/forward/mapper.d.ts +50 -0
- package/dist/forward/mapper.d.ts.map +1 -0
- package/dist/forward/mapper.js +141 -0
- package/dist/forward/mapper.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/adapters/generic-hmac.d.ts +3 -0
- package/dist/ingest/adapters/generic-hmac.d.ts.map +1 -0
- package/dist/ingest/adapters/generic-hmac.js +60 -0
- package/dist/ingest/adapters/generic-hmac.js.map +1 -0
- package/dist/ingest/adapters/payabli.d.ts +3 -0
- package/dist/ingest/adapters/payabli.d.ts.map +1 -0
- package/dist/ingest/adapters/payabli.js +57 -0
- package/dist/ingest/adapters/payabli.js.map +1 -0
- package/dist/ingest/adapters/registry.d.ts +4 -0
- package/dist/ingest/adapters/registry.d.ts.map +1 -0
- package/dist/ingest/adapters/registry.js +16 -0
- package/dist/ingest/adapters/registry.js.map +1 -0
- package/dist/ingest/adapters/types.d.ts +28 -0
- package/dist/ingest/adapters/types.d.ts.map +1 -0
- package/dist/ingest/adapters/types.js +5 -0
- package/dist/ingest/adapters/types.js.map +1 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +14 -0
- package/dist/logger.js.map +1 -0
- package/dist/mask/defaults.d.ts +7 -0
- package/dist/mask/defaults.d.ts.map +1 -0
- package/dist/mask/defaults.js +51 -0
- package/dist/mask/defaults.js.map +1 -0
- package/dist/mask/paths.d.ts +7 -0
- package/dist/mask/paths.d.ts.map +1 -0
- package/dist/mask/paths.js +36 -0
- package/dist/mask/paths.js.map +1 -0
- package/dist/util/payload-shape.d.ts +6 -0
- package/dist/util/payload-shape.d.ts.map +1 -0
- package/dist/util/payload-shape.js +19 -0
- package/dist/util/payload-shape.js.map +1 -0
- package/dist/util/strings.d.ts +3 -0
- package/dist/util/strings.d.ts.map +1 -0
- package/dist/util/strings.js +7 -0
- package/dist/util/strings.js.map +1 -0
- package/package.json +57 -0
- package/src/config/schema.ts +177 -0
- package/src/crypto/at-rest.ts +21 -0
- package/src/crypto/keys.ts +13 -0
- package/src/engine/codec.ts +30 -0
- package/src/engine/hooks.ts +76 -0
- package/src/engine/ingest.ts +194 -0
- package/src/engine/memory-store.ts +232 -0
- package/src/engine/relay.ts +294 -0
- package/src/engine/secrets.ts +51 -0
- package/src/engine/settings.ts +42 -0
- package/src/engine/store.ts +188 -0
- package/src/forward/backoff.ts +10 -0
- package/src/forward/dispatcher.ts +504 -0
- package/src/forward/endclose-client.ts +102 -0
- package/src/forward/enrich.ts +106 -0
- package/src/forward/mapper.ts +191 -0
- package/src/index.ts +47 -0
- package/src/ingest/adapters/generic-hmac.ts +70 -0
- package/src/ingest/adapters/payabli.ts +67 -0
- package/src/ingest/adapters/registry.ts +21 -0
- package/src/ingest/adapters/types.ts +33 -0
- package/src/logger.ts +26 -0
- package/src/mask/defaults.ts +56 -0
- package/src/mask/paths.ts +36 -0
- package/src/util/payload-shape.ts +19 -0
- package/src/util/strings.ts +7 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { hardDenyDeep, keyNameIsSensitive } from '../mask/defaults.js';
|
|
2
|
+
/** Enrichment cannot be applied to this event. Parks the event; never retried. */
|
|
3
|
+
export class EnrichmentError extends Error {
|
|
4
|
+
}
|
|
5
|
+
export class EnrichTimeoutError extends Error {
|
|
6
|
+
}
|
|
7
|
+
function describe(value) {
|
|
8
|
+
if (value === null)
|
|
9
|
+
return 'null';
|
|
10
|
+
if (Array.isArray(value))
|
|
11
|
+
return 'an array';
|
|
12
|
+
if (typeof value === 'number')
|
|
13
|
+
return `number ${String(value)}`;
|
|
14
|
+
if (typeof value === 'object')
|
|
15
|
+
return `a ${value.constructor?.name ?? 'non-plain object'}`;
|
|
16
|
+
return `a ${typeof value}`;
|
|
17
|
+
}
|
|
18
|
+
function isPlainObject(value) {
|
|
19
|
+
if (value === null || typeof value !== 'object')
|
|
20
|
+
return false;
|
|
21
|
+
const proto = Object.getPrototypeOf(value);
|
|
22
|
+
return proto === Object.prototype || proto === null;
|
|
23
|
+
}
|
|
24
|
+
function assertJson(value, path) {
|
|
25
|
+
if (value === null || typeof value === 'boolean' || typeof value === 'string')
|
|
26
|
+
return;
|
|
27
|
+
if (typeof value === 'number') {
|
|
28
|
+
if (Number.isFinite(value))
|
|
29
|
+
return;
|
|
30
|
+
throw new EnrichmentError(`enriched value at "${path}" is not JSON (${describe(value)})`);
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
value.forEach((v, i) => assertJson(v, `${path}[${i}]`));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (isPlainObject(value)) {
|
|
37
|
+
for (const [k, v] of Object.entries(value)) {
|
|
38
|
+
if (keyNameIsSensitive(k)) {
|
|
39
|
+
throw new EnrichmentError(`enriched value at "${path}.${k}" matches the hard denylist (cvv/ssn/account number/...) and cannot be forwarded`);
|
|
40
|
+
}
|
|
41
|
+
assertJson(v, `${path}.${k}`);
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
throw new EnrichmentError(`enriched value at "${path}" is not JSON (${describe(value)})`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check what an enrichment returned before it may enter a record: JSON only (no
|
|
49
|
+
* undefined-in-objects, functions, NaN, class instances), no sensitive key names at any
|
|
50
|
+
* depth, and the hard denylist applied to every string. `undefined` means "omit".
|
|
51
|
+
*/
|
|
52
|
+
export function validateEnrichedValue(value, field) {
|
|
53
|
+
if (value === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
assertJson(value, field);
|
|
56
|
+
return hardDenyDeep(value);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Run a host callback under a deadline. A synchronous throw becomes a rejection; the timer
|
|
60
|
+
* is cleared on settle so it never keeps the process alive. A timed-out call is abandoned,
|
|
61
|
+
* not cancelled: hosts must treat enrichments as reads that may run more than once.
|
|
62
|
+
*/
|
|
63
|
+
export function withTimeout(run, ms) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const timer = setTimeout(() => reject(new EnrichTimeoutError(`timed out after ${ms} ms`)), ms);
|
|
66
|
+
Promise.resolve()
|
|
67
|
+
.then(run)
|
|
68
|
+
.then((v) => {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
resolve(v);
|
|
71
|
+
}, (e) => {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
reject(e);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=enrich.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrich.js","sourceRoot":"","sources":["../../src/forward/enrich.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type RouteConfig } from '../config/schema.js';
|
|
2
|
+
import { type Json } from '../mask/paths.js';
|
|
3
|
+
export interface EndCloseRecord {
|
|
4
|
+
date: string;
|
|
5
|
+
data_stream_key: string;
|
|
6
|
+
amount: number;
|
|
7
|
+
currency?: string;
|
|
8
|
+
direction: 'credit' | 'debit';
|
|
9
|
+
description?: string;
|
|
10
|
+
external_id: string;
|
|
11
|
+
metadata: Record<string, Json>;
|
|
12
|
+
}
|
|
13
|
+
export interface MapReport {
|
|
14
|
+
/** output field -> source path (values marked when transformed) */
|
|
15
|
+
mapped: Record<string, string>;
|
|
16
|
+
hashed: string[];
|
|
17
|
+
/** output fields whose value comes from a host enrichment, filled at dispatch time */
|
|
18
|
+
enriched: string[];
|
|
19
|
+
/** payload leaf paths that do NOT leave the network */
|
|
20
|
+
not_forwarded: string[];
|
|
21
|
+
}
|
|
22
|
+
/** An enriched field mapEvent could not fill: the dispatcher runs the host function. */
|
|
23
|
+
export interface PendingEnrichment {
|
|
24
|
+
/** "metadata.<key>" or "description" */
|
|
25
|
+
field: string;
|
|
26
|
+
enrichment: string;
|
|
27
|
+
/** The source value after transforms; what the enrichment receives. */
|
|
28
|
+
input: Json;
|
|
29
|
+
source: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class MappingError extends Error {
|
|
32
|
+
}
|
|
33
|
+
/** "3,762.87" | "3762.87" | "$38.00" | 3762.87 → integer cents. */
|
|
34
|
+
export declare function toCents(value: Json): number;
|
|
35
|
+
/** Payabli transferTime arrives as "M/D/YYYY H:mm:ss"; End Close wants an ISO date. */
|
|
36
|
+
export declare function parseDate(value: Json, format: 'iso8601' | 'mdy_hms'): string;
|
|
37
|
+
export interface MappedEvent {
|
|
38
|
+
/** Enriched fields are absent here until the dispatcher fills them. */
|
|
39
|
+
record: EndCloseRecord;
|
|
40
|
+
report: MapReport;
|
|
41
|
+
pending: PendingEnrichment[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Map a webhook payload to an End Close record. Only fields named in the route's `map`
|
|
45
|
+
* block are forwarded — the map IS the allowlist. Every mapped value additionally passes
|
|
46
|
+
* through the hard denylist (PAN/SSN redaction), so no configuration can forward those
|
|
47
|
+
* in clear. This is the only place payload data crosses toward the network path.
|
|
48
|
+
*/
|
|
49
|
+
export declare function mapEvent(route: RouteConfig, payload: Json, receivedAt: string, maskingKey: Buffer): MappedEvent;
|
|
50
|
+
//# sourceMappingURL=mapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapper.d.ts","sourceRoot":"","sources":["../../src/forward/mapper.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,WAAW,EAEjB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAwB,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAGlE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,QAAQ,GAAG,OAAO,CAAA;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,sFAAsF;IACtF,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,uDAAuD;IACvD,aAAa,EAAE,MAAM,EAAE,CAAA;CACxB;AAED,wFAAwF;AACxF,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,KAAK,EAAE,IAAI,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,YAAa,SAAQ,KAAK;CAAG;AAE1C,mEAAmE;AACnE,wBAAgB,OAAO,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,CAW3C;AAED,uFAAuF;AACvF,wBAAgB,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAY5E;AA0BD,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,MAAM,EAAE,cAAc,CAAA;IACtB,MAAM,EAAE,SAAS,CAAA;IACjB,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,IAAI,EACb,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,WAAW,CAuEb"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createHmac } from 'node:crypto';
|
|
2
|
+
import { refEnrichment, refSource, refTransforms, } from '../config/schema.js';
|
|
3
|
+
import { hardDenyDeep } from '../mask/defaults.js';
|
|
4
|
+
import { getAtPath, leafPaths } from '../mask/paths.js';
|
|
5
|
+
import { escapeRe } from '../util/strings.js';
|
|
6
|
+
export class MappingError extends Error {
|
|
7
|
+
}
|
|
8
|
+
/** "3,762.87" | "3762.87" | "$38.00" | 3762.87 → integer cents. */
|
|
9
|
+
export function toCents(value) {
|
|
10
|
+
let s;
|
|
11
|
+
if (typeof value === 'number')
|
|
12
|
+
s = String(value);
|
|
13
|
+
else if (typeof value === 'string')
|
|
14
|
+
s = value.replace(/[$,\s]/g, '');
|
|
15
|
+
else
|
|
16
|
+
throw new MappingError(`amount is not a string or number: ${JSON.stringify(value)}`);
|
|
17
|
+
if (!/^-?\d+(\.\d{1,2})?$/.test(s))
|
|
18
|
+
throw new MappingError(`unparseable amount: ${s}`);
|
|
19
|
+
const negative = s.startsWith('-');
|
|
20
|
+
if (negative)
|
|
21
|
+
s = s.slice(1);
|
|
22
|
+
const [whole = '0', frac = ''] = s.split('.');
|
|
23
|
+
const cents = Number(whole) * 100 + Number(frac.padEnd(2, '0') || '0');
|
|
24
|
+
return negative ? -cents : cents;
|
|
25
|
+
}
|
|
26
|
+
/** Payabli transferTime arrives as "M/D/YYYY H:mm:ss"; End Close wants an ISO date. */
|
|
27
|
+
export function parseDate(value, format) {
|
|
28
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
29
|
+
throw new MappingError(`date is not a string: ${JSON.stringify(value)}`);
|
|
30
|
+
}
|
|
31
|
+
if (format === 'mdy_hms') {
|
|
32
|
+
const m = value.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s|$)/);
|
|
33
|
+
if (!m)
|
|
34
|
+
throw new MappingError(`unparseable M/D/YYYY date: ${value}`);
|
|
35
|
+
return `${m[3]}-${m[1].padStart(2, '0')}-${m[2].padStart(2, '0')}`;
|
|
36
|
+
}
|
|
37
|
+
const parsed = new Date(value);
|
|
38
|
+
if (Number.isNaN(parsed.getTime()))
|
|
39
|
+
throw new MappingError(`unparseable date: ${value}`);
|
|
40
|
+
return parsed.toISOString().slice(0, 10);
|
|
41
|
+
}
|
|
42
|
+
function hashValue(maskingKey, value) {
|
|
43
|
+
const raw = typeof value === 'string' ? value : JSON.stringify(value);
|
|
44
|
+
return 'hmac256:' + createHmac('sha256', maskingKey).update(raw, 'utf8').digest('hex');
|
|
45
|
+
}
|
|
46
|
+
/** trim/lowercase act on strings (elementwise over arrays from wildcard paths); hash consumes anything. */
|
|
47
|
+
function applyTransform(name, value, maskingKey) {
|
|
48
|
+
if (name === 'hash')
|
|
49
|
+
return hashValue(maskingKey, value);
|
|
50
|
+
if (Array.isArray(value))
|
|
51
|
+
return value.map((v) => applyTransform(name, v, maskingKey));
|
|
52
|
+
if (typeof value !== 'string')
|
|
53
|
+
return value;
|
|
54
|
+
return name === 'trim' ? value.trim() : value.toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
function resolve(ref, payload, maskingKey) {
|
|
57
|
+
let value = getAtPath(payload, refSource(ref));
|
|
58
|
+
if (value === undefined)
|
|
59
|
+
return undefined;
|
|
60
|
+
for (const t of refTransforms(ref))
|
|
61
|
+
value = applyTransform(t, value, maskingKey);
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
function dateSource(ref) {
|
|
65
|
+
return typeof ref === 'string' ? { source: ref, format: 'iso8601' } : ref;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Map a webhook payload to an End Close record. Only fields named in the route's `map`
|
|
69
|
+
* block are forwarded — the map IS the allowlist. Every mapped value additionally passes
|
|
70
|
+
* through the hard denylist (PAN/SSN redaction), so no configuration can forward those
|
|
71
|
+
* in clear. This is the only place payload data crosses toward the network path.
|
|
72
|
+
*/
|
|
73
|
+
export function mapEvent(route, payload, receivedAt, maskingKey) {
|
|
74
|
+
const { map: m } = route;
|
|
75
|
+
const report = { mapped: {}, hashed: [], enriched: [], not_forwarded: [] };
|
|
76
|
+
const pending = [];
|
|
77
|
+
const usedPaths = new Set();
|
|
78
|
+
const use = (field, ref) => {
|
|
79
|
+
const value = resolve(ref, payload, maskingKey);
|
|
80
|
+
if (value === undefined)
|
|
81
|
+
return undefined;
|
|
82
|
+
const source = refSource(ref);
|
|
83
|
+
usedPaths.add(source);
|
|
84
|
+
const enrichment = refEnrichment(ref);
|
|
85
|
+
if (enrichment !== undefined) {
|
|
86
|
+
// The host fills this field later; the record leaves here without it.
|
|
87
|
+
report.mapped[field] = `${source} → enrich:${enrichment}`;
|
|
88
|
+
report.enriched.push(field);
|
|
89
|
+
pending.push({ field, enrichment, input: value, source });
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
report.mapped[field] = source;
|
|
93
|
+
if (refTransforms(ref).includes('hash'))
|
|
94
|
+
report.hashed.push(field);
|
|
95
|
+
return value;
|
|
96
|
+
};
|
|
97
|
+
const externalId = use('external_id', m.external_id);
|
|
98
|
+
if (typeof externalId !== 'string' && typeof externalId !== 'number') {
|
|
99
|
+
throw new MappingError(`missing external id at ${refSource(m.external_id)}`);
|
|
100
|
+
}
|
|
101
|
+
const amount = toCents(use('amount', m.amount) ?? null);
|
|
102
|
+
let date;
|
|
103
|
+
if (m.date) {
|
|
104
|
+
const { source, format } = dateSource(m.date);
|
|
105
|
+
date = parseDate(getAtPath(payload, source) ?? null, format);
|
|
106
|
+
report.mapped['date'] = source;
|
|
107
|
+
usedPaths.add(source);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
date = receivedAt.slice(0, 10);
|
|
111
|
+
report.mapped['date'] = '(receive time)';
|
|
112
|
+
}
|
|
113
|
+
const metadata = {};
|
|
114
|
+
for (const [outputKey, ref] of Object.entries(m.metadata)) {
|
|
115
|
+
const value = use(`metadata.${outputKey}`, ref);
|
|
116
|
+
if (value !== undefined)
|
|
117
|
+
metadata[outputKey] = hardDenyDeep(value);
|
|
118
|
+
}
|
|
119
|
+
const record = {
|
|
120
|
+
date,
|
|
121
|
+
data_stream_key: m.data_stream_key,
|
|
122
|
+
amount,
|
|
123
|
+
direction: m.direction,
|
|
124
|
+
external_id: String(externalId),
|
|
125
|
+
metadata,
|
|
126
|
+
};
|
|
127
|
+
if (m.currency)
|
|
128
|
+
record.currency = m.currency;
|
|
129
|
+
if (m.description) {
|
|
130
|
+
const desc = use('description', m.description);
|
|
131
|
+
if (typeof desc === 'string')
|
|
132
|
+
record.description = hardDenyDeep(desc);
|
|
133
|
+
}
|
|
134
|
+
// Everything in the payload that was not explicitly mapped stays local.
|
|
135
|
+
const wildcardPrefixes = [...usedPaths]
|
|
136
|
+
.filter((p) => p.includes('*'))
|
|
137
|
+
.map((p) => new RegExp('^' + p.split('.').map((s) => (s === '*' ? '[^.]+' : escapeRe(s))).join('\\.') + '$'));
|
|
138
|
+
report.not_forwarded = leafPaths(payload).filter((leaf) => !usedPaths.has(leaf) && !wildcardPrefixes.some((re) => re.test(leaf)));
|
|
139
|
+
return { record, report, pending };
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapper.js","sourceRoot":"","sources":["../../src/forward/mapper.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export { createRelay, parseRoutes, assertKnownSources, assertKnownEnrichments, routeEnrichments } from './engine/relay.js';
|
|
2
|
+
export type { Relay, RelayOptions, DispatchOnceResult, FlushResult } from './engine/relay.js';
|
|
3
|
+
export type { DispatchCounts } from './forward/dispatcher.js';
|
|
4
|
+
export { DEFAULT_DISPATCH, DEFAULT_RETENTION } from './engine/settings.js';
|
|
5
|
+
export type { DispatchSettings, RetentionSettings } from './engine/settings.js';
|
|
6
|
+
export type { IngestResult, IngestResultOutcome } from './engine/ingest.js';
|
|
7
|
+
export { eventIdempotencyKey } from './engine/ingest.js';
|
|
8
|
+
export * from './engine/store.js';
|
|
9
|
+
export { MemoryEventStore, memoryStore } from './engine/memory-store.js';
|
|
10
|
+
export { aesGcmCodec, plainCodec } from './engine/codec.js';
|
|
11
|
+
export type { PayloadCodec } from './engine/codec.js';
|
|
12
|
+
export { RelayHooks } from './engine/hooks.js';
|
|
13
|
+
export type { RelayEvents, RelayEventName, RelayHandler, IngestOutcome, ForwardResult, EnrichOutcome, EngineErrorKind, } from './engine/hooks.js';
|
|
14
|
+
export { envSecrets, staticSecrets, hasSecret, requireSecret, SecretUnavailableError } from './engine/secrets.js';
|
|
15
|
+
export type { SecretResolver } from './engine/secrets.js';
|
|
16
|
+
export { noopLogger, consoleLogger } from './logger.js';
|
|
17
|
+
export type { Logger, LogMeta } from './logger.js';
|
|
18
|
+
export * from './config/schema.js';
|
|
19
|
+
export { mapEvent, toCents, parseDate, MappingError } from './forward/mapper.js';
|
|
20
|
+
export type { EndCloseRecord, MapReport, MappedEvent, PendingEnrichment } from './forward/mapper.js';
|
|
21
|
+
export { EnrichmentError, EnrichTimeoutError, validateEnrichedValue, withTimeout } from './forward/enrich.js';
|
|
22
|
+
export type { Enrichment, EnrichContext } from './forward/enrich.js';
|
|
23
|
+
export { EndCloseClient, TransientHttpError, PermanentHttpError } from './forward/endclose-client.js';
|
|
24
|
+
export type { BulkRequestSummary, BulkResultItem } from './forward/endclose-client.js';
|
|
25
|
+
export { adapterFor, hasAdapter } from './ingest/adapters/registry.js';
|
|
26
|
+
export { payabliAdapter } from './ingest/adapters/payabli.js';
|
|
27
|
+
export { genericHmacAdapter } from './ingest/adapters/generic-hmac.js';
|
|
28
|
+
export { headerValue } from './ingest/adapters/types.js';
|
|
29
|
+
export type { ProcessorAdapter, RawRequest, VerifyContext, VerifyResult } from './ingest/adapters/types.js';
|
|
30
|
+
export { hardDenyValue, hardDenyDeep, keyNameIsSensitive, REDACTED } from './mask/defaults.js';
|
|
31
|
+
export { getAtPath, leafPaths } from './mask/paths.js';
|
|
32
|
+
export type { Json } from './mask/paths.js';
|
|
33
|
+
export { encrypt, decrypt } from './crypto/at-rest.js';
|
|
34
|
+
export { deriveKey } from './crypto/keys.js';
|
|
35
|
+
export { jsonTopLevelKeys, requestHeaderNames } from './util/payload-shape.js';
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAC1H,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC7F,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAC1E,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAExD,cAAc,mBAAmB,CAAA;AACjC,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACxE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC3D,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,YAAY,EACV,WAAW,EACX,cAAc,EACd,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,EACb,eAAe,GAChB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACjH,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACvD,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAElD,cAAc,oBAAoB,CAAA;AAClC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAChF,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACpG,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC7G,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AACrG,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AACtF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AACxD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC3G,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC9F,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACtD,YAAY,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @end-close/relay — the embeddable store-and-forward engine behind the End Close relay.
|
|
2
|
+
export { createRelay, parseRoutes, assertKnownSources, assertKnownEnrichments, routeEnrichments } from './engine/relay.js';
|
|
3
|
+
export { DEFAULT_DISPATCH, DEFAULT_RETENTION } from './engine/settings.js';
|
|
4
|
+
export { eventIdempotencyKey } from './engine/ingest.js';
|
|
5
|
+
export * from './engine/store.js';
|
|
6
|
+
export { MemoryEventStore, memoryStore } from './engine/memory-store.js';
|
|
7
|
+
export { aesGcmCodec, plainCodec } from './engine/codec.js';
|
|
8
|
+
export { RelayHooks } from './engine/hooks.js';
|
|
9
|
+
export { envSecrets, staticSecrets, hasSecret, requireSecret, SecretUnavailableError } from './engine/secrets.js';
|
|
10
|
+
export { noopLogger, consoleLogger } from './logger.js';
|
|
11
|
+
export * from './config/schema.js';
|
|
12
|
+
export { mapEvent, toCents, parseDate, MappingError } from './forward/mapper.js';
|
|
13
|
+
export { EnrichmentError, EnrichTimeoutError, validateEnrichedValue, withTimeout } from './forward/enrich.js';
|
|
14
|
+
export { EndCloseClient, TransientHttpError, PermanentHttpError } from './forward/endclose-client.js';
|
|
15
|
+
export { adapterFor, hasAdapter } from './ingest/adapters/registry.js';
|
|
16
|
+
export { payabliAdapter } from './ingest/adapters/payabli.js';
|
|
17
|
+
export { genericHmacAdapter } from './ingest/adapters/generic-hmac.js';
|
|
18
|
+
export { headerValue } from './ingest/adapters/types.js';
|
|
19
|
+
export { hardDenyValue, hardDenyDeep, keyNameIsSensitive, REDACTED } from './mask/defaults.js';
|
|
20
|
+
export { getAtPath, leafPaths } from './mask/paths.js';
|
|
21
|
+
export { encrypt, decrypt } from './crypto/at-rest.js';
|
|
22
|
+
export { deriveKey } from './crypto/keys.js';
|
|
23
|
+
export { jsonTopLevelKeys, requestHeaderNames } from './util/payload-shape.js';
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yFAAyF;AAEzF,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAG1H,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAG1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAExD,cAAc,mBAAmB,CAAA;AACjC,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACxE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE3D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAU9C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAEjH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGvD,cAAc,oBAAoB,CAAA;AAClC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEhF,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAE7G,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AAErG,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AAExD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC9F,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEtD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic-hmac.d.ts","sourceRoot":"","sources":["../../../src/ingest/adapters/generic-hmac.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,gBAAgB,EAItB,MAAM,YAAY,CAAA;AAMnB,eAAO,MAAM,kBAAkB,EAAE,gBAsDhC,CAAA"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createHash, createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { getAtPath } from '../../mask/paths.js';
|
|
3
|
+
import { headerValue, } from './types.js';
|
|
4
|
+
// Generic HMAC-signature adapter: covers processors that sign `body` or `timestamp.body`
|
|
5
|
+
// with a shared secret. Configuration lives entirely in the route's auth block, so most
|
|
6
|
+
// future processors need no code.
|
|
7
|
+
export const genericHmacAdapter = {
|
|
8
|
+
name: 'generic_hmac',
|
|
9
|
+
verify(req, route, ctx) {
|
|
10
|
+
if (route.auth.mode !== 'hmac')
|
|
11
|
+
return { ok: false, reason: 'route auth mode mismatch' };
|
|
12
|
+
const auth = route.auth;
|
|
13
|
+
let signedContent = req.rawBody;
|
|
14
|
+
if (auth.signed_content === 'timestamp.body') {
|
|
15
|
+
if (!auth.timestamp_header)
|
|
16
|
+
return { ok: false, reason: 'timestamp_header not configured' };
|
|
17
|
+
const ts = headerValue(req.headers, auth.timestamp_header);
|
|
18
|
+
if (!ts)
|
|
19
|
+
return { ok: false, reason: `missing ${auth.timestamp_header} header` };
|
|
20
|
+
const skew = Math.abs(Date.now() / 1000 - Number(ts));
|
|
21
|
+
if (!Number.isFinite(skew) || skew > auth.tolerance_seconds) {
|
|
22
|
+
return { ok: false, reason: 'stale timestamp' };
|
|
23
|
+
}
|
|
24
|
+
signedContent = Buffer.concat([Buffer.from(`${ts}.`, 'utf8'), req.rawBody]);
|
|
25
|
+
}
|
|
26
|
+
const presented = headerValue(req.headers, auth.header);
|
|
27
|
+
if (!presented)
|
|
28
|
+
return { ok: false, reason: `missing ${auth.header} header` };
|
|
29
|
+
const expectedHex = createHmac(auth.algorithm, ctx.secret).update(signedContent).digest('hex');
|
|
30
|
+
// Accept optional "sha256=" style prefixes.
|
|
31
|
+
const presentedHex = (presented.includes('=') && !/^[0-9a-f]+$/i.test(presented)
|
|
32
|
+
? presented.slice(presented.indexOf('=') + 1)
|
|
33
|
+
: presented).toLowerCase();
|
|
34
|
+
const a = Buffer.from(expectedHex, 'utf8');
|
|
35
|
+
const b = Buffer.from(presentedHex, 'utf8');
|
|
36
|
+
if (a.length !== b.length) {
|
|
37
|
+
timingSafeEqual(a, a);
|
|
38
|
+
return { ok: false, reason: 'bad signature' };
|
|
39
|
+
}
|
|
40
|
+
if (!timingSafeEqual(a, b))
|
|
41
|
+
return { ok: false, reason: 'bad signature' };
|
|
42
|
+
return { ok: true };
|
|
43
|
+
},
|
|
44
|
+
extractEventId(body, req, route) {
|
|
45
|
+
if (route.auth.mode === 'hmac' && route.auth.event_id) {
|
|
46
|
+
const id = getAtPath(body, route.auth.event_id);
|
|
47
|
+
if ((typeof id === 'string' && id.length > 0) || typeof id === 'number')
|
|
48
|
+
return String(id);
|
|
49
|
+
}
|
|
50
|
+
return 'sha256:' + createHash('sha256').update(req.rawBody).digest('hex');
|
|
51
|
+
},
|
|
52
|
+
extractEventType(body, _req, route) {
|
|
53
|
+
if (route.auth.mode === 'hmac' && route.auth.event_type) {
|
|
54
|
+
const t = getAtPath(body, route.auth.event_type);
|
|
55
|
+
return typeof t === 'string' ? t : null;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=generic-hmac.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic-hmac.js","sourceRoot":"","sources":["../../../src/ingest/adapters/generic-hmac.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payabli.d.ts","sourceRoot":"","sources":["../../../src/ingest/adapters/payabli.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,gBAAgB,EAItB,MAAM,YAAY,CAAA;AAyBnB,eAAO,MAAM,cAAc,EAAE,gBAgC5B,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { getAtPath } from '../../mask/paths.js';
|
|
3
|
+
import { headerValue, } from './types.js';
|
|
4
|
+
// Payabli does not sign webhooks. Trust boundary: a customer-defined static header
|
|
5
|
+
// (configured on the Payabli notification via webHeaderParameters) compared in constant
|
|
6
|
+
// time, plus an optional source-IP allowlist (Payabli publishes one static IP per env:
|
|
7
|
+
// sandbox 52.3.204.115, production 54.166.54.170).
|
|
8
|
+
function constantTimeEquals(a, b) {
|
|
9
|
+
const ab = Buffer.from(a, 'utf8');
|
|
10
|
+
const bb = Buffer.from(b, 'utf8');
|
|
11
|
+
if (ab.length !== bb.length) {
|
|
12
|
+
// Compare against self to keep timing independent of the mismatch position.
|
|
13
|
+
timingSafeEqual(ab, ab);
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return timingSafeEqual(ab, bb);
|
|
17
|
+
}
|
|
18
|
+
// Stable-ID field per payload Event type, per Payabli's OpenAPI webhook schemas.
|
|
19
|
+
const EVENT_ID_PATHS = {
|
|
20
|
+
TransferFunded: 'transferId', // payout_batch_settlement_funded
|
|
21
|
+
PayOutBatchPaid: 'BatchId', // payout_batch_paid
|
|
22
|
+
ApprovedPayment: 'TransactionId', // transaction-level; field names pending the customer's sample
|
|
23
|
+
};
|
|
24
|
+
export const payabliAdapter = {
|
|
25
|
+
name: 'payabli',
|
|
26
|
+
verify(req, route, ctx) {
|
|
27
|
+
if (route.auth.mode !== 'static_header') {
|
|
28
|
+
return { ok: false, reason: 'route auth mode mismatch' };
|
|
29
|
+
}
|
|
30
|
+
if (route.auth.allowed_ips.length > 0 && !route.auth.allowed_ips.includes(req.remoteIp)) {
|
|
31
|
+
return { ok: false, reason: 'source ip not allowed' };
|
|
32
|
+
}
|
|
33
|
+
const presented = headerValue(req.headers, route.auth.header);
|
|
34
|
+
if (!presented)
|
|
35
|
+
return { ok: false, reason: `missing ${route.auth.header} header` };
|
|
36
|
+
if (!constantTimeEquals(presented, ctx.secret))
|
|
37
|
+
return { ok: false, reason: 'bad auth header' };
|
|
38
|
+
return { ok: true };
|
|
39
|
+
},
|
|
40
|
+
extractEventId(body, req) {
|
|
41
|
+
const eventType = getAtPath(body, 'Event');
|
|
42
|
+
if (typeof eventType === 'string') {
|
|
43
|
+
const path = EVENT_ID_PATHS[eventType];
|
|
44
|
+
if (path) {
|
|
45
|
+
const id = getAtPath(body, path);
|
|
46
|
+
if (typeof id === 'string' && id.length > 0)
|
|
47
|
+
return `${eventType}:${id}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return 'sha256:' + createHash('sha256').update(req.rawBody).digest('hex');
|
|
51
|
+
},
|
|
52
|
+
extractEventType(body) {
|
|
53
|
+
const t = getAtPath(body, 'Event');
|
|
54
|
+
return typeof t === 'string' ? t : null;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=payabli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payabli.js","sourceRoot":"","sources":["../../../src/ingest/adapters/payabli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProcessorAdapter } from './types.js';
|
|
2
|
+
export declare function adapterFor(source: string, extra?: Record<string, ProcessorAdapter>): ProcessorAdapter;
|
|
3
|
+
export declare function hasAdapter(source: string, extra?: Record<string, ProcessorAdapter>): boolean;
|
|
4
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/ingest/adapters/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AASlD,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GACvC,gBAAgB,CAIlB;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,OAAO,CAE5F"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { payabliAdapter } from './payabli.js';
|
|
2
|
+
import { genericHmacAdapter } from './generic-hmac.js';
|
|
3
|
+
const adapters = {
|
|
4
|
+
payabli: payabliAdapter,
|
|
5
|
+
generic_hmac: genericHmacAdapter,
|
|
6
|
+
};
|
|
7
|
+
export function adapterFor(source, extra) {
|
|
8
|
+
const adapter = extra?.[source] ?? adapters[source];
|
|
9
|
+
if (!adapter)
|
|
10
|
+
throw new Error(`no adapter for source: ${source}`);
|
|
11
|
+
return adapter;
|
|
12
|
+
}
|
|
13
|
+
export function hasAdapter(source, extra) {
|
|
14
|
+
return Boolean(extra?.[source] ?? adapters[source]);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/ingest/adapters/registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD,MAAM,QAAQ,GAAqC;IACjD,OAAO,EAAE,cAAc;IACvB,YAAY,EAAE,kBAAkB;CACjC,CAAA;AAED,MAAM,UAAU,UAAU,CACxB,MAAc,EACd,KAAwC;IAExC,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAA;IACnD,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,EAAE,CAAC,CAAA;IACjE,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,KAAwC;IACjF,OAAO,OAAO,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;AACrD,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RouteConfig } from '../../config/schema.js';
|
|
2
|
+
import type { Json } from '../../mask/paths.js';
|
|
3
|
+
export interface RawRequest {
|
|
4
|
+
rawBody: Buffer;
|
|
5
|
+
headers: Record<string, string | string[] | undefined>;
|
|
6
|
+
remoteIp: string;
|
|
7
|
+
}
|
|
8
|
+
export type VerifyResult = {
|
|
9
|
+
ok: true;
|
|
10
|
+
} | {
|
|
11
|
+
ok: false;
|
|
12
|
+
reason: string;
|
|
13
|
+
};
|
|
14
|
+
/** Resolved by the engine before verification so adapters stay pure and synchronous. */
|
|
15
|
+
export interface VerifyContext {
|
|
16
|
+
/** The value of the route's `auth.secret_env` reference. */
|
|
17
|
+
secret: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ProcessorAdapter {
|
|
20
|
+
name: string;
|
|
21
|
+
/** Verify authenticity over the raw bytes. Must be constant-time on secret comparisons. */
|
|
22
|
+
verify(req: RawRequest, route: RouteConfig, ctx: VerifyContext): VerifyResult;
|
|
23
|
+
/** Stable per-event ID used for idempotency. Falls back to a raw-body hash. */
|
|
24
|
+
extractEventId(body: Json, req: RawRequest, route: RouteConfig): string;
|
|
25
|
+
extractEventType(body: Json, req: RawRequest, route: RouteConfig): string | null;
|
|
26
|
+
}
|
|
27
|
+
export declare function headerValue(headers: Record<string, string | string[] | undefined>, name: string): string | undefined;
|
|
28
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/ingest/adapters/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AAE/C,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;IACtD,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvE,wFAAwF;AACxF,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,2FAA2F;IAC3F,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,CAAA;IAC7E,+EAA+E;IAC/E,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAAA;IACvE,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAAA;CACjF;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,EACtD,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAGpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/ingest/adapters/types.ts"],"names":[],"mappings":"AA0BA,MAAM,UAAU,WAAW,CACzB,OAAsD,EACtD,IAAY;IAEZ,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAA;IACrC,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACpC,CAAC"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type LogMeta = Record<string, string | number | boolean | null | undefined>;
|
|
2
|
+
/** The logging contract the engine depends on. Any host can supply its own implementation. */
|
|
3
|
+
export interface Logger {
|
|
4
|
+
debug(msg: string, meta?: LogMeta): void;
|
|
5
|
+
info(msg: string, meta?: LogMeta): void;
|
|
6
|
+
warn(msg: string, meta?: LogMeta): void;
|
|
7
|
+
error(msg: string, meta?: LogMeta): void;
|
|
8
|
+
}
|
|
9
|
+
export declare const noopLogger: Logger;
|
|
10
|
+
/** console-backed logger at warn level and above. */
|
|
11
|
+
export declare const consoleLogger: Logger;
|
|
12
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;AAElF,8FAA8F;AAC9F,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACxC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACvC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACvC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CACzC;AAED,eAAO,MAAM,UAAU,EAAE,MAKxB,CAAA;AAED,qDAAqD;AACrD,eAAO,MAAM,aAAa,EAAE,MAK3B,CAAA"}
|