Skip to content

Commit

Permalink
test: validate password
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Jul 6, 2020
1 parent ace8647 commit b21c026
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 30 deletions.
25 changes: 25 additions & 0 deletions app/crypto/key-generation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { generateSalt, generateDerivedKey } from './key-generation';

describe(generateDerivedKey.name, () => {
test('a bcrypt hash is returned', async () => {
const salt = '$2a$12$BwnByfKrfRbpxsazN712T.';
const password = 'f255cadb0af84854819c63f26c53e1a9';
const result = await generateDerivedKey({ salt, password });
expect(result).toEqual('$2a$12$BwnByfKrfRbpxsazN712T.ckDPUEMy2RJR6pyE8kOf2l3IMaxZ7R6');
});
});

describe(generateSalt.name, () => {
test('that bcrypt salt is returned', async () => {
const salt = await generateSalt();
expect(salt).toBeDefined();
expect(salt[0]).toEqual('$');
expect(salt.length).toEqual(29);
});

test('that salt fn is memoized per client', async () => {
const salt1 = await generateSalt();
const salt2 = await generateSalt();
expect(salt1).toEqual(salt2);
});
});
9 changes: 9 additions & 0 deletions app/crypto/key-generation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import bcryptjs from 'bcryptjs';
import { memoizeWith, identity } from 'ramda';

// 980aa096dd224bd69685583b363de2be
export async function generateDerivedKey({ password, salt }: { password: string; salt: string }) {
return bcryptjs.hash(password, salt);
}

export const generateSalt = memoizeWith(identity, async () => await bcryptjs.genSalt(12));
36 changes: 36 additions & 0 deletions app/crypto/validate-password.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import zxcvbn, { ZXCVBNResult } from 'zxcvbn';
import { validatePassword, blankPasswordValidation } from './validate-password';

jest.mock('zxcvbn', () => jest.fn(() => ({ score: 4 })));

const badPassword = 'password';

describe(validatePassword.name, () => {
test('that zxcvbn is called', () => {
validatePassword(badPassword);
expect(zxcvbn).toHaveBeenCalledWith(badPassword);
});

test('password of < 12 char length is invalid', () => {
const result = validatePassword(badPassword);
expect(result.meetsLengthRequirement).toBeFalsy();
});

test('really long passwords are truncated to 100chars', () => {
const reallyLongPw = [
'786293ebd1d043b685cd4d360d5c731d',
'9a47843cdc1b49c0992f7fa63a8c671a',
'd5515430068043fbb7200e6a71f05a42',
'6cfab267043f4265a52120f7174bd553',
'8b94e6678d8440eab7fb4dd0a5eae7ef',
'bdd440b629e34307b1aeebf0722cfccd',
'47c4f0539b7348ed81710cfeb50c1e2a',
'ec57a8cc23334e1e962e6441871626c3',
'4460527a5e10406796b4174c4dd979ed',
]
.join('')
.toString();
validatePassword(reallyLongPw);
expect(zxcvbn).toHaveBeenCalledWith(reallyLongPw.substr(0, 100));
});
});
9 changes: 0 additions & 9 deletions app/crypto/validate-password.spec.tsx

This file was deleted.

File renamed without changes.
9 changes: 4 additions & 5 deletions app/pages/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,11 @@ export const Home: FC = () => {
// ST2K2GK11JDC2DT8M9B5T28P2FHMXMECKDGDNCQCN
useEffect(() => {
if (!address) return;
// Demo address
// ST2K2GK11JDC2DT8M9B5T28P2FHMXMECKDGDNCQCN
dispatch(getTransactions(address));
dispatch(getAddressTransactions('ST2K2GK11JDC2DT8M9B5T28P2FHMXMECKDGDNCQCN'));
dispatch(getAddressDetails('ST2K2GK11JDC2DT8M9B5T28P2FHMXMECKDGDNCQCN'));
}, [dispatch, address]);

if (address === undefined) return <Spinner />;
if (!address) return <Spinner />;

const openInExplorer = (txId: string) =>
shell.openExternal(`https://testnet-explorer.blockstack.org/txid/${txId}`);
Expand All @@ -48,7 +47,7 @@ export const Home: FC = () => {
const balanceCard = <BalanceCard balance={balance === null ? '–' : balance} />;
const stackingPromoCard = <StackingPromoCard />;
const stackingRewardCard = (
<StackingRewardCard lifetime="0.0281 Bitcoin" lastCycle="0.000383 Bitcoin" />
<StackingRewardCard lifetime="0.0281 Bitcoin (sample)" lastCycle="0.000383 Bitcoin (sample)" />
);

return (
Expand Down
27 changes: 11 additions & 16 deletions app/store/keys/keys.actions.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { useHistory } from 'react-router';
import { push } from 'connected-react-router';
import { createAction, Dispatch } from '@reduxjs/toolkit';
import { useHistory } from 'react-router';
import log from 'electron-log';
import bcryptjs from 'bcryptjs';
import { memoizeWith, identity } from 'ramda';
import {
generateMnemonicRootKeychain,
deriveRootKeychainFromMnemonic,
deriveStxAddressChain,
} from '@blockstack/keychain';
import { ChainID } from '@blockstack/stacks-transactions';
import { encryptMnemonic, decryptMnemonic } from 'blockstack';
import { ChainID } from '@blockstack/stacks-transactions';

import { RootState } from '..';
import routes from '../../constants/routes.json';
import { MNEMONIC_ENTROPY } from '../../constants';
import { RootState } from '../index';
import { persistSalt, persistEncryptedMnemonic } from '../../utils/disk-store';
import { safeAwait } from '../../utils/safe-await';
import { selectMnemonic, selectKeysSlice } from './keys.reducer';
import { generateSalt, generateDerivedKey } from '../../crypto/key-generation';

type History = ReturnType<typeof useHistory>;

Expand All @@ -39,13 +38,6 @@ export function onboardingMnemonicGenerationStep({ stepDelayMs }: { stepDelayMs:
};
}

// 980aa096dd224bd69685583b363de2be
export async function generateDerivedKey({ password, salt }: { password: string; salt: string }) {
return bcryptjs.hash(password, salt);
}

const generateSalt = memoizeWith(identity, async () => await bcryptjs.genSalt(12));

export function setPassword({ password, history }: { password: string; history: History }) {
return async (dispatch: Dispatch, getState: () => RootState) => {
const mnemonic = selectMnemonic(getState());
Expand Down Expand Up @@ -81,16 +73,19 @@ export function decryptWallet({ password, history }: { password: string; history
dispatch(attemptWalletDecrypt());
const { salt, encryptedMnemonic } = selectKeysSlice(getState());

if (!salt || !encryptMnemonic) {
if (!salt || !encryptedMnemonic) {
log.error('Cannot decrypt wallet if no `salt` or `encryptedMnemonic` exists');
return;
}

const key = await generateDerivedKey({ password, salt });

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const [error, mnemonic] = await safeAwait(decryptMnemonic(encryptedMnemonic, key));
//
// TODO: remove casting within blockstack.js library
// https://github.com/blockstack/blockstack.js/pull/797
const [error, mnemonic] = await safeAwait(
decryptMnemonic(encryptedMnemonic, key, undefined as any)
);

if (error) {
dispatch(attemptWalletDecryptFailed({ decryptionError: 'Password incorrect' }));
Expand Down

0 comments on commit b21c026

Please sign in to comment.