/* 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 = () => {}