@cat-factory/server 0.245.0 → 0.247.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 (36) hide show
  1. package/dist/app.d.ts.map +1 -1
  2. package/dist/app.js +12 -0
  3. package/dist/app.js.map +1 -1
  4. package/dist/http/env.d.ts +17 -1
  5. package/dist/http/env.d.ts.map +1 -1
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +6 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/modules/notificationWebhook/NotificationWebhookController.d.ts +8 -2
  11. package/dist/modules/notificationWebhook/NotificationWebhookController.d.ts.map +1 -1
  12. package/dist/modules/notificationWebhook/NotificationWebhookController.js +35 -7
  13. package/dist/modules/notificationWebhook/NotificationWebhookController.js.map +1 -1
  14. package/dist/modules/persistence/SecretDelegationController.d.ts +52 -0
  15. package/dist/modules/persistence/SecretDelegationController.d.ts.map +1 -0
  16. package/dist/modules/persistence/SecretDelegationController.js +222 -0
  17. package/dist/modules/persistence/SecretDelegationController.js.map +1 -0
  18. package/dist/modules/publicApi/PublicNotificationWebhookController.d.ts.map +1 -1
  19. package/dist/modules/publicApi/PublicNotificationWebhookController.js +50 -5
  20. package/dist/modules/publicApi/PublicNotificationWebhookController.js.map +1 -1
  21. package/dist/modules/publicApi/openapiDocument.generated.d.ts +1 -1
  22. package/dist/modules/publicApi/openapiDocument.generated.d.ts.map +1 -1
  23. package/dist/modules/publicApi/openapiDocument.generated.js +1 -1
  24. package/dist/modules/publicApi/openapiDocument.generated.js.map +1 -1
  25. package/dist/persistence/rpc-allowlist.d.ts.map +1 -1
  26. package/dist/persistence/rpc-allowlist.js +52 -27
  27. package/dist/persistence/rpc-allowlist.js.map +1 -1
  28. package/dist/persistence/secretDelegation.d.ts +62 -0
  29. package/dist/persistence/secretDelegation.d.ts.map +1 -0
  30. package/dist/persistence/secretDelegation.js +75 -0
  31. package/dist/persistence/secretDelegation.js.map +1 -0
  32. package/dist/secrets/sealedSecretSources.d.ts +40 -0
  33. package/dist/secrets/sealedSecretSources.d.ts.map +1 -0
  34. package/dist/secrets/sealedSecretSources.js +73 -0
  35. package/dist/secrets/sealedSecretSources.js.map +1 -0
  36. package/package.json +8 -8
