Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions project/__tests__/create_security_unit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import request from 'supertest'
import express from 'express'
import { jest } from '@jest/globals'

// Mock all dependencies before importing the router
jest.mock('../../auth/index.js', () => {
return function() {
return (req, res, next) => {
req.user = { _id: 'test-user-id', agent: 'test-agent' }
next()
}
}
})

jest.mock('../../classes/Project/Project.js', () => {
return class MockProject {
async create(project) {
if (project.malicious) {
throw new Error('Test error')
}
return { ...project, _id: 'test-project-id' }
}
}
})

// Mock other classes that might be imported
jest.mock('../../classes/Project/ProjectFactory.js', () => ({}))
jest.mock('../../classes/Layer/Layer.js', () => ({}))
jest.mock('../../classes/Page/Page.js', () => ({}))
jest.mock('../../classes/Group/Group.js', () => ({}))
jest.mock('../../classes/HotKeys/Hotkeys.js', () => ({}))
jest.mock('../../utilities/validateEmail.js', () => ({ isValidEmail: () => true }))
jest.mock('../../utilities/isDefaultRole.js', () => () => ({}))
jest.mock('../../utilities/validateURL.js', () => () => ({ valid: true }))
jest.mock('../../layer/index.js', () => ({ default: express.Router() }))
jest.mock('../../page/index.js', () => ({ default: express.Router() }))

// Now import the router after mocks are set up
const projectRouter = await import('../index.js')

const app = express()
app.use(express.json())
app.use('/project', projectRouter.default)

describe('Project create route with checkIfSuspicious middleware #project_create_security_unit', () => {
it('should accept clean project data', async () => {
const cleanProject = {
label: 'My Test Project',
metadata: { title: 'Clean Title' },
layers: [],
manifest: 'https://example.com/manifest',
creator: 'test-user',
group: 'test-group'
}

const response = await request(app)
.post('/project/create')
.send(cleanProject)

expect(response.status).toBe(201)
expect(response.body.label).toBe('My Test Project')
})

it('should block project data with XSS script tags', async () => {
const maliciousProject = {
label: 'My Project<script>alert("xss")</script>',
metadata: { title: 'Clean Title' },
layers: [],
manifest: 'https://example.com/manifest',
creator: 'test-user',
group: 'test-group'
}

const response = await request(app)
.post('/project/create')
.send(maliciousProject)

expect(response.status).toBe(400)
expect(response.body.message).toBe('Request contains potentially malicious content')
})

it('should block project data with javascript URLs', async () => {
const maliciousProject = {
label: 'My Project',
metadata: { url: 'javascript:alert("xss")' },
layers: [],
manifest: 'https://example.com/manifest',
creator: 'test-user',
group: 'test-group'
}

const response = await request(app)
.post('/project/create')
.send(maliciousProject)

expect(response.status).toBe(400)
expect(response.body.message).toBe('Request contains potentially malicious content')
})

it('should block project data with event handlers', async () => {
const maliciousProject = {
label: 'My Project',
metadata: { title: '<div onclick="alert(1)">Title</div>' },
layers: [],
manifest: 'https://example.com/manifest',
creator: 'test-user',
group: 'test-group'
}

const response = await request(app)
.post('/project/create')
.send(maliciousProject)

expect(response.status).toBe(400)
expect(response.body.message).toBe('Request contains potentially malicious content')
})

it('should sanitize safe HTML in project data', async () => {
const projectWithHTML = {
label: '<b>My Bold Project</b>',
metadata: { description: '<p>A description with <em>emphasis</em></p>' },
layers: [],
manifest: 'https://example.com/manifest',
creator: 'test-user',
group: 'test-group'
}

const response = await request(app)
.post('/project/create')
.send(projectWithHTML)

expect(response.status).toBe(201)
expect(response.body.label).toBe('<b>My Bold Project</b>')
expect(response.body.metadata.description).toBe('<p>A description with <em>emphasis</em></p>')
})
})
96 changes: 96 additions & 0 deletions project/__tests__/middleware_integration_unit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import checkIfSuspicious from '../../utilities/checkIfSuspicious.js'
import { jest } from '@jest/globals'

describe('checkIfSuspicious middleware integration #project_security_integration_unit', () => {
let req, res, next

beforeEach(() => {
req = {
body: {},
user: { _id: 'test-user', agent: 'test-agent' }
}
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis()
}
next = jest.fn()
})

it('should be a function that can be used as middleware', () => {
expect(typeof checkIfSuspicious).toBe('function')
expect(checkIfSuspicious.length).toBe(3) // req, res, next
})

it('should allow clean project creation data to pass through', () => {
req.body = {
label: 'My New Project',
metadata: [
{ label: 'title', value: 'Project Title' },
{ label: 'description', value: 'A clean description' }
],
layers: [],
manifest: 'https://example.com/manifest.json',
creator: 'test-user',
group: 'test-group'
}

checkIfSuspicious(req, res, next)

expect(next).toHaveBeenCalled()
expect(res.status).not.toHaveBeenCalled()
expect(req.body.label).toBe('My New Project')
})

it('should reject project data with XSS in label', () => {
req.body = {
label: 'Project<script>alert("xss")</script>',
metadata: [],
layers: [],
manifest: 'https://example.com/manifest.json'
}

checkIfSuspicious(req, res, next)

expect(res.status).toHaveBeenCalledWith(400)
expect(res.json).toHaveBeenCalledWith({
message: 'Request contains potentially malicious content'
})
expect(next).not.toHaveBeenCalled()
})

it('should reject project data with XSS in metadata', () => {
req.body = {
label: 'Clean Project',
metadata: [
{ label: 'title', value: '<iframe src="javascript:alert(1)"></iframe>' }
],
layers: [],
manifest: 'https://example.com/manifest.json'
}

checkIfSuspicious(req, res, next)

expect(res.status).toHaveBeenCalledWith(400)
expect(res.json).toHaveBeenCalledWith({
message: 'Request contains potentially malicious content'
})
expect(next).not.toHaveBeenCalled()
})

it('should sanitize safe HTML while preserving structure', () => {
req.body = {
label: '<b>Bold Project Title</b>',
metadata: [
{ label: 'description', value: '<p>A paragraph with <em>emphasis</em></p>' }
],
layers: [],
manifest: 'https://example.com/manifest.json'
}

checkIfSuspicious(req, res, next)

expect(next).toHaveBeenCalled()
expect(req.body.label).toBe('<b>Bold Project Title</b>')
expect(req.body.metadata[0].value).toBe('<p>A paragraph with <em>emphasis</em></p>')
})
})
3 changes: 2 additions & 1 deletion project/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import Hotkeys from "../classes/HotKeys/Hotkeys.js"
import layerRouter from "../layer/index.js"
import pageRouter from "../page/index.js"
import cookieParser from "cookie-parser"
import checkIfSuspicious from "../utilities/checkIfSuspicious.js"

let router = express.Router({ mergeParams: true })
router.use(cors(common_cors))

router
.route("/create")
.post(auth0Middleware(), async (req, res) => {
.post(auth0Middleware(), checkIfSuspicious, async (req, res) => {
const user = req.user

if (!user?.agent) return respondWithError(res, 401, "Unauthenticated user")
Expand Down
Loading
Loading