Skip to content

Commit 9acaadb

Browse files
committed
fix: allow X-CloudScheduler header to bypass canonical redirect and trigger test email
1 parent 0595b74 commit 9acaadb

5 files changed

Lines changed: 37 additions & 5 deletions

File tree

‎cron.yaml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ cron:
77
url: /tasks/generate-stats
88
schedule: every 24 hours
99

10+
- description: admin test email (trigger on demand)
11+
url: /dailyemail/test
12+
schedule: every 8760 hours
13+
timezone: America/Chicago
14+
1015
- description: daily APOD email
1116
url: /dailyemail/2009/1/7
1217
schedule: every day 10:00

‎server.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ app.use((req, res, next) => {
1616
return next();
1717
}
1818

19-
// Skip GAE internal requests (Cron, Task Queues)
19+
// Skip GAE internal requests (Cron, Task Queues, Cloud Scheduler)
2020
if (
2121
req.headers['x-appengine-cron'] ||
22+
req.headers['x-cloudscheduler'] ||
2223
req.headers['x-appengine-taskname'] ||
2324
req.headers['x-appengine-queuename']
2425
) {

‎src/routes.js‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,11 @@ router.get('/usercount', async (req, res) => {
207207
}
208208
});
209209

210-
// Admin APOD Email Test Endpoint (Protected by GAE Cron header)
210+
// Admin APOD Email Test Endpoint (Protected by GAE Cron or Cloud Scheduler header)
211211
router.get('/dailyemail/test', async (req, res) => {
212-
// Only allow invocations from App Engine Cron / Cloud Scheduler
213-
if (!req.get('X-AppEngine-Cron')) {
212+
// Only allow invocations from App Engine Cron or Cloud Scheduler
213+
const isCron = req.get('X-AppEngine-Cron') || req.get('X-CloudScheduler');
214+
if (!isCron) {
214215
return res.status(403).send('Forbidden');
215216
}
216217

‎test/integration/redirect.test.js‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ describe('Canonical Redirection Middleware Tests', () => {
9999
expect(response.status).not.toBe(301);
100100
});
101101

102+
it('should NOT redirect Cloud Scheduler requests', async () => {
103+
const response = await request(app)
104+
.get('/dailyemail/test')
105+
.set('Host', 'apod-email-node.uc.r.appspot.com')
106+
.set('X-Forwarded-Proto', 'http')
107+
.set('X-CloudScheduler', 'true');
108+
109+
expect(response.status).not.toBe(301);
110+
});
111+
102112
it('should NOT redirect in non-production environments', async () => {
103113
// Override back to test environment
104114
process.env.NODE_ENV = 'test';

‎test/integration/routes.test.js‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ describe('Route Integration Tests', () => {
8686
}
8787
});
8888

89-
it('should trigger enqueueEmails with ADMIN_EMAIL and return 200', async () => {
89+
it('should trigger enqueueEmails with ADMIN_EMAIL when X-AppEngine-Cron is present and return 200', async () => {
9090
process.env.ADMIN_EMAIL = 'admin@example.com';
9191
const enqueueSpy = vi.spyOn(emailService, 'enqueueEmails').mockResolvedValue(1);
9292

@@ -101,6 +101,21 @@ describe('Route Integration Tests', () => {
101101
});
102102
});
103103

104+
it('should trigger enqueueEmails with ADMIN_EMAIL when X-CloudScheduler is present and return 200', async () => {
105+
process.env.ADMIN_EMAIL = 'admin@example.com';
106+
const enqueueSpy = vi.spyOn(emailService, 'enqueueEmails').mockResolvedValue(1);
107+
108+
const response = await request(app)
109+
.get('/dailyemail/test')
110+
.set('X-CloudScheduler', 'true');
111+
112+
expect(response.status).toBe(200);
113+
expect(response.text).toBe('Enqueued 1 test task.');
114+
expect(enqueueSpy).toHaveBeenCalledWith(null, null, null, null, {
115+
recipients: ['admin@example.com']
116+
});
117+
});
118+
104119
it('should ignore query params and only use ADMIN_EMAIL', async () => {
105120
process.env.ADMIN_EMAIL = 'admin@example.com';
106121
const enqueueSpy = vi.spyOn(emailService, 'enqueueEmails').mockResolvedValue(1);

0 commit comments

Comments
 (0)