diff --git a/spec/GridFSBucketStorageAdapter.spec.js b/spec/GridFSBucketStorageAdapter.spec.js index b54fa22a05..1e55d687ab 100644 --- a/spec/GridFSBucketStorageAdapter.spec.js +++ b/spec/GridFSBucketStorageAdapter.spec.js @@ -35,6 +35,22 @@ describe_only_db('mongo')('GridFSBucket and GridStore interop', () => { expect(gfsResult.toString('utf8')).toBe(originalString); }); + it('an encypted file created in GridStore should be available in GridFS', async () => { + const gsAdapter = new GridStoreAdapter(databaseURI); + const gfsAdapter = new GridFSBucketAdapter( + databaseURI, + {}, + '89E4AFF1-DFE4-4603-9574-BFA16BB446FD' + ); + await expectMissingFile(gfsAdapter, 'myFileName'); + const originalString = 'abcdefghi'; + await gfsAdapter.createFile('myFileName', originalString); + const gsResult = await gsAdapter.getFileData('myFileName'); + expect(gsResult.toString('utf8')).not.toBe(originalString); + const gfsResult = await gfsAdapter.getFileData('myFileName'); + expect(gfsResult.toString('utf8')).toBe(originalString); + }); + it('should save metadata', async () => { const gfsAdapter = new GridFSBucketAdapter(databaseURI); const originalString = 'abcdefghi'; diff --git a/src/Adapters/Files/GridFSBucketAdapter.js b/src/Adapters/Files/GridFSBucketAdapter.js index 11370ccc61..958f9764d8 100644 --- a/src/Adapters/Files/GridFSBucketAdapter.js +++ b/src/Adapters/Files/GridFSBucketAdapter.js @@ -10,16 +10,30 @@ import { MongoClient, GridFSBucket, Db } from 'mongodb'; import { FilesAdapter, validateFilename } from './FilesAdapter'; import defaults from '../../defaults'; +const crypto = require('crypto'); export class GridFSBucketAdapter extends FilesAdapter { _databaseURI: string; _connectionPromise: Promise; _mongoOptions: Object; + _algorithm: string; - constructor(mongoDatabaseURI = defaults.DefaultMongoURI, mongoOptions = {}) { + constructor( + mongoDatabaseURI = defaults.DefaultMongoURI, + mongoOptions = {}, + fileKey = undefined + ) { super(); this._databaseURI = mongoDatabaseURI; - + this._algorithm = 'aes-256-gcm'; + this._fileKey = + fileKey !== undefined + ? crypto + .createHash('sha256') + .update(String(fileKey)) + .digest('base64') + .substr(0, 32) + : null; const defaultMongoOptions = { useNewUrlParser: true, useUnifiedTopology: true, @@ -51,7 +65,19 @@ export class GridFSBucketAdapter extends FilesAdapter { const stream = await bucket.openUploadStream(filename, { metadata: options.metadata, }); - await stream.write(data); + if (this._fileKey !== null) { + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv(this._algorithm, this._fileKey, iv); + const encryptedResult = Buffer.concat([ + cipher.update(data), + cipher.final(), + iv, + cipher.getAuthTag(), + ]); + await stream.write(encryptedResult); + } else { + await stream.write(data); + } stream.end(); return new Promise((resolve, reject) => { stream.on('finish', resolve); @@ -82,7 +108,24 @@ export class GridFSBucketAdapter extends FilesAdapter { chunks.push(data); }); stream.on('end', () => { - resolve(Buffer.concat(chunks)); + const data = Buffer.concat(chunks); + if (this._fileKey !== null) { + const authTagLocation = data.length - 16; + const ivLocation = data.length - 32; + const authTag = data.slice(authTagLocation); + const iv = data.slice(ivLocation, authTagLocation); + const encrypted = data.slice(0, ivLocation); + const decipher = crypto.createDecipheriv( + this._algorithm, + this._fileKey, + iv + ); + decipher.setAuthTag(authTag); + return resolve( + Buffer.concat([decipher.update(encrypted), decipher.final()]) + ); + } + resolve(data); }); stream.on('error', (err) => { reject(err); diff --git a/src/Controllers/index.js b/src/Controllers/index.js index d10ad8001c..5bbd9308f1 100644 --- a/src/Controllers/index.js +++ b/src/Controllers/index.js @@ -105,12 +105,13 @@ export function getFilesController( filesAdapter, databaseAdapter, preserveFileName, + fileKey, } = options; if (!filesAdapter && databaseAdapter) { throw 'When using an explicit database adapter, you must also use an explicit filesAdapter.'; } const filesControllerAdapter = loadAdapter(filesAdapter, () => { - return new GridFSBucketAdapter(databaseURI); + return new GridFSBucketAdapter(databaseURI, {}, fileKey); }); return new FilesController(filesControllerAdapter, appId, { preserveFileName,