@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.
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +12 -0
- package/dist/app.js.map +1 -1
- package/dist/http/env.d.ts +17 -1
- package/dist/http/env.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/notificationWebhook/NotificationWebhookController.d.ts +8 -2
- package/dist/modules/notificationWebhook/NotificationWebhookController.d.ts.map +1 -1
- package/dist/modules/notificationWebhook/NotificationWebhookController.js +35 -7
- package/dist/modules/notificationWebhook/NotificationWebhookController.js.map +1 -1
- package/dist/modules/persistence/SecretDelegationController.d.ts +52 -0
- package/dist/modules/persistence/SecretDelegationController.d.ts.map +1 -0
- package/dist/modules/persistence/SecretDelegationController.js +222 -0
- package/dist/modules/persistence/SecretDelegationController.js.map +1 -0
- package/dist/modules/publicApi/PublicNotificationWebhookController.d.ts.map +1 -1
- package/dist/modules/publicApi/PublicNotificationWebhookController.js +50 -5
- package/dist/modules/publicApi/PublicNotificationWebhookController.js.map +1 -1
- package/dist/modules/publicApi/openapiDocument.generated.d.ts +1 -1
- package/dist/modules/publicApi/openapiDocument.generated.d.ts.map +1 -1
- package/dist/modules/publicApi/openapiDocument.generated.js +1 -1
- package/dist/modules/publicApi/openapiDocument.generated.js.map +1 -1
- package/dist/persistence/rpc-allowlist.d.ts.map +1 -1
- package/dist/persistence/rpc-allowlist.js +52 -27
- package/dist/persistence/rpc-allowlist.js.map +1 -1
- package/dist/persistence/secretDelegation.d.ts +62 -0
- package/dist/persistence/secretDelegation.d.ts.map +1 -0
- package/dist/persistence/secretDelegation.js +75 -0
- package/dist/persistence/secretDelegation.js.map +1 -0
- package/dist/secrets/sealedSecretSources.d.ts +40 -0
- package/dist/secrets/sealedSecretSources.d.ts.map +1 -0
- package/dist/secrets/sealedSecretSources.js +73 -0
- package/dist/secrets/sealedSecretSources.js.map +1 -0
- 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":"
|
|
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
|
|
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,
|
|
1
|
+
{"version":3,"file":"PublicNotificationWebhookController.js","sourceRoot":"","sources":["../../../src/modules/publicApi/PublicNotificationWebhookController.ts"],"names":[],"mappings":"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"}
|