api-safe-storage-spec.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import * as cp from 'node:child_process';
  2. import * as path from 'node:path';
  3. import { safeStorage } from 'electron/main';
  4. import { expect } from 'chai';
  5. import { ifdescribe } from './lib/spec-helpers';
  6. import * as fs from 'fs-extra';
  7. import { once } from 'node:events';
  8. describe('safeStorage module', () => {
  9. it('safeStorage before and after app is ready', async () => {
  10. const appPath = path.join(__dirname, 'fixtures', 'crash-cases', 'safe-storage');
  11. const appProcess = cp.spawn(process.execPath, [appPath]);
  12. let output = '';
  13. appProcess.stdout.on('data', data => { output += data; });
  14. appProcess.stderr.on('data', data => { output += data; });
  15. const code = (await once(appProcess, 'exit'))[0] ?? 1;
  16. if (code !== 0 && output) {
  17. console.log(output);
  18. }
  19. expect(code).to.equal(0);
  20. });
  21. });
  22. describe('safeStorage module', () => {
  23. before(() => {
  24. if (process.platform === 'linux') {
  25. safeStorage.setUsePlainTextEncryption(true);
  26. }
  27. });
  28. after(async () => {
  29. const pathToEncryptedString = path.resolve(__dirname, 'fixtures', 'api', 'safe-storage', 'encrypted.txt');
  30. if (await fs.pathExists(pathToEncryptedString)) {
  31. await fs.remove(pathToEncryptedString);
  32. }
  33. });
  34. describe('SafeStorage.isEncryptionAvailable()', () => {
  35. it('should return true when encryption key is available (macOS, Windows)', () => {
  36. expect(safeStorage.isEncryptionAvailable()).to.equal(true);
  37. });
  38. });
  39. ifdescribe(process.platform === 'linux')('SafeStorage.getSelectedStorageBackend()', () => {
  40. it('should return a valid backend', () => {
  41. expect(safeStorage.getSelectedStorageBackend()).to.equal('basic_text');
  42. });
  43. });
  44. describe('SafeStorage.encryptString()', () => {
  45. it('valid input should correctly encrypt string', () => {
  46. const plaintext = 'plaintext';
  47. const encrypted = safeStorage.encryptString(plaintext);
  48. expect(Buffer.isBuffer(encrypted)).to.equal(true);
  49. });
  50. it('UTF-16 characters can be encrypted', () => {
  51. const plaintext = '€ - utf symbol';
  52. const encrypted = safeStorage.encryptString(plaintext);
  53. expect(Buffer.isBuffer(encrypted)).to.equal(true);
  54. });
  55. });
  56. describe('SafeStorage.decryptString()', () => {
  57. it('valid input should correctly decrypt string', () => {
  58. const encrypted = safeStorage.encryptString('plaintext');
  59. expect(safeStorage.decryptString(encrypted)).to.equal('plaintext');
  60. });
  61. it('UTF-16 characters can be decrypted', () => {
  62. const plaintext = '€ - utf symbol';
  63. const encrypted = safeStorage.encryptString(plaintext);
  64. expect(safeStorage.decryptString(encrypted)).to.equal(plaintext);
  65. });
  66. it('unencrypted input should throw', () => {
  67. const plaintextBuffer = Buffer.from('I am unencoded!', 'utf-8');
  68. expect(() => {
  69. safeStorage.decryptString(plaintextBuffer);
  70. }).to.throw(Error);
  71. });
  72. it('non-buffer input should throw', () => {
  73. const notABuffer = {} as any;
  74. expect(() => {
  75. safeStorage.decryptString(notABuffer);
  76. }).to.throw(Error);
  77. });
  78. });
  79. describe('safeStorage persists encryption key across app relaunch', () => {
  80. it('can decrypt after closing and reopening app', async () => {
  81. const fixturesPath = path.resolve(__dirname, 'fixtures');
  82. const encryptAppPath = path.join(fixturesPath, 'api', 'safe-storage', 'encrypt-app');
  83. const encryptAppProcess = cp.spawn(process.execPath, [encryptAppPath]);
  84. let stdout: string = '';
  85. encryptAppProcess.stderr.on('data', data => { stdout += data; });
  86. encryptAppProcess.stderr.on('data', data => { stdout += data; });
  87. try {
  88. await once(encryptAppProcess, 'exit');
  89. const appPath = path.join(fixturesPath, 'api', 'safe-storage', 'decrypt-app');
  90. const relaunchedAppProcess = cp.spawn(process.execPath, [appPath]);
  91. let output = '';
  92. relaunchedAppProcess.stdout.on('data', data => { output += data; });
  93. relaunchedAppProcess.stderr.on('data', data => { output += data; });
  94. const [code] = await once(relaunchedAppProcess, 'exit');
  95. if (!output.includes('plaintext')) {
  96. console.log(code, output);
  97. }
  98. expect(output).to.include('plaintext');
  99. } catch (e) {
  100. console.log(stdout);
  101. throw e;
  102. }
  103. });
  104. });
  105. });