Skip to content

Commit

Permalink
Add secure file password UI component
Browse files Browse the repository at this point in the history
  • Loading branch information
AmirAbrams committed Oct 21, 2020
1 parent da8b5cd commit 4727ad0
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 16 deletions.
18 changes: 17 additions & 1 deletion web/src/components/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@ const ArrowButton: React.FunctionComponent<ArrowButtonProps> = ({
</StyledButton>
);

const StyledBackArrowButton = styled("div")`
display: block;
direction: column;
width: 100px;
cursor: pointer;
`;

const BackArrowButton: React.FunctionComponent<{
onClick: () => void;
marginTop?: string;
}> = ({ onClick, marginTop }) => (
<StyledBackArrowButton onClick={() => onClick()}>
<BackArrow style={{ marginTop: marginTop || "175%" }} />
</StyledBackArrowButton>
);

const LightButton = styled("button")<ButtonProps>`
align-self: ${(props) => (props.align ? props.align : "center")};
justify-content: center;
Expand All @@ -91,4 +107,4 @@ const LightButton = styled("button")<ButtonProps>`

export default StyledButton;

export { ArrowButton, BackButton, LightButton };
export { ArrowButton, BackArrowButton, BackButton, LightButton };
14 changes: 8 additions & 6 deletions web/src/components/wallet/MnemonicBackup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component, FormEvent } from "react";
import React, { Component } from "react";
import { Box } from "../ui/Box";
import { ArrowButton, BackButton, LightButton } from "../ui/Button";
import { Card } from "../ui/Card";
Expand All @@ -11,6 +11,7 @@ import { GetMnemonic } from "../../api/Wallet";
export interface MnemonicBackupProps {
onCancel: () => void;
onComplete: () => void;
onBackupSecureFile: (mnemonic: string) => void;
}

export interface MnemonicBackupState {
Expand All @@ -26,7 +27,7 @@ export class MnemonicBackup extends Component<
// bind events
this.componentDidMount = this.componentDidMount.bind(this);
this.componentDidUnmount = this.componentDidUnmount.bind(this);
this.handleFileCreation = this.handleFileCreation.bind(this);
this.backupSecureFile = this.backupSecureFile.bind(this);
this.getMnemonic = this.getMnemonic.bind(this);
}

Expand All @@ -36,9 +37,10 @@ export class MnemonicBackup extends Component<

componentDidUnmount(): void {}

