feat: implement permissions and actions management
- Add permissions logic for status options in corrective actions. - Create tests for permissions functionality. - Implement queries for user authentication and actions management. - Develop Actions page to display and manage corrective actions. - Create authentication pages for login, setup, and password reset. - Add IncidentDetail page to view incident details and associated actions. - Implement Settings page for user management and password changes.
This commit is contained in:
1 parent
708da6b9f7
commit
3f6dff5f24
94 files changed
+7213
-643
No files matched your search
@@ -0,0 +1,74 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { createTestContext, hoursAgo } from '../test/helpers.ts'
|
||||
|
||||
let ctx: Awaited<ReturnType<typeof createTestContext>>
|
||||
let reporter: Awaited<ReturnType<typeof ctx.createUser>>
|
||||
let number: number
|
||||
|
||||
const PNG = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0, 0, 0, 13, 0x49, 0x48, 0x44, 0x52])
|
||||
const form = (bytes: Uint8Array<ArrayBuffer>, name: string, type = 'image/png') => {
|
||||
const data = new FormData()
|
||||
data.append('file', new File([bytes], name, { type }))
|
||||
return data
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
ctx = await createTestContext()
|
||||
const site = await ctx.createSite()
|
||||
reporter = await ctx.createUser('reporter')
|
||||
const res = await reporter.post('/api/incidents', { title: 'Spill', type: 'hazard', severity: 'low', occurredAt: hoursAgo(1), siteId: site.id })
|
||||
number = res.body.incident.number
|
||||
})
|
||||
afterAll(() => ctx.close())
|
||||
|
||||
describe('attachments', () => {
|
||||
it('stores a photo and serves it back safely', async () => {
|
||||
const res = await reporter.post(`/api/incidents/${number}/attachments`, form(PNG, 'spill.png'))
|
||||
expect(res.status).toBe(201)
|
||||
expect(res.body.attachment).toMatchObject({ fileName: 'spill.png', contentType: 'image/png', sizeBytes: PNG.byteLength })
|
||||
|
||||
const file = await reporter.get(res.body.attachment.url)
|
||||
expect(file.status).toBe(200)
|
||||
expect(file.headers.get('content-type')).toBe('image/png')
|
||||
expect(file.headers.get('content-security-policy')).toContain('sandbox')
|
||||
expect(new Uint8Array(file.body)).toEqual(PNG)
|
||||
|
||||
const detail = await reporter.get(`/api/incidents/${number}`)
|
||||
expect(detail.body.incident.attachments).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('identifies files by content, not by name or claimed type', async () => {
|
||||
const html = new TextEncoder().encode('<script>alert(1)</script>')
|
||||
expect((await reporter.post(`/api/incidents/${number}/attachments`, form(html, 'photo.png', 'image/png'))).status).toBe(400)
|
||||
})
|
||||
|
||||
it('rejects files over the size limit', async () => {
|
||||
const big = new Uint8Array(ctx.config.maxUploadBytes + 1)
|
||||
big.set(PNG)
|
||||
expect((await reporter.post(`/api/incidents/${number}/attachments`, form(big, 'big.png'))).status).toBe(413)
|
||||
})
|
||||
|
||||
it('hides files from people who cannot see the incident', async () => {
|
||||
const res = await reporter.post(`/api/incidents/${number}/attachments`, form(PNG, 'second.png'))
|
||||
const outsider = await ctx.createUser('reporter', 'outsider@example.com')
|
||||
expect((await outsider.get(res.body.attachment.url)).status).toBe(404)
|
||||
expect((await outsider.delete(res.body.attachment.url)).status).toBe(404)
|
||||
})
|
||||
|
||||
it('lets the uploader remove their file', async () => {
|
||||
const res = await reporter.post(`/api/incidents/${number}/attachments`, form(PNG, 'remove-me.png'))
|
||||
expect((await reporter.delete(res.body.attachment.url)).status).toBe(204)
|
||||
expect((await reporter.get(res.body.attachment.url)).status).toBe(404)
|
||||
})
|
||||
})
|
||||
|
||||
describe('upload origin check', () => {
|
||||
it('rejects uploads posted from another site', async () => {
|
||||
const res = await ctx.app.request(`/api/incidents/${number}/attachments`, {
|
||||
method: 'POST',
|
||||
headers: { cookie: reporter.cookie!, origin: 'http://evil.example' },
|
||||
body: form(PNG, 'x.png'),
|
||||
})
|
||||
expect(res.status).toBe(403)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user