Add JSCS and JSHint to tests and fix errors

This commit is contained in:
Alan Friedman 2016-02-21 10:58:42 -05:00
parent 4338f1aa8a
commit cfa5da5695
14 changed files with 183 additions and 140 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["es2015"]
}

8
.jscsrc Normal file
View File

@ -0,0 +1,8 @@
{
"preset": "google",
"esnext": true,
"requireCamelCaseOrUpperCaseIdentifiers": {"ignoreProperties": true},
"maxErrors": null,
"maximumLineLength": null,
"excludeFiles": ["src/public"]
}

2
.jshintignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
src/public

3
.jshintrc Normal file
View File

@ -0,0 +1,3 @@
{
"esversion": 6
}

42
gulpfile.babel.js Normal file
View File

@ -0,0 +1,42 @@
import gulp from 'gulp';
import uglify from 'gulp-uglify';
import nodemon from 'gulp-nodemon';
import browserify from 'browserify';
import babel from 'babelify';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import childProcess from 'child_process';
let spawn = childProcess.spawn;
gulp.task('bundle', function() {
return browserify('src/js/main.js', {
debug: true
}).transform(babel.configure({
presets: ['es2015']
})).bundle()
.pipe(source('main.js'))
.pipe(buffer())
.pipe(uglify())
.pipe(gulp.dest('src/public'));
});
gulp.task('start', function() {
nodemon({
script: 'index.js',
ext: 'css js mustache',
ignore: ['src/public/main.js'],
env: {
'NODE_ENV': 'development'
},
tasks: ['bundle']
});
});
gulp.task('test', function() {
let test = spawn(
'mocha',
['test', '--compilers', 'js:babel-core/register'],
{stdio: 'inherit'}
);
});

View File

@ -1,29 +0,0 @@
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var nodemon = require('gulp-nodemon');
var browserify = require('browserify');
var babel = require('babelify');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
gulp.task('bundle', function() {
return browserify('src/js/main.js', { debug: true }).transform(babel.configure({
presets: ["es2015"]
})).bundle()
.pipe(source('main.js'))
.pipe(buffer())
.pipe(uglify())
.pipe(gulp.dest('src/public'))
});
gulp.task('start', function() {
nodemon({
script: 'index.js',
ext: 'css js mustache',
ignore: ['src/public/main.js'],
env: {
'NODE_ENV': 'development'
},
tasks: ['bundle']
})
});

View File

@ -1,3 +1,2 @@
require('babel-register')();
require('babel/register');
require('./src/app.js'); require('./src/app.js');

View File

@ -19,17 +19,19 @@
"uuid": "^2.0.1" "uuid": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"babel-core": "^6.5.2",
"babel-preset-es2015": "^6.3.13", "babel-preset-es2015": "^6.3.13",
"babel-register": "^6.5.2",
"compression": "^1.6.0", "compression": "^1.6.0",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"gulp-nodemon": "^2.0.6", "gulp-nodemon": "^2.0.6",
"jscs": "^2.10.1",
"jshint": "^2.9.1",
"mocha-jscs": "^4.2.0",
"mocha-jshint": "^2.3.1",
"vinyl-buffer": "^1.0.0", "vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0" "vinyl-source-stream": "^1.1.0"
}, },
"scripts": {
"start": "gulp start",
"bundle": "gulp bundle"
},
"author": "Daniel Seripap", "author": "Daniel Seripap",
"license": "MIT" "license": "MIT"
} }

View File

