diff --git a/.gitignore b/.gitignore index e920c16..e999e30 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ pids *.pid *.seed +#Test file creation directory +files + # Directory for instrumented libs generated by jscoverage/JSCover lib-cov diff --git a/README.md b/README.md index 717ee7d..2d62641 100644 --- a/README.md +++ b/README.md @@ -24,22 +24,21 @@ When using parse-server-fs-adapter across multiple parse-server instances it's i "filesAdapter": { "module": "@parse/fs-files-adapter", "options": { - "filesSubDirectory": "my/files/folder", // optional - "fileKey": "someKey" //optional, but mandatory if you want to encrypt files + "filesSubDirectory": "my/files/folder", // optional, defaults to ./files + "encryptionKey": "someKey" //optional, but mandatory if you want to encrypt files } } } ``` ### Passing as an instance -***Notice: If used with parse-server versions <= 4.2.0, DO NOT PASS in `PARSE_SERVER_FILE_KEY` or `fileKey` from parse-server. Instead pass your key directly to `FSFilesAdapter` using your own environment variable or hardcoding the string. parse-server versions > 4.2.0 can pass in `PARSE_SERVER_FILE_KEY` or `fileKey`.*** ```javascript var FSFilesAdapter = require('@parse/fs-files-adapter'); var fsAdapter = new FSFilesAdapter({ - "filesSubDirectory": "my/files/folder", // optional - "fileKey": "someKey" //optional, but mandatory if you want to encrypt files + "filesSubDirectory": "my/files/folder", // optional, defaults to ./files + "encryptionKey": "someKey" //optional, but mandatory if you want to encrypt files }); var api = new ParseServer({ @@ -49,16 +48,16 @@ var api = new ParseServer({ }) ``` -### Rotating to a new fileKey -Periodically you may want to rotate your fileKey for security reasons. When this is the case, you can start up a development parse-server that has the same configuration as your production server. In the development server, initialize the file adapter with the new key and do the following in your `index.js`: +### Rotating to a new encryptionKey +Periodically you may want to rotate your encryptionKey for security reasons. When this is the case, you can start up a development parse-server that has the same configuration as your production server. In the development server, initialize the file adapter with the new key and do the following in your `index.js`: #### Files were previously unencrypted and you want to encrypt ```javascript var FSFilesAdapter = require('@parse/fs-files-adapter'); var fsAdapter = new FSFilesAdapter({ - "filesSubDirectory": "my/files/folder", // optional - "fileKey": "newKey" //Use the newKey + "filesSubDirectory": "my/files/folder", // optional, defaults to ./files + "encryptionKey": "newKey" //Use the newKey }); var api = new ParseServer({ @@ -69,28 +68,28 @@ var api = new ParseServer({ //This can take awhile depending on how many files and how larger they are. It will attempt to rotate the key of all files in your filesSubDirectory //It is not recommended to do this on the production server, deploy a development server to complete the process. -const {rotated, notRotated} = await api.filesAdapter.rotateFileKey(); +const {rotated, notRotated} = await api.filesAdapter.rotateEncryptionKey(); console.log('Files rotated to newKey: ' + rotated); console.log('Files that couldn't be rotated to newKey: ' + notRotated); ``` -After successfully rotating your key, you should change the `fileKey` to `newKey` on your production server and then restart the server. +After successfully rotating your key, you should change the `encryptionKey` to `newKey` on your production server and then restart the server. #### Files were previously encrypted with `oldKey` and you want to encrypt with `newKey` -The same process as above, but pass in your `oldKey` to `rotateFileKey()`. +The same process as above, but pass in your `oldKey` to `rotateEncryptionKey()`. ```javascript //This can take awhile depending on how many files and how larger they are. It will attempt to rotate the key of all files in your filesSubDirectory -const {rotated, notRotated} = await api.filesAdapter.rotateFileKey({oldKey: oldKey}); +const {rotated, notRotated} = await api.filesAdapter.rotateEncryptionKey({oldKey: oldKey}); console.log('Files rotated to newKey: ' + rotated); console.log('Files that couldn't be rotated to newKey: ' + notRotated); ``` #### Only rotate a select list of files that were previously encrypted with `oldKey` and you want to encrypt with `newKey` -This is useful if for some reason there errors and some of the files werent rotated and returned in `notRotated`. The same process as above, but pass in your `oldKey` along with the array of `fileNames` to `rotateFileKey()`. +This is useful if for some reason there errors and some of the files werent rotated and returned in `notRotated`. The same process as above, but pass in your `oldKey` along with the array of `fileNames` to `rotateEncryptionKey()`. ```javascript //This can take awhile depending on how many files and how larger they are. It will attempt to rotate the key of all files in your filesSubDirectory -const {rotated, notRotated} = await api.filesAdapter.rotateFileKey({oldKey: oldKey, fileNames: ["fileName1.png","fileName2.png"]}); +const {rotated, notRotated} = await api.filesAdapter.rotateEncryptionKey({oldKey: oldKey, fileNames: ["fileName1.png","fileName2.png"]}); console.log('Files rotated to newKey: ' + rotated); console.log('Files that couldn't be rotated to newKey: ' + notRotated); ``` diff --git a/index.js b/index.js index f17727f..5c70b7c 100644 --- a/index.js +++ b/index.js @@ -12,10 +12,10 @@ const algorithm = 'aes-256-gcm'; function FileSystemAdapter(options) { options = options || {}; - this._fileKey = null; + this._encryptionKey = null; - if (options.fileKey !== undefined){ - this._fileKey = crypto.createHash('sha256').update(String(options.fileKey)).digest('base64').substr(0, 32); + if (options.encryptionKey !== undefined){ + this._encryptionKey = crypto.createHash('sha256').update(String(options.encryptionKey)).digest('base64').substr(0, 32); } let filesSubDirectory = options.filesSubDirectory || ''; this._filesDir = filesSubDirectory; @@ -30,11 +30,11 @@ FileSystemAdapter.prototype.createFile = function(filename, data) { const stream = fs.createWriteStream(filepath); return new Promise((resolve, reject) => { try{ - if(this._fileKey !== null){ + if(this._encryptionKey !== null){ const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv( algorithm, - this._fileKey, + this._encryptionKey, iv ); const encryptedResult = Buffer.concat([ @@ -96,14 +96,14 @@ FileSystemAdapter.prototype.getFileData = function(filename) { }); stream.on('end', () => { const data = Buffer.concat(chunks); - if(this._fileKey !== null){ + if(this._encryptionKey !== 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); try{ - const decipher = crypto.createDecipheriv(algorithm, this._fileKey, iv); + const decipher = crypto.createDecipheriv(algorithm, this._encryptionKey, iv); decipher.setAuthTag(authTag); const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); return resolve(decrypted); @@ -119,12 +119,12 @@ FileSystemAdapter.prototype.getFileData = function(filename) { }); } -FileSystemAdapter.prototype.rotateFileKey = function(options = {}) { +FileSystemAdapter.prototype.rotateEncryptionKey = function(options = {}) { const applicationDir = this._getApplicationDir(); var fileNames = []; var oldKeyFileAdapter = {}; if (options.oldKey !== undefined) { - oldKeyFileAdapter = new FileSystemAdapter({filesSubDirectory: this._filesDir, fileKey: options.oldKey}); + oldKeyFileAdapter = new FileSystemAdapter({filesSubDirectory: this._filesDir, encryptionKey: options.oldKey}); }else{ oldKeyFileAdapter = new FileSystemAdapter({filesSubDirectory: this._filesDir}); } diff --git a/spec/secureFiles.spec.js b/spec/secureFiles.spec.js index acf68e4..4123401 100644 --- a/spec/secureFiles.spec.js +++ b/spec/secureFiles.spec.js @@ -19,10 +19,49 @@ describe('File encryption tests', () => { }) }); - it("should save encrypted file", async function(done) { + it('should create file location based on config', async function(done) { + var fsAdapter = new FileSystemAdapter(); + var config = {mount: '/parse', applicationId: 'yolo'} + let location = fsAdapter.getFileLocation(config, 'hello.txt') + expect(location).toBe('/parse/files/yolo/hello.txt'); + done() + }, 5000) + + it("should save encrypted file in default directory", async function(done) { + var adapter = new FileSystemAdapter({ + encryptionKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD' + }); + var filename = 'file2.txt'; + const filePath = 'files/'+filename; + await adapter.createFile(filename, "hello world", 'text/utf8'); + const result = await adapter.getFileData(filename); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual("hello world"); + const data = fs.readFileSync(filePath); + expect(data.toString('utf-8')).not.toEqual("hello world"); + done() + }, 5000); + + it("should save encrypted file in specified directory", async function(done) { var adapter = new FileSystemAdapter({ filesSubDirectory: directory, - fileKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD' + encryptionKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD' + }); + var filename = 'file2.txt'; + const filePath = 'files/'+directory+'/'+filename; + await adapter.createFile(filename, "hello world", 'text/utf8'); + const result = await adapter.getFileData(filename); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual("hello world"); + const data = fs.readFileSync(filePath); + expect(data.toString('utf-8')).not.toEqual("hello world"); + done() + }, 5000); + + it("should save encrypted file in specified directory when directory starts with /", async function(done) { + var adapter = new FileSystemAdapter({ + filesSubDirectory: '/sub1/sub2', + encryptionKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD' }); var filename = 'file2.txt'; const filePath = 'files/'+directory+'/'+filename; @@ -41,7 +80,7 @@ describe('File encryption tests', () => { }); const encryptedAdapter = new FileSystemAdapter({ filesSubDirectory: directory, - fileKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD' + encryptionKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD' }); const fileName1 = 'file1.txt'; const data1 = "hello world"; @@ -61,7 +100,7 @@ describe('File encryption tests', () => { expect(result.toString('utf-8')).toEqual(data2); const unEncryptedData2 = fs.readFileSync(filePath2); //Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter - const {rotated, notRotated} = await encryptedAdapter.rotateFileKey(); + const {rotated, notRotated} = await encryptedAdapter.rotateEncryptionKey(); expect(rotated.length).toEqual(2); expect(rotated.filter(function(value){ return value === fileName1;}).length).toEqual(1); expect(rotated.filter(function(value){ return value === fileName2;}).length).toEqual(1); @@ -80,14 +119,14 @@ describe('File encryption tests', () => { }, 5000); it("should rotate key of all old encrypted files to files encrypted with a new key", async function(done) { - const oldFileKey = 'oldKeyThatILoved'; + const oldEncryptionKey = 'oldKeyThatILoved'; const oldEncryptedAdapter = new FileSystemAdapter({ filesSubDirectory: directory, - fileKey: oldFileKey + encryptionKey: oldEncryptionKey }); const encryptedAdapter = new FileSystemAdapter({ filesSubDirectory: directory, - fileKey: 'newKeyThatILove' + encryptionKey: 'newKeyThatILove' }); const fileName1 = 'file1.txt'; const data1 = "hello world"; @@ -107,7 +146,7 @@ describe('File encryption tests', () => { expect(result.toString('utf-8')).toEqual(data2); const oldEncryptedData2 = fs.readFileSync(filePath2); //Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter - const {rotated, notRotated} = await encryptedAdapter.rotateFileKey({oldKey: oldFileKey}); + const {rotated, notRotated} = await encryptedAdapter.rotateEncryptionKey({oldKey: oldEncryptionKey}); expect(rotated.length).toEqual(2); expect(rotated.filter(function(value){ return value === fileName1;}).length).toEqual(1); expect(rotated.filter(function(value){ return value === fileName2;}).length).toEqual(1); @@ -126,10 +165,10 @@ describe('File encryption tests', () => { }, 5000); it("should rotate key of all old encrypted files to unencrypted files", async function(done) { - const oldFileKey = 'oldKeyThatILoved'; + const oldEncryptionKey = 'oldKeyThatILoved'; const oldEncryptedAdapter = new FileSystemAdapter({ filesSubDirectory: directory, - fileKey: oldFileKey + encryptionKey: oldEncryptionKey }); const unEncryptedAdapter = new FileSystemAdapter({ filesSubDirectory: directory @@ -152,7 +191,7 @@ describe('File encryption tests', () => { expect(result.toString('utf-8')).toEqual(data2); const oldEncryptedData2 = fs.readFileSync(filePath2); //Check if unEncrypted adapter can read data and make sure it's not the same as oldEncrypted adapter - const {rotated, notRotated} = await unEncryptedAdapter.rotateFileKey({oldKey: oldFileKey}); + const {rotated, notRotated} = await unEncryptedAdapter.rotateEncryptionKey({oldKey: oldEncryptionKey}); expect(rotated.length).toEqual(2); expect(rotated.filter(function(value){ return value === fileName1;}).length).toEqual(1); expect(rotated.filter(function(value){ return value === fileName2;}).length).toEqual(1); @@ -171,14 +210,14 @@ describe('File encryption tests', () => { }, 5000); it("should only encrypt specified fileNames with the new key", async function(done) { - const oldFileKey = 'oldKeyThatILoved'; + const oldEncryptionKey = 'oldKeyThatILoved'; const oldEncryptedAdapter = new FileSystemAdapter({ filesSubDirectory: directory, - fileKey: oldFileKey + encryptionKey: oldEncryptionKey }); const encryptedAdapter = new FileSystemAdapter({ filesSubDirectory: directory, - fileKey: 'newKeyThatILove' + encryptionKey: 'newKeyThatILove' }); const unEncryptedAdapter = new FileSystemAdapter({ filesSubDirectory: directory @@ -205,7 +244,7 @@ describe('File encryption tests', () => { const data3 = "hello past world"; await unEncryptedAdapter.createFile(fileName3, data3, 'text/utf8'); //Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter - const {rotated, notRotated} = await encryptedAdapter.rotateFileKey({oldKey: oldFileKey, fileNames: [fileName1,fileName2]}); + const {rotated, notRotated} = await encryptedAdapter.rotateEncryptionKey({oldKey: oldEncryptionKey, fileNames: [fileName1,fileName2]}); expect(rotated.length).toEqual(2); expect(rotated.filter(function(value){ return value === fileName1;}).length).toEqual(1); expect(rotated.filter(function(value){ return value === fileName2;}).length).toEqual(1); @@ -225,14 +264,14 @@ describe('File encryption tests', () => { }, 5000); it("should return fileNames of those it can't encrypt with the new key", async function(done) { - const oldFileKey = 'oldKeyThatILoved'; + const oldEncryptionKey = 'oldKeyThatILoved'; const oldEncryptedAdapter = new FileSystemAdapter({ filesSubDirectory: directory, - fileKey: oldFileKey + encryptionKey: oldEncryptionKey }); const encryptedAdapter = new FileSystemAdapter({ filesSubDirectory: directory, - fileKey: 'newKeyThatILove' + encryptionKey: 'newKeyThatILove' }); const unEncryptedAdapter = new FileSystemAdapter({ filesSubDirectory: directory @@ -263,7 +302,7 @@ describe('File encryption tests', () => { expect(result instanceof Buffer).toBe(true); expect(result.toString('utf-8')).toEqual(data3); //Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter - const {rotated, notRotated} = await encryptedAdapter.rotateFileKey({oldKey: oldFileKey}); + const {rotated, notRotated} = await encryptedAdapter.rotateEncryptionKey({oldKey: oldEncryptionKey}); expect(rotated.length).toEqual(2); expect(rotated.filter(function(value){ return value === fileName1;}).length).toEqual(1); expect(rotated.filter(function(value){ return value === fileName2;}).length).toEqual(1); diff --git a/spec/test.spec.js b/spec/test.spec.js index a7112cd..62d1bd7 100644 --- a/spec/test.spec.js +++ b/spec/test.spec.js @@ -10,3 +10,9 @@ describe('FileSystemAdapter tests', () => { filesAdapterTests.testAdapter("FileSystemAdapter", fsAdapter); }) + +describe('FileSystemAdapter tests - no options', () => { + var fsAdapter = new FileSystemAdapter(); + + filesAdapterTests.testAdapter("FileSystemAdapter", fsAdapter); +})