storage-upload/test/media/security.test.mjs
Jonatan Nilsson 68eef3a6b6 Complete refactor, features and optimization of code
Removed large amount of dependencies.
Added vips support and automatically resizing based on any criteria.
Faster and leaner.
Added ability to fetch listing of files in folders.
2022-01-05 14:47:51 +00:00

376 lines
10 KiB
JavaScript

import { Eltro as t, assert} from 'eltro'
import { createContext } from '../helper.server.mjs'
import { verifyToken, verifyBody, throwIfNotPublic } from '../../api/media/security.mjs'
import { HttpError } from '../../api/error.mjs'
import encode from '../../api/jwt/encode.mjs'
import config from '../../api/config.mjs'
t.describe('#throwIfNotPublic()', function() {
t.before(function() {
config.set('sites', {
justatest: {
},
justatest2: {
public: false,
},
justatest3: {
public: true,
},
})
})
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() {
t.before(function() {
config.set('sites', {
justatest: {
keys: {
'default@HS512': 'mysharedkey',
}
},
})
})
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]}`)
})
})
t.test('should fail if an item has the name original', function() {
let ctx = createContext({ req: { body: {
original: {}
} } })
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, /original/i)
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]}`)
})
})
t.test('should allow empty value or object in resize', 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.resize = check[0]
assert.doesNotThrow(function() {
verifyBody(ctx)
}, `should not throw with ${check[1]} in resize`)
})
})
t.test('should fail if resize 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.resize = 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, /resize/i)
assert.match(err.message, /valid/i)
return true
}, `should fail if body item resize is ${check[1]}`)
})
})
})