@ -4,6 +4,10 @@ Simple encrypted web chat. Powered by [socket.io](http://socket.io) and the [web
### Installation ### Installation
# For es6 compatability, be sure you have the latest stable version of Node JS installed
npm install -g n
n stable
npm install npm install
# Bundle JS files # Bundle JS files

View File

@ -7,7 +7,8 @@ export default class CryptoUtil {
backdrop: 'static', backdrop: 'static',
show: false, show: false,
keyboard: false keyboard: false
}) });
$('#no-crypto').modal('show'); $('#no-crypto').modal('show');
return; return;
} }
@ -28,7 +29,7 @@ export default class CryptoUtil {
} }
convertArrayBufferViewToString(buffer) { convertArrayBufferViewToString(buffer) {
let str = ""; let str = '';
for (let i = 0; i < buffer.byteLength; i++) { for (let i = 0; i < buffer.byteLength; i++) {
str += String.fromCharCode(buffer[i]); str += String.fromCharCode(buffer[i]);
} }
@ -39,36 +40,36 @@ export default class CryptoUtil {
createSigningKey() { createSigningKey() {
return this._crypto.subtle.generateKey( return this._crypto.subtle.generateKey(
{ {
name: "HMAC", name: 'HMAC',
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512" hash: {name: 'SHA-256'}, //can be 'SHA-1', 'SHA-256', 'SHA-384', or 'SHA-512'
//length: 256, //optional, if you want your key length to differ from the hash function's block length //length: 256, //optional, if you want your key length to differ from the hash function's block length
}, },
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["sign", "verify"] //can be any combination of "sign" and "verify" ['sign', 'verify'] //can be any combination of 'sign' and 'verify'
); );
} }
createPrimaryKeys() { createPrimaryKeys() {
return this._crypto.subtle.generateKey( return this._crypto.subtle.generateKey(
{ {
name: "RSA-OAEP", name: 'RSA-OAEP',
modulusLength: 2048, //can be 1024, 2048, or 4096 modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512" hash: {name: 'SHA-256'}, //can be 'SHA-1', 'SHA-256', 'SHA-384', or 'SHA-512'
}, },
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"] ['encrypt', 'decrypt'] //must be ['encrypt', 'decrypt'] or ['wrapKey', 'unwrapKey']
); );
} }
createSecretKey() { createSecretKey() {
return this._crypto.subtle.generateKey( return this._crypto.subtle.generateKey(
{ {
name: "AES-CBC", name: 'AES-CBC',
length: 256, //can be 128, 192, or 256 length: 256, //can be 128, 192, or 256
}, },
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey" ['encrypt', 'decrypt'] //can be 'encrypt', 'decrypt', 'wrapKey', or 'unwrapKey'
); );
} }
@ -76,10 +77,10 @@ export default class CryptoUtil {
// Secret key will be recipient's public key // Secret key will be recipient's public key
return this._crypto.subtle.encrypt( return this._crypto.subtle.encrypt(
{ {
name: "RSA-OAEP", name: 'RSA-OAEP',
modulusLength: 2048, modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
}, },
secretKey, secretKey,
data //ArrayBuffer of data you want to encrypt data //ArrayBuffer of data you want to encrypt
@ -90,10 +91,10 @@ export default class CryptoUtil {
// key will be my private key // key will be my private key
return this._crypto.subtle.decrypt( return this._crypto.subtle.decrypt(
{ {
name: "RSA-OAEP", name: 'RSA-OAEP',
modulusLength: 2048, modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
//label: Uint8Array([...]) //optional //label: Uint8Array([...]) //optional
}, },
key, key,
@ -105,10 +106,10 @@ export default class CryptoUtil {
// Secret key will be recipient's public key // Secret key will be recipient's public key
return this._crypto.subtle.encrypt( return this._crypto.subtle.encrypt(
{ {
name: "RSA-OAEP", name: 'RSA-OAEP',
modulusLength: 2048, modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
}, },
signingKey, signingKey,
data //ArrayBuffer of data you want to encrypt data //ArrayBuffer of data you want to encrypt
@ -119,10 +120,10 @@ export default class CryptoUtil {
// key will be my private key // key will be my private key
return this._crypto.subtle.decrypt( return this._crypto.subtle.decrypt(
{ {
name: "RSA-OAEP", name: 'RSA-OAEP',
modulusLength: 2048, modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
//label: Uint8Array([...]) //optional //label: Uint8Array([...]) //optional
}, },
key, key,
@ -133,7 +134,7 @@ export default class CryptoUtil {
encryptMessage(data, secretKey, iv) { encryptMessage(data, secretKey, iv) {
return this._crypto.subtle.encrypt( return this._crypto.subtle.encrypt(
{ {
name: "AES-CBC", name: 'AES-CBC',
//Don't re-use initialization vectors! //Don't re-use initialization vectors!
//Always generate a new iv every time your encrypt! //Always generate a new iv every time your encrypt!
iv: iv, iv: iv,
@ -146,7 +147,7 @@ export default class CryptoUtil {
decryptMessage(data, secretKey, iv) { decryptMessage(data, secretKey, iv) {
return this._crypto.subtle.decrypt( return this._crypto.subtle.decrypt(
{ {
name: "AES-CBC", name: 'AES-CBC',
iv: iv, //The initialization vector you used to encrypt iv: iv, //The initialization vector you used to encrypt
}, },
secretKey, //from generateKey or importKey above secretKey, //from generateKey or importKey above
@ -156,55 +157,55 @@ export default class CryptoUtil {
importSecretKey(jwkData, format) { importSecretKey(jwkData, format) {
return this._crypto.subtle.importKey( return this._crypto.subtle.importKey(
format || "jwk", //can be "jwk" or "raw" format || 'jwk', //can be 'jwk' or 'raw'
//this is an example jwk key, "raw" would be an ArrayBuffer //this is an example jwk key, 'raw' would be an ArrayBuffer
jwkData, jwkData,
{ //this is the algorithm options { //this is the algorithm options
name: "AES-CBC", name: 'AES-CBC',
}, },
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey" ['encrypt', 'decrypt'] //can be 'encrypt', 'decrypt', 'wrapKey', or 'unwrapKey'
); );
} }
importPrimaryKey(jwkData, format) { importPrimaryKey(jwkData, format) {
// Will be someone's public key // Will be someone's public key
let hashObj = { let hashObj = {
name: "RSA-OAEP" name: 'RSA-OAEP'
}; };
if (!this._crypto.webkitSubtle) { if (!this._crypto.webkitSubtle) {
hashObj.hash = {name: "SHA-256"}; hashObj.hash = {name: 'SHA-256'};
} }
return this._crypto.subtle.importKey( return this._crypto.subtle.importKey(
format || "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only) format || 'jwk', //can be 'jwk' (public or private), 'spki' (public only), or 'pkcs8' (private only)
jwkData, jwkData,
hashObj, hashObj,
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt"] //"encrypt" or "wrapKey" for public key import or ['encrypt'] //'encrypt' or 'wrapKey' for public key import or
//"decrypt" or "unwrapKey" for private key imports //'decrypt' or 'unwrapKey' for private key imports
); );
} }
exportKey(key, format) { exportKey(key, format) {
// Will be public primary key or public signing key // Will be public primary key or public signing key
return this._crypto.subtle.exportKey( return this._crypto.subtle.exportKey(
format || "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only) format || 'jwk', //can be 'jwk' (public or private), 'spki' (public only), or 'pkcs8' (private only)
key //can be a publicKey or privateKey, as long as extractable was true key //can be a publicKey or privateKey, as long as extractable was true
); );
} }
importSigningKey(jwkData) { importSigningKey(jwkData) {
return this._crypto.subtle.importKey( return this._crypto.subtle.importKey(
"raw", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only) 'raw', //can be 'jwk' (public or private), 'spki' (public only), or 'pkcs8' (private only)
//this is an example jwk key, other key types are Uint8Array objects //this is an example jwk key, other key types are Uint8Array objects
jwkData, jwkData,
{ //these are the algorithm options { //these are the algorithm options
name: "HMAC", name: 'HMAC',
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512" hash: {name: 'SHA-256'}, //can be 'SHA-1', 'SHA-256', 'SHA-384', or 'SHA-512'
//length: 256, //optional, if you want your key length to differ from the hash function's block length //length: 256, //optional, if you want your key length to differ from the hash function's block length
}, },
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["verify"] //"verify" for public key import, "sign" for private key imports ['verify'] //'verify' for public key import, 'sign' for private key imports
); );
} }
@ -212,8 +213,8 @@ export default class CryptoUtil {
// Will use my private key // Will use my private key
return this._crypto.subtle.sign( return this._crypto.subtle.sign(
{ {
name: "HMAC", name: 'HMAC',
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
}, },
keyToSignWith, //from generateKey or importKey above keyToSignWith, //from generateKey or importKey above
data //ArrayBuffer of data you want to sign data //ArrayBuffer of data you want to sign
@ -224,8 +225,8 @@ export default class CryptoUtil {
// Will verify with sender's public key // Will verify with sender's public key
return this._crypto.subtle.verify( return this._crypto.subtle.verify(
{ {
name: "HMAC", name: 'HMAC',
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
}, },
keyToVerifyWith, //from generateKey or importKey above keyToVerifyWith, //from generateKey or importKey above
signature, //ArrayBuffer of the signature signature, //ArrayBuffer of the signature
@ -233,5 +234,4 @@ export default class CryptoUtil {
); );
} }
} }

View File

@ -41,9 +41,9 @@ $(function() {
let keys = {}; let keys = {};
if (!roomId) return; if (!roomId) { return; }
$('input.share-text').val(document.location.protocol + "//" + document.location.host + roomId); $('input.share-text').val(document.location.protocol + '//' + document.location.host + roomId);
$('input.share-text').click(function() { $('input.share-text').click(function() {
$(this).focus(); $(this).focus();
@ -71,7 +71,7 @@ $(function() {
} else { } else {
fs(window.TEMPORARY, fs(window.TEMPORARY,
100, 100,
log.bind(log, "WARNING: Your browser is not in incognito mode!")); log.bind(log, 'WARNING: Your browser is not in incognito mode!'));
} }
// If the username is valid // If the username is valid
@ -88,7 +88,7 @@ $(function() {
private: data[0].privateKey private: data[0].privateKey
}; };
return Promise.all([ return Promise.all([
cryptoUtil.exportKey(data[0].publicKey, "spki") cryptoUtil.exportKey(data[0].publicKey, 'spki')
]); ]);
}) })
.then(function(exportedKeys) { .then(function(exportedKeys) {
@ -115,7 +115,9 @@ $(function() {
// Adds the visual chat message to the message list // Adds the visual chat message to the message list
function addChatMessage(data, options) { function addChatMessage(data, options) {
if (!data.message.trim().length) return; if (!data.message.trim().length) {
return;
}
// Don't fade the message in if there is an 'X was typing' // Don't fade the message in if there is an 'X was typing'
let $typingMessages = getTypingMessages(data); let $typingMessages = getTypingMessages(data);
@ -267,10 +269,10 @@ $(function() {
}); });
function getSelectedText() { function getSelectedText() {
var text = ""; var text = '';
if (typeof window.getSelection != "undefined") { if (typeof window.getSelection != 'undefined') {
text = window.getSelection().toString(); text = window.getSelection().toString();
} else if (typeof document.selection != "undefined" && document.selection.type == "Text") { } else if (typeof document.selection != 'undefined' && document.selection.type == 'Text') {
text = document.selection.createRange().text; text = document.selection.createRange().text;
} }
return text; return text;
@ -289,7 +291,7 @@ $(function() {
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
let currentUser = user; let currentUser = user;
Promise.all([ Promise.all([
cryptoUtil.importPrimaryKey(currentUser.publicKey, "spki") cryptoUtil.importPrimaryKey(currentUser.publicKey, 'spki')
]) ])
.then(function(keys) { .then(function(keys) {
users.push({ users.push({
@ -327,7 +329,9 @@ $(function() {
// Sends a chat message // Sends a chat message
function sendMessage() { function sendMessage() {
// Don't send unless other users exist // Don't send unless other users exist
if (users.length <= 1) return; if (users.length <= 1) {
return;
}
let message = $inputMessage.val(); let message = $inputMessage.val();
// Prevent markup from being injected into the message // Prevent markup from being injected into the message
@ -368,7 +372,7 @@ $(function() {
let secretKeyStr; let secretKeyStr;
// Export secret key // Export secret key
cryptoUtil.exportKey(secretKey, "raw") cryptoUtil.exportKey(secretKey, 'raw')
.then(function(data) { .then(function(data) {
return cryptoUtil.encryptSecretKey(data, thisUser.publicKey); return cryptoUtil.encryptSecretKey(data, thisUser.publicKey);
}) })
@ -376,7 +380,7 @@ $(function() {
let encData = new Uint8Array(encryptedSecretKey); let encData = new Uint8Array(encryptedSecretKey);
secretKeyStr = cryptoUtil.convertArrayBufferViewToString(encData); secretKeyStr = cryptoUtil.convertArrayBufferViewToString(encData);
// Export HMAC signing key // Export HMAC signing key
return cryptoUtil.exportKey(signingKey, "raw"); return cryptoUtil.exportKey(signingKey, 'raw');
}) })
.then(function(data) { .then(function(data) {
// Encrypt signing key with user's public key // Encrypt signing key with user's public key
@ -432,7 +436,7 @@ $(function() {
let message = data.message; let message = data.message;
let messageData = cryptoUtil.convertStringToArrayBufferView(message); let messageData = cryptoUtil.convertStringToArrayBufferView(message);
let username = data.username; let username = data.username;
let senderId = data.id let senderId = data.id;
let vector = data.vector; let vector = data.vector;
let vectorData = cryptoUtil.convertStringToArrayBufferView(vector); let vectorData = cryptoUtil.convertStringToArrayBufferView(vector);
let secretKeys = data.secretKeys; let secretKeys = data.secretKeys;
@ -449,7 +453,7 @@ $(function() {
cryptoUtil.decryptSecretKey(secretKeyArrayBuffer, keys.private) cryptoUtil.decryptSecretKey(secretKeyArrayBuffer, keys.private)
.then(function(data) { .then(function(data) {
return cryptoUtil.importSecretKey(new Uint8Array(data), "raw"); return cryptoUtil.importSecretKey(new Uint8Array(data), 'raw');
}) })
.then(function(data) { .then(function(data) {
let secretKey = data; let secretKey = data;
@ -457,11 +461,11 @@ $(function() {
}) })
.then(function(data) { .then(function(data) {
decryptedMessageData = data; decryptedMessageData = data;
decryptedMessage = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(data)) decryptedMessage = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(data));
return cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, keys.private) return cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, keys.private);
}) })
.then(function(data) { .then(function(data) {
return cryptoUtil.importSigningKey(new Uint8Array(data), "raw"); return cryptoUtil.importSigningKey(new Uint8Array(data), 'raw');
}) })
.then(function(data) { .then(function(data) {
let signingKey = data; let signingKey = data;
@ -522,9 +526,9 @@ $(function() {
let li; let li;
if (user.username === window.username) { if (user.username === window.username) {
// User is me // User is me
li = $("<li>" + user.username + " <span class='you'>(you)</span></li>").css('color', getUsernameColor(user.username)); li = $('<li>' + user.username + ' <span class="you">(you)</span></li>').css('color', getUsernameColor(user.username));
} else { } else {
li = $("<li>" + user.username + "</li>").css('color', getUsernameColor(user.username)); li = $('<li>' + user.username + '</li>').css('color', getUsernameColor(user.username));
} }
$('#participants-modal ul.users') $('#participants-modal ul.users')
.append(li); .append(li);

View File

@ -29,7 +29,7 @@ class Room {
}); });
socket.on('add user', (data) => { socket.on('add user', (data) => {
if (addedUser) return; if (addedUser) { return; }
data.id = uuid.v4(); data.id = uuid.v4();
this.users.push(data); this.users.push(data);

5
test/app.js Normal file
View File

@ -0,0 +1,5 @@
import mochaJSCS from 'mocha-jscs';
import mochaJSHint from 'mocha-jshint';
mochaJSCS();
mochaJSHint();