implemented storage uploader
This commit is contained in:
parent
aa15a46b61
commit
4dee8dfbb2
18 changed files with 516 additions and 7 deletions
|
@ -1,14 +1,20 @@
|
||||||
|
|
||||||
export default function defaults(options, defaults) {
|
function defaults(options, def) {
|
||||||
options = options || {}
|
options = options || {}
|
||||||
|
|
||||||
Object.keys(defaults).forEach(function(key) {
|
Object.keys(def).forEach(function(key) {
|
||||||
if (typeof options[key] === 'undefined') {
|
if (typeof options[key] === 'undefined') {
|
||||||
// No need to do clone since we mostly deal with
|
// No need to do clone since we mostly deal with
|
||||||
// flat objects
|
// flat objects
|
||||||
options[key] = defaults[key]
|
options[key] = def[key]
|
||||||
|
}
|
||||||
|
else if (typeof options[key] === 'object' &&
|
||||||
|
typeof def[key] === 'object') {
|
||||||
|
options[key] = defaults(options[key], def[key])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default defaults
|
||||||
|
|
12
api/error.js
Normal file
12
api/error.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
export async function errorMiddleware(ctx, next) {
|
||||||
|
try {
|
||||||
|
await next()
|
||||||
|
} catch (e) {
|
||||||
|
ctx.status = 422
|
||||||
|
ctx.body = {
|
||||||
|
status: 422,
|
||||||
|
message: e.message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
api/jwt.js
Normal file
19
api/jwt.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import jwt from 'jsonwebtoken'
|
||||||
|
|
||||||
|
export function sign(value, secret) {
|
||||||
|
return jwt.sign(value, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verify(token, secret) {
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
jwt.verify(token, secret, (err, res) => {
|
||||||
|
if (err) return reject(err)
|
||||||
|
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decode(token) {
|
||||||
|
return jwt.decode(token)
|
||||||
|
}
|
38
api/media/multer.js
Normal file
38
api/media/multer.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
const fs = require('fs')
|
||||||
|
import multer from 'multer'
|
||||||
|
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
cb(null, '/tmp/my-uploads')
|
||||||
|
},
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
console.log(file)
|
||||||
|
cb(null, file.fieldname + '-' + Date.now())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function uploadFile(ctx, siteName) {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
const date = new Date()
|
||||||
|
|
||||||
|
// Generate 'YYYYMMDD_HHMMSS_' prefix
|
||||||
|
const prefix = date
|
||||||
|
.toISOString()
|
||||||
|
.replace(/-/g, '')
|
||||||
|
.replace('T', '_')
|
||||||
|
.replace(/:/g, '')
|
||||||
|
.replace(/\..+/, '_')
|
||||||
|
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: `./public/${siteName}`,
|
||||||
|
filename: (req, file, cb) =>
|
||||||
|
cb(null, `${prefix}${file.originalname}`),
|
||||||
|
})
|
||||||
|
|
||||||
|
multer({ storage: storage })
|
||||||
|
.single('file')(ctx.req, ctx.res, (err, data) => {
|
||||||
|
if (err) return rej(err)
|
||||||
|
return res(ctx.req.file)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import config from '../../config'
|
||||||
|
import { verifyToken } from './security'
|
||||||
|
import { uploadFile, rename } from './multer'
|
||||||
|
|
||||||
|
export async function upload(ctx) {
|
||||||
|
let site = await verifyToken(ctx)
|
||||||
|
|
||||||
|
let result = await uploadFile(ctx, site)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
filename: result.filename,
|
||||||
|
path: `/${site}/${result.filename}`
|
||||||
|
}
|
||||||
|
}
|
21
api/media/security.js
Normal file
21
api/media/security.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import * as jwt from '../jwt'
|
||||||
|
import config from '../../config'
|
||||||
|
|
||||||
|
export async function verifyToken(ctx) {
|
||||||
|
if (!ctx.query.token) {
|
||||||
|
throw new Error('Token is missing in query')
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoded = jwt.decode(ctx.query.token)
|
||||||
|
|
||||||
|
if (!decoded || !decoded.site) {
|
||||||
|
throw new Error('Token is invalid')
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = await jwt.verify(
|
||||||
|
ctx.query.token,
|
||||||
|
config.get(`sites:${decoded.site}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
return output.site
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
import Router from 'koa-router'
|
import Router from 'koa-router'
|
||||||
|
|
||||||
import * as test from './test/routes'
|
import * as test from './test/routes'
|
||||||
|
import * as media from './media/routes'
|
||||||
|
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
|
||||||
router.get('/', test.testStatic)
|
router.get('/', test.testStatic)
|
||||||
|
router.post('/media', media.upload)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
12
package.json
12
package.json
|
@ -7,10 +7,11 @@
|
||||||
"dev": "nodemon index.js",
|
"dev": "nodemon index.js",
|
||||||
"start": "node index.js",
|
"start": "node index.js",
|
||||||
"test": "env NODE_ENV=test mocha --require babel-register --recursive --reporter dot",
|
"test": "env NODE_ENV=test mocha --require babel-register --recursive --reporter dot",
|
||||||
"docker": "docker run -it --rm --name my-running-script -v \"$PWD\":/usr/src/app -w /usr/src/app node:slim",
|
"docker": "docker run -it --rm --name my-running-script -v \"$PWD\":/usr/src/app -w /usr/src/app node:alpine",
|
||||||
"docker:test": "npm run docker -- npm install --no-optional && npm run test",
|
"install:test": "npm install --no-optional && npm run test",
|
||||||
"docker:dev": "npm run docker -- npm install --no-optional && npm run dev",
|
"install:dev": "npm install --no-optional && npm run dev",
|
||||||
"docker:prod": "npm run docker -- npm install --no-optional && npm run start"
|
"docker:test": "npm run docker -- npm run install:test",
|
||||||
|
"docker:dev": "npm run docker -- npm run install:dev"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -26,10 +27,13 @@
|
||||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
|
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
|
||||||
"babel-register": "^6.26.0",
|
"babel-register": "^6.26.0",
|
||||||
"koa": "^2.3.0",
|
"koa": "^2.3.0",
|
||||||
|
"multer": "^1.3.0",
|
||||||
"koa-router": "^7.2.1",
|
"koa-router": "^7.2.1",
|
||||||
|
"jsonwebtoken": "^8.1.0",
|
||||||
"nconf": "^0.8.5"
|
"nconf": "^0.8.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"app-root-path": "^2.0.1",
|
||||||
"assert-extended": "^1.0.1",
|
"assert-extended": "^1.0.1",
|
||||||
"mocha": "^4.0.1",
|
"mocha": "^4.0.1",
|
||||||
"nodemon": "^1.12.1",
|
"nodemon": "^1.12.1",
|
||||||
|
|
0
public/development/.gitkeep
Normal file
0
public/development/.gitkeep
Normal file
|
@ -2,9 +2,11 @@ import Koa from 'koa'
|
||||||
|
|
||||||
import config from './config'
|
import config from './config'
|
||||||
import router from './api/router'
|
import router from './api/router'
|
||||||
|
import { errorMiddleware } from './api/error'
|
||||||
|
|
||||||
const app = new Koa()
|
const app = new Koa()
|
||||||
|
|
||||||
|
app.use(errorMiddleware)
|
||||||
app.use(router.routes())
|
app.use(router.routes())
|
||||||
app.use(router.allowedMethods())
|
app.use(router.allowedMethods())
|
||||||
|
|
||||||
|
|
32
test/defaults.test.js
Normal file
32
test/defaults.test.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import assert from 'assert-extended'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
describe('defaults', () => {
|
||||||
|
const defaults = require('../api/defaults').default
|
||||||
|
|
||||||
|
describe('#defaults()', () => {
|
||||||
|
it('should apply defaults to flat objects', () => {
|
||||||
|
let assertOutput = { a: 1 }
|
||||||
|
let output = defaults(null, { a: 1 })
|
||||||
|
|
||||||
|
assert.deepEqual(output, assertOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should allow overriding defult properties', () => {
|
||||||
|
let assertOutput = { a: 2 }
|
||||||
|
let output = defaults(assertOutput, { a: 1 })
|
||||||
|
|
||||||
|
assert.deepEqual(output, assertOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should allow nesting through objects', () => {
|
||||||
|
let def = { a: { b: 2 } }
|
||||||
|
let output = defaults({ a: { c: 3} }, def)
|
||||||
|
|
||||||
|
assert.deepEqual(output.a, {
|
||||||
|
b: 2,
|
||||||
|
c: 3,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
54
test/error.test.js
Normal file
54
test/error.test.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import assert from 'assert-extended'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
import { createContext } from './helper.server'
|
||||||
|
|
||||||
|
describe('Error (Middleware)', () => {
|
||||||
|
const error = require('../api/error')
|
||||||
|
|
||||||
|
let ctx
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
ctx = createContext({ })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#errorMiddleware()', () => {
|
||||||
|
let stub
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
stub = sinon.stub()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call next and not do anything if success', async () => {
|
||||||
|
await error.errorMiddleware(ctx, stub)
|
||||||
|
|
||||||
|
assert.ok(stub.called)
|
||||||
|
assert.strictEqual(ctx.body, undefined)
|
||||||
|
assert.strictEqual(ctx.status, undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support stub throwing', async () => {
|
||||||
|
let assertError = new Error('testetytest')
|
||||||
|
stub.throws(assertError)
|
||||||
|
|
||||||
|
await error.errorMiddleware(ctx, stub)
|
||||||
|
|
||||||
|
assert.ok(ctx.body)
|
||||||
|
assert.strictEqual(ctx.status, 422)
|
||||||
|
assert.strictEqual(ctx.body.status, 422)
|
||||||
|
assert.strictEqual(ctx.body.message, assertError.message)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support stub resolving false', async () => {
|
||||||
|
let assertError = new Error('herpaderpderp')
|
||||||
|
stub.rejects(assertError)
|
||||||
|
|
||||||
|
await error.errorMiddleware(ctx, stub)
|
||||||
|
|
||||||
|
assert.ok(ctx.body)
|
||||||
|
assert.strictEqual(ctx.status, 422)
|
||||||
|
assert.strictEqual(ctx.body.status, 422)
|
||||||
|
assert.strictEqual(ctx.body.message, assertError.message)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -2,7 +2,16 @@
|
||||||
// import sinon from 'sinon'
|
// import sinon from 'sinon'
|
||||||
import server from '../server'
|
import server from '../server'
|
||||||
import client from './helper.client'
|
import client from './helper.client'
|
||||||
|
import defaults from '../api/defaults'
|
||||||
|
|
||||||
after(() => server.close())
|
after(() => server.close())
|
||||||
|
|
||||||
export const createClient = client
|
export const createClient = client
|
||||||
|
|
||||||
|
export function createContext(opts) {
|
||||||
|
return defaults(opts, {
|
||||||
|
query: { },
|
||||||
|
req: { },
|
||||||
|
res: { },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
61
test/jwt.test.js
Normal file
61
test/jwt.test.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import assert from 'assert-extended'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
describe('jwt', () => {
|
||||||
|
const jsonwebtoken = require('jsonwebtoken')
|
||||||
|
const jwt = require('../api/jwt')
|
||||||
|
|
||||||
|
describe('#sign', () => {
|
||||||
|
it('should call security correctly', () => {
|
||||||
|
let token = jwt.sign({ a: 1 }, 'asdf')
|
||||||
|
assert.ok(token)
|
||||||
|
|
||||||
|
let decoded = jsonwebtoken.decode(token)
|
||||||
|
assert.strictEqual(decoded.a, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support custom secret', done => {
|
||||||
|
const assertSecret = 'sdfagsda'
|
||||||
|
let token = jwt.sign({ a: 1 }, assertSecret)
|
||||||
|
|
||||||
|
jsonwebtoken.verify(token, assertSecret, done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#decode()', () => {
|
||||||
|
it('should decode correctly', () => {
|
||||||
|
let data = { a: 1, b: 2 }
|
||||||
|
let token = jwt.sign(data, 'asdf')
|
||||||
|
let decoded = jwt.decode(token)
|
||||||
|
|
||||||
|
assert.strictEqual(decoded.a, data.a)
|
||||||
|
assert.strictEqual(decoded.b, data.b)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#verify', () => {
|
||||||
|
it('should verify correctly', () => {
|
||||||
|
const assertSecret = 'asdfasdf'
|
||||||
|
const assertResult = 23532
|
||||||
|
let token = jwt.sign({ a: assertResult }, assertSecret)
|
||||||
|
|
||||||
|
return assert.isFulfilled(jwt.verify(token, assertSecret))
|
||||||
|
.then(data => assert.strictEqual(data.a, assertResult))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if secret does not match', () => {
|
||||||
|
const assertSecret = 'asdfasdf'
|
||||||
|
let token = jwt.sign({ a: 1 }, assertSecret)
|
||||||
|
|
||||||
|
return assert.isRejected(jwt.verify(token, assertSecret + 'a'))
|
||||||
|
.then(err => assert.match(err.message, /[Ss]ignature/))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail token has been mishandled', () => {
|
||||||
|
let token = jwt.sign({ a: 1 }, 'asdf')
|
||||||
|
|
||||||
|
return assert.isRejected(jwt.verify(token + 'a', 'asdf'))
|
||||||
|
.then(err => assert.match(err.message, /[Ss]ignature/))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
61
test/media/api.test.js
Normal file
61
test/media/api.test.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import assert from 'assert'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import 'assert-extended'
|
||||||
|
import appRoot from 'app-root-path'
|
||||||
|
|
||||||
|
import createClient from '../helper.client'
|
||||||
|
|
||||||
|
describe('Media (API)', () => {
|
||||||
|
let config = require('../../config')
|
||||||
|
let jwt = require('../../api/jwt')
|
||||||
|
let testFile
|
||||||
|
let client
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
config.set('sites', {
|
||||||
|
development: 'hello-world'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(done => {
|
||||||
|
if (testFile) {
|
||||||
|
return fs.unlink(appRoot.resolve(`/public${testFile}`), done)
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
client = createClient()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /media', function temp() {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
let err = await assert.isRejected(
|
||||||
|
client.sendFileAsync('/media',
|
||||||
|
appRoot.resolve('/test/media/test.png')))
|
||||||
|
|
||||||
|
assert.strictEqual(err.status, 422)
|
||||||
|
assert.match(err.message, /[Tt]oken/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should upload file and create file', async () => {
|
||||||
|
let token = jwt.sign({ site: 'development' }, 'hello-world')
|
||||||
|
|
||||||
|
let data = await assert.isFulfilled(
|
||||||
|
client.sendFileAsync(
|
||||||
|
`/media?token=${token}`,
|
||||||
|
appRoot.resolve('/test/media/test.png')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.ok(data)
|
||||||
|
assert.ok(data.filename)
|
||||||
|
assert.ok(data.path)
|
||||||
|
|
||||||
|
testFile = data.path
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,73 @@
|
||||||
|
import assert from 'assert-extended'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
import { createContext } from '../helper.server'
|
||||||
|
|
||||||
|
describe('Media (Routes)', () => {
|
||||||
|
const multer = require('../../api/media/multer')
|
||||||
|
const routes = require('../../api/media/routes')
|
||||||
|
const security = require('../../api/media/security')
|
||||||
|
const config = require('../../config')
|
||||||
|
|
||||||
|
let sandbox
|
||||||
|
let ctx
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.sandbox.create()
|
||||||
|
ctx = createContext({
|
||||||
|
req: {
|
||||||
|
file: { },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#upload', () => {
|
||||||
|
let stubVerifyToken
|
||||||
|
let stubUpload
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
stubVerifyToken = sandbox.stub(security, 'verifyToken')
|
||||||
|
stubUpload = sandbox.stub(multer, 'uploadFile').resolves({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call security correctly', async () => {
|
||||||
|
const assertError = new Error('temp')
|
||||||
|
stubVerifyToken.rejects(assertError)
|
||||||
|
|
||||||
|
let err = await assert.isRejected(routes.upload(ctx))
|
||||||
|
|
||||||
|
assert.ok(stubVerifyToken.called)
|
||||||
|
assert.strictEqual(err, assertError)
|
||||||
|
assert.strictEqual(stubVerifyToken.firstCall.args[0], ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call upload correctly', async () => {
|
||||||
|
const assertSiteName = 'benshapiro'
|
||||||
|
const assertError = new Error('hello')
|
||||||
|
stubVerifyToken.resolves(assertSiteName)
|
||||||
|
stubUpload.rejects(assertError)
|
||||||
|
|
||||||
|
let err = await assert.isRejected(routes.upload(ctx))
|
||||||
|
|
||||||
|
assert.ok(stubUpload.called)
|
||||||
|
assert.strictEqual(err, assertError)
|
||||||
|
assert.strictEqual(stubUpload.firstCall.args[0], ctx)
|
||||||
|
assert.strictEqual(stubUpload.firstCall.args[1], assertSiteName)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should otherwise set context status to 204 and file in result', async () => {
|
||||||
|
const assertFilename = 'asdfsafd'
|
||||||
|
const assertSite = 'mario'
|
||||||
|
stubVerifyToken.resolves(assertSite)
|
||||||
|
stubUpload.resolves({ filename: assertFilename })
|
||||||
|
await routes.upload(ctx)
|
||||||
|
|
||||||
|
assert.strictEqual(ctx.body.filename, assertFilename)
|
||||||
|
assert.strictEqual(ctx.body.path, `/${assertSite}/${assertFilename}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
101
test/media/security.test.js
Normal file
101
test/media/security.test.js
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import assert from 'assert-extended'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
import { createContext } from '../helper.server'
|
||||||
|
|
||||||
|
describe('Media (Security)', () => {
|
||||||
|
const security = require('../../api/media/security')
|
||||||
|
const jwt = require('../../api/jwt')
|
||||||
|
const config = require('../../config')
|
||||||
|
|
||||||
|
let sandbox
|
||||||
|
let ctx
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.sandbox.create()
|
||||||
|
config.set('sites', {
|
||||||
|
test: 'secret',
|
||||||
|
})
|
||||||
|
ctx = createContext({
|
||||||
|
query: {
|
||||||
|
token: 'asdf',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#verifyToken()', () => {
|
||||||
|
let stubVerify
|
||||||
|
let stubDecode
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
stubVerify = sandbox.stub(jwt, 'verify')
|
||||||
|
stubDecode = sandbox.stub(jwt, 'decode').returns({ site: 1 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if query token is missing', async () => {
|
||||||
|
delete ctx.query.token
|
||||||
|
|
||||||
|
let err = await assert.isRejected(security.verifyToken(ctx))
|
||||||
|
|
||||||
|
assert.ok(err)
|
||||||
|
assert.match(err.message, /[tT]oken/)
|
||||||
|
assert.match(err.message, /[Mm]issing/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if token is invalid', async () => {
|
||||||
|
const assertToken = 'asdfasdfas'
|
||||||
|
ctx.query.token = assertToken
|
||||||
|
stubDecode.returns(null)
|
||||||
|
|
||||||
|
let err = await assert.isRejected(security.verifyToken(ctx))
|
||||||
|
|
||||||
|
assert.ok(err)
|
||||||
|
assert.ok(stubDecode.called)
|
||||||
|
assert.strictEqual(stubDecode.firstCall.args[0], assertToken)
|
||||||
|
assert.match(err.message, /[tT]oken/)
|
||||||
|
assert.match(err.message, /[Ii]nvalid/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if token does not have site', async () => {
|
||||||
|
stubDecode.returns({ s: 1 })
|
||||||
|
|
||||||
|
let err = await assert.isRejected(security.verifyToken(ctx))
|
||||||
|
|
||||||
|
assert.ok(err)
|
||||||
|
assert.ok(stubDecode.called)
|
||||||
|
assert.match(err.message, /[tT]oken/)
|
||||||
|
assert.match(err.message, /[Ii]nvalid/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if secret does not match one in config', async () => {
|
||||||
|
const assertError = new Error('lethal')
|
||||||
|
const assertToken = 'ewgowae'
|
||||||
|
ctx.query.token = assertToken
|
||||||
|
config.set('sites', { herp: 'derp' })
|
||||||
|
|
||||||
|
|
||||||
|
stubDecode.returns({ site: 'herp' })
|
||||||
|
stubVerify.rejects(assertError)
|
||||||
|
|
||||||
|
let err = await assert.isRejected(security.verifyToken(ctx))
|
||||||
|
|
||||||
|
assert.ok(stubVerify.called)
|
||||||
|
assert.strictEqual(err, assertError)
|
||||||
|
assert.strictEqual(stubVerify.firstCall.args[0], assertToken)
|
||||||
|
assert.strictEqual(stubVerify.firstCall.args[1], 'derp')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should otherwise return the site name', async () => {
|
||||||
|
const assertSiteName = 'asdfasdfasdf'
|
||||||
|
stubVerify.resolves({ site: assertSiteName })
|
||||||
|
|
||||||
|
let site = await security.verifyToken(ctx)
|
||||||
|
|
||||||
|
assert.strictEqual(site, assertSiteName)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
BIN
test/media/test.png
Normal file
BIN
test/media/test.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 278 KiB |
Loading…
Reference in a new issue