@abyss-project/tools 2.0.0 → 2.2.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 (101) hide show
  1. package/dist/api/index.d.ts +4 -0
  2. package/dist/api/index.js +4 -0
  3. package/dist/api/index.js.map +1 -1
  4. package/dist/api/probe-scan.api.d.ts +15 -0
  5. package/dist/api/probe-scan.api.js +74 -0
  6. package/dist/api/probe-scan.api.js.map +1 -0
  7. package/dist/api/probe-scan.api.test.d.ts +1 -0
  8. package/dist/api/probe-scan.api.test.js +37 -0
  9. package/dist/api/probe-scan.api.test.js.map +1 -0
  10. package/dist/api/probe-scan.application.api.d.ts +7 -0
  11. package/dist/api/probe-scan.application.api.js +40 -0
  12. package/dist/api/probe-scan.application.api.js.map +1 -0
  13. package/dist/api/probe-scan.application.api.test.d.ts +1 -0
  14. package/dist/api/probe-scan.application.api.test.js +37 -0
  15. package/dist/api/probe-scan.application.api.test.js.map +1 -0
  16. package/dist/api/probe-webhook.api.d.ts +4 -0
  17. package/dist/api/probe-webhook.api.js +34 -0
  18. package/dist/api/probe-webhook.api.js.map +1 -0
  19. package/dist/api/probe-webhook.api.test.d.ts +1 -0
  20. package/dist/api/probe-webhook.api.test.js +33 -0
  21. package/dist/api/probe-webhook.api.test.js.map +1 -0
  22. package/dist/api/probe-webhook.application.api.d.ts +4 -0
  23. package/dist/api/probe-webhook.application.api.js +14 -0
  24. package/dist/api/probe-webhook.application.api.js.map +1 -0
  25. package/dist/api/probe-webhook.application.api.test.d.ts +1 -0
  26. package/dist/api/probe-webhook.application.api.test.js +33 -0
  27. package/dist/api/probe-webhook.application.api.test.js.map +1 -0
  28. package/dist/index.d.ts +31 -0
  29. package/dist/index.js +32 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/types/enum/index.d.ts +1 -0
  32. package/dist/types/enum/index.js +1 -0
  33. package/dist/types/enum/index.js.map +1 -1
  34. package/dist/types/enum/probe-scan.enum.d.ts +14 -0
  35. package/dist/types/enum/probe-scan.enum.js +21 -0
  36. package/dist/types/enum/probe-scan.enum.js.map +1 -0
  37. package/dist/types/interface/api/requests/index.d.ts +2 -0
  38. package/dist/types/interface/api/requests/index.js +2 -0
  39. package/dist/types/interface/api/requests/index.js.map +1 -1
  40. package/dist/types/interface/api/requests/probe-scan.request.d.ts +22 -0
  41. package/dist/types/interface/api/requests/probe-scan.request.js +3 -0
  42. package/dist/types/interface/api/requests/probe-scan.request.js.map +1 -0
  43. package/dist/types/interface/api/requests/probe-webhook.request.d.ts +6 -0
  44. package/dist/types/interface/api/requests/probe-webhook.request.js +3 -0
  45. package/dist/types/interface/api/requests/probe-webhook.request.js.map +1 -0
  46. package/dist/types/interface/api/responses/index.d.ts +2 -0
  47. package/dist/types/interface/api/responses/index.js +2 -0
  48. package/dist/types/interface/api/responses/index.js.map +1 -1
  49. package/dist/types/interface/api/responses/probe-scan.response.d.ts +18 -0
  50. package/dist/types/interface/api/responses/probe-scan.response.js +3 -0
  51. package/dist/types/interface/api/responses/probe-scan.response.js.map +1 -0
  52. package/dist/types/interface/api/responses/probe-webhook.response.d.ts +10 -0
  53. package/dist/types/interface/api/responses/probe-webhook.response.js +3 -0
  54. package/dist/types/interface/api/responses/probe-webhook.response.js.map +1 -0
  55. package/dist/types/interface/models/index.d.ts +3 -0
  56. package/dist/types/interface/models/index.js +3 -0
  57. package/dist/types/interface/models/index.js.map +1 -1
  58. package/dist/types/interface/models/probe-scan.dto.d.ts +22 -0
  59. package/dist/types/interface/models/probe-scan.dto.js +3 -0
  60. package/dist/types/interface/models/probe-scan.dto.js.map +1 -0
  61. package/dist/types/interface/models/probe-webhook-delivery.dto.d.ts +7 -0
  62. package/dist/types/interface/models/probe-webhook-delivery.dto.js +3 -0
  63. package/dist/types/interface/models/probe-webhook-delivery.dto.js.map +1 -0
  64. package/dist/types/interface/models/probe-webhook.dto.d.ts +9 -0
  65. package/dist/types/interface/models/probe-webhook.dto.js +3 -0
  66. package/dist/types/interface/models/probe-webhook.dto.js.map +1 -0
  67. package/dist/types/tests/probe.dto.test.d.ts +1 -0
  68. package/dist/types/tests/probe.dto.test.js +62 -0
  69. package/dist/types/tests/probe.dto.test.js.map +1 -0
  70. package/dist/utils/index.d.ts +1 -0
  71. package/dist/utils/index.js +1 -0
  72. package/dist/utils/index.js.map +1 -1
  73. package/dist/utils/probe-webhook-signature.utils.d.ts +8 -0
  74. package/dist/utils/probe-webhook-signature.utils.js +22 -0
  75. package/dist/utils/probe-webhook-signature.utils.js.map +1 -0
  76. package/package.json +1 -1
  77. package/src/api/index.ts +6 -0
  78. package/src/api/probe-scan.api.test.ts +21 -0
  79. package/src/api/probe-scan.api.ts +191 -0
  80. package/src/api/probe-scan.application.api.test.ts +21 -0
  81. package/src/api/probe-scan.application.api.ts +157 -0
  82. package/src/api/probe-webhook.api.test.ts +17 -0
  83. package/src/api/probe-webhook.api.ts +81 -0
  84. package/src/api/probe-webhook.application.api.test.ts +18 -0
  85. package/src/api/probe-webhook.application.api.ts +87 -0
  86. package/src/index.ts +86 -0
  87. package/src/types/enum/index.ts +1 -0
  88. package/src/types/enum/probe-scan.enum.ts +44 -0
  89. package/src/types/interface/api/requests/index.ts +2 -0
  90. package/src/types/interface/api/requests/probe-scan.request.ts +50 -0
  91. package/src/types/interface/api/requests/probe-webhook.request.ts +12 -0
  92. package/src/types/interface/api/responses/index.ts +2 -0
  93. package/src/types/interface/api/responses/probe-scan.response.ts +38 -0
  94. package/src/types/interface/api/responses/probe-webhook.response.ts +17 -0
  95. package/src/types/interface/models/index.ts +3 -0
  96. package/src/types/interface/models/probe-scan.dto.ts +48 -0
  97. package/src/types/interface/models/probe-webhook-delivery.dto.ts +26 -0
  98. package/src/types/interface/models/probe-webhook.dto.ts +25 -0
  99. package/src/types/tests/probe.dto.test.ts +97 -0
  100. package/src/utils/index.ts +1 -0
  101. package/src/utils/probe-webhook-signature.utils.ts +56 -0
