Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commite61e6cd

feat(vapron): VAPRON_API_KEY tenant auth on outbound calls

feat(vapron): VAPRON_API_KEY tenant auth on outbound calls

Vapron's tenant onboarding issues an API key; gluecron previously only
HMAC-signed the outbound push webhook and had nowhere to put it. New
/admin/integrations field (webhook group) — when set, every outbound
Vapron call carries Authorization: Bearer <key> alongside the HMAC
signature (key = caller identity, signature = payload integrity).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ccanty labs committed on July 14, 2026Parent: fb37dd6
4 files changed+460e61e6cdacbb09492880c51a172971788dce11da9
4 changed files+46−0
Modifiedsrc/__tests__/vapron-deploy.test.ts+24−0View fileUnifiedSplit
207207 expect(headers["X-Gluecron-Signature"]).toBeUndefined();
208208 });
209209
210 it("sends Authorization: Bearer <VAPRON_API_KEY> when the tenant key is set", async () => {
211 process.env.VAPRON_API_KEY = "vap_tenant_key_123";
212 try {
213 const { calls, fn } = captureFetch();
214
215 await triggerVapronDeploy(makeArgs(), { fetchImpl: fn, sleep: noSleep });
216
217 const headers = calls[0]!.init.headers as Record<string, string>;
218 expect(headers["Authorization"]).toBe("Bearer vap_tenant_key_123");
219 } finally {
220 delete process.env.VAPRON_API_KEY;
221 }
222 });
223
224 it("omits Authorization when no VAPRON_API_KEY is configured", async () => {
225 delete process.env.VAPRON_API_KEY;
226 const { calls, fn } = captureFetch();
227
228 await triggerVapronDeploy(makeArgs(), { fetchImpl: fn, sleep: noSleep });
229
230 const headers = calls[0]!.init.headers as Record<string, string>;
231 expect(headers["Authorization"]).toBeUndefined();
232 });
233
210234 it("attaches X-Gluecron-Event=push and a non-empty X-Gluecron-Delivery id", async () => {
211235 const { calls, fn } = captureFetch();
212236
Modifiedsrc/hooks/post-receive.ts+4−0View fileUnifiedSplit
533533 "X-Gluecron-Delivery": cryptoRandomId(),
534534 };
535535 if (signature) headers["X-Gluecron-Signature"] = signature;
536 // Vapron tenant auth: API key rides as a bearer alongside the HMAC
537 // signature (key = who is calling, signature = payload integrity).
538 if (config.vapronApiKey)
539 headers["Authorization"] = `Bearer ${config.vapronApiKey}`;
536540
537541 let lastStatus = 0;
538542 let lastError = "";
Modifiedsrc/lib/config.ts+9−0View fileUnifiedSplit
5050 ""
5151 );
5252 },
53 /**
54 * Tenant API key issued by Vapron. When set, every outbound call to
55 * Vapron carries `Authorization: Bearer <key>` — Vapron's tenant model
56 * authenticates callers by API key (the HMAC signature above remains a
57 * payload-integrity check and is sent alongside when configured).
58 */
59 get vapronApiKey() {
60 return process.env.VAPRON_API_KEY || "";
61 },
5362 /**
5463 * Shared HMAC secret for the outbound deploy webhook to Vapron's
5564 * `POST /api/webhooks/gluecron-push` endpoint. Used to compute the
Modifiedsrc/lib/system-config.ts+9−0View fileUnifiedSplit
268268 isSecret: true,
269269 group: "webhook",
270270 },
271 {
272 key: "VAPRON_API_KEY",
273 envFallback: "VAPRON_API_KEY",
274 label: "Vapron API key",
275 helper:
276 "Tenant API key from Vapron onboarding. Sent as `Authorization: Bearer …` on every outbound call to Vapron.",
277 isSecret: true,
278 group: "webhook",
279 },
271280 {
272281 key: "VAPRON_EVENT_TOKEN",
273282 envFallback: "VAPRON_EVENT_TOKEN",
274283