fix: enforce gallery access and consolidate gallery workflows (#1357)
Harden gallery authentication and authorization, consolidate gallery workflows, and prevent token-bearing URLs from leaking through nginx request error logs.
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
|
||||
jest.mock('axios', () => ({ post: jest.fn() }));
|
||||
jest.mock('../../src/utils/networkValidation', () => ({
|
||||
validateExternalUrlAsync: jest.fn(async () => ({ valid: true, reason: 'ok' })),
|
||||
validateExternalUrlAsync: jest.fn(async () => ({ valid: true, reason: 'ok', hostname: 'relay.example', addresses: [{ address: '93.184.216.34', family: 4 }] })),
|
||||
}));
|
||||
jest.mock('../../src/utils/logger', () => ({
|
||||
info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn(),
|
||||
@@ -48,7 +48,7 @@ beforeEach(() => {
|
||||
process.env.EMAIL_WEBHOOK_SECRET = SECRET;
|
||||
transport.__testing.setAllowPrivateUrls(false);
|
||||
transport.__testing.resetSecretWarning();
|
||||
validateExternalUrlAsync.mockResolvedValue({ valid: true, reason: 'ok' });
|
||||
validateExternalUrlAsync.mockResolvedValue({ valid: true, reason: 'ok', hostname: 'relay.example', addresses: [{ address: '93.184.216.34', family: 4 }] });
|
||||
axios.post.mockResolvedValue({ status: 200, data: streamOf('') });
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
jest.mock('../../src/utils/logger', () => ({ error: jest.fn() }));
|
||||
const { scheduledTask } = require('../../src/services/scheduledTask');
|
||||
beforeEach(() => jest.useFakeTimers());
|
||||
afterEach(() => jest.useRealTimers());
|
||||
it('starts once, skips overlap and drains the accepted run on stop', async () => {
|
||||
let finish;
|
||||
const work = jest.fn(() => new Promise(resolve => { finish = resolve; }));
|
||||
const task = scheduledTask(work, { interval: 100 });
|
||||
task.start(); task.start();
|
||||
expect(jest.getTimerCount()).toBe(1);
|
||||
await jest.advanceTimersByTimeAsync(300);
|
||||
expect(work).toHaveBeenCalledTimes(1);
|
||||
let stopped = false;
|
||||
const stop = task.stop().then(() => { stopped = true; });
|
||||
await Promise.resolve(); expect(stopped).toBe(false);
|
||||
finish(); await stop; expect(stopped).toBe(true);
|
||||
expect(jest.getTimerCount()).toBe(0);
|
||||
await jest.advanceTimersByTimeAsync(1000); expect(work).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('cancels a delayed first run and can restart cleanly', async () => {
|
||||
const work = jest.fn(); const task = scheduledTask(work, { interval: 100, initialDelay: 10 });
|
||||
task.start(); await task.stop(); await jest.advanceTimersByTimeAsync(200); expect(work).not.toHaveBeenCalled();
|
||||
task.start(); await jest.advanceTimersByTimeAsync(10); expect(work).toHaveBeenCalledTimes(1);
|
||||
await task.stop();
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
jest.mock('../../src/database/db', () => ({ db: jest.fn() }));
|
||||
const secure = require('../../src/services/secureImageService');
|
||||
beforeEach(() => { jest.useFakeTimers(); secure.dispose(); });
|
||||
afterEach(() => { secure.dispose(); jest.useRealTimers(); });
|
||||
it('owns one timer for many tokens and sweeps expired capabilities', () => {
|
||||
for (let i = 0; i < 100; i++) secure.generateSecureToken(i, 'gallery_public_1_1', { expiresIn: 1 });
|
||||
expect(jest.getTimerCount()).toBe(1); expect(secure.tokenCache.size).toBe(100);
|
||||
jest.advanceTimersByTime(60000); expect(secure.tokenCache.size).toBe(0);
|
||||
secure.dispose(); expect(jest.getTimerCount()).toBe(0);
|
||||
});
|
||||
it('disposes all session/rate caches and restarts on demand', () => {
|
||||
secure.generateSecureToken(1, 'session'); secure.sessionTokens.set('a', 'b'); secure.rateLimitCache.set('a', 'b');
|
||||
secure.dispose(); expect(secure.sessionTokens.size + secure.rateLimitCache.size + secure.tokenCache.size).toBe(0);
|
||||
secure.generateSecureToken(1, 'session'); expect(jest.getTimerCount()).toBe(1);
|
||||
});
|
||||
Reference in New Issue
Block a user