Files
speckle-server/packages/server/modules/fileuploads/index.js
T
2022-03-31 14:18:44 +02:00

163 lines
4.4 KiB
JavaScript

/* istanbul ignore file */
'use strict'
const debug = require('debug')
const appRoot = require('app-root-path')
const Busboy = require('busboy')
const { matomoMiddleware } = require(`${appRoot}/logging/matomoHelper`)
const {
contextMiddleware,
validateScopes,
authorizeResolver
} = require(`${appRoot}/modules/shared`)
const {
checkBucket,
uploadFile,
getFileInfo,
getFileStream
} = require('./services/fileuploads')
const { getStream } = require('../core/services/streams')
exports.init = async (app) => {
if (process.env.DISABLE_FILE_UPLOADS) {
debug('speckle:modules')('📄 FileUploads module is DISABLED')
return
} else {
debug('speckle:modules')('📄 Init FileUploads module')
}
if (!process.env.S3_BUCKET) {
debug('speckle:modules')(
'ERROR: S3_BUCKET env variable was not specified. File uploads will be DISABLED.'
)
return
}
await checkBucket()
let checkStreamPermissions = async (req) => {
if (!req.context || !req.context.auth) {
return { hasPermissions: false, httpErrorCode: 401 }
}
try {
await validateScopes(req.context.scopes, 'streams:write')
} catch (err) {
return { hasPermissions: false, httpErrorCode: 401 }
}
try {
await authorizeResolver(
req.context.userId,
req.params.streamId,
'stream:contributor'
)
} catch (err) {
return { hasPermissions: false, httpErrorCode: 401 }
}
return { hasPermissions: true, httpErrorCode: 200 }
}
app.get(
'/api/file/:fileId',
contextMiddleware,
matomoMiddleware,
async (req, res) => {
if (process.env.DISABLE_FILE_UPLOADS) {
return res.status(503).send('File uploads are disabled on this server')
}
let fileInfo = await getFileInfo({ fileId: req.params.fileId })
if (!fileInfo) return res.status(404).send('File not found')
// Check stream read access
let streamId = fileInfo.streamId
const stream = await getStream({ streamId: streamId, userId: req.context.userId })
if (!stream) {
return res.status(404).send('File stream not found')
}
if (!stream.isPublic && req.context.auth === false) {
return res.status(401).send('You must be logged in to access private streams')
}
if (!stream.isPublic) {
try {
await validateScopes(req.context.scopes, 'streams:read')
} catch (err) {
return res.status(401).send("The provided auth token can't read streams")
}
try {
await authorizeResolver(req.context.userId, streamId, 'stream:reviewer')
} catch (err) {
return res.status(401).send("You don't have access to this private stream")
}
}
let fileStream = await getFileStream({ fileId: req.params.fileId })
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename="${fileInfo.fileName}"`
})
fileStream.pipe(res)
}
),
app.post(
'/api/file/:fileType/:streamId/:branchName?',
contextMiddleware,
matomoMiddleware,
async (req, res) => {
if (process.env.DISABLE_FILE_UPLOADS) {
return res.status(503).send('File uploads are disabled on this server')
}
let { hasPermissions, httpErrorCode } = await checkStreamPermissions(req)
if (!hasPermissions) {
return res.status(httpErrorCode).end()
}
let fileUploadPromises = []
let busboy = Busboy({ headers: req.headers })
busboy.on('file', (name, file, info) => {
const { filename } = info
let promise = uploadFile({
streamId: req.params.streamId,
branchName: req.params.branchName || '',
userId: req.context.userId,
fileName: filename,
fileType: req.params.fileType,
fileStream: file
})
fileUploadPromises.push(promise)
})
busboy.on('finish', async function () {
let fileIds = []
for (let promise of fileUploadPromises) {
let fileId = await promise
fileIds.push(fileId)
}
res.send(fileIds)
})
busboy.on('error', async (err) => {
console.log(`FileUpload error: ${err}`)
res.status(400).end('Upload request error. The server logs have more details')
})
req.pipe(busboy)
}
)
}
exports.finalize = () => {}