@@ -0,0 +1,222 @@
1
+ import { Hono } from 'hono';
2
+ import { describeError } from '@cat-factory/kernel';
3
+ import { verifyMachineRequest } from '../../auth/machineGate.js';
4
+ import { logger } from '../../observability/logger.js';
5
+ import { sealedSecretSourceSpec, } from '../../secrets/sealedSecretSources.js';
6
+ /**
7
+ * The mothership-mode SECRET DELEGATION API: `POST /internal/secrets/unseal` and
8
+ * `POST /internal/secrets/seal`.
9
+ *
10
+ * Product decision 3 of the mothership initiative splits the keys: a laptop seals its own
11
+ * agent/model credentials under a LOCAL key, and the mothership's `ENCRYPTION_KEY` never reaches
12
+ * it. That is what makes handing a sealed blob over the persistence RPC safe: only ciphertext
13
+ * crosses. The cost is the mirror image: an org credential that a hosted teammate (or the
14
+ * mothership's own engine) sealed is unreadable on the node, so provisioning an environment,
15
+ * probing a release-health monitor and enriching an incident all failed there. Those surfaces were
16
+ * parked off the allow-list rather than shipped broken.
17
+ *
18
+ * This pair closes that, and does it the way `notificationRelayController` closed external
19
+ * delivery: **the wire names the ROW, never the ciphertext.** The node posts a source from a
20
+ * CLOSED table (`SEALED_SECRET_SOURCES`) plus the row's identifiers; the mothership re-reads the
21
+ * authoritative row from its OWN registry, checks the owning account against the node's token
22
+ * scope, and decrypts under its own key. So this is not a decryption oracle: a compromised node
23
+ * token can only ask for a value it could already have read had it held the key, in an account it
24
+ * can already reach. An envelope-taking endpoint would have admitted ciphertext obtained anywhere
25
+ * at all, which is precisely the design the notification relay rejected.
26
+ *
27
+ * The SEAL half exists because delegation has to be symmetric to be correct. A mothership-mode
28
+ * node PROVISIONS environments, so it produces org secrets as well as consuming them; sealing
29
+ * those under the local key would store a row the org can never open: the same silent split, one
30
+ * write later, and invisible until a hosted teammate or the mothership's own teardown needed it.
31
+ * Sealing takes no row read (encryption depends on the key, not the stored row), so it binds on
32
+ * the workspace alone. It is not an encryption oracle in any useful sense either: the caller may
33
+ * already write arbitrary bytes into that field through the allow-listed repository `upsert`.
34
+ *
35
+ * Security order mirrors every other `/internal/*` surface: the `machine` audience pin FIRST (so
36
+ * availability is not probeable without a token), then the capability probe (503), then the
37
+ * workspace → account scope binding, with a uniform 404 for anything out of scope, absent, or
38
+ * holding no sealed value (no existence leak, and no distinguishing "wrong account" from "no such
39
+ * row"). Every denial is audit-logged with the node + user ids; the PLAINTEXT is never logged, at
40
+ * any level, and neither is the envelope. Every failure is bound with kernel's `describeError`
41
+ * rather than a local message read, because this is the one surface whose SUBJECT is a credential:
42
+ * a repository driver or WebCrypto error here routinely echoes back the value it choked on, and
43
+ * `describeError` is what runs it through `redactSecrets` first.
44
+ *
45
+ * Mounted on BOTH facades so either a Node or a Cloudflare deployment can be a mothership. The
46
+ * capability that decides whether it answers is `secretCipherFor`, absent it a 503 after the auth
47
+ * check. A facade wires that only where it holds an `ENCRYPTION_KEY` (no key ⇒ nothing is sealed at
48
+ * all) AND its own main database (⇒ it is AUTHORITATIVE for the rows it would be asked about).
49
+ * `repositories` is NOT the discriminator, and reading it as one is the trap: a mothership-mode
50
+ * node populates that registry too, with the RPC-backed remote repos, so it would pass the check on
51
+ * precisely the deployment that must never answer, then seal under its LOCAL key. The registry read
52
+ * below stays a separate guard because the two are different facts.
53
+ */
54
+ export function secretDelegationController() {
55
+ const app = new Hono();
56
+ app.post('/internal/secrets/unseal', async (c) => {
57
+ const gate = await openDelegation(c);
58
+ if ('response' in gate)
59
+ return gate.response;
60
+ const { log, scopeAccountIds, container, workspaces, cipherFor } = gate;
61
+ const body = await readJson(c);
62
+ const spec = body ? sealedSecretSourceSpec(body.source) : undefined;
63
+ if (!body || !spec || typeof body.workspaceId !== 'string') {
64
+ return validationError(c, 'source and workspaceId are required');
65
+ }
66
+ const key = readKey(body.key, spec);
67
+ if (!key) {
68
+ return validationError(c, `source '${body.source}' takes ${spec.keyArity} key argument(s)`);
69
+ }
70
+ const sourceLog = log.child({ source: body.source, workspaceId: body.workspaceId });
71
+ const inScope = await accountInScope(workspaces, body.workspaceId, scopeAccountIds);
72
+ if (!inScope) {
73
+ sourceLog.warn('secret delegation: workspace out of scope');
74
+ return notFound(c);
75
+ }
76
+ // Read the AUTHORITATIVE row through the source's declared point read. `workspaceId` is
77
+ // PREPENDED by us rather than taken from the caller's `key`, so a node cannot address a row
78
+ // outside the workspace it just proved it may reach even by naming one positionally.
79
+ let envelope;
80
+ try {
81
+ const row = await callRepositoryRead(container, spec, [body.workspaceId, ...key]);
82
+ envelope =
83
+ row && typeof row === 'object' ? row[spec.field] : null;
84
+ }
85
+ catch (error) {
86
+ sourceLog.error('secret delegation: row read failed', describeError(error));
87
+ return internalError(c);
88
+ }
89
+ // An absent row and a row whose field holds nothing are the SAME uniform 404. The node read
90
+ // the row to get here, so neither is an ordinary answer it should route around, and telling
91
+ // them apart would confirm the row's existence to a caller that cannot reach it.
92
+ if (typeof envelope !== 'string' || envelope.length === 0) {
93
+ sourceLog.warn('secret delegation: no sealed value for the named row');
94
+ return notFound(c);
95
+ }
96
+ let plaintext;
97
+ try {
98
+ plaintext = await cipherFor(spec.info).decrypt(envelope);
99
+ }
100
+ catch (error) {
101
+ // The mothership's OWN key failed on its OWN row: key drift (ADR 0026 D6.2), not a caller
102
+ // fault. Reported as a 500 so the node retries rather than treating it as "nothing here",
103
+ // and logged with the cause so the drift sweep's finding has a runtime counterpart.
104
+ sourceLog.error('secret delegation: decrypt failed', describeError(error));
105
+ return internalError(c);
106
+ }
107
+ sourceLog.info('secret delegation: unsealed', { label: spec.label });
108
+ return c.json({ ok: true, plaintext });
109
+ });
110
+ app.post('/internal/secrets/seal', async (c) => {
111
+ const gate = await openDelegation(c);
112
+ if ('response' in gate)
113
+ return gate.response;
114
+ const { log, scopeAccountIds, workspaces, cipherFor } = gate;
115
+ const body = await readJson(c);
116
+ const spec = body ? sealedSecretSourceSpec(body.source) : undefined;
117
+ if (!body ||
118
+ !spec ||
119
+ typeof body.workspaceId !== 'string' ||
120
+ typeof body.plaintext !== 'string') {
121
+ return validationError(c, 'source, workspaceId and plaintext are required');
122
+ }
123
+ const sourceLog = log.child({ source: body.source, workspaceId: body.workspaceId });
124
+ const inScope = await accountInScope(workspaces, body.workspaceId, scopeAccountIds);
125
+ if (!inScope) {
126
+ sourceLog.warn('secret delegation: workspace out of scope');
127
+ return notFound(c);
128
+ }
129
+ let envelope;
130
+ try {
131
+ envelope = await cipherFor(spec.info).encrypt(body.plaintext);
132
+ }
133
+ catch (error) {
134
+ sourceLog.error('secret delegation: encrypt failed', describeError(error));
135
+ return internalError(c);
136
+ }
137
+ sourceLog.info('secret delegation: sealed', { label: spec.label });
138
+ return c.json({ ok: true, envelope });
139
+ });
140
+ return app;
141
+ }
142
+ async function openDelegation(c) {
143
+ const container = c.get('container');
144
+ // Auth FIRST (before the capability probe) so a token-less caller cannot tell a mothership
145
+ // with a cipher from one without.
146
+ const payload = await verifyMachineRequest(c);
147
+ if (!payload) {
148
+ return {
149
+ response: c.json({ ok: false, error: { code: 'forbidden', message: 'invalid machine token' } }, 403),
150
+ };
151
+ }
152
+ const cipherFor = container.secretCipherFor;
153
+ const workspaceRepository = container.repositories?.workspaceRepository;
154
+ if (!cipherFor || typeof workspaceRepository?.accountOf !== 'function') {
155
+ return {
156
+ response: c.json({ ok: false, error: { code: 'internal', message: 'secret delegation not enabled' } }, 503),
157
+ };
158
+ }
159
+ return {
160
+ log: logger.child({
161
+ scope: 'secretDelegation',
162
+ nodeId: payload.nodeId,
163
+ userId: payload.userId,
164
+ }),
165
+ scopeAccountIds: payload.scope.accountIds,
166
+ container,
167
+ workspaces: workspaceRepository,
168
+ cipherFor,
169
+ };
170
+ }
171
+ /**
172
+ * The `workspace` scope rule, verbatim from the persistence RPC: resolve the workspace's owning
173
+ * account and require it in the token's scope. A read that throws fails CLOSED.
174
+ */
175
+ async function accountInScope(workspaces, workspaceId, scopeAccountIds) {
176
+ const accountId = await Promise.resolve(workspaces.accountOf(workspaceId)).catch(() => undefined);
177
+ return !!accountId && scopeAccountIds.includes(accountId);
178
+ }
179
+ /** Invoke the source's declared point read on the mothership's own registry. */
180
+ async function callRepositoryRead(container, spec, args) {
181
+ const repositories = container.repositories;
182
+ const repo = repositories?.[spec.repo];
183
+ const method = repo?.[spec.method];
184
+ if (typeof method !== 'function') {
185
+ throw new Error(`${spec.repo}.${spec.method} is not wired on this mothership`);
186
+ }
187
+ return method.apply(repo, args);
188
+ }
189
+ /**
190
+ * The request's row identifiers, or undefined when the arity disagrees with the source's
191
+ * declaration. Arity is checked rather than tolerated because the values are SPREAD into the
192
+ * declared read: a short list silently reads a different row than the caller named, and a long
193
+ * one passes an argument the port never declared.
194
+ */
195
+ function readKey(raw, spec) {
196
+ const key = raw === undefined ? [] : raw;
197
+ if (!Array.isArray(key) || key.length !== spec.keyArity)
198
+ return undefined;
199
+ for (const value of key) {
200
+ if (value !== null && typeof value !== 'string')
201
+ return undefined;
202
+ }
203
+ return key;
204
+ }
205
+ async function readJson(c) {
206
+ try {
207
+ return (await c.req.json());
208
+ }
209
+ catch {
210
+ return undefined;
211
+ }
212
+ }
213
+ function validationError(c, message) {
214
+ return c.json({ ok: false, error: { code: 'validation', message } }, 422);
215
+ }
216
+ function notFound(c) {
217
+ return c.json({ ok: false, error: { code: 'not_found', message: 'Not found' } }, 404);
218
+ }
219
+ function internalError(c) {
220
+ return c.json({ ok: false, error: { code: 'internal', message: 'Internal error' } }, 500);
221
+ }
222
+ //# sourceMappingURL=SecretDelegationController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecretDelegationController.js","sourceRoot":"","sources":["../../../src/modules/persistence/SecretDelegationController.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PublicNotificationWebhookController.d.ts","sourceRoot":"","sources":["../../../src/modules/publicApi/PublicNotificationWebhookController.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AA+C/C,wBAAgB,mCAAmC,IAAI,IAAI,CAAC,MAAM,CAAC,CAoClE"}
1
+ {"version":3,"file":"PublicNotificationWebhookController.d.ts","sourceRoot":"","sources":["../../../src/modules/publicApi/PublicNotificationWebhookController.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AA+C/C,wBAAgB,mCAAmC,IAAI,IAAI,CAAC,MAAM,CAAC,CAyFlE"}
@@ -1,4 +1,4 @@
1
- import { deletePublicNotificationWebhookContract, getPublicNotificationWebhookContract, putPublicNotificationWebhookContract, } from '@cat-factory/contracts';
1
+ import { DEFAULT_NOTIFICATION_WEBHOOK_ID, deletePublicNamedNotificationWebhookContract, deletePublicNotificationWebhookContract, getPublicNamedNotificationWebhookContract, getPublicNotificationWebhookContract, listPublicNotificationWebhooksContract, putPublicNamedNotificationWebhookContract, putPublicNotificationWebhookContract, } from '@cat-factory/contracts';
2
2
  import { buildHonoRoute } from '@toad-contracts/hono';
3
3
  import { Hono } from 'hono';
4
4
  import { authorize, refuse } from './publicApiAuth.js';
@@ -23,7 +23,7 @@ import { authorize, refuse } from './publicApiAuth.js';
23
23
  // one is the reversible one.
24
24
  // 2. **No parallel logic.** Every verb delegates to the same `NotificationWebhookService` method
25
25
  // the session controller calls, so the SSRF guard on the endpoint, the keep-on-omit rule for
26
- // each field and the one-row-per-workspace invariant hold identically whichever surface
26
+ // each field and the per-workspace endpoint cap hold identically whichever surface
27
27
  // writes. A `ValidationError` from the URL guard propagates to `handleError` rather than being
28
28
  // re-mapped here, so the refusal keeps its `details`.
29
29
  // 3. **The secret stays write-only.** `put` accepts one and `get` reports `hasSecret`; nothing
@@ -50,7 +50,7 @@ export function publicNotificationWebhookController() {
50
50
  const webhooks = webhooksOf(c);
51
51
  if (!webhooks)
52
52
  return unavailable(c);
53
- return c.json({ webhook: await webhooks.get(gate.auth.workspaceId) }, 200);
53
+ return c.json({ webhook: await webhooks.get(gate.auth.workspaceId, DEFAULT_NOTIFICATION_WEBHOOK_ID) }, 200);
54
54
  });
