@abyss-project/probe 2.0.1 → 3.0.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.
Files changed (58) hide show
  1. package/dist/api/index.d.ts +2 -1
  2. package/dist/api/index.js +2 -1
  3. package/dist/api/index.js.map +1 -1
  4. package/dist/api/probe-delivery-subscription.api.d.ts +11 -0
  5. package/dist/api/probe-delivery-subscription.api.js +42 -0
  6. package/dist/api/probe-delivery-subscription.api.js.map +1 -0
  7. package/dist/api/probe-delivery-subscription.application.api.d.ts +11 -0
  8. package/dist/api/probe-delivery-subscription.application.api.js +38 -0
  9. package/dist/api/probe-delivery-subscription.application.api.js.map +1 -0
  10. package/dist/api/probe-webhook.application.api.d.ts +1 -1
  11. package/dist/api/probe-webhook.application.api.js.map +1 -1
  12. package/dist/index.d.ts +29 -7
  13. package/dist/index.js +29 -7
  14. package/dist/index.js.map +1 -1
  15. package/dist/types/enum/api-error.enum.d.ts +2 -1
  16. package/dist/types/enum/api-error.enum.js +1 -0
  17. package/dist/types/enum/api-error.enum.js.map +1 -1
  18. package/dist/types/enum/probe-scan.enum.js.map +1 -1
  19. package/dist/types/interface/api/requests/index.d.ts +1 -0
  20. package/dist/types/interface/api/requests/index.js +1 -0
  21. package/dist/types/interface/api/requests/index.js.map +1 -1
  22. package/dist/types/interface/api/requests/probe-delivery-subscription.request.d.ts +23 -0
  23. package/dist/types/interface/api/requests/probe-delivery-subscription.request.js +3 -0
  24. package/dist/types/interface/api/requests/probe-delivery-subscription.request.js.map +1 -0
  25. package/dist/types/interface/api/requests/probe-webhook.request.d.ts +1 -4
  26. package/dist/types/interface/api/responses/index.d.ts +1 -0
  27. package/dist/types/interface/api/responses/index.js +1 -0
  28. package/dist/types/interface/api/responses/index.js.map +1 -1
  29. package/dist/types/interface/api/responses/probe-delivery-subscription.response.d.ts +10 -0
  30. package/dist/types/interface/api/responses/probe-delivery-subscription.response.js +3 -0
  31. package/dist/types/interface/api/responses/probe-delivery-subscription.response.js.map +1 -0
  32. package/dist/types/tests/probe.dto.test.js +22 -0
  33. package/dist/types/tests/probe.dto.test.js.map +1 -1
  34. package/dist/utils/probe-webhook-signature.utils.js.map +1 -1
  35. package/package.json +10 -1
  36. package/src/api/index.ts +7 -6
  37. package/src/api/probe-delivery-subscription.api.ts +183 -0
  38. package/src/api/probe-delivery-subscription.application.api.ts +186 -0
  39. package/src/api/probe-webhook.application.api.ts +128 -141
  40. package/src/index.ts +262 -205
  41. package/src/types/enum/api-error.enum.ts +35 -33
  42. package/src/types/enum/probe-scan.enum.ts +45 -44
  43. package/src/types/interface/api/requests/index.ts +4 -3
  44. package/src/types/interface/api/requests/probe-delivery-subscription.request.ts +42 -0
  45. package/src/types/interface/api/requests/probe-webhook.request.ts +31 -33
  46. package/src/types/interface/api/responses/index.ts +4 -3
  47. package/src/types/interface/api/responses/probe-delivery-subscription.response.ts +27 -0
  48. package/src/types/interface/models/probe-webhook-delivery.dto.ts +70 -73
  49. package/src/types/tests/probe.dto.test.ts +192 -156
  50. package/src/utils/probe-webhook-signature.utils.ts +57 -56
  51. package/dist/api/probe-webhook.api.d.ts +0 -6
  52. package/dist/api/probe-webhook.api.js +0 -41
  53. package/dist/api/probe-webhook.api.js.map +0 -1
  54. package/dist/api/probe-webhook.api.test.d.ts +0 -1
  55. package/dist/api/probe-webhook.api.test.js +0 -43
  56. package/dist/api/probe-webhook.api.test.js.map +0 -1
  57. package/src/api/probe-webhook.api.test.ts +0 -17
  58. package/src/api/probe-webhook.api.ts +0 -119
