<commit_before>crypto = require('crypto')

ALGORITHM = 'aes-256-ctr'

keyFn = (password, salt, callback)->
	return crypto.pbkdf2(password, salt, 10000, 64, 'sha1', callback)

keyFn32 = (password, salt, keyLength, callback)->
	return crypto.pbkdf2(password, salt, 10000, 32, 'sha1', callback)

class AccessTokenEncryptor

	constructor: (settings) ->

		@settings = settings
		@cipherLabel = @settings.cipherLabel
		throw Error("cipherLabel must not contain a colon (:)") if @cipherLabel?.match(/:/)

		@cipherPassword = @settings.cipherPasswords[@cipherLabel]
		throw Error("cipherPassword not set") if not @cipherPassword?
		throw Error("cipherPassword too short") if @cipherPassword.length < 16

	encryptJson: (json, callback) ->
		string = JSON.stringify(json)
		crypto.randomBytes 32, (err, bytes) =>
			return callback(err) if err
			salt = bytes.slice(0, 16)
			iv = bytes.slice(16, 32)

			keyFn32 @cipherPassword, salt, 32, (err, key) =>
				if err?
					logger.err err:err, "error getting Fn key"
					return callback(err)

				cipher = crypto.createCipheriv(ALGORITHM, key, iv)
				crypted = cipher.update(string, 'utf8', 'base64') + cipher.final('base64')

				callback(null, "#{@cipherLabel}:#{salt.toString('hex')}:#{crypted}:#{iv.toString('hex')}")

	decryptToJson: (encryptedJson, callback) ->
		[label, salt, cipherText, iv] = encryptedJson.split(':', 4)
		password = @settings.cipherPasswords[label]
		return callback(new Error("invalid password")) if not password? or password.length < 16

		if iv
			@decryptToJsonV2(password, salt, cipherText, iv, callback)
		else
			@decryptToJsonV1(password, salt, cipherText, callback)

	decryptToJsonV1: (password, salt, cipherText, callback) ->
		keyFn password, Buffer.from(salt, 'hex'), (err, key) =>
			if err?
				logger.err err:err, "error getting Fn key"
				return callback(err)
			decipher = crypto.createDecipher(ALGORITHM, key)
			dec = decipher.update(cipherText, 'base64', 'utf8') + decipher.final('utf8')
			try
				json = JSON.parse(dec)
			catch e
				return callback(new Error("error decrypting token"))
			callback(null, json, true)

	decryptToJsonV2: (password, salt, cipherText, iv, callback) ->
		keyFn32 password, Buffer.from(salt, 'hex'), 32, (err, key) =>
			if err?
				logger.err err:err, "error getting Fn key"
				return callback(err)

			decipher = crypto.createDecipheriv(ALGORITHM, key, Buffer.from(iv, 'hex'))
			dec = decipher.update(cipherText, 'base64', 'utf8') + decipher.final('utf8')
			try
				json = JSON.parse(dec)
			catch e
				return callback(new Error("error decrypting token"))
			callback(null, json)

module.exports = AccessTokenEncryptor<commit_msg>decaffeinate: Convert AccessTokenEncryptor.coffee to JS<commit_after>/*
 * decaffeinate suggestions:
 * DS101: Remove unnecessary use of Array.from
 * DS102: Remove unnecessary code created because of implicit returns
 * DS207: Consider shorter variations of null checks
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
const crypto = require('crypto');

const ALGORITHM = 'aes-256-ctr';

const keyFn = (password, salt, callback)=> crypto.pbkdf2(password, salt, 10000, 64, 'sha1', callback);

const keyFn32 = (password, salt, keyLength, callback)=> crypto.pbkdf2(password, salt, 10000, 32, 'sha1', callback);

class AccessTokenEncryptor {

	constructor(settings) {

		this.settings = settings;
		this.cipherLabel = this.settings.cipherLabel;
		if (this.cipherLabel != null ? this.cipherLabel.match(/:/) : undefined) { throw Error("cipherLabel must not contain a colon (:)"); }

		this.cipherPassword = this.settings.cipherPasswords[this.cipherLabel];
		if ((this.cipherPassword == null)) { throw Error("cipherPassword not set"); }
		if (this.cipherPassword.length < 16) { throw Error("cipherPassword too short"); }
	}

	encryptJson(json, callback) {
		const string = JSON.stringify(json);
		return crypto.randomBytes(32, (err, bytes) => {
			if (err) { return callback(err); }
			const salt = bytes.slice(0, 16);
			const iv = bytes.slice(16, 32);

			return keyFn32(this.cipherPassword, salt, 32, (err, key) => {
				if (err != null) {
					logger.err({err}, "error getting Fn key");
					return callback(err);
				}

				const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
				const crypted = cipher.update(string, 'utf8', 'base64') + cipher.final('base64');

				return callback(null, `${this.cipherLabel}:${salt.toString('hex')}:${crypted}:${iv.toString('hex')}`);
			});
		});
	}

	decryptToJson(encryptedJson, callback) {
		const [label, salt, cipherText, iv] = Array.from(encryptedJson.split(':', 4));
		const password = this.settings.cipherPasswords[label];
		if ((password == null) || (password.length < 16)) { return callback(new Error("invalid password")); }

		if (iv) {
			return this.decryptToJsonV2(password, salt, cipherText, iv, callback);
		} else {
			return this.decryptToJsonV1(password, salt, cipherText, callback);
		}
	}

	decryptToJsonV1(password, salt, cipherText, callback) {
		return keyFn(password, Buffer.from(salt, 'hex'), (err, key) => {
			let json;
			if (err != null) {
				logger.err({err}, "error getting Fn key");
				return callback(err);
			}
			const decipher = crypto.createDecipher(ALGORITHM, key);
			const dec = decipher.update(cipherText, 'base64', 'utf8') + decipher.final('utf8');
			try {
				json = JSON.parse(dec);
			} catch (e) {
				return callback(new Error("error decrypting token"));
			}
			return callback(null, json, true);
		});
	}

	decryptToJsonV2(password, salt, cipherText, iv, callback) {
		return keyFn32(password, Buffer.from(salt, 'hex'), 32, (err, key) => {
			let json;
			if (err != null) {
				logger.err({err}, "error getting Fn key");
				return callback(err);
			}

			const decipher = crypto.createDecipheriv(ALGORITHM, key, Buffer.from(iv, 'hex'));
			const dec = decipher.update(cipherText, 'base64', 'utf8') + decipher.final('utf8');
			try {
				json = JSON.parse(dec);
			} catch (e) {
				return callback(new Error("error decrypting token"));
			}
			return callback(null, json);
		});
	}
}

module.exports = AccessTokenEncryptor;
{'fraction_non_alphanumeric': 0.15016339869281045, 'fraction_numerical': 0.02173202614379085, 'mean_word_length': 4.460303300624442, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 20, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6272587', 'n_tokens_mistral': 2363, 'n_tokens_neox': 1994, 'n_words': 506}