insomnia/app/sync/session.js
Gregory Schier 1d45367aa1 Added eslint and fixed all problems (#101)
* Fixed duplication kve bug

* Added semistandard and updated code

* Actually got it working

* Even better

* I think it should work on Windows now
2017-03-03 12:09:08 -08:00

276 lines
7.5 KiB
JavaScript

import srp from 'srp-js';
import * as crypt from './crypt';
import * as util from '../common/fetch';
import {trackEvent, setAccountId} from '../analytics';
/** Create a new session for the user */
export async function login (rawEmail, rawPassphrase) {
// ~~~~~~~~~~~~~~~ //
// Sanitize Inputs //
// ~~~~~~~~~~~~~~~ //
const email = _sanitizeEmail(rawEmail);
const passphrase = _sanitizePassphrase(rawPassphrase);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Fetch Salt and Submit A To Server //
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
const {saltKey, saltAuth} = await util.post('/auth/login-s', {email});
const authSecret = await crypt.deriveKey(passphrase, email, saltKey);
const secret1 = await crypt.srpGenKey();
const c = new srp.Client(
_getSrpParams(),
Buffer.from(saltAuth, 'hex'),
Buffer.from(email, 'utf8'),
Buffer.from(authSecret, 'hex'),
Buffer.from(secret1, 'hex')
);
const srpA = c.computeA().toString('hex');
const {sessionStarterId, srpB} = await util.post('/auth/login-a', {srpA, email});
// ~~~~~~~~~~~~~~~~~~~~~ //
// Compute and Submit M1 //
// ~~~~~~~~~~~~~~~~~~~~~ //
c.setB(new Buffer(srpB, 'hex'));
const srpM1 = c.computeM1().toString('hex');
const {srpM2} = await util.post('/auth/login-m1', {srpM1, sessionStarterId});
// ~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Verify Server Identity M2 //
// ~~~~~~~~~~~~~~~~~~~~~~~~~ //
c.checkM2(new Buffer(srpM2, 'hex'));
// ~~~~~~~~~~~~~~~~~~~~~~ //
// Initialize the Session //
// ~~~~~~~~~~~~~~~~~~~~~~ //
// Compute K (used for session ID)
const sessionId = c.computeK().toString('hex');
// Get and store some extra info (salts and keys)
const {
publicKey,
encPrivateKey,
encSymmetricKey,
saltEnc,
accountId,
firstName,
lastName
} = await whoami(sessionId);
const derivedSymmetricKey = await crypt.deriveKey(passphrase, email, saltEnc);
const symmetricKeyStr = await crypt.decryptAES(derivedSymmetricKey, JSON.parse(encSymmetricKey));
// Store the information for later
setSessionData(
sessionId,
accountId,
firstName,
lastName,
email,
JSON.parse(symmetricKeyStr),
JSON.parse(publicKey),
JSON.parse(encPrivateKey),
);
// Set the ID for Google Analytics
setAccountId(accountId);
trackEvent('Session', 'Login');
}
export function syncCreateResourceGroup (parentResourceId, name, encSymmetricKey) {
return util.post('/api/resource_groups', {parentResourceId, name, encSymmetricKey});
}
export function syncGetResourceGroup (id) {
return util.get(`/api/resource_groups/${id}`);
}
export function syncPull (body) {
return util.post('/sync/pull', body);
}
export function syncPush (body) {
return util.post('/sync/push', body);
}
export function syncResetData () {
return util.post('/auth/reset');
}
export function syncFixDupes (resourceGroupIds) {
return util.post('/sync/fix-dupes', {ids: resourceGroupIds});
}
export function unshareWithAllTeams (resourceGroupId) {
return util.put(`/api/resource_groups/${resourceGroupId}/unshare`);
}
export async function shareWithTeam (resourceGroupId, teamId, rawPassphrase) {
// Ask the server what we need to do to invite the member
const instructions = await util.post(`/api/resource_groups/${resourceGroupId}/share-a`, {teamId});
// Compute keys necessary to invite the member
const passPhrase = _sanitizePassphrase(rawPassphrase);
const {email, saltEnc, encPrivateKey, encSymmetricKey} = await whoami();
const secret = await crypt.deriveKey(passPhrase, email, saltEnc);
let symmetricKey;
try {
symmetricKey = crypt.decryptAES(secret, JSON.parse(encSymmetricKey));
} catch (err) {
throw new Error('Invalid password');
}
const privateKey = crypt.decryptAES(JSON.parse(symmetricKey), JSON.parse(encPrivateKey));
const privateKeyJWK = JSON.parse(privateKey);
const resourceGroupSymmetricKey = crypt.decryptRSAWithJWK(
privateKeyJWK,
instructions.encSymmetricKey
);
// Build the invite data request
const newKeys = {};
for (const accountId of Object.keys(instructions.keys)) {
const accountPublicKeyJWK = JSON.parse(instructions.keys[accountId]);
newKeys[accountId] = crypt.encryptRSAWithJWK(
accountPublicKeyJWK,
resourceGroupSymmetricKey,
);
}
// Actually share it with the team
await util.post(`/api/resource_groups/${resourceGroupId}/share-b`, {teamId, keys: newKeys});
}
export function getPublicKey () {
return getSessionData().publicKey;
}
export function getPrivateKey () {
const {symmetricKey, encPrivateKey} = getSessionData();
const privateKeyStr = crypt.decryptAES(symmetricKey, encPrivateKey);
return JSON.parse(privateKeyStr);
}
export function getCurrentSessionId () {
return window.localStorage.getItem('currentSessionId');
}
export function getAccountId () {
return getSessionData().accountId;
}
export function getEmail () {
return getSessionData().email;
}
export function getFirstName () {
return getSessionData().firstName;
}
export function getFullName () {
const {firstName, lastName} = getSessionData();
return `${firstName || ''} ${lastName || ''}`.trim();
}
/**
* get Data about the current session
* @returns Object
*/
export function getSessionData () {
const sessionId = getCurrentSessionId();
if (!sessionId) {
return {};
}
const dataStr = window.localStorage.getItem(getSessionKey(sessionId));
return JSON.parse(dataStr);
}
/** Set data for the new session and store it encrypted with the sessionId */
export function setSessionData (sessionId,
accountId,
firstName,
lastName,
email,
symmetricKey,
publicKey,
encPrivateKey) {
const dataStr = JSON.stringify({
id: sessionId,
accountId: accountId,
symmetricKey: symmetricKey,
publicKey: publicKey,
encPrivateKey: encPrivateKey,
email: email,
firstName: firstName,
lastName: lastName
});
window.localStorage.setItem(getSessionKey(sessionId), dataStr);
// NOTE: We're setting this last because the stuff above might fail
window.localStorage.setItem('currentSessionId', sessionId);
}
/** Unset the session data (log out) */
export function unsetSessionData () {
const sessionId = getCurrentSessionId();
window.localStorage.removeItem(getSessionKey(sessionId));
window.localStorage.removeItem(`currentSessionId`);
}
/** Check if we (think) we have a session */
export function isLoggedIn () {
return getCurrentSessionId();
}
/** Log out and delete session data */
export async function logout () {
try {
await util.post('/auth/logout');
} catch (e) {
// Not a huge deal if this fails, but we don't want it to prevent the
// user from signing out.
console.warn('Failed to logout', e);
}
unsetSessionData();
trackEvent('Session', 'Logout');
}
export async function listTeams () {
return util.get('/api/teams');
}
export async function endTrial () {
await util.put('/api/billing/end-trial');
trackEvent('Session', 'End Trial');
}
export function whoami (sessionId = null) {
return util.get('/auth/whoami', sessionId);
}
export function getSessionKey (sessionId) {
return `session__${(sessionId || '').slice(0, 10)}`;
}
// ~~~~~~~~~~~~~~~~ //
// Helper Functions //
// ~~~~~~~~~~~~~~~~ //
function _getSrpParams () {
return srp.params[2048];
}
function _sanitizeEmail (email) {
return email.trim().toLowerCase();
}
function _sanitizePassphrase (passphrase) {
return passphrase.trim().normalize('NFKD');
}