2017-02-16 17:10:12 +00:00
|
|
|
import srp from 'srp-js';
|
2016-10-21 17:20:36 +00:00
|
|
|
import * as crypt from './crypt';
|
2016-11-10 02:40:53 +00:00
|
|
|
import * as util from '../common/fetch';
|
2017-11-18 22:47:54 +00:00
|
|
|
import {trackEvent} from '../common/analytics';
|
2016-10-21 17:20:36 +00:00
|
|
|
|
2016-11-10 02:40:53 +00:00
|
|
|
/** Create a new session for the user */
|
2016-10-21 17:20:36 +00:00
|
|
|
export async function login (rawEmail, rawPassphrase) {
|
|
|
|
// ~~~~~~~~~~~~~~~ //
|
|
|
|
// Sanitize Inputs //
|
|
|
|
// ~~~~~~~~~~~~~~~ //
|
|
|
|
|
|
|
|
const email = _sanitizeEmail(rawEmail);
|
|
|
|
const passphrase = _sanitizePassphrase(rawPassphrase);
|
|
|
|
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
|
|
// Fetch Salt and Submit A To Server //
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
|
|
|
2016-11-07 20:24:38 +00:00
|
|
|
const {saltKey, saltAuth} = await util.post('/auth/login-s', {email});
|
2016-10-21 17:20:36 +00:00
|
|
|
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');
|
2016-11-10 02:40:53 +00:00
|
|
|
const {sessionStarterId, srpB} = await util.post('/auth/login-a', {srpA, email});
|
2016-10-21 17:20:36 +00:00
|
|
|
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~ //
|
|
|
|
// Compute and Submit M1 //
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~ //
|
|
|
|
|
|
|
|
c.setB(new Buffer(srpB, 'hex'));
|
|
|
|
const srpM1 = c.computeM1().toString('hex');
|
2017-03-03 20:09:08 +00:00
|
|
|
const {srpM2} = await util.post('/auth/login-m1', {srpM1, sessionStarterId});
|
2016-10-21 17:20:36 +00:00
|
|
|
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
|
|
|
// 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,
|
2016-11-07 20:24:38 +00:00
|
|
|
firstName,
|
2017-03-03 20:09:08 +00:00
|
|
|
lastName
|
2017-01-11 03:18:15 +00:00
|
|
|
} = await whoami(sessionId);
|
2016-10-21 17:20:36 +00:00
|
|
|
|
|
|
|
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,
|
2016-11-07 20:24:38 +00:00
|
|
|
lastName,
|
2016-10-21 17:20:36 +00:00
|
|
|
email,
|
|
|
|
JSON.parse(symmetricKeyStr),
|
|
|
|
JSON.parse(publicKey),
|
|
|
|
JSON.parse(encPrivateKey),
|
|
|
|
);
|
2016-10-29 05:40:09 +00:00
|
|
|
|
|
|
|
// Set the ID for Google Analytics
|
2016-11-10 01:34:03 +00:00
|
|
|
trackEvent('Session', 'Login');
|
2016-10-21 17:20:36 +00:00
|
|
|
}
|
|
|
|
|
2017-01-09 21:59:52 +00:00
|
|
|
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');
|
|
|
|
}
|
|
|
|
|
2017-01-11 03:18:15 +00:00
|
|
|
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});
|
|
|
|
}
|
|
|
|
|
2016-10-21 17:20:36 +00:00
|
|
|
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 () {
|
2017-03-03 20:09:08 +00:00
|
|
|
return window.localStorage.getItem('currentSessionId');
|
2016-10-21 17:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getAccountId () {
|
|
|
|
return getSessionData().accountId;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getEmail () {
|
|
|
|
return getSessionData().email;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getFirstName () {
|
|
|
|
return getSessionData().firstName;
|
|
|
|
}
|
|
|
|
|
2016-11-07 20:24:38 +00:00
|
|
|
export function getFullName () {
|
|
|
|
const {firstName, lastName} = getSessionData();
|
|
|
|
return `${firstName || ''} ${lastName || ''}`.trim();
|
|
|
|
}
|
|
|
|
|
2016-10-21 17:20:36 +00:00
|
|
|
/**
|
|
|
|
* get Data about the current session
|
|
|
|
* @returns Object
|
|
|
|
*/
|
|
|
|
export function getSessionData () {
|
|
|
|
const sessionId = getCurrentSessionId();
|
2017-01-23 22:41:31 +00:00
|
|
|
if (!sessionId) {
|
2016-10-21 17:20:36 +00:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2017-03-03 20:09:08 +00:00
|
|
|
const dataStr = window.localStorage.getItem(getSessionKey(sessionId));
|
2016-10-21 17:20:36 +00:00
|
|
|
return JSON.parse(dataStr);
|
|
|
|
}
|
|
|
|
|
2016-11-10 02:40:53 +00:00
|
|
|
/** Set data for the new session and store it encrypted with the sessionId */
|
2016-11-07 20:24:38 +00:00
|
|
|
export function setSessionData (sessionId,
|
|
|
|
accountId,
|
|
|
|
firstName,
|
|
|
|
lastName,
|
|
|
|
email,
|
|
|
|
symmetricKey,
|
|
|
|
publicKey,
|
|
|
|
encPrivateKey) {
|
2016-10-21 17:20:36 +00:00
|
|
|
const dataStr = JSON.stringify({
|
|
|
|
id: sessionId,
|
|
|
|
accountId: accountId,
|
|
|
|
symmetricKey: symmetricKey,
|
|
|
|
publicKey: publicKey,
|
|
|
|
encPrivateKey: encPrivateKey,
|
|
|
|
email: email,
|
|
|
|
firstName: firstName,
|
2017-03-03 20:09:08 +00:00
|
|
|
lastName: lastName
|
2016-10-21 17:20:36 +00:00
|
|
|
});
|
|
|
|
|
2017-03-03 20:09:08 +00:00
|
|
|
window.localStorage.setItem(getSessionKey(sessionId), dataStr);
|
2016-10-21 17:20:36 +00:00
|
|
|
|
|
|
|
// NOTE: We're setting this last because the stuff above might fail
|
2017-03-03 20:09:08 +00:00
|
|
|
window.localStorage.setItem('currentSessionId', sessionId);
|
2016-10-21 17:20:36 +00:00
|
|
|
}
|
|
|
|
|
2016-11-10 02:40:53 +00:00
|
|
|
/** Unset the session data (log out) */
|
2016-10-21 17:20:36 +00:00
|
|
|
export function unsetSessionData () {
|
|
|
|
const sessionId = getCurrentSessionId();
|
2017-03-03 20:09:08 +00:00
|
|
|
window.localStorage.removeItem(getSessionKey(sessionId));
|
|
|
|
window.localStorage.removeItem(`currentSessionId`);
|
2016-10-21 17:20:36 +00:00
|
|
|
}
|
|
|
|
|
2016-11-10 02:40:53 +00:00
|
|
|
/** Check if we (think) we have a session */
|
2016-10-21 17:20:36 +00:00
|
|
|
export function isLoggedIn () {
|
2017-01-23 22:41:31 +00:00
|
|
|
return getCurrentSessionId();
|
2016-10-21 17:20:36 +00:00
|
|
|
}
|
|
|
|
|
2016-11-10 02:40:53 +00:00
|
|
|
/** Log out and delete session data */
|
2016-10-21 17:20:36 +00:00
|
|
|
export async function logout () {
|
2016-11-17 18:45:54 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2016-10-21 17:20:36 +00:00
|
|
|
unsetSessionData();
|
2016-11-10 01:34:03 +00:00
|
|
|
trackEvent('Session', 'Logout');
|
2016-10-21 17:20:36 +00:00
|
|
|
}
|
|
|
|
|
2016-12-21 23:37:48 +00:00
|
|
|
export async function listTeams () {
|
|
|
|
return util.get('/api/teams');
|
|
|
|
}
|
|
|
|
|
2017-01-11 03:18:15 +00:00
|
|
|
export async function endTrial () {
|
|
|
|
await util.put('/api/billing/end-trial');
|
|
|
|
trackEvent('Session', 'End Trial');
|
2016-11-07 20:24:38 +00:00
|
|
|
}
|
|
|
|
|
2017-01-23 22:41:31 +00:00
|
|
|
export function whoami (sessionId = null) {
|
2017-01-11 03:18:15 +00:00
|
|
|
return util.get('/auth/whoami', sessionId);
|
|
|
|
}
|
2016-10-21 17:20:36 +00:00
|
|
|
|
2017-01-09 21:59:52 +00:00
|
|
|
export function getSessionKey (sessionId) {
|
2017-03-03 20:09:08 +00:00
|
|
|
return `session__${(sessionId || '').slice(0, 10)}`;
|
2017-01-09 21:59:52 +00:00
|
|
|
}
|
|
|
|
|
2016-10-21 17:20:36 +00:00
|
|
|
// ~~~~~~~~~~~~~~~~ //
|
|
|
|
// Helper Functions //
|
|
|
|
// ~~~~~~~~~~~~~~~~ //
|
|
|
|
|
|
|
|
function _getSrpParams () {
|
|
|
|
return srp.params[2048];
|
|
|
|
}
|
|
|
|
|
|
|
|
function _sanitizeEmail (email) {
|
|
|
|
return email.trim().toLowerCase();
|
|
|
|
}
|
|
|
|
|
|
|
|
function _sanitizePassphrase (passphrase) {
|
|
|
|
return passphrase.trim().normalize('NFKD');
|
|
|
|
}
|