index.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. 'use strict';
  2. // FileSystemAdapter
  3. //
  4. // Stores files in local file system
  5. // Requires write access to the server's file system.
  6. const fs = require('fs');
  7. const path = require('path');
  8. const pathSep = require('path').sep;
  9. const crypto = require("crypto");
  10. const algorithm = 'aes-256-gcm';
  11. function FileSystemAdapter(options) {
  12. options = options || {};
  13. this._encryptionKey = null;
  14. if (options.encryptionKey !== undefined) {
  15. this._encryptionKey = crypto.createHash('sha256').update(String(options.encryptionKey)).digest('base64').substring(0, 32);
  16. }
  17. const filesSubDirectory = options.filesSubDirectory || '';
  18. this._filesDir = filesSubDirectory;
  19. this._mkdir(this._getApplicationDir());
  20. if (!this._applicationDirExist()) {
  21. throw "Files directory doesn't exist.";
  22. }
  23. }
  24. FileSystemAdapter.prototype.createFile = function(filename, data) {
  25. const filepath = this._getLocalFilePath(filename);
  26. const stream = fs.createWriteStream(filepath);
  27. return new Promise((resolve, reject) => {
  28. try {
  29. if (this._encryptionKey !== null) {
  30. const iv = crypto.randomBytes(16);
  31. const cipher = crypto.createCipheriv(
  32. algorithm,
  33. this._encryptionKey,
  34. iv
  35. );
  36. const encryptedResult = Buffer.concat([
  37. cipher.update(data),
  38. cipher.final(),
  39. iv,
  40. cipher.getAuthTag(),
  41. ]);
  42. stream.write(encryptedResult);
  43. stream.end();
  44. stream.on('finish', function() {
  45. resolve(data);
  46. });
  47. } else {
  48. stream.write(data);
  49. stream.end();
  50. stream.on('finish', function() {
  51. resolve(data);
  52. });
  53. }
  54. } catch(err) {
  55. return reject(err);
  56. }
  57. });
  58. }
  59. FileSystemAdapter.prototype.deleteFile = function(filename) {
  60. const filepath = this._getLocalFilePath(filename);
  61. const chunks = [];
  62. const stream = fs.createReadStream(filepath);
  63. return new Promise((resolve, reject) => {
  64. stream.read();
  65. stream.on('data', (data) => {
  66. chunks.push(data);
  67. });
  68. stream.on('end', () => {
  69. const data = Buffer.concat(chunks);
  70. fs.unlink(filepath, (err) => {
  71. if(err !== null) {
  72. return reject(err);
  73. }
  74. resolve(data);
  75. });
  76. });
  77. stream.on('error', (err) => {
  78. reject(err);
  79. });
  80. });
  81. }
  82. FileSystemAdapter.prototype.getFileData = function(filename) {
  83. const filepath = this._getLocalFilePath(filename);
  84. const stream = fs.createReadStream(filepath);
  85. stream.read();
  86. return new Promise((resolve, reject) => {
  87. const chunks = [];
  88. stream.on('data', (data) => {
  89. chunks.push(data);
  90. });
  91. stream.on('end', () => {
  92. const data = Buffer.concat(chunks);
  93. if (this._encryptionKey !== null) {
  94. const authTagLocation = data.length - 16;
  95. const ivLocation = data.length - 32;
  96. const authTag = data.slice(authTagLocation);
  97. const iv = data.slice(ivLocation,authTagLocation);
  98. const encrypted = data.slice(0,ivLocation);
  99. try {
  100. const decipher = crypto.createDecipheriv(algorithm, this._encryptionKey, iv);
  101. decipher.setAuthTag(authTag);
  102. const decrypted = Buffer.concat([ decipher.update(encrypted), decipher.final() ]);
  103. return resolve(decrypted);
  104. } catch(err) {
  105. return reject(err);
  106. }
  107. }
  108. resolve(data);
  109. });
  110. stream.on('error', (err) => {
  111. reject(err);
  112. });
  113. });
  114. }
  115. FileSystemAdapter.prototype.rotateEncryptionKey = async function(options = {}) {
  116. const applicationDir = this._getApplicationDir();
  117. let fileNames = [];
  118. let oldKeyFileAdapter = {};
  119. if (options.oldKey !== undefined) {
  120. oldKeyFileAdapter = new FileSystemAdapter({ filesSubDirectory: this._filesDir, encryptionKey: options.oldKey });
  121. } else {
  122. oldKeyFileAdapter = new FileSystemAdapter({ filesSubDirectory: this._filesDir });
  123. }
  124. if (options.fileNames !== undefined) {
  125. fileNames = options.fileNames;
  126. } else {
  127. fileNames = fs.readdirSync(applicationDir);
  128. fileNames = fileNames.filter(fileName => fileName.indexOf('.') !== 0);
  129. }
  130. let fileNamesNotRotated = fileNames;
  131. const fileNamesRotated = [];
  132. for (const fileName of fileNames) {
  133. try {
  134. const plainTextData = await oldKeyFileAdapter.getFileData(fileName)
  135. // Overwrite file with data encrypted with new key
  136. await this.createFile(fileName, plainTextData)
  137. fileNamesRotated.push(fileName);
  138. fileNamesNotRotated = fileNamesNotRotated.filter(function(value) { return value !== fileName; });
  139. } catch(err) {
  140. continue;
  141. }
  142. }
  143. return { rotated: fileNamesRotated, notRotated: fileNamesNotRotated };
  144. }
  145. FileSystemAdapter.prototype.getFileLocation = function(config, filename) {
  146. return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename);
  147. }
  148. /*
  149. Helpers
  150. --------------- */
  151. FileSystemAdapter.prototype._getApplicationDir = function() {
  152. if (this._filesDir) {
  153. return path.join('files', this._filesDir);
  154. } else {
  155. return 'files';
  156. }
  157. }
  158. FileSystemAdapter.prototype._applicationDirExist = function() {
  159. return fs.existsSync(this._getApplicationDir());
  160. }
  161. FileSystemAdapter.prototype._getLocalFilePath = function(filename) {
  162. const applicationDir = this._getApplicationDir();
  163. if (!fs.existsSync(applicationDir)) {
  164. this._mkdir(applicationDir);
  165. }
  166. return path.join(applicationDir, encodeURIComponent(filename));
  167. }
  168. FileSystemAdapter.prototype._mkdir = function(dirPath) {
  169. // snippet found on -> https://gist.github.com/danherbert-epam/3960169
  170. const dirs = dirPath.split(pathSep);
  171. let root = "";
  172. while (dirs.length > 0) {
  173. const dir = dirs.shift();
  174. if (dir === "") { // If directory starts with a /, the first path will be an empty string.
  175. root = pathSep;
  176. }
  177. if (!fs.existsSync(path.join(root, dir))) {
  178. try {
  179. fs.mkdirSync(path.join(root, dir));
  180. } catch (err) {
  181. if (err.code == 'EACCES') {
  182. throw new Error("PERMISSION ERROR: In order to use the FileSystemAdapter, write access to the server's file system is required.");
  183. }
  184. }
  185. }
  186. root = path.join(root, dir, pathSep);
  187. }
  188. }
  189. module.exports = FileSystemAdapter;
  190. module.exports.default = FileSystemAdapter;