fix: SPA deep-link assets and broken home screen CSS
Three root causes addressed: 1. Added <base href="/"> to index.html so all relative asset paths (css/app.css, js/*.js) resolve from the root regardless of the current SPA route. Without this, /instance/117 requested /instance/css/app.css, which hit the SPA fallback and returned HTML; helmet's nosniff then refused it as a stylesheet. 2. Removed upgrade-insecure-requests from the CSP (useDefaults: false). This directive told browsers to upgrade HTTP→HTTPS for every asset request, breaking all resource loading on HTTP-only deployments. 3. Changed script-src-attr from 'none' to 'unsafe-inline' to allow the inline onclick handlers used throughout the UI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -237,3 +237,43 @@ describe('DELETE /api/instances/:vmid', () => {
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
})
|
||||
|
||||
// ── Static assets & SPA routing ───────────────────────────────────────────────
|
||||
|
||||
describe('static assets and SPA routing', () => {
|
||||
it('serves index.html at root', async () => {
|
||||
const res = await request(app).get('/')
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers['content-type']).toMatch(/html/)
|
||||
})
|
||||
|
||||
it('serves index.html for deep SPA routes (e.g. /instance/117)', async () => {
|
||||
const res = await request(app).get('/instance/117')
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers['content-type']).toMatch(/html/)
|
||||
})
|
||||
|
||||
it('serves CSS with correct content-type (not sniffed as HTML)', async () => {
|
||||
const res = await request(app).get('/css/app.css')
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers['content-type']).toMatch(/text\/css/)
|
||||
})
|
||||
|
||||
it('does not set upgrade-insecure-requests in CSP (HTTP deployments must work)', async () => {
|
||||
const res = await request(app).get('/')
|
||||
const csp = res.headers['content-security-policy'] ?? ''
|
||||
expect(csp).not.toContain('upgrade-insecure-requests')
|
||||
})
|
||||
|
||||
it('allows inline event handlers in CSP (onclick attributes)', async () => {
|
||||
const res = await request(app).get('/')
|
||||
const csp = res.headers['content-security-policy'] ?? ''
|
||||
// script-src-attr must not be 'none' — that blocks onclick handlers
|
||||
expect(csp).not.toContain("script-src-attr 'none'")
|
||||
})
|
||||
|
||||
it('index.html contains base href / for correct asset resolution on deep routes', async () => {
|
||||
const res = await request(app).get('/')
|
||||
expect(res.text).toContain('<base href="/">')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user