handleFileCreation = (e: FormEvent) => {
//if we don't prevent form submission, causes a browser reload
e.preventDefault();
backupSecureFile = async () => {
if (this.state && this.state.mnemonic) {
this.props.onBackupSecureFile(this.state.mnemonic);
}
};

private getMnemonic = async () => {
Expand Down Expand Up @@ -118,7 +120,7 @@ export class MnemonicBackup extends Component<
<Divider />
</Box>
<Box width="30%" margin="0 0 0 3em">
<LightButton onClick={this.handleFileCreation}>
<LightButton onClick={this.backupSecureFile}>
Create a secure file
</LightButton>
<Box display="flex" width="100%" margin="2em 0 0 2em">
Expand Down
148 changes: 148 additions & 0 deletions web/src/components/wallet/SecureFilePassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React, { Component } from "react";
import { Box } from "../ui/Box";
import { ArrowButton, BackArrowButton } from "../ui/Button";
import { Card } from "../ui/Card";
import { Container } from "../ui/Container";
import { Input } from "../ui/Input";
import { SecureFileIcon } from "../ui/Images";
import { H3, Text } from "../ui/Text";
import { getAesEncryptor } from "../../shared/AesEncryption";
import { ValidationResult } from "../../shared/ValidationResult";

export interface WalletSecureFilePasswordProps {
mnemonic: string;
onCancel: () => void;
validationResult?: ValidationResult<string>;
}

export interface WalletSecureFilePasswordState {
password: string;
confirmPassword: string;
error?: string;
fileCipherText?: string;
}

export class WalletSecureFilePassword extends Component<
WalletSecureFilePasswordProps,
WalletSecureFilePasswordState
> {
constructor(props: WalletSecureFilePasswordProps) {
super(props);
// bind events
this.componentDidMount = this.componentDidMount.bind(this);
this.componentDidUnmount = this.componentDidUnmount.bind(this);
this.encryptSecureMnemonicFile = this.encryptSecureMnemonicFile.bind(this);

this.state = { password: "", confirmPassword: "" };
}

componentDidMount(): void {}

componentDidUnmount(): void {}

private encryptSecureMnemonicFile(): void {
if (this.state.password !== this.state.confirmPassword) {
this.setState({ error: "Passwords do not match" });
return;
} else if (!/.{6,}/.test(this.state.password)) {
this.setState({ error: "Password must be > 6 characters" });
return;
}
if (this.state.password) {
try {
const { encrypt } = getAesEncryptor(this.state.password);
const encryptedMnemonic = "data:application/json;base64," + encrypt(this.props.mnemonic);
console.log(encryptedMnemonic);
this.setState({ fileCipherText: encryptedMnemonic });
} catch (e) {
console.log("Error:", e);
}
}
}

render() {
const { onCancel } = this.props;
const { error } = this.state;
return (
<>
<Container height="50vh" margin="10% 5% 0 0">
<Box direction="column" align="center" width="100%">
<Box
direction="column"
width="800px"
align="start"
margin="0 auto 0 auto"
>
<div style={{ display: "flex" }}>
<BackArrowButton onClick={() => onCancel()} />
<Card
width="100%"
align="center"
minHeight="225px"
padding="2em 4em 2em 2em"
>
<Box display="flex" direction="row" margin="0">
<Box width="120px" margin="0">
<SecureFileIcon width="60px" height="60px" />
</Box>
<Box margin="0 0 0 2em">
<H3>Secure file</H3>
<Text fontSize="14px">
Create a secure file password{" "}
</Text>
<Input
value={this.state.password}
name="password"
onChange={(e) =>
this.setState({ password: e.target.value })
}
placeholder="Password"
type="password"
margin="1em 0 1em 0"
padding="0 1em 0 1em"
autoFocus={true}
error={error ? true : false}
/>
<Text fontSize="14px">Confirm Password</Text>
<Input
value={this.state.confirmPassword}
name="confirmPassword"
onChange={(e) =>
this.setState({ confirmPassword: e.target.value })
}
placeholder="Password"
type="password"
margin="1em 0 1em 0"
padding="0 1em 0 1em"
error={error ? true : false}
/>
{this.state && this.state.error ? (
<Text align="center" color="#e30429">
{this.state.error}
</Text>
) : (
<></>
)}
</Box>
</Box>
</Card>
</div>
</Box>
<Box
direction="column"
width="800px"
align="right"
margin="0 auto 0 auto"
>
<ArrowButton
label="Continue"
type="button"
onClick={() => this.encryptSecureMnemonicFile()}
/>
</Box>
</Box>
</Container>
</>
);
}
}
29 changes: 20 additions & 9 deletions web/src/components/wallet/Setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { WalletMnemonicRestore } from "./MnemonicRestore";
import { WalletPassword } from "./WalletPassword";
import { PickedDispatchProps } from "../../state/shared/PickedDispatchProps";
import { ManageWalletActions } from "../../state/actions/manageWallet";
import { WalletSecureFilePassword } from "./SecureFilePassword";

enum SetupState {
Init = 1,
Expand All @@ -20,6 +21,7 @@ enum SetupState {
Restore,
RestoreWithMnemonic,
RestoreWithSecureFile,
BackupSecureFile,
CreatePassword,
Waiting,
Complete,
Expand All @@ -37,6 +39,7 @@ type WalletViewDispatchProps = WalletSetupProps & WalletViewDispatch;

export interface WalletSetupState {
setupState: SetupState;
mnemonic?: string;
}

export class WalletSetup extends Component<WalletViewDispatchProps, WalletSetupState> {
Expand Down Expand Up @@ -123,8 +126,24 @@ export class WalletSetup extends Component<WalletViewDispatchProps, WalletSetupS
onComplete={() =>
this.setState({ setupState: SetupState.CreatePassword })
}
onBackupSecureFile={(mnemonic) => this.setState({ setupState: SetupState.BackupSecureFile, mnemonic: mnemonic })}
/>
)}
{this.state &&
this.state.setupState === SetupState.BackupSecureFile && this.state.mnemonic && (
<WalletSecureFilePassword
mnemonic={this.state.mnemonic}
onCancel={() => this.setState({ setupState: SetupState.NewWarned })}
/>
)}
{this.state &&
this.state.setupState === SetupState.RestoreWithSecureFile && (
<WalletFileRestore
onComplete={() => this.props.onComplete()}
onCancel={() => this.setState({ setupState: SetupState.Restore })}
onRestoreMnemonic={(words) => this.props.walletImportMnemonic(words)}
/>
)}
{this.state && this.state.setupState === SetupState.CreatePassword && (
<WalletPassword
onComplete={() => this.props.onComplete()}
Expand All @@ -149,15 +168,7 @@ export class WalletSetup extends Component<WalletViewDispatchProps, WalletSetupS
onComplete={() => this.props.onComplete()}
onCancel={() => this.setState({ setupState: SetupState.Restore })}
/>
)}
{this.state &&
this.state.setupState === SetupState.RestoreWithSecureFile && (
<WalletFileRestore
onComplete={() => this.props.onComplete()}
onCancel={() => this.setState({ setupState: SetupState.Restore })}
onRestoreMnemonic={(words) => this.props.walletImportMnemonic(words)}
/>
)}
)}
</>
);
}
Expand Down
28 changes: 28 additions & 0 deletions web/src/shared/ValidationResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export interface ValidationResult<T> {
value: T;
success: boolean;
validationMessages: string[];
isError: boolean
}

interface NameIndictator {
name: string;
scope: string;
}

export interface NameIndicatorWithValue<T> extends NameIndictator {
value: T;
}

export type NamedValue<T> = T extends void ? NameIndictator : NameIndicatorWithValue<T>;

export const createValidatedFailurePayload = <T>(fieldScope: string, fieldName: string, message: string, fieldValue: T, isError = false): NamedValue<ValidationResult<T>> => ({
scope: fieldScope,
name: fieldName,
value: {
success: false,
validationMessages: [message],
value: fieldValue,
isError: isError
}
})

0 comments on commit 4727ad0

Please sign in to comment.