Search code examples
javascriptnode.jspromisebluebirdcryptojs

Promise.method() function not working the way I expect it to work


I am writing some code that encrypts a communication channel between two users. The steps are as follows

  • Check if a cipher for the channel exists in mongoDb
  • If yes, fetch the cipher and encrypt the incoming data
  • If no, create a new cipher, save it to mongoDb and encrypt the incoming data

The code uses crypto module which is one of the few synchronous libs in core nodejs

Create a cipher and return it as a promise

cipher.createCipher = Promise.method((pw) => {
  if (!pw) {
    throw new Error('Passphrase must be provided');
  }
  return crypto.createCipher('aes192', pw);
});

Encrypt data using Promise.method()

cipher.encryptTextAsPromise = Promise.method((cipher, plainText) => {
  if (!( typeof plainText === 'string')) {
    throw new Error("2nd param must be plain text");
    let cipherText = '';
    cipher.on('readable', () => {
      var data = cipher.read();
      if (data)
        cipherText += data.toString('hex');
    });
    cipher.on('end', () => {
      return cipherText;
    });
    cipher.write(plainText);
    cipher.end();
  }
});

Encrypt data via callback functions.

cipher.encryptText = (cipher, plainText, callback) => {
  if (!( typeof plainText === 'string')) {
    throw new Error("2nd param must be plain text");
  }
  try {
    let cipherText = '';
    cipher.on('readable', () => {
      var data = cipher.read();
      if (data)
        cipherText += data.toString('hex');
    });
    cipher.on('end', () => {
      callback(null, cipherText);
    });
    cipher.write(plainText);
    cipher.end();
  } catch (e) {
    callback(e, null);
  }
}

I am unable to chain these two together. What I have is a horrible anti-pattern that is worse than getting stuck in callback hell

cipher.createCipher('secretWord')
  .then((data) => {
    cipher.encryptTextasPromise(data, 'hello world')
    .then((data) => {
      console.log(data);
    })
    .catch((err) => {
      console.log(err);
    })
  })
  .catch((err) => {
    console.log(err);
  })

mongoDbSearch(username)
  .then((data) => {
    if (data) {
      // Cipher exists, retrieve and encrypt
    }else {
      // Create new cipher and save
      someMongoSave()
        .then((data) => {
          // Cipher exists now, retrieve and encrypt
        })
    }
  })
  .catch((mongoErr) => {
    console.log(mongoErr);
  })

The code is a bit incomplete as I am still trying to grasp this concept. Furthermore the my attempt to chain createCipher and encryptTextAsPromise is returning undefined in the data. I have tried writing these as normal callback and then using Promise.promisfyAll() as well, which just feels like another anti-pattern.


Solution

  • Promise.method would appear to make sense for createCipher, but probably not for encryptTextAsPromise.

    Here's a version that should point you the right direction, distilling various of T.J. Crowder's, Jaromanda X's, and undefined's comments on the question; see code comments for more:

    // For this one, `Promise.method` still makes sense (although doing it with
    // your own promise is also perfectly reasonable)
    cipher.createCipher = Promise.method(pw => {
      if (!pw) {
        throw new Error('Passphrase must be provided');
      }
      return crypto.createCipher('aes192', pw);
    });
    
    // For this one, your own promise makes sense
    cipher.encryptTextAsPromise = (cipher, plainText) => {
      return new Promise(resolve => {
        if (!( typeof plainText === 'string')) {
          // It's fine to throw here (it will get converted into a rejection),
          // or add `reject` to the arguments list above and call
          // that and return instead:
          // reject(new Error("2nd param must be plain text"));
          // return;
          throw new Error("2nd param must be plain text");
        }
        let cipherText = '';
        cipher.on('readable', () => {
          var data = cipher.read();
          if (data)
            cipherText += data.toString('hex');
        });
        cipher.on('end', () => {
          resolve(cipherText);
        });
        cipher.write(plainText);
        cipher.end();
      });
    };
    
    // Usage:
    cipher.createCipher('secretWord')
    .then(data => cipher.encryptTextAsPromise(data, 'hello world'))
    .then(data => console.log(data)) // See 1 below
    .catch(err => console.log(err));
    
    mongoDbSearch(username)
    .then(data => data || someMongoSave(data)) // You pass `data` to `someMongoSave`, presumably?
    .then(data => {
        // Cipher exists, retrieve and encrypt
    })
    .catch(mongoErr => {
        console.log(mongoErr);
    });
    

    Separately, re

    if (!( typeof plainText === 'string')) {
    

    there's a !== operator in JavaScript. Jus' sayin'. ;-D