package/src/index.ts CHANGED
@@ -32,6 +32,10 @@ import * as extractionApi from './api/extraction.api';
32
32
  import * as extractionSettingsApi from './api/extraction-settings.api';
33
33
  import * as extractionPlanPresetApi from './api/extraction-plan-preset.api';
34
34
  import * as monitorApi from './api/monitor.api';
35
+ import * as probeScanApi from './api/probe-scan.api';
36
+ import * as probeWebhookApi from './api/probe-webhook.api';
37
+ import * as probeScanApplicationApi from './api/probe-scan.application.api';
38
+ import * as probeWebhookApplicationApi from './api/probe-webhook.application.api';
35
39
 
36
40
  export * from './api';
37
41
  export * from './types';
@@ -42,8 +46,24 @@ export * from './server-sent-events';
42
46
  const DEFAULT_BASE_URL = 'https://tools-api.abyss-project.fr/api/';
43
47
 
44
48
  export const API_KEY_HEADER = 'abyss-api-key';
49
+ /**
50
+ * Application-level API key header (Phase 3a — AbyssProbe application/API-key
51
+ * authentication). Same literal value every other Abyss service's own core
52
+ * package independently declares for its `ApplicationMiddleware` (abyss-
53
+ * storage, abyss-monitor, abyss-console — see
54
+ * probe-application.middleware.ts for the mirrored resolution logic).
55
+ */
56
+ export const API_KEY_APPLICATION_HEADER = 'abyss-api-key-application';
45
57
  export const ADMIN_TOKEN_HEADER = 'abyss-admin-token';
