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
|
# Local development config file
|
||||||
config/*.json
|
config/*.json
|
||||||
|
|
||||||
# Public folder should be ignored
|
|
||||||
public/*
|
|
||||||
|
|
||||||
# lol
|
# lol
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import path from 'path'
|
||||||
import sharp from 'sharp'
|
import sharp from 'sharp'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import config from '../config.mjs'
|
import config from '../config.mjs'
|
||||||
import { HttpError } from 'flaska'
|
import { HttpError, CorsHandler } from 'flaska'
|
||||||
import * as security from './security.mjs'
|
import * as security from './security.mjs'
|
||||||
import * as formidable from './formidable.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', [server.queryHandler()], this.resize.bind(this))
|
||||||
server.flaska.post('/media/resize/:filename', [server.queryHandler(), server.jsonHandler()], this.resizeExisting.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.delete('/media/:filename', [server.queryHandler()], this.remove.bind(this))
|
||||||
|
server.flaska.options('/::path', [server.queryHandler(), this.security.verifyCorsEnabled], CorsHandler({}))
|
||||||
}
|
}
|
||||||
|
|
||||||
init(server) {
|
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) {
|
export function throwIfNotPublic(site) {
|
||||||
let sites = config.get('sites')
|
let sites = config.get('sites')
|
||||||
if (!sites[site] || sites[site].public !== true) {
|
if (!sites[site] || sites[site].public !== true) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "storage-upload",
|
"name": "storage-upload",
|
||||||
"version": "2.2.7",
|
"version": "2.2.8",
|
||||||
"description": "Micro service for uploading and image resizing files to a storage server.",
|
"description": "Micro service for uploading and image resizing files to a storage server.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"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"
|
"default@HS256": "asdf1234"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"development_cors": {
|
||||||
|
"keys": {
|
||||||
|
"default@HS256": "asdf1234"
|
||||||
|
},
|
||||||
|
"cors": true
|
||||||
|
},
|
||||||
"existing": {
|
"existing": {
|
||||||
"public": true,
|
"public": true,
|
||||||
"keys": {
|
"keys": {
|
||||||
|
|
|
@ -15,7 +15,7 @@ function resolve(file) {
|
||||||
|
|
||||||
const currYear = new Date().getFullYear().toString()
|
const currYear = new Date().getFullYear().toString()
|
||||||
|
|
||||||
t.describe('Media (API)', () => {
|
t.timeout(10000).describe('Media (API)', () => {
|
||||||
let client
|
let client
|
||||||
let secret = 'asdf1234'
|
let secret = 'asdf1234'
|
||||||
let testFiles = []
|
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 () => {
|
t.test('should require authentication', async () => {
|
||||||
resetLog()
|
resetLog()
|
||||||
assert.strictEqual(log.error.callCount, 0)
|
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 () => {
|
t.test('should require authentication', async () => {
|
||||||
resetLog()
|
resetLog()
|
||||||
assert.strictEqual(log.error.callCount, 0)
|
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 () => {
|
t.test('should require authentication', async () => {
|
||||||
resetLog()
|
resetLog()
|
||||||
assert.strictEqual(log.error.callCount, 0)
|
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 sourceFilename
|
||||||
let sourcePath
|
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 () => {
|
t.test('should require authentication', async () => {
|
||||||
resetLog()
|
resetLog()
|
||||||
assert.strictEqual(log.error.callCount, 0)
|
assert.strictEqual(log.error.callCount, 0)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Eltro as t, assert} from 'eltro'
|
||||||
import { HttpError } from 'flaska'
|
import { HttpError } from 'flaska'
|
||||||
|
|
||||||
import { createContext } from '../helper.server.mjs'
|
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 encode from '../../api/jwt/encode.mjs'
|
||||||
import config from '../../api/config.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() {
|
t.describe('#verifyToken()', function() {
|
||||||
let backup = {}
|
let backup = {}
|
||||||
t.before(function() {
|
t.before(function() {
|
||||||
|
|
Loading…
Reference in a new issue