2021-10-09 00:11:52 +00:00
|
|
|
import { Eltro as t, assert} from 'eltro'
|
2022-08-13 21:52:45 +00:00
|
|
|
import { HttpError } from 'flaska'
|
2021-10-09 00:11:52 +00:00
|
|
|
|
|
|
|
import { createContext } from '../helper.server.mjs'
|
2022-01-05 14:47:51 +00:00
|
|
|
import { verifyToken, verifyBody, throwIfNotPublic } from '../../api/media/security.mjs'
|
2021-10-09 00:11:52 +00:00
|
|
|
import encode from '../../api/jwt/encode.mjs'
|
|
|
|
import config from '../../api/config.mjs'
|
|
|
|
|
2022-01-05 14:47:51 +00:00
|
|
|
t.describe('#throwIfNotPublic()', function() {
|
2022-08-13 21:52:45 +00:00
|
|
|
let backup = {}
|
|
|
|
|
2022-01-05 14:47:51 +00:00
|
|
|
t.before(function() {
|
2022-08-13 21:52:45 +00:00
|
|
|
backup = config.sources[1].store
|
|
|
|
config.sources[1].store = {
|
|
|
|
sites: {
|
|
|
|
justatest: {
|
|
|
|
},
|
|
|
|
justatest2: {
|
|
|
|
public: false,
|
|
|
|
},
|
|
|
|
justatest3: {
|
|
|
|
public: true,
|
|
|
|
},
|
2022-01-05 14:47:51 +00:00
|
|
|
},
|
2022-08-13 21:52:45 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.after(function() {
|
|
|
|
config.sources[1].store = backup
|
2022-01-05 14:47:51 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
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}`)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-10-09 00:11:52 +00:00
|
|
|
t.describe('#verifyToken()', function() {
|
2022-08-13 21:52:45 +00:00
|
|
|
let backup = {}
|
2021-10-09 00:11:52 +00:00
|
|
|
t.before(function() {
|
2022-08-13 21:52:45 +00:00
|
|
|
backup = config.sources[1].store
|
|
|
|
config.sources[1].store = {
|
|
|
|
sites: {
|
|
|
|
justatest: {
|
|
|
|
keys: {
|
|
|
|
'default@HS512': 'mysharedkey',
|
|
|
|
}
|
|
|
|
},
|
2021-10-09 00:11:52 +00:00
|
|
|
},
|
2022-08-13 21:52:45 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.after(function() {
|
|
|
|
config.sources[1].store = backup
|
2021-10-09 00:11:52 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
t.test('should fail if query token is missing', function() {
|
|
|
|
let ctx = createContext({ })
|
2021-10-11 00:21:57 +00:00
|
|
|
ctx.query.delete('token')
|
2021-10-09 00:11:52 +00:00
|
|
|
|
2021-10-11 00:21:57 +00:00
|
|
|
assert.throws(function() { verifyToken(ctx) }, function(err) {
|
|
|
|
assert.ok(err instanceof HttpError)
|
|
|
|
assert.ok(err instanceof Error)
|
|
|
|
assert.strictEqual(err.status, 422)
|
2022-01-05 14:47:51 +00:00
|
|
|
assert.match(err.message, /query/i)
|
|
|
|
assert.match(err.message, /token/i)
|
2021-10-11 00:21:57 +00:00
|
|
|
return true
|
|
|
|
})
|
2021-10-09 00:11:52 +00:00
|
|
|
})
|
|
|
|
|
2021-10-11 00:21:57 +00:00
|
|
|
function assertInvalidToken(err) {
|
|
|
|
assert.ok(err instanceof HttpError)
|
|
|
|
assert.ok(err instanceof Error)
|
|
|
|
assert.strictEqual(err.status, 422)
|
2022-01-05 14:47:51 +00:00
|
|
|
assert.match(err.message, /invalid/i)
|
|
|
|
assert.match(err.message, /token/i)
|
2021-10-11 00:21:57 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-10-09 00:11:52 +00:00
|
|
|
t.test('should fail if token is invalid', function() {
|
|
|
|
let ctx = createContext({ })
|
2021-10-11 00:21:57 +00:00
|
|
|
ctx.query.set('token', 'asdfasdgassdga')
|
2021-10-09 00:11:52 +00:00
|
|
|
|
2021-10-11 00:21:57 +00:00
|
|
|
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
|
|
|
|
assert.ok(ctx.log.error.lastCall)
|
|
|
|
assert.match(ctx.log.error.lastCall[0].message, /3 dots/)
|
2021-10-09 00:11:52 +00:00
|
|
|
|
2021-10-11 00:21:57 +00:00
|
|
|
ctx.query.set('token', 'asdfasdgassdga.asdfasdg.sadfsadfas')
|
2021-10-09 00:11:52 +00:00
|
|
|
|
2021-10-11 00:21:57 +00:00
|
|
|
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
|
2022-01-05 14:47:51 +00:00
|
|
|
assert.match(ctx.log.error.lastCall[0].message, /invalid/i)
|
2021-10-09 00:11:52 +00:00
|
|
|
|
2021-10-11 00:21:57 +00:00
|
|
|
ctx.query.set('token', encode(
|
2021-10-09 00:11:52 +00:00
|
|
|
{ typ: 'JWT', alg: 'HS256' },
|
|
|
|
{ iss: 'justatest' },
|
|
|
|
'mysharedkey'
|
2021-10-11 00:21:57 +00:00
|
|
|
))
|
|
|
|
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
|
|
|
|
assert.match(ctx.log.error.lastCall[0].message, /pubkey/)
|
2021-10-09 00:11:52 +00:00
|
|
|
|
2021-10-11 00:21:57 +00:00
|
|
|
ctx.query.set('token', encode(
|
2021-10-09 00:11:52 +00:00
|
|
|
{ typ: 'JWT', alg: 'HS512' },
|
|
|
|
{ iss: 'notexist' },
|
|
|
|
'mysharedkey'
|
2021-10-11 00:21:57 +00:00
|
|
|
))
|
|
|
|
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
|
|
|
|
assert.match(ctx.log.error.lastCall[0].message, /notexist/)
|
2021-10-09 00:11:52 +00:00
|
|
|
|
2021-10-11 00:21:57 +00:00
|
|
|
ctx.query.set('token', encode(
|
2021-10-09 00:11:52 +00:00
|
|
|
{ typ: 'JWT', alg: 'HS512' },
|
|
|
|
{ iss: 'justatest' },
|
|
|
|
'mysharedkey2'
|
2021-10-11 00:21:57 +00:00
|
|
|
))
|
|
|
|
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
|
|
|
|
assert.match(ctx.log.error.lastCall[0].message, /HS512/)
|
2022-01-05 14:47:51 +00:00
|
|
|
assert.match(ctx.log.error.lastCall[0].message, /Verification/i)
|
2021-10-09 00:11:52 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
t.test('should otherwise return the issuer', function() {
|
|
|
|
let ctx = createContext({ })
|
2021-10-11 00:21:57 +00:00
|
|
|
ctx.query.set('token', encode(
|
2021-10-09 00:11:52 +00:00
|
|
|
{ typ: 'JWT', alg: 'HS512' },
|
|
|
|
{ iss: 'justatest' },
|
|
|
|
'mysharedkey'
|
2021-10-11 00:21:57 +00:00
|
|
|
))
|
2021-10-09 00:11:52 +00:00
|
|
|
let site = verifyToken(ctx)
|
|
|
|
assert.strictEqual(site, 'justatest')
|
|
|
|
})
|
|
|
|
})
|
2022-01-05 14:47:51 +00:00
|
|
|
|
|
|
|
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]}`)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-01-06 09:01:10 +00:00
|
|
|
let testInvalidNames = ['filename', 'path']
|
2022-01-05 14:47:51 +00:00
|
|
|
|
2022-01-06 09:01:10 +00:00
|
|
|
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')
|
|
|
|
})
|
2022-01-05 14:47:51 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
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]}`)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-01-06 09:51:43 +00:00
|
|
|
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]}`)
|
|
|
|
})
|
2022-01-05 14:47:51 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-01-06 09:51:43 +00:00
|
|
|
let validNumberOperations = [
|
|
|
|
'blur',
|
|
|
|
'trim',
|
|
|
|
]
|
2022-01-05 14:47:51 +00:00
|
|
|
|
2022-01-06 09:51:43 +00:00
|
|
|
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]}`)
|
|
|
|
})
|
2022-01-05 14:47:51 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|