Read Recent updates afresh per opening and guard their URLs by origin
Address the review of PR #176 (Feature 21, task 96e2dc3f): - MQ-176-01: useUpdates drops its list as soon as the sheet closes (gcTime 0), so each opening reads GET /api/v1/updates afresh and only marks seen through the newest of that read, never a cached list. - MQ-176-02: parseUpdate checks each url with appPath from push/notification.ts, by parsed origin, instead of a prefix check that tabs and newlines got past; the url kept is the path as parsed. - MQ-176-03: TestMarkUpdatesSeen asserts updated_at is kept and settings subscribers are not told. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { parseUpdates } from './api.ts'
|
||||
|
||||
const origin = 'https://mealquest.example'
|
||||
const where = 'GET /api/v1/updates'
|
||||
|
||||
// body is a GET /api/v1/updates body holding one update that opens url.
|
||||
function body(url: string) {
|
||||
return {
|
||||
updates: [{ type: 'plan_ready', title: 'Your week is drafted', body: '', url, created_at: '2026-10-10T16:00:00.000Z' }],
|
||||
}
|
||||
}
|
||||
|
||||
describe('parseUpdates', () => {
|
||||
it('keeps a same-origin path with its query and fragment, as parsed', () => {
|
||||
expect(parseUpdates(where, body('/plan/shopping?week=2026-10-05#tuesday'), origin)[0]!.url).toBe(
|
||||
'/plan/shopping?week=2026-10-05#tuesday',
|
||||
)
|
||||
expect(parseUpdates(where, body('/plan/../discover'), origin)[0]!.url).toBe('/discover')
|
||||
})
|
||||
|
||||
// Each of these would open another site, or run script, once parsed: URL
|
||||
// parsing strips tabs and newlines and reads a backslash as a slash.
|
||||
it.each([
|
||||
['a tab', '/\t/evil.example/x'],
|
||||
['a newline', '/\n/evil.example/x'],
|
||||
['a carriage return', '/\r/evil.example/x'],
|
||||
['a backslash', '/\\evil.example/x'],
|
||||
['dot segments', '/..//evil.example/x'],
|
||||
['a protocol-relative URL', '//evil.example/x'],
|
||||
['an absolute URL', 'https://evil.example/x'],
|
||||
['javascript:', 'javascript:alert(1)'],
|
||||
['a relative path', 'plan'],
|
||||
])('refuses an update url with %s', (_, url) => {
|
||||
expect(() => parseUpdates(where, body(url), origin)).toThrow(
|
||||
`${where}: update url ${JSON.stringify(url)} is not a path on this app`,
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user