storage-upload/test/media/security.test.mjs

468 lines
12 KiB
JavaScript

import { Eltro as t, assert} from 'eltro'
import { HttpError } from 'flaska'
import { createContext } from '../helper.server.mjs'
import { verifyToken, verifyBody, throwIfNotPublic } from '../../api/media/security.mjs'
import encode from '../../api/jwt/encode.mjs'
import config from '../../api/config.mjs'
t.describe('#throwIfNotPublic()', function() {
let backup = {}
t.before(function() {
backup = config.sources[1].store
config.sources[1].store = {
sites: {
justatest: {
},
justatest2: {
public: false,
},
justatest3: {
public: true,
},
},
}
})
t.after(function() {
config.sources[1].store = backup
})
t.test('should throw for sites that do not exist or are null', function() {
let tests = [
'justatest',
'justatest2',
'nonexisting1',
null,
]
tests.forEach(function(test) {
assert.throws(function() { throwIfNotPublic(test) }, function(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 404)
assert.match(err.message, new RegExp(test))
assert.match(err.message, /exist/i)
return true
}, `should throw with site ${test}`)
})
})
t.test('should pass for sites that allow public listing', function() {
let tests = [
'justatest3',
]
tests.forEach(function(test) {
assert.doesNotThrow(function() { throwIfNotPublic(test) }, `should not throw with site ${test}`)
})
})
})
t.describe('#verifyToken()', function() {
let backup = {}
t.before(function() {
backup = config.sources[1].store
config.sources[1].store = {
sites: {
justatest: {
keys: {
'default@HS512': 'mysharedkey',
}
},
},
}
})
t.after(function() {
config.sources[1].store = backup
})
t.test('should fail if query token is missing', function() {
let ctx = createContext({ })
ctx.query.delete('token')
assert.throws(function() { verifyToken(ctx) }, function(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, /query/i)
assert.match(err.message, /token/i)
return true
})
})
function assertInvalidToken(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, /invalid/i)
assert.match(err.message, /token/i)
return true
}
t.test('should fail if token is invalid', function() {
let ctx = createContext({ })
ctx.query.set('token', 'asdfasdgassdga')
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
assert.ok(ctx.log.error.lastCall)
assert.match(ctx.log.error.lastCall[0].message, /3 dots/)
ctx.query.set('token', 'asdfasdgassdga.asdfasdg.sadfsadfas')
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
assert.match(ctx.log.error.lastCall[0].message, /invalid/i)
ctx.query.set('token', encode(
{ typ: 'JWT', alg: 'HS256' },
{ iss: 'justatest' },
'mysharedkey'
))
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
assert.match(ctx.log.error.lastCall[0].message, /pubkey/)
ctx.query.set('token', encode(
{ typ: 'JWT', alg: 'HS512' },
{ iss: 'notexist' },
'mysharedkey'
))
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
assert.match(ctx.log.error.lastCall[0].message, /notexist/)
ctx.query.set('token', encode(
{ typ: 'JWT', alg: 'HS512' },
{ iss: 'justatest' },
'mysharedkey2'
))
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
assert.match(ctx.log.error.lastCall[0].message, /HS512/)
assert.match(ctx.log.error.lastCall[0].message, /Verification/i)
})
t.test('should otherwise return the issuer', function() {
let ctx = createContext({ })
ctx.query.set('token', encode(
{ typ: 'JWT', alg: 'HS512' },
{ iss: 'justatest' },
'mysharedkey'
))
let site = verifyToken(ctx)
assert.strictEqual(site, 'justatest')
})
})
t.describe('#verifyBody()', function() {
t.test('should succeed with empty body', function() {
let ctx = createContext({ req: { body: { } } })
verifyBody(ctx)
})
t.test('should fail with invalid body', function() {
let ctx = createContext({ req: { body: {
item: {}
} } })
let tests = [
[null, 'null'],
['', 'empty string'],
['asdf', 'string'],
[0, 'empty number'],
[123, 'number'],
[[], 'array'],
]
tests.forEach(function (check) {
ctx.req.body.item = check[0]
assert.throws(function() { verifyBody(ctx) }, function(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, /body/i)
assert.match(err.message, /item/i)
assert.match(err.message, /valid/i)
return true
}, `should fail if body entry is ${check[1]}`)
})
})
let testInvalidNames = ['filename', 'path']
testInvalidNames.forEach(function(invalidName) {
t.test(`should fail if an item has the name ${invalidName}`, function() {
let ctx = createContext({ req: { body: {
[invalidName]: {}
} } })
assert.throws(function() { verifyBody(ctx) }, function(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, /body/i)
assert.match(err.message, /name/i)
assert.match(err.message, new RegExp(invalidName))
assert.match(err.message, /allowed/i)
return true
}, 'should fail if body item has the name original')
})
})
t.test('should require format string present in item', function() {
let ctx = createContext({ req: { body: {
item: {}
} } })
let tests = [
[undefined, 'undefined'],
[null, 'null'],
['', 'empty string'],
[{}, 'object'],
[0, 'empty number'],
[123, 'number'],
[[], 'array'],
['resize', 'not allow resize'],
['out', 'not allow out'],
]
tests.forEach(function (check) {
ctx.req.body.item = {
format: check[0],
}
assert.throws(function() { verifyBody(ctx) }, function(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, /body/i)
assert.match(err.message, /format/i)
assert.match(err.message, /missing/i)
return true
}, `should fail if body item format is ${check[1]}`)
})
})
t.test('should require object of same name as format', function() {
let ctx = createContext({ req: { body: {
item: {}
} } })
let tests = [
[undefined, 'undefined'],
[null, 'null'],
['', 'emptystring'],
['asdf', 'string'],
[0, 'emptynumber'],
[123, 'number'],
[[], 'array'],
]
tests.forEach(function (check) {
ctx = createContext({ req: { body: {
item: {}
} } })
ctx.req.body.item.format = check[1]
ctx.req.body.item[check[1]] = check[0]
assert.throws(function() { verifyBody(ctx) }, function(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, /body/i)
assert.match(err.message, /format/i)
assert.match(err.message, /options/i)
assert.match(err.message, /valid/i)
return true
}, `should fail if body item format options is ${check[1]}`)
})
})
t.test('should allow empty value or string in out in item', function() {
let ctx = createContext({ req: { body: {
item: {
format: 'test',
test: {},
}
} } })
let tests = [
[undefined, 'undefined'],
[null, 'null'],
['', 'empty string'],
['file', 'allow string file'],
['base64', 'allow base64'],
]
tests.forEach(function (check) {
ctx.req.body.item.out = check[0]
assert.doesNotThrow(function() {
verifyBody(ctx)
}, `should not throw with ${check[1]} in out`)
})
})
t.test('should fail if out is invalid value', function() {
let ctx = createContext({ req: { body: {
item: {
format: 'test',
test: {},
}
} } })
let tests = [
[{}, 'object'],
[0, 'empty number'],
[123, 'number'],
[[], 'array'],
['resize', 'not allow resize'],
['out', 'not allow out'],
['example', 'not allow example'],
]
tests.forEach(function (check) {
ctx.req.body.item.out = check[0]
assert.throws(function() { verifyBody(ctx) }, function(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, /body/i)
assert.match(err.message, /item/i)
assert.match(err.message, /out/i)
assert.match(err.message, /valid/i)
return true
}, `should fail if body item out is ${check[1]}`)
})
})
let validObjectOperations = [
'resize',
'extend',
'flatten',
]
validObjectOperations.forEach(function(operation) {
t.test(`should allow empty value or object in ${operation}`, function() {
let ctx = createContext({ req: { body: {
item: {
format: 'test',
test: {},
}
} } })
let tests = [
[undefined, 'undefined'],
[null, 'null'],
[{}, 'object'],
]
tests.forEach(function (check) {
ctx.req.body.item[operation] = check[0]
assert.doesNotThrow(function() {
verifyBody(ctx)
}, `should not throw with ${check[1]} in ${operation}`)
})
})
t.test(`should fail if ${operation} if specified is invalid`, function() {
let ctx = createContext({ req: { body: {
item: {
format: 'test',
test: {},
}
} } })
let tests = [
['', 'emptystring'],
['asdf', 'string'],
[0, 'emptynumber'],
[123, 'number'],
[[], 'array'],
]
tests.forEach(function (check) {
ctx.req.body.item[operation] = check[0]
assert.throws(function() { verifyBody(ctx) }, function(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, /body/i)
assert.match(err.message, /item/i)
assert.match(err.message, new RegExp(operation))
assert.match(err.message, /valid/i)
return true
}, `should fail if body item ${operation} is ${check[1]}`)
})
})
})
let validNumberOperations = [
'blur',
'trim',
]
validNumberOperations.forEach(function(operation) {
t.test(`should allow empty value or number in ${operation}`, function() {
let ctx = createContext({ req: { body: {
item: {
format: 'test',
test: {},
}
} } })
let tests = [
[undefined, 'undefined'],
[null, 'null'],
[0, 'number'],
[0.5, 'positive number'],
]
tests.forEach(function (check) {
ctx.req.body.item[operation] = check[0]
assert.doesNotThrow(function() {
verifyBody(ctx)
}, `should not throw with ${check[1]} in ${operation}`)
})
})
t.test(`should fail if ${operation} if specified is invalid`, function() {
let ctx = createContext({ req: { body: {
item: {
format: 'test',
test: {},
}
} } })
let tests = [
['', 'emptystring'],
['asdf', 'string'],
[{}, 'object'],
[[], 'array'],
]
tests.forEach(function (check) {
ctx.req.body.item[operation] = check[0]
assert.throws(function() { verifyBody(ctx) }, function(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, /body/i)
assert.match(err.message, /item/i)
assert.match(err.message, new RegExp(operation))
assert.match(err.message, /valid/i)
return true
}, `should fail if body item ${operation} is ${check[1]}`)
})
})
})
})