55
55
  // Register, edit or rotate. A field the body omits KEEPS its stored value (the service's rule,
56
56
  // not this controller's), so an integration that only wants to add `runEvents` sends that one
@@ -62,7 +62,7 @@ export function publicNotificationWebhookController() {
62
62
  const webhooks = webhooksOf(c);
63
63
  if (!webhooks)
64
64
  return unavailable(c);
65
- const saved = await webhooks.put(gate.auth.workspaceId, c.req.valid('json'));
65
+ const saved = await webhooks.put(gate.auth.workspaceId, DEFAULT_NOTIFICATION_WEBHOOK_ID, c.req.valid('json'));
66
66
  return c.json(saved, 200);
67
67
  });
68
68
  // Deregister. Idempotent, so a teardown script need not first ask whether anything is there.
@@ -73,7 +73,52 @@ export function publicNotificationWebhookController() {
73
73
  const webhooks = webhooksOf(c);
74
74
  if (!webhooks)
75
75
  return unavailable(c);
76
- await webhooks.remove(gate.auth.workspaceId);
76
+ await webhooks.remove(gate.auth.workspaceId, DEFAULT_NOTIFICATION_WEBHOOK_ID);
77
+ return c.body(null, 204);
78
+ });
79
+ // ---- the collection: several named endpoints, one per integration ----
80
+ //
81
+ // Every one of these is the corresponding singular route with the id supplied instead of
82
+ // defaulted. That is the whole difference, deliberately: the enrolment rules a caller has to
83
+ // reason about (keep-on-omit, the write-only secret, the SSRF guard, `admin` scope) must not
84
+ // depend on which of the two shapes it happened to call.
85
+ buildHonoRoute(app, listPublicNotificationWebhooksContract, async (c) => {
86
+ const gate = await authorize(c, listPublicNotificationWebhooksContract.minScope);
87
+ if ('fail' in gate)
88
+ return refuse(c, gate.fail);
89
+ const webhooks = webhooksOf(c);
90
+ if (!webhooks)
91
+ return unavailable(c);
92
+ return c.json({ webhooks: await webhooks.list(gate.auth.workspaceId) }, 200);
93
+ });
94
+ buildHonoRoute(app, getPublicNamedNotificationWebhookContract, async (c) => {
95
+ const gate = await authorize(c, getPublicNamedNotificationWebhookContract.minScope);
96
+ if ('fail' in gate)
97
+ return refuse(c, gate.fail);
98
+ const webhooks = webhooksOf(c);
99
+ if (!webhooks)
100
+ return unavailable(c);
101
+ const webhook = await webhooks.get(gate.auth.workspaceId, c.req.valid('param').webhookId);
102
+ return c.json({ webhook }, 200);
103
+ });
104
+ buildHonoRoute(app, putPublicNamedNotificationWebhookContract, async (c) => {
105
+ const gate = await authorize(c, putPublicNamedNotificationWebhookContract.minScope);
106
+ if ('fail' in gate)
107
+ return refuse(c, gate.fail);
108
+ const webhooks = webhooksOf(c);
109
+ if (!webhooks)
110
+ return unavailable(c);
111
+ const saved = await webhooks.put(gate.auth.workspaceId, c.req.valid('param').webhookId, c.req.valid('json'));
112
+ return c.json(saved, 200);
113
+ });
114
+ buildHonoRoute(app, deletePublicNamedNotificationWebhookContract, async (c) => {
115
+ const gate = await authorize(c, deletePublicNamedNotificationWebhookContract.minScope);
116
+ if ('fail' in gate)
117
+ return refuse(c, gate.fail);
118
+ const webhooks = webhooksOf(c);
119
+ if (!webhooks)
120
+ return unavailable(c);
121
+ await webhooks.remove(gate.auth.workspaceId, c.req.valid('param').webhookId);
77
122
  return c.body(null, 204);
78
123
  });
