Add cors support for sites that request it
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
This commit is contained in:
parent
ec81950c42
commit
81c9603ea0
8 changed files with 180 additions and 12 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -60,9 +60,6 @@ typings/
|
|||
# Local development config file
|
||||
config/*.json
|
||||
|
||||
# Public folder should be ignored
|
||||
public/*
|
||||
|
||||
# lol
|
||||
package-lock.json
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import path from 'path'
|
|||
import sharp from 'sharp'
|
||||
import fs from 'fs/promises'
|
||||
import config from '../config.mjs'
|
||||
import { HttpError } from 'flaska'
|
||||
import { HttpError, CorsHandler } from 'flaska'
|
||||
import * as security from './security.mjs'
|
||||
import * as formidable from './formidable.mjs'
|
||||
|
||||
|
@ -30,6 +30,7 @@ export default class MediaRoutes {
|
|||
server.flaska.post('/media/resize', [server.queryHandler()], this.resize.bind(this))
|
||||
server.flaska.post('/media/resize/:filename', [server.queryHandler(), server.jsonHandler()], this.resizeExisting.bind(this))
|
||||
server.flaska.delete('/media/:filename', [server.queryHandler()], this.remove.bind(this))
|
||||
server.flaska.options('/::path', [server.queryHandler(), this.security.verifyCorsEnabled], CorsHandler({}))
|
||||
}
|
||||
|
||||
init(server) {
|
||||
|
|
|
@ -25,6 +25,20 @@ export function verifyToken(ctx) {
|
|||
}
|
||||
}
|
||||
|
||||
export function verifyCorsEnabled(ctx) {
|
||||
let site
|
||||
try {
|
||||
site = verifyToken(ctx)
|
||||
} catch (err) {
|
||||
throw new HttpError(404)
|
||||
}
|
||||
|
||||
let sites = config.get('sites')
|
||||
if (!sites[site] || sites[site].cors !== true) {
|
||||
throw new HttpError(404)
|
||||
}
|
||||
}
|
||||
|
||||
export function throwIfNotPublic(site) {
|
||||
let sites = config.get('sites')
|
||||
if (!sites[site] || sites[site].public !== true) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "storage-upload",
|
||||
"version": "2.2.7",
|
||||
"version": "2.2.8",
|
||||
"description": "Micro service for uploading and image resizing files to a storage server.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
|
0
public/development_cors/.gitkeep
Normal file
0
public/development_cors/.gitkeep
Normal file
|
@ -35,6 +35,12 @@ export function startServer() {
|
|||
"default@HS256": "asdf1234"
|
||||
}
|
||||
},
|
||||
"development_cors": {
|
||||
"keys": {
|
||||
"default@HS256": "asdf1234"
|
||||
},
|
||||
"cors": true
|
||||
},
|
||||
"existing": {
|
||||
"public": true,
|
||||
"keys": {
|
||||
|
|
|
@ -15,7 +15,7 @@ function resolve(file) {
|
|||
|
||||
const currYear = new Date().getFullYear().toString()
|
||||
|
||||
t.describe('Media (API)', () => {
|
||||
t.timeout(10000).describe('Media (API)', () => {
|
||||
let client
|
||||
let secret = 'asdf1234'
|
||||
let testFiles = []
|
||||
|
@ -34,7 +34,65 @@ t.describe('Media (API)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
t.timeout(10000).describe('POST /media', function temp() {
|
||||
t.describe('OPTIONS', function() {
|
||||
t.test('should fail options for every single route', async () => {
|
||||
const testPaths = [
|
||||
'/media',
|
||||
'/media/:site',
|
||||
'/media',
|
||||
'/media/noprefix',
|
||||
'/media/resize',
|
||||
'/media/resize/:filename',
|
||||
'/media/:filename',
|
||||
]
|
||||
|
||||
for (let path of testPaths) {
|
||||
let err = await assert.isRejected(
|
||||
client.customRequest('OPTIONS', path, null)
|
||||
)
|
||||
assert.strictEqual(err.status, 404)
|
||||
}
|
||||
})
|
||||
|
||||
t.test('should fail options for every single route even with token', async () => {
|
||||
let token = encode(null, { iss: 'development' }, secret)
|
||||
const testPaths = [
|
||||
'/media',
|
||||
'/media/:site',
|
||||
'/media',
|
||||
'/media/noprefix',
|
||||
'/media/resize',
|
||||
'/media/resize/:filename',
|
||||
'/media/:filename',
|
||||
]
|
||||
|
||||
for (let path of testPaths) {
|
||||
let err = await assert.isRejected(
|
||||
client.customRequest('OPTIONS', path + `?token=${token}`, null)
|
||||
)
|
||||
assert.strictEqual(err.status, 404)
|
||||
}
|
||||
})
|
||||
|
||||
t.test('should work if specified has cors enabled', async () => {
|
||||
let token = encode(null, { iss: 'development_cors' }, secret)
|
||||
const testPaths = [
|
||||
'/media',
|
||||
'/media/:site',
|
||||
'/media',
|
||||
'/media/noprefix',
|
||||
'/media/resize',
|
||||
'/media/resize/:filename',
|
||||
'/media/:filename',
|
||||
]
|
||||
|
||||
for (let path of testPaths) {
|
||||
await client.customRequest('OPTIONS', path + `?token=${token}`, null)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('POST /media', function temp() {
|
||||
t.test('should require authentication', async () => {
|
||||
resetLog()
|
||||
assert.strictEqual(log.error.callCount, 0)
|
||||
|
@ -144,7 +202,7 @@ t.describe('Media (API)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
t.timeout(10000).describe('POST /media/noprefix', function temp() {
|
||||
t.describe('POST /media/noprefix', function temp() {
|
||||
t.test('should require authentication', async () => {
|
||||
resetLog()
|
||||
assert.strictEqual(log.error.callCount, 0)
|
||||
|
@ -260,7 +318,7 @@ t.describe('Media (API)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
t.timeout(10000).describe('POST /media/resize', function temp() {
|
||||
t.describe('POST /media/resize', function temp() {
|
||||
t.test('should require authentication', async () => {
|
||||
resetLog()
|
||||
assert.strictEqual(log.error.callCount, 0)
|
||||
|
@ -500,7 +558,7 @@ t.describe('Media (API)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
t.timeout(10000).describe('POST /media/resize/:filename', function temp() {
|
||||
t.describe('POST /media/resize/:filename', function temp() {
|
||||
let sourceFilename
|
||||
let sourcePath
|
||||
|
||||
|
@ -664,7 +722,7 @@ t.describe('Media (API)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
t.timeout(10000).describe('DELETE /media/:filename', function temp() {
|
||||
t.describe('DELETE /media/:filename', function temp() {
|
||||
t.test('should require authentication', async () => {
|
||||
resetLog()
|
||||
assert.strictEqual(log.error.callCount, 0)
|
||||
|
|
|
@ -2,7 +2,7 @@ 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 { verifyToken, verifyBody, throwIfNotPublic, verifyCorsEnabled } from '../../api/media/security.mjs'
|
||||
import encode from '../../api/jwt/encode.mjs'
|
||||
import config from '../../api/config.mjs'
|
||||
|
||||
|
@ -61,6 +61,98 @@ t.describe('#throwIfNotPublic()', function() {
|
|||
})
|
||||
})
|
||||
|
||||
t.describe('#verifyCorsEnabled()', function() {
|
||||
let ctx
|
||||
let backup = {}
|
||||
|
||||
t.beforeEach(function() {
|
||||
ctx = createContext({ })
|
||||
})
|
||||
|
||||
t.before(function() {
|
||||
backup = config.sources[1].store
|
||||
config.sources[1].store = {
|
||||
sites: {
|
||||
justatest: {
|
||||
keys: {
|
||||
'default@HS512': 'mysharedkey',
|
||||
}
|
||||
},
|
||||
justatest2: {
|
||||
keys: {
|
||||
'default@HS512': 'mysharedkey',
|
||||
},
|
||||
cors: false,
|
||||
},
|
||||
justatest3: {
|
||||
keys: {
|
||||
'default@HS512': 'mysharedkey',
|
||||
},
|
||||
cors: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
t.after(function() {
|
||||
config.sources[1].store = backup
|
||||
})
|
||||
|
||||
t.test('should throw 404 for sites that do not exist or are null', function() {
|
||||
let tests = [
|
||||
'justatest',
|
||||
'justatest2',
|
||||
'nonexisting1',
|
||||
null,
|
||||
]
|
||||
|
||||
tests.forEach(function(test) {
|
||||
ctx.query.set('token', encode({ typ: 'JWT', alg: 'HS512' }, { iss: test }, 'mysharedkey'))
|
||||
|
||||
assert.throws(function() { verifyCorsEnabled(ctx) }, function(err) {
|
||||
assert.ok(err instanceof HttpError)
|
||||
assert.ok(err instanceof Error)
|
||||
assert.strictEqual(err.status, 404)
|
||||
assert.notOk(err.message)
|
||||
return true
|
||||
}, `should throw with site ${test}`)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should throw 404 for sites that use wrong token', function() {
|
||||
let tests = [
|
||||
'justatest',
|
||||
'justatest2',
|
||||
'nonexisting1',
|
||||
null,
|
||||
]
|
||||
|
||||
tests.forEach(function(test) {
|
||||
ctx.query.set('token', encode({ typ: 'JWT', alg: 'HS512' }, { iss: test }, 'herp'))
|
||||
|
||||
assert.throws(function() { verifyCorsEnabled(ctx) }, function(err) {
|
||||
assert.ok(err instanceof HttpError)
|
||||
assert.ok(err instanceof Error)
|
||||
assert.strictEqual(err.status, 404)
|
||||
assert.notOk(err.message)
|
||||
return true
|
||||
}, `should throw with site ${test}`)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should pass for sites that have cors enabled', function() {
|
||||
let tests = [
|
||||
'justatest3',
|
||||
]
|
||||
|
||||
tests.forEach(function(test) {
|
||||
ctx.query.set('token', encode({ typ: 'JWT', alg: 'HS512' }, { iss: test }, 'mysharedkey'))
|
||||
|
||||
assert.doesNotThrow(function() { verifyCorsEnabled(ctx) }, `should not throw with site ${test}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#verifyToken()', function() {
|
||||
let backup = {}
|
||||
t.before(function() {
|
||||
|
|
Loading…
Reference in a new issue