diff --git a/.nycrc b/.nycrc new file mode 100644 index 0000000..ab1a82c --- /dev/null +++ b/.nycrc @@ -0,0 +1,9 @@ +{ + "reporter": [ + "lcov", + "text-summary" + ], + "exclude": [ + "**/spec/**" + ] +} diff --git a/.travis.yml b/.travis.yml index 6b7a345..69746e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ branches: only: - master language: node_js -node_js: - - "4.3" -after_success: ./node_modules/.bin/codecov +node_js: '10' +env: + global: + - COVERAGE_OPTION='./node_modules/.bin/nyc' +script: npm run coverage +after_success: bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index 900c568..717ee7d 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,21 @@ [![Build Status](https://travis-ci.org/parse-community/parse-server-fs-adapter.svg?branch=master)](https://travis-ci.org/parse-community/parse-server-fs-adapter) [![codecov.io](https://codecov.io/github/parse-community/parse-server-fs-adapter/coverage.svg?branch=master)](https://codecov.io/github/parse-community/parse-server-fs-adapter?branch=master) -parse-server file system storage adapter +parse-server file system storage adapter. -# installation +# Multiple instances of parse-server +When using parse-server-fs-adapter across multiple parse-server instances it's important to establish "centralization" of your file storage (this is the same premise as the other file adapters, you are sending/recieving files through a dedicated link). You can accomplish this at the file storage level by Samba mounting (or any other type of mounting) your storage to each of your parse-server instances, e.g if you are using parse-server via docker (volume mount your SMB drive to `- /Volumes/SMB-Drive/MyParseApp1/files:/parse-server/files`). All parse-server instances need to be able to read and write to the same storage in order for parse-server-fs-adapter to work properly with parse-server. If the file storage isn't centralized, parse-server will have trouble locating files and you will get random behavior on client-side. + +# Installation `npm install --save @parse/fs-files-adapter` -# usage with parse-server +# Usage with parse-server -### using a config file +### Using a config file -``` +```javascript { "appId": 'my_app_id', "masterKey": 'my_master_key', @@ -21,25 +24,73 @@ parse-server file system storage adapter "filesAdapter": { "module": "@parse/fs-files-adapter", "options": { - "filesSubDirectory": "my/files/folder" // optional + "filesSubDirectory": "my/files/folder", // optional + "fileKey": "someKey" //optional, but mandatory if you want to encrypt files } } } ``` -### passing as an instance +### 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 +}); +var api = new ParseServer({ + appId: 'my_app', + masterKey: 'master_key', + filesAdapter: fsAdapter +}) ``` + +### 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`: + +#### 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 - }); + "filesSubDirectory": "my/files/folder", // optional + "fileKey": "newKey" //Use the newKey +}); var api = new ParseServer({ appId: 'my_app', masterKey: 'master_key', filesAdapter: fsAdapter }) + +//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(); +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. + + +#### 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()`. +```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}); +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()`. +```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"]}); +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 5563804..f17727f 100644 --- a/index.js +++ b/index.js @@ -7,9 +7,16 @@ var fs = require('fs'); var path = require('path'); var pathSep = require('path').sep; +const crypto = require("crypto"); +const algorithm = 'aes-256-gcm'; function FileSystemAdapter(options) { options = options || {}; + this._fileKey = null; + + if (options.fileKey !== undefined){ + this._fileKey = crypto.createHash('sha256').update(String(options.fileKey)).digest('base64').substr(0, 32); + } let filesSubDirectory = options.filesSubDirectory || ''; this._filesDir = filesSubDirectory; this._mkdir(this._getApplicationDir()); @@ -19,44 +26,147 @@ function FileSystemAdapter(options) { } FileSystemAdapter.prototype.createFile = function(filename, data) { + let filepath = this._getLocalFilePath(filename); + const stream = fs.createWriteStream(filepath); return new Promise((resolve, reject) => { - let filepath = this._getLocalFilePath(filename); - fs.writeFile(filepath, data, (err) => { - if(err !== null) { - return reject(err); - } - resolve(data); - }); + try{ + if(this._fileKey !== null){ + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv( + algorithm, + this._fileKey, + iv + ); + const encryptedResult = Buffer.concat([ + cipher.update(data), + cipher.final(), + iv, + cipher.getAuthTag(), + ]); + stream.write(encryptedResult); + stream.end(); + stream.on('finish', function() { + resolve(data); + }); + }else{ + stream.write(data); + stream.end(); + stream.on('finish', function() { + resolve(data); + }); + } + }catch(err){ + return reject(err); + } }); } FileSystemAdapter.prototype.deleteFile = function(filename) { + let filepath = this._getLocalFilePath(filename); + const chunks = []; + const stream = fs.createReadStream(filepath); return new Promise((resolve, reject) => { - let filepath = this._getLocalFilePath(filename); - fs.readFile( filepath , function (err, data) { - if(err !== null) { - return reject(err); - } - fs.unlink(filepath, (unlinkErr) => { - if(err !== null) { - return reject(unlinkErr); + stream.read(); + stream.on('data', (data) => { + chunks.push(data); + }); + stream.on('end', () => { + const data = Buffer.concat(chunks); + fs.unlink(filepath, (err) => { + if(err !== null) { + return reject(err); } resolve(data); }); }); - + stream.on('error', (err) => { + reject(err); + }); }); } FileSystemAdapter.prototype.getFileData = function(filename) { + let filepath = this._getLocalFilePath(filename); + const stream = fs.createReadStream(filepath); + stream.read(); return new Promise((resolve, reject) => { - let filepath = this._getLocalFilePath(filename); - fs.readFile( filepath , function (err, data) { - if(err !== null) { - return reject(err); + const chunks = []; + stream.on('data', (data) => { + chunks.push(data); + }); + stream.on('end', () => { + 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); + try{ + const decipher = crypto.createDecipheriv(algorithm, this._fileKey, iv); + decipher.setAuthTag(authTag); + const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); + return resolve(decrypted); + }catch(err){ + return reject(err); + } } resolve(data); }); + stream.on('error', (err) => { + reject(err); + }); + }); +} + +FileSystemAdapter.prototype.rotateFileKey = function(options = {}) { + const applicationDir = this._getApplicationDir(); + var fileNames = []; + var oldKeyFileAdapter = {}; + if (options.oldKey !== undefined) { + oldKeyFileAdapter = new FileSystemAdapter({filesSubDirectory: this._filesDir, fileKey: options.oldKey}); + }else{ + oldKeyFileAdapter = new FileSystemAdapter({filesSubDirectory: this._filesDir}); + } + if (options.fileNames !== undefined){ + fileNames = options.fileNames; + }else{ + fileNames = fs.readdirSync(applicationDir); + fileNames = fileNames.filter(fileName => fileName.indexOf('.') !== 0); + } + return new Promise((resolve, _reject) => { + var fileNamesNotRotated = fileNames; + var fileNamesRotated = []; + var fileNameTotal = fileNames.length; + var fileNameIndex = 0; + fileNames.forEach(fileName => { + oldKeyFileAdapter + .getFileData(fileName) + .then(plainTextData => { + //Overwrite file with data encrypted with new key + this.createFile(fileName, plainTextData) + .then(() => { + fileNamesRotated.push(fileName); + fileNamesNotRotated = fileNamesNotRotated.filter(function(value){ return value !== fileName;}) + fileNameIndex += 1; + if (fileNameIndex == fileNameTotal){ + resolve({rotated: fileNamesRotated, notRotated: fileNamesNotRotated}); + } + }) + .catch(() => { + fileNameIndex += 1; + if (fileNameIndex == fileNameTotal){ + resolve({rotated: fileNamesRotated, notRotated: fileNamesNotRotated}); + } + }) + }) + .catch(() => { + fileNameIndex += 1; + if (fileNameIndex == fileNameTotal){ + resolve({rotated: fileNamesRotated, notRotated: fileNamesNotRotated}); + } + }); + }); }); } diff --git a/package.json b/package.json index 7fd41cd..0e76684 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,15 @@ { "name": "@parse/fs-files-adapter", - "version": "1.0.1", + "version": "1.0.2", "description": "File system adapter for parse-server", "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/parse-community/parse-server-fs-adapter" + }, "scripts": { - "test": "istanbul cover -x **/spec/** jasmine --captureExceptions" + "test": "jasmine", + "coverage": "nyc jasmine" }, "keywords": [ "parse-server", @@ -15,9 +20,8 @@ "author": "Parse", "license": "MIT", "devDependencies": { - "codecov": "^1.0.1", - "istanbul": "^0.4.2", - "jasmine": "^2.4.1", + "nyc": "^15.1.0", + "jasmine": "^3.5.0", "parse-server-conformance-tests": "^1.0.0" } } diff --git a/spec/secureFiles.spec.js b/spec/secureFiles.spec.js new file mode 100644 index 0000000..acf68e4 --- /dev/null +++ b/spec/secureFiles.spec.js @@ -0,0 +1,284 @@ +'use strict'; +let FileSystemAdapter = require('../index.js'); +var fs = require('fs'); +var path = require('path'); + +describe('File encryption tests', () => { + const directory = 'sub1/sub2'; + + afterEach(function() { + //Use adapter to make directory if needed + var adapter = new FileSystemAdapter({ + filesSubDirectory: directory + }); + const filePath = path.join('files', directory); + var fileNames = fs.readdirSync(filePath); + fileNames.filter(fileName => fileName.indexOf('.') === 0); + fileNames.forEach(fileName => { + fs.unlinkSync(path.join(filePath, fileName)); + }) + }); + + it("should save encrypted file", async function(done) { + var adapter = new FileSystemAdapter({ + filesSubDirectory: directory, + fileKey: '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 rotate key of all unencrypted files to encrypted files", async function(done) { + const unEncryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory + }); + const encryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory, + fileKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD' + }); + const fileName1 = 'file1.txt'; + const data1 = "hello world"; + const fileName2 = 'file2.txt'; + const data2 = "hello new world"; + const filePath1 = 'files/'+directory+'/'+fileName1; + const filePath2 = 'files/'+directory+'/'+fileName2; + //Store unecrypted files + await unEncryptedAdapter.createFile(fileName1, data1, 'text/utf8'); + var result = await unEncryptedAdapter.getFileData(fileName1); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data1); + const unEncryptedData1 = fs.readFileSync(filePath1); + await unEncryptedAdapter.createFile(fileName2, data2, 'text/utf8'); + result = await unEncryptedAdapter.getFileData(fileName2); + expect(result instanceof Buffer).toBe(true); + 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(); + 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); + expect(notRotated.length).toEqual(0); + result = await encryptedAdapter.getFileData(fileName1); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data1); + const encryptedData1 = fs.readFileSync(filePath1); + expect(encryptedData1.toString('utf-8')).not.toEqual(unEncryptedData1); + result = await encryptedAdapter.getFileData(fileName2); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data2); + const encryptedData2 = fs.readFileSync(filePath2); + expect(encryptedData2.toString('utf-8')).not.toEqual(unEncryptedData2); + done() + }, 5000); + + it("should rotate key of all old encrypted files to files encrypted with a new key", async function(done) { + const oldFileKey = 'oldKeyThatILoved'; + const oldEncryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory, + fileKey: oldFileKey + }); + const encryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory, + fileKey: 'newKeyThatILove' + }); + const fileName1 = 'file1.txt'; + const data1 = "hello world"; + const fileName2 = 'file2.txt'; + const data2 = "hello new world"; + const filePath1 = 'files/'+directory+'/'+fileName1; + const filePath2 = 'files/'+directory+'/'+fileName2; + //Store original encrypted files + await oldEncryptedAdapter.createFile(fileName1, data1, 'text/utf8'); + var result = await oldEncryptedAdapter.getFileData(fileName1); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data1); + const oldEncryptedData1 = fs.readFileSync(filePath1); + await oldEncryptedAdapter.createFile(fileName2, data2, 'text/utf8'); + result = await oldEncryptedAdapter.getFileData(fileName2); + expect(result instanceof Buffer).toBe(true); + 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}); + 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); + expect(notRotated.length).toEqual(0); + var result2 = await encryptedAdapter.getFileData(fileName1); + expect(result2 instanceof Buffer).toBe(true); + expect(result2.toString('utf-8')).toEqual(data1); + const encryptedData1 = fs.readFileSync(filePath1); + expect(encryptedData1.toString('utf-8')).not.toEqual(oldEncryptedData1); + result = await encryptedAdapter.getFileData(fileName2); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data2); + const encryptedData2 = fs.readFileSync(filePath2); + expect(encryptedData2.toString('utf-8')).not.toEqual(oldEncryptedData2); + done() + }, 5000); + + it("should rotate key of all old encrypted files to unencrypted files", async function(done) { + const oldFileKey = 'oldKeyThatILoved'; + const oldEncryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory, + fileKey: oldFileKey + }); + const unEncryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory + }); + const fileName1 = 'file1.txt'; + const data1 = "hello world"; + const fileName2 = 'file2.txt'; + const data2 = "hello new world"; + const filePath1 = 'files/'+directory+'/'+fileName1; + const filePath2 = 'files/'+directory+'/'+fileName2; + //Store original encrypted files + await oldEncryptedAdapter.createFile(fileName1, data1, 'text/utf8'); + var result = await oldEncryptedAdapter.getFileData(fileName1); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data1); + const oldEncryptedData1 = fs.readFileSync(filePath1); + await oldEncryptedAdapter.createFile(fileName2, data2, 'text/utf8'); + result = await oldEncryptedAdapter.getFileData(fileName2); + expect(result instanceof Buffer).toBe(true); + 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}); + 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); + expect(notRotated.length).toEqual(0); + var result2 = await unEncryptedAdapter.getFileData(fileName1); + expect(result2 instanceof Buffer).toBe(true); + expect(result2.toString('utf-8')).toEqual(data1); + const encryptedData1 = fs.readFileSync(filePath1); + expect(encryptedData1.toString('utf-8')).not.toEqual(oldEncryptedData1); + result = await unEncryptedAdapter.getFileData(fileName2); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data2); + const encryptedData2 = fs.readFileSync(filePath2); + expect(encryptedData2.toString('utf-8')).not.toEqual(oldEncryptedData2); + done() + }, 5000); + + it("should only encrypt specified fileNames with the new key", async function(done) { + const oldFileKey = 'oldKeyThatILoved'; + const oldEncryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory, + fileKey: oldFileKey + }); + const encryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory, + fileKey: 'newKeyThatILove' + }); + const unEncryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory + }); + const fileName1 = 'file1.txt'; + const data1 = "hello world"; + const fileName2 = 'file2.txt'; + const data2 = "hello new world"; + const filePath1 = 'files/'+directory+'/'+fileName1; + const filePath2 = 'files/'+directory+'/'+fileName2; + //Store original encrypted files + await oldEncryptedAdapter.createFile(fileName1, data1, 'text/utf8'); + var result = await oldEncryptedAdapter.getFileData(fileName1); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data1); + const oldEncryptedData1 = fs.readFileSync(filePath1); + await oldEncryptedAdapter.createFile(fileName2, data2, 'text/utf8'); + result = await oldEncryptedAdapter.getFileData(fileName2); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data2); + const oldEncryptedData2 = fs.readFileSync(filePath2); + //Inject unecrypted file to see if causes an issue + const fileName3 = 'file3.txt'; + 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]}); + 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); + expect(notRotated.length).toEqual(0); + expect(rotated.filter(function(value){ return value === fileName3;}).length).toEqual(0); + var result2 = await encryptedAdapter.getFileData(fileName1); + expect(result2 instanceof Buffer).toBe(true); + expect(result2.toString('utf-8')).toEqual(data1); + const encryptedData1 = fs.readFileSync(filePath1); + expect(encryptedData1.toString('utf-8')).not.toEqual(oldEncryptedData1); + result = await encryptedAdapter.getFileData(fileName2); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data2); + const encryptedData2 = fs.readFileSync(filePath2); + expect(encryptedData2.toString('utf-8')).not.toEqual(oldEncryptedData2); + done() + }, 5000); + + it("should return fileNames of those it can't encrypt with the new key", async function(done) { + const oldFileKey = 'oldKeyThatILoved'; + const oldEncryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory, + fileKey: oldFileKey + }); + const encryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory, + fileKey: 'newKeyThatILove' + }); + const unEncryptedAdapter = new FileSystemAdapter({ + filesSubDirectory: directory + }); + const fileName1 = 'file1.txt'; + const data1 = "hello world"; + const fileName2 = 'file2.txt'; + const data2 = "hello new world"; + const filePath1 = 'files/'+directory+'/'+fileName1; + const filePath2 = 'files/'+directory+'/'+fileName2; + //Store original encrypted files + await oldEncryptedAdapter.createFile(fileName1, data1, 'text/utf8'); + var result = await oldEncryptedAdapter.getFileData(fileName1); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data1); + const oldEncryptedData1 = fs.readFileSync(filePath1); + await oldEncryptedAdapter.createFile(fileName2, data2, 'text/utf8'); + result = await oldEncryptedAdapter.getFileData(fileName2); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data2); + const oldEncryptedData2 = fs.readFileSync(filePath2); + //Inject unecrypted file to cause an issue + const fileName3 = 'file3.txt'; + const data3 = "hello past world"; + const filePath3 = 'files/'+directory+'/'+fileName3; + await unEncryptedAdapter.createFile(fileName3, data3, 'text/utf8'); + var result = await unEncryptedAdapter.getFileData(fileName3); + 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}); + 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); + expect(notRotated.length).toEqual(1); + expect(notRotated.filter(function(value){ return value === fileName3;}).length).toEqual(1); + var result2 = await encryptedAdapter.getFileData(fileName1); + expect(result2 instanceof Buffer).toBe(true); + expect(result2.toString('utf-8')).toEqual(data1); + const encryptedData1 = fs.readFileSync(filePath1); + expect(encryptedData1.toString('utf-8')).not.toEqual(oldEncryptedData1); + result = await encryptedAdapter.getFileData(fileName2); + expect(result instanceof Buffer).toBe(true); + expect(result.toString('utf-8')).toEqual(data2); + const encryptedData2 = fs.readFileSync(filePath2); + expect(encryptedData2.toString('utf-8')).not.toEqual(oldEncryptedData2); + done() + }, 5000); +}) diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json index e8f0320..cf314b2 100644 --- a/spec/support/jasmine.json +++ b/spec/support/jasmine.json @@ -1,6 +1,7 @@ { "spec_dir": "spec", "spec_files": [ - "test.spec.js" + "test.spec.js", + "secureFiles.spec.js" ] } diff --git a/spec/test.spec.js b/spec/test.spec.js index 773a8e3..a7112cd 100644 --- a/spec/test.spec.js +++ b/spec/test.spec.js @@ -8,5 +8,5 @@ describe('FileSystemAdapter tests', () => { filesSubDirectory: 'sub1/sub2' }); - filesAdapterTests.testAdapter("FileSystemAdapter", fsAdapter); + filesAdapterTests.testAdapter("FileSystemAdapter", fsAdapter); })