79
124
  return app;
@@ -1 +1 @@
1
- {"version":3,"file":"PublicNotificationWebhookController.js","sourceRoot":"","sources":["../../../src/modules/publicApi/PublicNotificationWebhookController.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uCAAuC,EACvC,oCAAoC,EACpC,oCAAoC,GACrC,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEtD,sEAAsE;AACtE,kGAAkG;AAClG,mBAAmB;AACnB,EAAE;AACF,gGAAgG;AAChG,iGAAiG;AACjG,uFAAuF;AACvF,kGAAkG;AAClG,iGAAiG;AACjG,gGAAgG;AAChG,EAAE;AACF,sCAAsC;AACtC,EAAE;AACF,mGAAmG;AACnG,iGAAiG;AACjG,6FAA6F;AAC7F,4FAA4F;AAC5F,mGAAmG;AACnG,iCAAiC;AACjC,kGAAkG;AAClG,iGAAiG;AACjG,4FAA4F;AAC5F,mGAAmG;AACnG,0DAA0D;AAC1D,gGAAgG;AAChG,+FAA+F;AAC/F,8FAA8F;AAC9F,qDAAqD;AACrD,EAAE;AACF,gGAAgG;AAChG,8FAA8F;AAC9F,8EAA8E;AAE9E,2FAA2F;AAC3F,SAAS,UAAU,CAAmB,CAAa;IACjD,OAAO,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,oBAAoB,IAAI,IAAI,CAAA;AACxD,CAAC;AAED,MAAM,WAAW,GAAG,CAAmB,CAAa,EAAE,EAAE,CACtD,CAAC,CAAC,IAAI,CACJ,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,0CAA0C,EAAE,EAAE,EACvF,GAAG,CACJ,CAAA;AAEH,MAAM,UAAU,mCAAmC;IACjD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAU,CAAA;IAE9B,+FAA+F;IAC/F,sFAAsF;IACtF,cAAc,CAAC,GAAG,EAAE,oCAAoC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACpE,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,oCAAoC,CAAC,QAAQ,CAAC,CAAA;QAC9E,IAAI,MAAM,IAAI,IAAI;YAAE,OAAO,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/C,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;QAC9B,IAAI,CAAC,QAAQ;YAAE,OAAO,WAAW,CAAC,CAAC,CAAC,CAAA;QACpC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;IAC5E,CAAC,CAAC,CAAA;IAEF,+FAA+F;IAC/F,8FAA8F;IAC9F,8EAA8E;IAC9E,cAAc,CAAC,GAAG,EAAE,oCAAoC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACpE,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,oCAAoC,CAAC,QAAQ,CAAC,CAAA;QAC9E,IAAI,MAAM,IAAI,IAAI;YAAE,OAAO,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/C,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;QAC9B,IAAI,CAAC,QAAQ;YAAE,OAAO,WAAW,CAAC,CAAC,CAAC,CAAA;QACpC,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;QAC5E,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IAC3B,CAAC,CAAC,CAAA;IAEF,6FAA6F;IAC7F,cAAc,CAAC,GAAG,EAAE,uCAAuC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACvE,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,uCAAuC,CAAC,QAAQ,CAAC,CAAA;QACjF,IAAI,MAAM,IAAI,IAAI;YAAE,OAAO,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/C,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;QAC9B,IAAI,CAAC,QAAQ;YAAE,OAAO,WAAW,CAAC,CAAC,CAAC,CAAA;QACpC,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QAC5C,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IAC1B,CAAC,CAAC,CAAA;IAEF,OAAO,GAAG,CAAA;AACZ,CAAC"}
1
+ {"version":3,"file":"PublicNotificationWebhookController.js","sourceRoot":"","sources":["../../../src/modules/publicApi/PublicNotificationWebhookController.ts"],"names":[],"mappings":"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"}