46
58
 
59
+ /**
60
+ * Header carrying the HMAC-SHA256 signature of a probe webhook delivery's
61
+ * raw JSON body (see apps/api/src/services/probe-webhook-delivery.service.ts).
62
+ * Verify it with {@link verifyProbeWebhookSignature} (re-exported from './utils').
63
+ */
64
+ export const PROBE_WEBHOOK_SIGNATURE_HEADER = 'x-probe-signature';
65
+ export const PROBE_WEBHOOK_SIGNATURE_ALGORITHM = 'sha256';
66
+
47
67
  const NUMBER_RETRY_API = 10;
48
68
  const BASE_DELAY_BETWEEN_RETRY = 5000;
49
69
  const RETRY_CODES = [
@@ -316,6 +336,48 @@ type AbyssToolsCoreSDK = {
316
336
  monitor: {
317
337
  up: typeof monitorApi.upMonitor;
318
338
  };
339
+ probe: {
340
+ /** `POST /scan` — sync (default) or async depending on `options.mode`. */
341
+ scan: typeof probeScanApi.scan;
342
+ /** `POST /scan` forced to `mode: sync` — precisely-typed 200 response. */
343
+ scanSync: typeof probeScanApi.scanSync;
344
+ /** `POST /scan?mode=async` — precisely-typed 202 response. */
345
+ scanAsync: typeof probeScanApi.scanAsync;
346
+ /** `GET /scan/:scanId` — poll a scan's status/verdict. */
347
+ getScan: typeof probeScanApi.getScan;
348
+ /** `GET /scan` — paginated history/search. */
349
+ listScans: typeof probeScanApi.listScans;
350
+ webhook: {
351
+ create: typeof probeWebhookApi.createProbeWebhook;
352
+ list: typeof probeWebhookApi.listProbeWebhook;
353
+ delete: typeof probeWebhookApi.deleteProbeWebhook;
354
+ };
355
+ /**
356
+ * Application/API-key-auth counterpart (Phase 3a) — for inter-service
357
+ * callers (abyss-storage, external integrations) instead of a
358
+ * personal/org session. Same request/response contracts as the methods
359
+ * above, hitting `/application/:applicationId/...` and authenticated
360
+ * with the `abyss-api-key-application` header ({@link
361
+ * API_KEY_APPLICATION_HEADER}) instead of the personal `abyss-api-key`.
362
+ */
363
+ application: {
364
+ /** `POST /application/:applicationId/scan` — sync (default) or async depending on `options.mode`. */
365
+ scan: typeof probeScanApplicationApi.scanApplication;
366
+ /** `POST /application/:applicationId/scan` forced to `mode: sync` — precisely-typed 200 response. */
367
+ scanSync: typeof probeScanApplicationApi.scanSyncApplication;
368
+ /** `POST /application/:applicationId/scan?mode=async` — precisely-typed 202 response. */
369
+ scanAsync: typeof probeScanApplicationApi.scanAsyncApplication;
370
+ /** `GET /application/:applicationId/scan/:scanId` — poll a scan's status/verdict. */
371
+ getScan: typeof probeScanApplicationApi.getScanApplication;
372
+ /** `GET /application/:applicationId/scan` — paginated history/search. */
373
+ listScans: typeof probeScanApplicationApi.listScansApplication;
374
+ webhook: {
375
+ create: typeof probeWebhookApplicationApi.createProbeWebhookApplication;
376
+ list: typeof probeWebhookApplicationApi.listProbeWebhookApplication;
377
+ delete: typeof probeWebhookApplicationApi.deleteProbeWebhookApplication;
378
+ };
379
+ };
380
+ };
319
381
  tag: {
320
382
  list: typeof tagApi.listTag;
321
383
  create: typeof tagApi.createTag;
@@ -707,6 +769,30 @@ export class AbyssToolsCore {
707
769
  monitor: {
708
770
  up: monitorApi.upMonitor,
709
771
  },
772
+ probe: {
773
+ scan: probeScanApi.scan,
774
+ scanSync: probeScanApi.scanSync,
775
+ scanAsync: probeScanApi.scanAsync,
776
+ getScan: probeScanApi.getScan,
777
+ listScans: probeScanApi.listScans,
778
+ webhook: {
779
+ create: probeWebhookApi.createProbeWebhook,
780
+ list: probeWebhookApi.listProbeWebhook,
781
+ delete: probeWebhookApi.deleteProbeWebhook,
782
+ },
783
+ application: {
784
+ scan: probeScanApplicationApi.scanApplication,
785
+ scanSync: probeScanApplicationApi.scanSyncApplication,
786
+ scanAsync: probeScanApplicationApi.scanAsyncApplication,
787
+ getScan: probeScanApplicationApi.getScanApplication,
788
+ listScans: probeScanApplicationApi.listScansApplication,
789
+ webhook: {
790
+ create: probeWebhookApplicationApi.createProbeWebhookApplication,
791
+ list: probeWebhookApplicationApi.listProbeWebhookApplication,
792
+ delete: probeWebhookApplicationApi.deleteProbeWebhookApplication,
793
+ },
794
+ },
795
+ },
710
796
  tag: {
711
797
  list: tagApi.listTag,
712
798
  create: tagApi.createTag,
@@ -11,3 +11,4 @@ export * from './extraction-feedback-issue-type.enum';
11
11
  export * from './provider-kind.enum';
12
12
  export * from './interpreter-kind.enum';
13
13
  export * from './combine-strategy.enum';
14
+ export * from './probe-scan.enum';
@@ -0,0 +1,44 @@
1
+ // Mirrors apps/api/src/database/probe-scan.enum.ts EXACTLY (same string
2
+ // values — this SDK talks to that API over the wire, so the values must
3
+ // match byte-for-byte). Kept as a single file here too, on purpose: the
4
+ // server groups all three enums in one file for the same reason (they are
5
+ // one cohesive scan vocabulary), see that file's header comment.
6
+
7
+ /**
8
+ * Antivirus scan verdict. `null` on the wire while the scan has not reached
9
+ * `DONE` yet (see {@link ProbeScanStatus}) — a scan that never completes
10
+ * (e.g. the engine throws) is left at its last real status with no
11
+ * fabricated verdict.
12
+ */
13
+ export enum ProbeScanVerdict {
14
+ CLEAN = 'clean',
15
+ INFECTED = 'infected',
16
+ }
17
+
18
+ /**
19
+ * How the scan was (or will be) run: `SYNC` blocks the request until the
20
+ * engine returns the verdict inline; `ASYNC` queues the job and delivers the
21
+ * verdict later via a registered webhook and/or the per-request
22
+ * `callbackUrl`, and/or by polling `GET /scan/:scanId`.
23
+ */
24
+ export enum ProbeScanMode {
25
+ SYNC = 'sync',
26
+ ASYNC = 'async',
27
+ }
28
+
29
+ /**
30
+ * Scan lifecycle. A SYNC scan skips `QUEUED` entirely (created directly at
31
+ * `SCANNING`). An ASYNC scan is created at `QUEUED`, flipped to `SCANNING`
32
+ * once a worker picks the job off the queue, then to `DONE` or `ERROR`.
33
+ *
34
+ * `ERROR` (async-only) is a TERMINAL, no-verdict state — there is no
35
+ * automatic requeue/retry (the uploaded bytes are deleted the moment the job
36
+ * ends, success or failure). A caller that wants to retry submits a brand
37
+ * new `POST /scan?mode=async`.
38
+ */
39
+ export enum ProbeScanStatus {
40
+ QUEUED = 'queued',
41
+ SCANNING = 'scanning',
42
+ DONE = 'done',
43
+ ERROR = 'error',
44
+ }
@@ -27,3 +27,5 @@ export * from './metrics.request';
27
27
  export * from './tag.request';
28
28
  export * from './transfer.request';
29
29
  export * from './extraction.request';
30
+ export * from './probe-scan.request';
31
+ export * from './probe-webhook.request';
@@ -0,0 +1,50 @@
1
+ import { ProbeScanMode, ProbeScanStatus, ProbeScanVerdict } from '../../../enum';
2
+
3
+ // Mirrors apps/api/src/api/controllers/probe-scan.controller.ts's ScanBody +
4
+ // HistoryQuery, and apps/api/src/api/validators/probe-scan.validator.ts's
5
+ // historyQuery() Joi schema (field names/optionality).
6
+
7
+ /**
8
+ * Options accepted by `sdk.probe.scan(file, options)` — `POST /scan`. `mode`
9
+ * and `organizationId` travel as query params (`?mode=async&organization_id=`);
10
+ * `context` and `callbackUrl` travel as multipart text fields alongside the
11
+ * file — the sdk client handles that split, callers just pass one object.
12
+ */
13
+ export interface IScanOptions {
14
+ /** sync (default — verdict returned inline) or async (queued, 202 + webhook/poll). */
15
+ mode?: ProbeScanMode;
16
+ /** Personal scope (default) or an organization the caller belongs to. */
17
+ organizationId?: string;
18
+ /** Free-form caller reference (e.g. `{ applicationFileId }`), stored verbatim. */
19
+ context?: Record<string, unknown>;
20
+ /** ASYNC-only per-request delivery override (https only). Ignored for a SYNC scan. */
21
+ callbackUrl?: string;
22
+ /** Filename reported to the server (defaults to `'file'`). Cosmetic only. */
23
+ filename?: string;
24
+ /** Content-Type hint used when constructing a Blob from a raw buffer/Uint8Array in a browser context. */
25
+ mimeType?: string;
26
+ }
27
+
28
+ /**
29
+ * `GET /scan` query — all filters optional, combined with AND.
30
+ * `contextKey`/`contextValue` must be supplied together (business-reference
31
+ * search inside the free-form `context` JSONB column) — supplying one
32
+ * without the other is rejected by the API (400).
33
+ */
34
+ export interface IListScansQuery {
35
+ organizationId?: string;
36
+ verdict?: ProbeScanVerdict;
37
+ mode?: ProbeScanMode;
38
+ status?: ProbeScanStatus;
39
+ /** Date range on `createdAt`, inclusive at both ends. */
40
+ from?: Date;
41
+ to?: Date;
42
+ /** Case-insensitive partial match on the original file name. */
43
+ fileName?: string;
44
+ contextKey?: string;
45
+ contextValue?: string;
46
+ /** @default 1 */
47
+ page?: number;
48
+ /** @default 20, server max 200. */
49
+ limit?: number;
50
+ }
@@ -0,0 +1,12 @@
1
+ // Mirrors apps/api/src/api/controllers/probe-webhook.controller.ts's
2
+ // ScopeQuery/CreateBody and probe-webhook.validator.ts's Joi schemas.
3
+
4
+ /** Shared by create/list/delete — personal scope (default) or an organization. */
5
+ export interface IProbeWebhookScopeQuery {
6
+ organizationId?: string;
7
+ }
8
+
9
+ export interface ICreateProbeWebhookBody {
10
+ /** HTTPS endpoint the verdict is POSTed to. Required, `https://` only. */
11
+ callbackUrl: string;
12
+ }
@@ -31,3 +31,5 @@ export * from './recent.response';
31
31
  export * from './transfer.response';
32
32
  export * from './extraction.response';
33
33
  export * from './monitor.response';
34
+ export * from './probe-scan.response';
35
+ export * from './probe-webhook.response';
@@ -0,0 +1,38 @@
1
+ import { IResponse } from '../type-message/response';
2
+ import { BasePaginate } from '../type-message/base-paginate';
3
+ import { ProbeScanStatus, ProbeScanVerdict } from '../../../enum';
4
+ import { IProbeScanDetail, IProbeScanHistoryItem } from '../../models';
5
+
6
+ // Mirrors apps/api/src/api/controllers/probe-scan.controller.ts's local
7
+ // ScanSyncResponseData / ScanAsyncResponseData / GetScanResponseData /
8
+ // PaginateScanResponseData EXACTLY.
9
+
10
+ /** `POST /scan` (default, or explicit `?mode=sync`) — HTTP 200. */
11
+ export interface IScanSyncData {
12
+ scanId: string;
13
+ verdict: ProbeScanVerdict;
14
+ viruses: string[];
15
+ }
16
+ export type IScanSyncResponse = IResponse<IScanSyncData>;
17
+
18
+ /** `POST /scan?mode=async` — HTTP 202, job queued. */
19
+ export interface IScanAsyncData {
20
+ scanId: string;
21
+ status: ProbeScanStatus.QUEUED;
22
+ }
23
+ export type IScanAsyncResponse = IResponse<IScanAsyncData>;
24
+
25
+ /**
26
+ * `POST /scan`'s actual response body is one or the other of the two data
27
+ * shapes above, discriminated by the request's own `mode` (sync -> 200 +
28
+ * {@link IScanSyncData}, async -> 202 + {@link IScanAsyncData}). A caller
29
+ * that only ever uses one mode should prefer the sdk's `scanSync`/`scanAsync`
30
+ * wrappers, which return the precise, non-union type.
31
+ */
32
+ export type IScanResponse = IScanSyncResponse | IScanAsyncResponse;
33
+
34
+ /** `GET /scan/:scanId` — HTTP 200. */
35
+ export type IGetScanResponse = IResponse<IProbeScanDetail>;
36
+
37
+ /** `GET /scan` — HTTP 200, paginated history/search. */
38
+ export type IPaginateScanResponse = IResponse<BasePaginate<IProbeScanHistoryItem>>;
@@ -0,0 +1,17 @@
1
+ import { IResponse } from '../type-message/response';
2
+ import { IProbeWebhook, IProbeWebhookCreated } from '../../models';
3
+
4
+ // Mirrors apps/api/src/api/controllers/probe-webhook.controller.ts's local
5
+ // response shapes EXACTLY.
6
+
7
+ /** `POST /webhook` — HTTP 201. The only response that ever carries `secret`. */
8
+ export type ICreateProbeWebhookResponse = IResponse<IProbeWebhookCreated>;
9
+
10
+ /** `GET /webhook` — HTTP 200. A flat scope-filtered list, NOT paginated. */
11
+ export interface IListProbeWebhookData {
12
+ webhooks: IProbeWebhook[];
13
+ }
14
+ export type IListProbeWebhookResponse = IResponse<IListProbeWebhookData>;
15
+
16
+ /** `DELETE /webhook/:webhookId` — HTTP 200. */
17
+ export type IDeleteProbeWebhookResponse = IResponse<{ ok: true }>;
@@ -32,3 +32,6 @@ export * from './content-share.dto';
32
32
  export * from './extraction.dto';
33
33
  export * from './extraction-plan.dto';
34
34
  export * from './extraction-spatial-template.dto';
35
+ export * from './probe-scan.dto';
36
+ export * from './probe-webhook.dto';
37
+ export * from './probe-webhook-delivery.dto';
@@ -0,0 +1,48 @@
1
+ import { ProbeScanMode, ProbeScanStatus, ProbeScanVerdict } from '../../enum';
2
+
3
+ // Mirrors apps/api/src/api/controllers/probe-scan.controller.ts's local
4
+ // response interfaces. AbyssProbe deliberately returns TWO different scan
5
+ // shapes depending on the endpoint — never the raw `probe_scans` row, and
6
+ // never a single one-size-fits-all model — so this file mirrors that with
7
+ // two distinct interfaces rather than forcing one canonical "IProbeScan".
8
+
9
+ /**
10
+ * `GET /scan/:scanId` shape (poll the queued -> scanning -> done/error
11
+ * progression). Deliberately minimal: no fileName/fileSize/mimeType/mode/
12
+ * timestamps, no internal ownership columns (userId/organizationId/
13
+ * callbackUrl) — a caller polling by id already knows what it submitted.
14
+ */
15
+ export interface IProbeScanDetail {
16
+ scanId: string;
17
+ status: ProbeScanStatus;
18
+ /** null until `status` is DONE. */
19
+ verdict: ProbeScanVerdict | null;
20
+ /** null until `status` is DONE; [] when DONE and clean. */
21
+ viruses: string[] | null;
22
+ /** Free-form caller reference echoed back verbatim (see IProbeScanHistoryItem#context). */
23
+ context: Record<string, unknown> | null;
24
+ }
25
+
26
+ /**
27
+ * One row of `GET /scan`'s paginated history/search list. Richer than
28
+ * {@link IProbeScanDetail} (adds fileName/fileSize/mimeType/mode/timestamps)
29
+ * but still never echoes internal ownership columns (userId, organizationId,
30
+ * callbackUrl) — a caller already knows their own scope from the request
31
+ * they made.
32
+ */
33
+ export interface IProbeScanHistoryItem {
34
+ scanId: string;
35
+ fileName: string;
36
+ fileSize: number;
37
+ mimeType: string;
38
+ verdict: ProbeScanVerdict | null;
39
+ viruses: string[] | null;
40
+ mode: ProbeScanMode;
41
+ status: ProbeScanStatus;
42
+ context: Record<string, unknown> | null;
43
+ /** Set the moment the engine call starts (SYNC: row-creation time; ASYNC: when a worker picks the job off the queue). */
44
+ startedAt: Date;
45
+ /** Set once the engine call returns. null while status is not DONE. */
46
+ finishedAt: Date | null;
47
+ createdAt: Date;
48
+ }
@@ -0,0 +1,26 @@
1
+ import { ProbeScanVerdict } from '../../enum';
2
+
3
+ // Mirrors apps/api/src/services/probe-webhook-delivery.service.ts's
4
+ // ProbeWebhookPayload EXACTLY (same field set, same JSON.stringify'd body a
5
+ // receiver's raw request body will deserialize into) — this is the payload
6
+ // AbyssProbe POSTs to every registered webhook's `callbackUrl` (and to a
7
+ // scan's own per-request `callbackUrl`, see probe-scan docs) once an ASYNC
8
+ // scan reaches a verdict (`status: DONE`).
9
+ //
10
+ // `verdict` is typed as the concrete enum here (not `string` like the
11
+ // server's internal type) because delivery only ever happens on a DONE scan,
12
+ // where the verdict is always set — see dispatchProbeScanWebhooks's guard.
13
+
14
+ /**
15
+ * Body of a webhook delivery, signed via HMAC-SHA256 of
16
+ * `JSON.stringify(payload)` — see {@link PROBE_WEBHOOK_SIGNATURE_HEADER} and
17
+ * {@link verifyProbeWebhookSignature} (this package's root / utils).
18
+ */
19
+ export interface IProbeWebhookDeliveryPayload {
20
+ scanId: string;
21
+ /** Free-form caller reference, echoed back verbatim (see IProbeScanHistoryItem#context). */
22
+ context: Record<string, unknown> | null;
23
+ verdict: ProbeScanVerdict;
24
+ /** [] when clean. */
25
+ viruses: string[];
26
+ }
@@ -0,0 +1,25 @@
1
+ // Mirrors apps/api/src/api/controllers/probe-webhook.controller.ts's local
2
+ // WebhookResponseData / CreateWebhookResponseData. Public shape of a
3
+ // registered `probe_webhooks` row — never carries `secret` except right
4
+ // after creation (see IProbeWebhookCreated below); never echoes the
5
+ // internal ownership columns (userId/organizationId/applicationId) either,
6
+ // same discipline as the scan models.
7
+
8
+ export interface IProbeWebhook {
9
+ id: string;
10
+ /** HTTPS endpoint the verdict is POSTed to. */
11
+ callbackUrl: string;
12
+ /** Disabled webhooks are skipped by dispatch (soft-off, not deleted). */
13
+ active: boolean;
14
+ createdAt: Date;
15
+ }
16
+
17
+ /**
18
+ * Create-only response shape: the ONE time `secret` is ever returned by the
19
+ * API ("Secret generated server-side, returned once on create"). Store it
20
+ * immediately — there is no reset-secret / re-reveal endpoint (see this
21
+ * package's header note on the deliberately small CRUD surface).
22
+ */
23
+ export interface IProbeWebhookCreated extends IProbeWebhook {
24
+ secret: string;
25
+ }
@@ -0,0 +1,97 @@
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-webhook-delivery.service.ts) at
4
+ // compile time: any incompatible rename/removal on either side fails
5
+ // `npm run build` here. No jest runner in this SDK package; compile guard
6
+ // only (mirrors ../api/extraction-plan-preset.api.test.ts). Run: npx tsc --noEmit.
7
+
8
+ import {
9
+ ProbeScanVerdict,
10
+ ProbeScanMode,
11
+ ProbeScanStatus,
12
+ IProbeScanDetail,
13
+ IProbeScanHistoryItem,
14
+ IProbeWebhook,
15
+ IProbeWebhookCreated,
16
+ IProbeWebhookDeliveryPayload,
17
+ IScanSyncData,
18
+ IScanAsyncData,
19
+ } from '..';
20
+
21
+ // Enum exhaustiveness: fails to compile if a member is added/removed/renamed
22
+ // here without updating this file — a cheap drift guard for values
23
+ // transported verbatim over the wire (this SDK does not control the
24
+ // server's enum, so a silent value drift is exactly what this must catch).
25
+ const _verdicts: Record<ProbeScanVerdict, true> = { clean: true, infected: true };
26
+ const _modes: Record<ProbeScanMode, true> = { sync: true, async: true };
27
+ const _statuses: Record<ProbeScanStatus, true> = {
28
+ queued: true,
29
+ scanning: true,
30
+ done: true,
31
+ error: true,
32
+ };
33
+
34
+ // GET /scan/:scanId
35
+ const _scanDetail: IProbeScanDetail = {
36
+ scanId: 's1',
37
+ status: ProbeScanStatus.DONE,
38
+ verdict: ProbeScanVerdict.CLEAN,
39
+ viruses: [],
40
+ context: null,
41
+ };
42
+
43
+ // GET /scan history row — richer than IProbeScanDetail (fileName/fileSize/
44
+ // mimeType/mode/timestamps), still never the internal ownership columns.
45
+ const _historyItem: IProbeScanHistoryItem = {
46
+ scanId: 's1',
47
+ fileName: 'invoice.pdf',
48
+ fileSize: 1024,
49
+ mimeType: 'application/pdf',
50
+ verdict: ProbeScanVerdict.INFECTED,
51
+ viruses: ['Eicar-Test-Signature'],
52
+ mode: ProbeScanMode.ASYNC,
53
+ status: ProbeScanStatus.DONE,
54
+ context: { applicationFileId: 'af1' },
55
+ startedAt: new Date(),
56
+ finishedAt: new Date(),
57
+ createdAt: new Date(),
58
+ };
59
+
60
+ // POST /scan — sync (200) / async (202) data shapes.
61
+ const _scanSyncData: IScanSyncData = {
62
+ scanId: 's1',
63
+ verdict: ProbeScanVerdict.CLEAN,
64
+ viruses: [],
65
+ };
66
+ const _scanAsyncData: IScanAsyncData = { scanId: 's1', status: ProbeScanStatus.QUEUED };
67
+
68
+ // Webhook subscription — list/get shape vs the create-only `secret` shape.
69
+ const _webhook: IProbeWebhook = {
70
+ id: 'w1',
71
+ callbackUrl: 'https://example.com/hook',
72
+ active: true,
73
+ createdAt: new Date(),
74
+ };
75
+ const _webhookCreated: IProbeWebhookCreated = { ..._webhook, secret: 'shh' };
76
+
77
+ // Webhook delivery payload — the exact body signed by x-probe-signature
78
+ // (see verifyProbeWebhookSignature).
79
+ const _deliveryPayload: IProbeWebhookDeliveryPayload = {
80
+ scanId: 's1',
81
+ context: null,
82
+ verdict: ProbeScanVerdict.INFECTED,
83
+ viruses: ['Eicar-Test-Signature'],
84
+ };
85
+
86
+ void _verdicts;
87
+ void _modes;
88
+ void _statuses;
89
+ void _scanDetail;
90
+ void _historyItem;
91
+ void _scanSyncData;
92
+ void _scanAsyncData;
93
+ void _webhook;
94
+ void _webhookCreated;
95
+ void _deliveryPayload;
96
+
97
+ export {};
@@ -4,3 +4,4 @@ export * from './shape.utils';
4
4
  export * from './icons.utils';
5
5
  export * from './enum.utils';
6
6
  export * from './extraction-webhook.utils';
7
+ export * from './probe-webhook-signature.utils';
@@ -0,0 +1,56 @@
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
+ // extraction's `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 (returned once at creation — see IProbeWebhookCreated). */
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-var-requires
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
+ }