@@ -1,156 +1,192 @@
1
- // Type-level compile test for AbyssProbe's DTO/response shapes — asserts
2
- // they match the server contract (probe-scan.controller.ts /
3
- // probe-webhook.controller.ts / probe-usage.controller.ts /
4
- // probe-webhook-delivery.service.ts) at compile time: any incompatible
5
- // rename/removal on either side fails `npm run build` here. No jest runner
6
- // in this SDK package; compile guard only (mirrors
7
- // ../api/extraction-plan-preset.api.test.ts). Run: npx tsc --noEmit.
8
-
9
- import {
10
- ProbeScanVerdict,
11
- ProbeScanMode,
12
- ProbeScanStatus,
13
- IProbeScanDetail,
14
- IProbeScanHistoryItem,
15
- IProbeWebhook,
16
- IProbeWebhookDelivery,
17
- IProbeWebhookDeliveryPayload,
18
- IScanSyncData,
19
- IScanAsyncData,
20
- IUpdateProbeWebhookBody,
21
- IProbeUsageData,
22
- } from '..';
23
- // estimateProbeScanCost lives in src/utils/ (a SIBLING of src/types/, not
24
- // re-exported by the types barrel this file otherwise imports from) — see
25
- // probe-scan-cost.utils.ts's own header comment for why it is a plain
26
- // top-level export rather than a `sdk.probe.*` method.
27
- import { estimateProbeScanCost } from '../../utils';
28
-
29
- // Enum exhaustiveness: fails to compile if a member is added/removed/renamed
30
- // here without updating this file — a cheap drift guard for values
31
- // transported verbatim over the wire (this SDK does not control the
32
- // server's enum, so a silent value drift is exactly what this must catch).
33
- const _verdicts: Record<ProbeScanVerdict, true> = { clean: true, infected: true };
34
- const _modes: Record<ProbeScanMode, true> = { sync: true, async: true };
35
- const _statuses: Record<ProbeScanStatus, true> = {
36
- queued: true,
37
- scanning: true,
38
- done: true,
39
- error: true,
40
- };
41
-
42
- // GET /scan/:scanId — WS5-4: full metadata parity with IProbeScanHistoryItem
43
- // (fileName/fileSize/mimeType/mode/startedAt/finishedAt/costUnits).
44
- const _scanDetail: IProbeScanDetail = {
45
- scanId: 's1',
46
- status: ProbeScanStatus.DONE,
47
- verdict: ProbeScanVerdict.CLEAN,
48
- viruses: [],
49
- context: null,
50
- fileName: 'invoice.pdf',
51
- fileSize: 1024,
52
- mimeType: 'application/pdf',
53
- mode: ProbeScanMode.SYNC,
54
- startedAt: new Date(),
55
- finishedAt: new Date(),
56
- costUnits: 3,
57
- };
58
-
59
- // GET /scan history row — WS5-1: costUnits added.
60
- const _historyItem: IProbeScanHistoryItem = {
61
- scanId: 's1',
62
- fileName: 'invoice.pdf',
63
- fileSize: 1024,
64
- mimeType: 'application/pdf',
65
- verdict: ProbeScanVerdict.INFECTED,
66
- viruses: ['Eicar-Test-Signature'],
67
- mode: ProbeScanMode.ASYNC,
68
- status: ProbeScanStatus.DONE,
69
- context: { applicationFileId: 'af1' },
70
- startedAt: new Date(),
71
- finishedAt: new Date(),
72
- costUnits: 1,
73
- createdAt: new Date(),
74
- };
75
-
76
- // POST /scan — sync (200) / async (202) data shapes. WS5-1: costUnits added to the sync shape (always known/billed by the time a 200 is returned).
77
- const _scanSyncData: IScanSyncData = {
78
- scanId: 's1',
79
- verdict: ProbeScanVerdict.CLEAN,
80
- viruses: [],
81
- costUnits: 3,
82
- };
83
- const _scanAsyncData: IScanAsyncData = { scanId: 's1', status: ProbeScanStatus.QUEUED };
84
-
85
- // Webhook subscription — WS5-7: secret is now ALWAYS present (list/create/
86
- // update) and the create response returns IProbeWebhook.
87
- const _webhook: IProbeWebhook = {
88
- id: 'w1',
89
- callbackUrl: 'https://example.com/hook',
90
- secret: 'shh',
91
- active: true,
92
- createdAt: new Date(),
93
- };
94
-
95
- // PATCH /webhook/:webhookId body (WS5-5) — every field optional (at least one required server-side).
96
- const _updateBody: IUpdateProbeWebhookBody = { active: false };
97
- const _updateBody2: IUpdateProbeWebhookBody = { callbackUrl: 'https://example.com/hook2' };
98
-
99
- // Webhook delivery payload — the exact body signed by x-probe-signature
100
- // (see verifyProbeWebhookSignature). WS5-1: costUnits added.
101
- const _deliveryPayload: IProbeWebhookDeliveryPayload = {
102
- scanId: 's1',
103
- context: null,
104
- verdict: ProbeScanVerdict.INFECTED,
105
- viruses: ['Eicar-Test-Signature'],
106
- costUnits: 5,
107
- };
108
-
109
- // GET /webhook/:webhookId/deliveries row (WS5-6) — a persisted delivery-attempt.
110
- const _deliveryRecord: IProbeWebhookDelivery = {
111
- id: 'd1',
112
- webhookId: 'w1',
113
- scanId: 's1',
114
- url: 'https://example.com/hook',
115
- statusCode: 200,
116
- success: true,
117
- attemptedAt: new Date(),
118
- error: null,
119
- createdAt: new Date(),
120
- };
121
- // webhookId is nullable (ad-hoc per-request callbackUrl with no matching registration).
122
- const _deliveryRecordAdHoc: IProbeWebhookDelivery = { ..._deliveryRecord, webhookId: null };
123
-
124
- // GET /usage data shape (WS5-3).
125
- const _usage: IProbeUsageData = { used: 10, limit: 1000, remaining: 990 };
126
- // -1 is the established "unlimited" sentinel (see estimateProbeScanCost's
127
- // server-side counterpart, utils/billing-emit.util.ts#UNLIMITED_EXPIRY_DAYS).
128
- const _usageUnlimited: IProbeUsageData = { used: 10, limit: -1, remaining: -1 };
129
-
130
- // estimateProbeScanCost (WS5-2) — pure shared cost model, ASYNC must cost
131
- // strictly less than SYNC for the same size (the scanner UI's "background
132
- // analysis saves tokens" tooltip depends on this). A one-byte compile-time
133
- // smoke check; the full behavioural suite lives in probe-scan-cost.utils.test.ts.
134
- const _asyncCost: number = estimateProbeScanCost(5 * 1024 * 1024, ProbeScanMode.ASYNC);
135
- const _syncCost: number = estimateProbeScanCost(5 * 1024 * 1024, ProbeScanMode.SYNC);
136
- if (_asyncCost >= _syncCost) {
137
- throw new Error('estimateProbeScanCost: ASYNC must cost strictly less than SYNC');
138
- }
139
-
140
- void _verdicts;
141
- void _modes;
142
- void _statuses;
143
- void _scanDetail;
144
- void _historyItem;
145
- void _scanSyncData;
146
- void _scanAsyncData;
147
- void _webhook;
148
- void _updateBody;
149
- void _updateBody2;
150
- void _deliveryPayload;
151
- void _deliveryRecord;
152
- void _deliveryRecordAdHoc;
153
- void _usage;
154
- void _usageUnlimited;
155
-
156
- export {};
1
+ // Type-level compile test for AbyssProbe's DTO/response shapes — asserts
2
+ // they match the server contract (probe-scan.controller.ts /
3
+ // probe-webhook-application.controller.ts / probe-usage.controller.ts /
4
+ // probe-webhook-delivery.service.ts) at compile time: any incompatible
5
+ // rename/removal on either side fails `npm run build` here. No jest runner
6
+ // in this SDK package; compile guard only (mirrors
7
+ // ../api/extraction-plan-preset.api.test.ts). Run: npx tsc --noEmit.
8
+
9
+ import {
10
+ ProbeScanVerdict,
11
+ ProbeScanMode,
12
+ ProbeScanStatus,
13
+ IProbeScanDetail,
14
+ IProbeScanHistoryItem,
15
+ IProbeWebhook,
16
+ IProbeWebhookDelivery,
17
+ IProbeWebhookDeliveryPayload,
18
+ IScanSyncData,
19
+ IScanAsyncData,
20
+ IUpdateProbeWebhookBody,
21
+ IProbeUsageData,
22
+ ICreateProbeDeliverySubscriptionBody,
23
+ IUpdateProbeDeliverySubscriptionBody,
24
+ IPaginateProbeDeliveryQuery,
25
+ IPaginateProbeDeliverySubscriptionQuery,
26
+ ICreateProbeDeliverySubscriptionResponse,
27
+ IGetProbeDeliverySubscriptionResponse,
28
+ Code,
29
+ } from '..';
30
+ // estimateProbeScanCost lives in src/utils/ (a SIBLING of src/types/, not
31
+ // re-exported by the types barrel this file otherwise imports from) — see
32
+ // probe-scan-cost.utils.ts's own header comment for why it is a plain
33
+ // top-level export rather than a `sdk.probe.*` method.
34
+ import { estimateProbeScanCost } from '../../utils';
35
+
36
+ // Enum exhaustiveness: fails to compile if a member is added/removed/renamed
37
+ // here without updating this file — a cheap drift guard for values
38
+ // transported verbatim over the wire (this SDK does not control the
39
+ // server's enum, so a silent value drift is exactly what this must catch).
40
+ const _verdicts: Record<ProbeScanVerdict, true> = { clean: true, infected: true };
41
+ const _modes: Record<ProbeScanMode, true> = { sync: true, async: true };
42
+ const _statuses: Record<ProbeScanStatus, true> = {
43
+ queued: true,
44
+ scanning: true,
45
+ done: true,
46
+ error: true,
47
+ };
48
+
49
+ // GET /scan/:scanId — WS5-4: full metadata parity with IProbeScanHistoryItem
50
+ // (fileName/fileSize/mimeType/mode/startedAt/finishedAt/costUnits).
51
+ const _scanDetail: IProbeScanDetail = {
52
+ scanId: 's1',
53
+ status: ProbeScanStatus.DONE,
54
+ verdict: ProbeScanVerdict.CLEAN,
55
+ viruses: [],
56
+ context: null,
57
+ fileName: 'invoice.pdf',
58
+ fileSize: 1024,
59
+ mimeType: 'application/pdf',
60
+ mode: ProbeScanMode.SYNC,
61
+ startedAt: new Date(),
62
+ finishedAt: new Date(),
63
+ costUnits: 3,
64
+ };
65
+
66
+ // GET /scan history row — WS5-1: costUnits added.
67
+ const _historyItem: IProbeScanHistoryItem = {
68
+ scanId: 's1',
69
+ fileName: 'invoice.pdf',
70
+ fileSize: 1024,
71
+ mimeType: 'application/pdf',
72
+ verdict: ProbeScanVerdict.INFECTED,
73
+ viruses: ['Eicar-Test-Signature'],
74
+ mode: ProbeScanMode.ASYNC,
75
+ status: ProbeScanStatus.DONE,
76
+ context: { applicationFileId: 'af1' },
77
+ startedAt: new Date(),
78
+ finishedAt: new Date(),
79
+ costUnits: 1,
80
+ createdAt: new Date(),
81
+ };
82
+
83
+ // POST /scan — sync (200) / async (202) data shapes. WS5-1: costUnits added to the sync shape (always known/billed by the time a 200 is returned).
84
+ const _scanSyncData: IScanSyncData = {
85
+ scanId: 's1',
86
+ verdict: ProbeScanVerdict.CLEAN,
87
+ viruses: [],
88
+ costUnits: 3,
89
+ };
90
+ const _scanAsyncData: IScanAsyncData = { scanId: 's1', status: ProbeScanStatus.QUEUED };
91
+
92
+ // Webhook subscription — WS5-7: secret is now ALWAYS present (list/create/
93
+ // update) and the create response returns IProbeWebhook.
94
+ const _webhook: IProbeWebhook = {
95
+ id: 'w1',
96
+ callbackUrl: 'https://example.com/hook',
97
+ secret: 'shh',
98
+ active: true,
99
+ createdAt: new Date(),
100
+ };
101
+
102
+ // PATCH /application/:applicationId/webhook/:webhookId body (WS5-5) — every field optional (at least one required server-side).
103
+ const _updateBody: IUpdateProbeWebhookBody = { active: false };
104
+ const _updateBody2: IUpdateProbeWebhookBody = { callbackUrl: 'https://example.com/hook2' };
105
+
106
+ // Webhook delivery payload — the exact body signed by x-probe-signature
107
+ // (see verifyProbeWebhookSignature). WS5-1: costUnits added.
108
+ const _deliveryPayload: IProbeWebhookDeliveryPayload = {
109
+ scanId: 's1',
110
+ context: null,
111
+ verdict: ProbeScanVerdict.INFECTED,
112
+ viruses: ['Eicar-Test-Signature'],
113
+ costUnits: 5,
114
+ };
115
+
116
+ // GET /application/:applicationId/webhook/:webhookId/deliveries row (WS5-6) — a persisted delivery-attempt.
117
+ const _deliveryRecord: IProbeWebhookDelivery = {
118
+ id: 'd1',
119
+ webhookId: 'w1',
120
+ scanId: 's1',
121
+ url: 'https://example.com/hook',
122
+ statusCode: 200,
123
+ success: true,
124
+ attemptedAt: new Date(),
125
+ error: null,
126
+ createdAt: new Date(),
127
+ };
128
+ // webhookId stays nullable: it mirrors the column's real type.
129
+ const _deliveryRecordAdHoc: IProbeWebhookDelivery = { ..._deliveryRecord, webhookId: null };
130
+
131
+ // Delivery subscriptions (P5 #792) — the client's subscriptions live in Flow; the body names the
132
+ // one event and the URL, the scope is the caller (or `organizationId`, or the application).
133
+ const _createSubscription: ICreateProbeDeliverySubscriptionBody = {
134
+ eventTypes: ['PROBE_SCAN_COMPLETED'],
135
+ url: 'https://example.com/hook',
136
+ };
137
+ const _updateSubscription: IUpdateProbeDeliverySubscriptionBody = { isEnabled: false };
138
+ const _subscriptionQuery: IPaginateProbeDeliverySubscriptionQuery = {
139
+ organizationId: 'o1',
140
+ isEnabled: true,
141
+ page: 1,
142
+ limit: 20,
143
+ };
144
+ const _deliveryQuery: IPaginateProbeDeliveryQuery = { organizationId: 'o1', page: 2 };
145
+ // The secret is on the creation answer only.
146
+ declare const created: ICreateProbeDeliverySubscriptionResponse;
147
+ declare const read: IGetProbeDeliverySubscriptionResponse;
148
+ const _secret: string = created.data.secret;
149
+ // @ts-expect-error a read never carries the secret
150
+ const _noSecret: string = read.data.secret;
151
+ const _callbackCode: Code = Code.probeCallbackRequiresSubscription;
152
+
153
+ // GET /usage data shape (WS5-3).
154
+ const _usage: IProbeUsageData = { used: 10, limit: 1000, remaining: 990 };
155
+ // -1 is the established "unlimited" sentinel (see estimateProbeScanCost's
156
+ // server-side counterpart, utils/billing-emit.util.ts#UNLIMITED_EXPIRY_DAYS).
157
+ const _usageUnlimited: IProbeUsageData = { used: 10, limit: -1, remaining: -1 };
158
+
159
+ // estimateProbeScanCost (WS5-2) — pure shared cost model, ASYNC must cost
160
+ // strictly less than SYNC for the same size (the scanner UI's "background
161
+ // analysis saves tokens" tooltip depends on this). A one-byte compile-time
162
+ // smoke check; the full behavioural suite lives in probe-scan-cost.utils.test.ts.
163
+ const _asyncCost: number = estimateProbeScanCost(5 * 1024 * 1024, ProbeScanMode.ASYNC);
164
+ const _syncCost: number = estimateProbeScanCost(5 * 1024 * 1024, ProbeScanMode.SYNC);
165
+ if (_asyncCost >= _syncCost) {
166
+ throw new Error('estimateProbeScanCost: ASYNC must cost strictly less than SYNC');
167
+ }
168
+
169
+ void _verdicts;
170
+ void _modes;
171
+ void _statuses;
172
+ void _scanDetail;
173
+ void _historyItem;
174
+ void _scanSyncData;
175
+ void _scanAsyncData;
176
+ void _webhook;
177
+ void _createSubscription;
178
+ void _updateSubscription;
179
+ void _subscriptionQuery;
180
+ void _deliveryQuery;
181
+ void _secret;
182
+ void _noSecret;
183
+ void _callbackCode;
184
+ void _updateBody;
185
+ void _updateBody2;
186
+ void _deliveryPayload;
187
+ void _deliveryRecord;
188
+ void _deliveryRecordAdHoc;
189
+ void _usage;
190
+ void _usageUnlimited;
191
+
192
+ export {};
@@ -1,56 +1,57 @@
1
- // ──────────────────────────────────────────────
2
- // AbyssProbe webhook signature verification (helper for RECEIVERS of a
3
- // webhook delivery — the abyss-main UI/abyss-storage/any external
4
- // integrator that registered a callbackUrl).
5
- //
6
- // Mirrors this package's own extraction-webhook.utils.ts
7
- // (verifyExtractionWebhook) — the same package's own precedent for this
8
- // exact problem — adapted to AbyssProbe's actual wire format: the signature
9
- // header (see PROBE_WEBHOOK_SIGNATURE_HEADER in this package's root index)
10
- // carries the RAW hex HMAC digest with no `sha256=` prefix (unlike
11
- // the extraction signature header `x-rcb-signature: sha256=<hex>`) — see
12
- // apps/api/src/services/probe-webhook-delivery.service.ts's `sign()`.
13
- //
14
- // Lazy-requires `crypto` so the SDK stays browser-compatible when this
15
- // utility is never invoked (a browser page is never the receiving end of a
16
- // server-to-server webhook).
17
- // ──────────────────────────────────────────────
18
-
19
- export interface VerifyProbeWebhookSignatureInput {
20
- /** The exact bytes/string the receiver's HTTP framework handed it — must be the RAW body, not a re-serialized JSON.parse/stringify round trip (whitespace/key-order would change the HMAC). */
21
- rawBody: string | Buffer;
22
- /** The incoming `x-probe-signature` header value. */
23
- signatureHeader: string | undefined;
24
- /** This webhook registration's secret (re-displayable on IProbeWebhook). */
25
- secret: string;
26
- }
27
-
28
- /**
29
- * Recompute the HMAC-SHA256 of `rawBody` with `secret` and constant-time
30
- * compare it against `signatureHeader`. Returns `false` (never throws) for
31
- * any malformed input — a hostile/truncated header must not crash the
32
- * receiver.
33
- */
34
- export function verifyProbeWebhookSignature(input: VerifyProbeWebhookSignatureInput): boolean {
35
- if (!input.signatureHeader || !input.secret) return false;
36
-
37
- // eslint-disable-next-line @typescript-eslint/no-require-imports
38
- const crypto = require('crypto');
39
- const computed: string = crypto
40
- .createHmac('sha256', input.secret)
41
- .update(input.rawBody)
42
- .digest('hex');
43
-
44
- if (input.signatureHeader.length !== computed.length) return false;
45
-
46
- try {
47
- return crypto.timingSafeEqual(
48
- Buffer.from(input.signatureHeader, 'hex'),
49
- Buffer.from(computed, 'hex'),
50
- );
51
- } catch {
52
- // Buffer.from(..., 'hex') on a non-hex string yields a shorter buffer,
53
- // which would otherwise make timingSafeEqual throw on a length mismatch.
54
- return false;
55
- }
56
- }
1
+ // ──────────────────────────────────────────────
2
+ // AbyssProbe webhook signature verification (helper for the RECEIVER of the
3
+ // internal webhook — AbyssStorage's antivirus subscription — until its client
4
+ // subscriptions move to Flow, P5 #792 L2). A client's own subscription is
5
+ // signed by Flow, whose helper verifies it.
6
+ //
7
+ // Mirrors this package's own extraction-webhook.utils.ts
8
+ // (verifyExtractionWebhook) — the same package's own precedent for this
9
+ // exact problem — adapted to AbyssProbe's actual wire format: the signature
10
+ // header (see PROBE_WEBHOOK_SIGNATURE_HEADER in this package's root index)
11
+ // carries the RAW hex HMAC digest with no `sha256=` prefix (unlike
12
+ // the extraction signature header `x-rcb-signature: sha256=<hex>`) — see
13
+ // apps/api/src/services/probe-webhook-delivery.service.ts's `sign()`.
14
+ //
15
+ // Lazy-requires `crypto` so the SDK stays browser-compatible when this
16
+ // utility is never invoked (a browser page is never the receiving end of a
17
+ // server-to-server webhook).
18
+ // ──────────────────────────────────────────────
19
+
20
+ export interface VerifyProbeWebhookSignatureInput {
21
+ /** The exact bytes/string the receiver's HTTP framework handed it — must be the RAW body, not a re-serialized JSON.parse/stringify round trip (whitespace/key-order would change the HMAC). */
22
+ rawBody: string | Buffer;
23
+ /** The incoming `x-probe-signature` header value. */
24
+ signatureHeader: string | undefined;
25
+ /** This webhook registration's secret (re-displayable on IProbeWebhook). */
26
+ secret: string;
27
+ }
28
+
29
+ /**
30
+ * Recompute the HMAC-SHA256 of `rawBody` with `secret` and constant-time
31
+ * compare it against `signatureHeader`. Returns `false` (never throws) for
32
+ * any malformed input — a hostile/truncated header must not crash the
33
+ * receiver.
34
+ */
35
+ export function verifyProbeWebhookSignature(input: VerifyProbeWebhookSignatureInput): boolean {
36
+ if (!input.signatureHeader || !input.secret) return false;
37
+
38
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
39
+ const crypto = require('crypto');
40
+ const computed: string = crypto
41
+ .createHmac('sha256', input.secret)
42
+ .update(input.rawBody)
43
+ .digest('hex');
44
+
45
+ if (input.signatureHeader.length !== computed.length) return false;
46
+
47
+ try {
48
+ return crypto.timingSafeEqual(
49
+ Buffer.from(input.signatureHeader, 'hex'),
50
+ Buffer.from(computed, 'hex'),
51
+ );
52
+ } catch {
53
+ // Buffer.from(..., 'hex') on a non-hex string yields a shorter buffer,
54
+ // which would otherwise make timingSafeEqual throw on a length mismatch.
55
+ return false;
56
+ }
57
+ }
@@ -1,6 +0,0 @@
1
- import type { ICreateProbeWebhookBody, IUpdateProbeWebhookBody, IProbeWebhookScopeQuery, IListProbeWebhookDeliveriesQuery, ICreateProbeWebhookResponse, IListProbeWebhookResponse, IDeleteProbeWebhookResponse, IUpdateProbeWebhookResponse, IListProbeWebhookDeliveriesResponse } from '../types';
2
- export declare const createProbeWebhook: (body: ICreateProbeWebhookBody, scope?: IProbeWebhookScopeQuery, apiKey?: string) => Promise<ICreateProbeWebhookResponse>;
3
- export declare const listProbeWebhook: (scope?: IProbeWebhookScopeQuery, apiKey?: string) => Promise<IListProbeWebhookResponse>;
4
- export declare const deleteProbeWebhook: (webhookId: string, scope?: IProbeWebhookScopeQuery, apiKey?: string) => Promise<IDeleteProbeWebhookResponse>;
5
- export declare const updateProbeWebhook: (webhookId: string, body: IUpdateProbeWebhookBody, scope?: IProbeWebhookScopeQuery, apiKey?: string) => Promise<IUpdateProbeWebhookResponse>;
6
- export declare const listProbeWebhookDeliveries: (webhookId: string, query?: IListProbeWebhookDeliveriesQuery, apiKey?: string) => Promise<IListProbeWebhookDeliveriesResponse>;
@@ -1,41 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.listProbeWebhookDeliveries = exports.updateProbeWebhook = exports.deleteProbeWebhook = exports.listProbeWebhook = exports.createProbeWebhook = void 0;
4
- const __1 = require("..");
5
- const DEFAULT_API_KEY_HEADER = 'abyss-api-key';
6
- function authHeader(apiKey) {
7
- return apiKey ? { [DEFAULT_API_KEY_HEADER]: apiKey } : {};
8
- }
9
- function serializeProbeQuery(query) {
10
- const out = {};
11
- for (const [key, value] of Object.entries(query)) {
12
- if (value === undefined || value === null)
13
- continue;
14
- const wireKey = key === 'organizationId' ? 'organization_id' : key;
15
- out[wireKey] = value instanceof Date ? value.toISOString() : value;
16
- }
17
- return out;
18
- }
19
- const createProbeWebhook = async (body, scope = {}, apiKey) => (await __1.AbyssProbeCore.axios.post('webhook', body, {
20
- params: serializeProbeQuery(scope),
21
- headers: authHeader(apiKey),
22
- })).data;
23
- exports.createProbeWebhook = createProbeWebhook;
24
- const listProbeWebhook = async (scope = {}, apiKey) => (await __1.AbyssProbeCore.axios.get('webhook', {
25
- params: serializeProbeQuery(scope),
26
- headers: authHeader(apiKey),
27
- })).data;
28
- exports.listProbeWebhook = listProbeWebhook;
29
- const deleteProbeWebhook = async (webhookId, scope = {}, apiKey) => (await __1.AbyssProbeCore.axios.delete(`webhook/${webhookId}`, {
30
- params: serializeProbeQuery(scope),
31
- headers: authHeader(apiKey),
32
- })).data;
33
- exports.deleteProbeWebhook = deleteProbeWebhook;
34
- const updateProbeWebhook = async (webhookId, body, scope = {}, apiKey) => (await __1.AbyssProbeCore.axios.patch(`webhook/${webhookId}`, body, {
35
- params: serializeProbeQuery(scope),
36
- headers: authHeader(apiKey),
37
- })).data;
38
- exports.updateProbeWebhook = updateProbeWebhook;
39
- const listProbeWebhookDeliveries = async (webhookId, query = {}, apiKey) => (await __1.AbyssProbeCore.axios.get(`webhook/${webhookId}/deliveries`, { params: serializeProbeQuery(query), headers: authHeader(apiKey) })).data;
40
- exports.listProbeWebhookDeliveries = listProbeWebhookDeliveries;
41
- //# sourceMappingURL=probe-webhook.api.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"probe-webhook.api.js","sourceRoot":"","sources":["../../src/api/probe-webhook.api.ts"],"names":[],"mappings":";;;AAaA,0BAAoC;AAapC,MAAM,sBAAsB,GAAG,eAAe,CAAC;AAE/C,SAAS,UAAU,CAAC,MAAe;IACjC,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5D,CAAC;AAGD,SAAS,mBAAmB,CAAmB,KAAQ;IACrD,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;QAC5E,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;YAAE,SAAS;QACpD,MAAM,OAAO,GAAG,GAAG,KAAK,gBAAgB,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC;QACnE,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IACrE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAOM,MAAM,kBAAkB,GAAG,KAAK,EACrC,IAA6B,EAC7B,QAAiC,EAAE,EACnC,MAAe,EACuB,EAAE,CACxC,CACE,MAAM,kBAAc,CAAC,KAAK,CAAC,IAAI,CAA8B,SAAS,EAAE,IAAI,EAAE;IAC5E,MAAM,EAAE,mBAAmB,CAAC,KAAK,CAAC;IAClC,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC;CAC5B,CAAC,CACH,CAAC,IAAI,CAAC;AAVI,QAAA,kBAAkB,sBAUtB;AAGF,MAAM,gBAAgB,GAAG,KAAK,EACnC,QAAiC,EAAE,EACnC,MAAe,EACqB,EAAE,CACtC,CACE,MAAM,kBAAc,CAAC,KAAK,CAAC,GAAG,CAA4B,SAAS,EAAE;IACnE,MAAM,EAAE,mBAAmB,CAAC,KAAK,CAAC;IAClC,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC;CAC5B,CAAC,CACH,CAAC,IAAI,CAAC;AATI,QAAA,gBAAgB,oBASpB;AAGF,MAAM,kBAAkB,GAAG,KAAK,EACrC,SAAiB,EACjB,QAAiC,EAAE,EACnC,MAAe,EACuB,EAAE,CACxC,CACE,MAAM,kBAAc,CAAC,KAAK,CAAC,MAAM,CAA8B,WAAW,SAAS,EAAE,EAAE;IACrF,MAAM,EAAE,mBAAmB,CAAC,KAAK,CAAC;IAClC,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC;CAC5B,CAAC,CACH,CAAC,IAAI,CAAC;AAVI,QAAA,kBAAkB,sBAUtB;AAQF,MAAM,kBAAkB,GAAG,KAAK,EACrC,SAAiB,EACjB,IAA6B,EAC7B,QAAiC,EAAE,EACnC,MAAe,EACuB,EAAE,CACxC,CACE,MAAM,kBAAc,CAAC,KAAK,CAAC,KAAK,CAA8B,WAAW,SAAS,EAAE,EAAE,IAAI,EAAE;IAC1F,MAAM,EAAE,mBAAmB,CAAC,KAAK,CAAC;IAClC,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC;CAC5B,CAAC,CACH,CAAC,IAAI,CAAC;AAXI,QAAA,kBAAkB,sBAWtB;AAMF,MAAM,0BAA0B,GAAG,KAAK,EAC7C,SAAiB,EACjB,QAA0C,EAAE,EAC5C,MAAe,EAC+B,EAAE,CAChD,CACE,MAAM,kBAAc,CAAC,KAAK,CAAC,GAAG,CAC5B,WAAW,SAAS,aAAa,EACjC,EAAE,MAAM,EAAE,mBAAmB,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CACpE,CACF,CAAC,IAAI,CAAC;AAVI,QAAA,0BAA0B,8BAU9B"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,43 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const api = __importStar(require("./probe-webhook.api"));
37
- const _create = api.createProbeWebhook;
38
- const _list = api.listProbeWebhook;
39
- const _delete = api.deleteProbeWebhook;
40
- void _create;
41
- void _list;
42
- void _delete;
43
- //# sourceMappingURL=probe-webhook.api.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"probe-webhook.api.test.js","sourceRoot":"","sources":["../../src/api/probe-webhook.api.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,yDAA2C;AAI3C,MAAM,OAAO,GAAkC,GAAG,CAAC,kBAAkB,CAAC;AACtE,MAAM,KAAK,GAAgC,GAAG,CAAC,gBAAgB,CAAC;AAChE,MAAM,OAAO,GAAkC,GAAG,CAAC,kBAAkB,CAAC;AAEtE,KAAK,OAAO,CAAC;AACb,KAAK,KAAK,CAAC;AACX,KAAK,OAAO,CAAC"}
@@ -1,17 +0,0 @@
1
- // Type-level compile test for the probe-webhook api surface.
2
- // No jest runner in this SDK package; compile guard only.
3
- // Run: npx tsc --noEmit (from packages/abyss_probe_core), or `npm run build`.
4
-
5
- import * as api from './probe-webhook.api';
6
-
7
- // All three functions must be exported — deliberately no update/reset-secret
8
- // (see probe-webhook.controller.ts's header comment on the small CRUD surface).
9
- const _create: typeof api.createProbeWebhook = api.createProbeWebhook;
10
- const _list: typeof api.listProbeWebhook = api.listProbeWebhook;
11
- const _delete: typeof api.deleteProbeWebhook = api.deleteProbeWebhook;
12
-
13
- void _create;
14
- void _list;
15
- void _delete;
16
-
17
- export {};