|
10 | 10 | import { MongoClient, GridFSBucket, Db } from 'mongodb'; |
11 | 11 | import { FilesAdapter, validateFilename } from './FilesAdapter'; |
12 | 12 | import defaults from '../../defaults'; |
| 13 | +const crypto = require('crypto'); |
13 | 14 |
|
14 | 15 | export class GridFSBucketAdapter extends FilesAdapter { |
15 | 16 | _databaseURI: string; |
16 | 17 | _connectionPromise: Promise<Db>; |
17 | 18 | _mongoOptions: Object; |
| 19 | + _algorithm: string; |
18 | 20 |
|
19 | | - constructor(mongoDatabaseURI = defaults.DefaultMongoURI, mongoOptions = {}) { |
| 21 | + constructor( |
| 22 | + mongoDatabaseURI = defaults.DefaultMongoURI, |
| 23 | + mongoOptions = {}, |
| 24 | + fileKey = undefined |
| 25 | + ) { |
20 | 26 | super(); |
21 | 27 | this._databaseURI = mongoDatabaseURI; |
22 | | - |
| 28 | + this._algorithm = 'aes-256-gcm'; |
| 29 | + this._fileKey = |
| 30 | + fileKey !== undefined |
| 31 | + ? crypto |
| 32 | + .createHash('sha256') |
| 33 | + .update(String(fileKey)) |
| 34 | + .digest('base64') |
| 35 | + .substr(0, 32) |
| 36 | + : null; |
23 | 37 | const defaultMongoOptions = { |
24 | 38 | useNewUrlParser: true, |
25 | 39 | useUnifiedTopology: true, |
@@ -51,7 +65,19 @@ export class GridFSBucketAdapter extends FilesAdapter { |
51 | 65 | const stream = await bucket.openUploadStream(filename, { |
52 | 66 | metadata: options.metadata, |
53 | 67 | }); |
54 | | - await stream.write(data); |
| 68 | + if (this._fileKey !== null) { |
| 69 | + const iv = crypto.randomBytes(16); |
| 70 | + const cipher = crypto.createCipheriv(this._algorithm, this._fileKey, iv); |
| 71 | + const encryptedResult = Buffer.concat([ |
| 72 | + cipher.update(data), |
| 73 | + cipher.final(), |
| 74 | + iv, |
| 75 | + cipher.getAuthTag(), |
| 76 | + ]); |
| 77 | + await stream.write(encryptedResult); |
| 78 | + } else { |
| 79 | + await stream.write(data); |
| 80 | + } |
55 | 81 | stream.end(); |
56 | 82 | return new Promise((resolve, reject) => { |
57 | 83 | stream.on('finish', resolve); |
@@ -82,7 +108,24 @@ export class GridFSBucketAdapter extends FilesAdapter { |
82 | 108 | chunks.push(data); |
83 | 109 | }); |
84 | 110 | stream.on('end', () => { |
85 | | - resolve(Buffer.concat(chunks)); |
| 111 | + const data = Buffer.concat(chunks); |
| 112 | + if (this._fileKey !== null) { |
| 113 | + const authTagLocation = data.length - 16; |
| 114 | + const ivLocation = data.length - 32; |
| 115 | + const authTag = data.slice(authTagLocation); |
| 116 | + const iv = data.slice(ivLocation, authTagLocation); |
| 117 | + const encrypted = data.slice(0, ivLocation); |
| 118 | + const decipher = crypto.createDecipheriv( |
| 119 | + this._algorithm, |
| 120 | + this._fileKey, |
| 121 | + iv |
| 122 | + ); |
| 123 | + decipher.setAuthTag(authTag); |
| 124 | + return resolve( |
| 125 | + Buffer.concat([decipher.update(encrypted), decipher.final()]) |
| 126 | + ); |
| 127 | + } |
| 128 | + resolve(data); |
86 | 129 | }); |
87 | 130 | stream.on('error', (err) => { |
88 | 131 | reject(err); |
|
0 commit comments