fix: secure webhook config endpoint and validate config on Ombi enable/test
Build and Push Docker Image / build (push) Successful in 1m4s
Docs Check / Markdown lint (push) Successful in 1m49s
Licence Check / Licence compatibility and copyright header verification (push) Failing after 2m22s
CI / Security audit (push) Successful in 2m44s
CI / Swagger Validation & Coverage (push) Successful in 2m59s
Docs Check / Mermaid diagram parse check (push) Successful in 3m11s
CI / Tests & coverage (push) Successful in 3m27s

- Add requireAuth to GET /api/webhook/config to enforce authentication
- Add SOFARR_BASE_URL and SOFARR_WEBHOOK_SECRET validation to POST /api/ombi/webhook/enable and /test
- Return 400 with descriptive errors when webhook config is missing on Ombi routes
- Clean up test environment in webhook.test.js afterEach
- Add regression tests for all new validation logic
- Update CHANGELOG.md with security fixes
This commit is contained in:
2026-05-22 09:50:30 +01:00
parent f1e0a77fad
commit dbf45ec31d
6 changed files with 392 additions and 22 deletions
+143 -10
View File
@@ -405,17 +405,90 @@ describe('GET /api/ombi/webhook/status', () => {
expect(res.body.error).toBe('Not authenticated');
});
it('returns disabled status when SOFARR_BASE_URL is missing', async () => {
delete process.env.SOFARR_BASE_URL;
app = createApp({ skipRateLimits: true });
const { cookies } = await authenticateUser(app, 'TestUser', false);
const res = await request(app)
.get('/api/ombi/webhook/status')
.set('Cookie', cookies)
.expect(200);
expect(res.body.enabled).toBe(false);
expect(res.body.webhookUrl).toBeNull();
expect(res.body.applicationToken).toBeNull();
expect(res.body.triggers).toEqual({
requestAvailable: false,
requestApproved: false,
requestDeclined: false,
requestPending: false,
requestProcessing: false
});
expect(res.body.stats).toBeNull();
});
it('returns disabled status when SOFARR_WEBHOOK_SECRET is missing', async () => {
delete process.env.SOFARR_WEBHOOK_SECRET;
app = createApp({ skipRateLimits: true });
const { cookies } = await authenticateUser(app, 'TestUser', false);
const res = await request(app)
.get('/api/ombi/webhook/status')
.set('Cookie', cookies)
.expect(200);
expect(res.body.enabled).toBe(false);
expect(res.body.webhookUrl).toBeNull();
expect(res.body.applicationToken).toBeNull();
expect(res.body.triggers).toEqual({
requestAvailable: false,
requestApproved: false,
requestDeclined: false,
requestPending: false,
requestProcessing: false
});
expect(res.body.stats).toBeNull();
});
it('returns disabled status when both SOFARR_BASE_URL and SOFARR_WEBHOOK_SECRET are missing', async () => {
delete process.env.SOFARR_BASE_URL;
delete process.env.SOFARR_WEBHOOK_SECRET;
app = createApp({ skipRateLimits: true });
const { cookies } = await authenticateUser(app, 'TestUser', false);
const res = await request(app)
.get('/api/ombi/webhook/status')
.set('Cookie', cookies)
.expect(200);
expect(res.body.enabled).toBe(false);
expect(res.body.webhookUrl).toBeNull();
expect(res.body.applicationToken).toBeNull();
expect(res.body.triggers).toEqual({
requestAvailable: false,
requestApproved: false,
requestDeclined: false,
requestPending: false,
requestProcessing: false
});
expect(res.body.stats).toBeNull();
});
it('returns disabled status when Ombi not configured', async () => {
process.env.OMBI_INSTANCES = JSON.stringify([]);
app = createApp({ skipRateLimits: true });
const { cookies } = await authenticateUser(app, 'TestUser', false);
const res = await request(app)
.get('/api/ombi/webhook/status')
.set('Cookie', cookies)
.expect(200);
expect(res.body.enabled).toBe(false);
expect(res.body.webhookUrl).toBeNull();
expect(res.body.applicationToken).toBeNull();
@@ -559,18 +632,48 @@ describe('POST /api/ombi/webhook/enable', () => {
expect(res.body.error).toBe('CSRF token missing');
});
it('returns 400 when Ombi not configured', async () => {
process.env.OMBI_INSTANCES = JSON.stringify([]);
it('returns 400 when SOFARR_BASE_URL is missing', async () => {
delete process.env.SOFARR_BASE_URL;
app = createApp({ skipRateLimits: true });
const { cookies, csrfToken } = await authenticateUser(app, 'TestUser', false);
const res = await request(app)
.post('/api/ombi/webhook/enable')
.set('Cookie', cookies)
.set('X-CSRF-Token', csrfToken)
.expect(400);
expect(res.body.error).toBe('SOFARR_BASE_URL not configured');
});
it('returns 400 when SOFARR_WEBHOOK_SECRET is missing', async () => {
delete process.env.SOFARR_WEBHOOK_SECRET;
app = createApp({ skipRateLimits: true });
const { cookies, csrfToken } = await authenticateUser(app, 'TestUser', false);
const res = await request(app)
.post('/api/ombi/webhook/enable')
.set('Cookie', cookies)
.set('X-CSRF-Token', csrfToken)
.expect(400);
expect(res.body.error).toBe('SOFARR_WEBHOOK_SECRET not configured');
});
it('returns 400 when Ombi not configured', async () => {
process.env.OMBI_INSTANCES = JSON.stringify([]);
app = createApp({ skipRateLimits: true });
const { cookies, csrfToken } = await authenticateUser(app, 'TestUser', false);
const res = await request(app)
.post('/api/ombi/webhook/enable')
.set('Cookie', cookies)
.set('X-CSRF-Token', csrfToken)
.expect(400);
expect(res.body.error).toBe('Ombi not configured');
});
@@ -627,18 +730,48 @@ describe('POST /api/ombi/webhook/test', () => {
expect(res.body.error).toBe('CSRF token missing');
});
it('returns 400 when Ombi not configured', async () => {
process.env.OMBI_INSTANCES = JSON.stringify([]);
it('returns 400 when SOFARR_BASE_URL is missing', async () => {
delete process.env.SOFARR_BASE_URL;
app = createApp({ skipRateLimits: true });
const { cookies, csrfToken } = await authenticateUser(app, 'TestUser', false);
const res = await request(app)
.post('/api/ombi/webhook/test')
.set('Cookie', cookies)
.set('X-CSRF-Token', csrfToken)
.expect(400);
expect(res.body.error).toBe('SOFARR_BASE_URL not configured');
});
it('returns 400 when SOFARR_WEBHOOK_SECRET is missing', async () => {
delete process.env.SOFARR_WEBHOOK_SECRET;
app = createApp({ skipRateLimits: true });
const { cookies, csrfToken } = await authenticateUser(app, 'TestUser', false);
const res = await request(app)
.post('/api/ombi/webhook/test')
.set('Cookie', cookies)
.set('X-CSRF-Token', csrfToken)
.expect(400);
expect(res.body.error).toBe('SOFARR_WEBHOOK_SECRET not configured');
});
it('returns 400 when Ombi not configured', async () => {
process.env.OMBI_INSTANCES = JSON.stringify([]);
app = createApp({ skipRateLimits: true });
const { cookies, csrfToken } = await authenticateUser(app, 'TestUser', false);
const res = await request(app)
.post('/api/ombi/webhook/test')
.set('Cookie', cookies)
.set('X-CSRF-Token', csrfToken)
.expect(400);
expect(res.body.error).toBe('Ombi not configured');
});