Skip to content

Commit 60b19dc

Browse files
committed
Add secure file password UI component
1 parent da8b5cd commit 60b19dc

File tree

5 files changed

+220
-15
lines changed

5 files changed

+220
-15
lines changed

web/src/components/ui/Button.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ const ArrowButton: React.FunctionComponent<ArrowButtonProps> = ({
7373
</StyledButton>
7474
);
7575

76+
const StyledBackArrowButton = styled("div")`
77+
display: block;
78+
direction: column;
79+
width: 100px;
80+
cursor: pointer;
81+
`;
82+
83+
const BackArrowButton: React.FunctionComponent<{
84+
onClick: () => void;
85+
marginTop?: string;
86+
}> = ({ onClick, marginTop }) => (
87+
<StyledBackArrowButton onClick={() => onClick()}>
88+
<BackArrow style={{ marginTop: marginTop || "175%" }} />
89+
</StyledBackArrowButton>
90+
);
91+
7692
const LightButton = styled("button")<ButtonProps>`
7793
align-self: ${(props) => (props.align ? props.align : "center")};
7894
justify-content: center;
@@ -91,4 +107,4 @@ const LightButton = styled("button")<ButtonProps>`
91107

92108
export default StyledButton;
93109

94-
export { ArrowButton, BackButton, LightButton };
110+
export { ArrowButton, BackArrowButton, BackButton, LightButton };

web/src/components/wallet/MnemonicBackup.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { GetMnemonic } from "../../api/Wallet";
1111
export interface MnemonicBackupProps {
1212
onCancel: () => void;
1313
onComplete: () => void;
14+
onBackupSecureFile: (mnemonic: string) => void;
1415
}
1516

1617
export interface MnemonicBackupState {
@@ -26,7 +27,7 @@ export class MnemonicBackup extends Component<
2627
// bind events
2728
this.componentDidMount = this.componentDidMount.bind(this);
2829
this.componentDidUnmount = this.componentDidUnmount.bind(this);
29-
this.handleFileCreation = this.handleFileCreation.bind(this);
30+
this.backupSecureFile = this.backupSecureFile.bind(this);
3031
this.getMnemonic = this.getMnemonic.bind(this);
3132
}
3233

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

3738
componentDidUnmount(): void {}
3839

39-
handleFileCreation = (e: FormEvent) => {
40-
//if we don't prevent form submission, causes a browser reload
41-
e.preventDefault();
40+
backupSecureFile = async () => {
41+
if (this.state && this.state.mnemonic) {
42+
this.props.onBackupSecureFile(this.state.mnemonic);
43+
}
4244
};
4345

4446
private getMnemonic = async () => {
@@ -118,7 +120,7 @@ export class MnemonicBackup extends Component<
118120
<Divider />
119121
</Box>
120122
<Box width="30%" margin="0 0 0 3em">
121-
<LightButton onClick={this.handleFileCreation}>
123+
<LightButton onClick={this.backupSecureFile}>
122124
Create a secure file
123125
</LightButton>
124126
<Box display="flex" width="100%" margin="2em 0 0 2em">
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import React, { Component } from "react";
2+
import { Box } from "../ui/Box";
3+
import { ArrowButton, BackArrowButton } from "../ui/Button";
4+
import { Card } from "../ui/Card";
5+
import { Container } from "../ui/Container";
6+
import { Input } from "../ui/Input";
7+
import { SecureFileIcon } from "../ui/Images";
8+
import { H3, Text } from "../ui/Text";
9+
import { getAesEncryptor } from "../../shared/AesEncryption";
10+
import { ValidationResult } from "../../shared/ValidationResult";
11+
12+
export interface WalletSecureFilePasswordProps {
13+
mnemonic: string;
14+
onCancel: () => void;
15+
validationResult?: ValidationResult<string>;
16+
}
17+
18+
export interface WalletSecureFilePasswordState {
19+
password: string;
20+
confirmPassword: string;
21+
error?: string;
22+
fileCipherText?: string;
23+
}
24+
25+
export class WalletSecureFilePassword extends Component<
26+
WalletSecureFilePasswordProps,
27+
WalletSecureFilePasswordState
28+
> {
29+
constructor(props: WalletSecureFilePasswordProps) {
30+
super(props);
31+
// bind events
32+
this.componentDidMount = this.componentDidMount.bind(this);
33+
this.componentDidUnmount = this.componentDidUnmount.bind(this);
34+
this.encryptSecureMnemonicFile = this.encryptSecureMnemonicFile.bind(this);
35+
36+
this.state = { password: "", confirmPassword: "" };
37+
}
38+
39+
componentDidMount(): void {}
40+
41+
componentDidUnmount(): void {}
42+
43+
private encryptSecureMnemonicFile(): void {
44+
if (this.state.password !== this.state.confirmPassword) {
45+
this.setState({ error: "Passwords do not match" });
46+
return;
47+
} else if (!/.{6,}/.test(this.state.password)) {
48+
this.setState({ error: "Password must be > 6 characters" });
49+
return;
50+
}
51+
if (this.state.password) {
52+
try {
53+
const { encrypt } = getAesEncryptor(this.state.password);
54+
const encryptedMnemonic = "data:application/json;base64," + encrypt(this.props.mnemonic);
55+
console.log(encryptedMnemonic);
56+
this.setState({ fileCipherText: encryptedMnemonic });
57+
} catch (e) {
58+
console.log("Error:", e);
59+
}
60+
}
61+
}
62+
63+
render() {
64+
const { onCancel } = this.props;
65+
const { error } = this.state;
66+
return (
67+
<>
68+
<Container height="50vh" margin="10% 5% 0 0">
69+
<Box direction="column" align="center" width="100%">
70+
<Box
71+
direction="column"
72+
width="800px"
73+
align="start"
74+
margin="0 auto 0 auto"
75+
>
76+
<div style={{ display: "flex" }}>
77+
<BackArrowButton onClick={() => onCancel()} />
78+
<Card
79+
width="100%"
80+
align="center"
81+
minHeight="225px"
82+
padding="2em 4em 2em 2em"
83+
>
84+
<Box display="flex" direction="row" margin="0">
85+
<Box width="120px" margin="0">
86+
<SecureFileIcon width="60px" height="60px" />
87+
</Box>
88+
<Box margin="0 0 0 2em">
89+
<H3>Secure file</H3>
90+
<Text fontSize="14px">
91+
Create a secure file password{" "}
92+
</Text>
93+
<Input
94+
value={this.state.password}
95+
name="password"
96+
onChange={(e) =>
97+
this.setState({ password: e.target.value })
98+
}
99+
placeholder="Password"
100+
type="password"
101+
margin="1em 0 1em 0"
102+
padding="0 1em 0 1em"
103+
autoFocus={true}
104+
error={error ? true : false}
105+
/>
106+
<Text fontSize="14px">Confirm Password</Text>
107+
<Input
108+
value={this.state.confirmPassword}
109+
name="confirmPassword"
110+
onChange={(e) =>
111+
this.setState({ confirmPassword: e.target.value })
112+
}
113+
placeholder="Password"
114+
type="password"
115+
margin="1em 0 1em 0"
116+
padding="0 1em 0 1em"
117+
error={error ? true : false}
118+
/>
119+
{this.state && this.state.error ? (
120+
<Text align="center" color="#e30429">
121+
{this.state.error}
122+
</Text>
123+
) : (
124+
<></>
125+
)}
126+
</Box>
127+
</Box>
128+
</Card>
129+
</div>
130+
</Box>
131+
<Box
132+
direction="column"
133+
width="800px"
134+
align="right"
135+
margin="0 auto 0 auto"
136+
>
137+
<ArrowButton
138+
label="Continue"
139+
type="button"
140+
onClick={() => this.encryptSecureMnemonicFile()}
141+
/>
142+
</Box>
143+
</Box>
144+
</Container>
145+
</>
146+
);
147+
}
148+
}

web/src/components/wallet/Setup.tsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { WalletMnemonicRestore } from "./MnemonicRestore";
1212
import { WalletPassword } from "./WalletPassword";
1313
import { PickedDispatchProps } from "../../state/shared/PickedDispatchProps";
1414
import { ManageWalletActions } from "../../state/actions/manageWallet";
15+
import { WalletSecureFilePassword } from "./SecureFilePassword";
1516

1617
enum SetupState {
1718
Init = 1,
@@ -20,6 +21,7 @@ enum SetupState {
2021
Restore,
2122
RestoreWithMnemonic,
2223
RestoreWithSecureFile,
24+
BackupSecureFile,
2325
CreatePassword,
2426
Waiting,
2527
Complete,
@@ -37,6 +39,7 @@ type WalletViewDispatchProps = WalletSetupProps & WalletViewDispatch;
3739

3840
export interface WalletSetupState {
3941
setupState: SetupState;
42+
mnemonic?: string;
4043
}
4144

4245
export class WalletSetup extends Component<WalletViewDispatchProps, WalletSetupState> {
@@ -123,8 +126,24 @@ export class WalletSetup extends Component<WalletViewDispatchProps, WalletSetupS
123126
onComplete={() =>
124127
this.setState({ setupState: SetupState.CreatePassword })
125128
}
129+
onBackupSecureFile={(mnemonic) => this.setState({ setupState: SetupState.BackupSecureFile, mnemonic: mnemonic })}
126130
/>
127131
)}
132+
{this.state &&
133+
this.state.setupState === SetupState.BackupSecureFile && this.state.mnemonic && (
134+
<WalletSecureFilePassword
135+
mnemonic={this.state.mnemonic}
136+
onCancel={() => this.setState({ setupState: SetupState.NewWarned })}
137+
/>
138+
)}
139+
{this.state &&
140+
this.state.setupState === SetupState.RestoreWithSecureFile && (
141+
<WalletFileRestore
142+
onComplete={() => this.props.onComplete()}
143+
onCancel={() => this.setState({ setupState: SetupState.Restore })}
144+
onRestoreMnemonic={(words) => this.props.walletImportMnemonic(words)}
145+
/>
146+
)}
128147
{this.state && this.state.setupState === SetupState.CreatePassword && (
129148
<WalletPassword
130149
onComplete={() => this.props.onComplete()}
@@ -149,15 +168,7 @@ export class WalletSetup extends Component<WalletViewDispatchProps, WalletSetupS
149168
onComplete={() => this.props.onComplete()}
150169
onCancel={() => this.setState({ setupState: SetupState.Restore })}
151170
/>
152-
)}
153-
{this.state &&
154-
this.state.setupState === SetupState.RestoreWithSecureFile && (
155-
<WalletFileRestore
156-
onComplete={() => this.props.onComplete()}
157-
onCancel={() => this.setState({ setupState: SetupState.Restore })}
158-
onRestoreMnemonic={(words) => this.props.walletImportMnemonic(words)}
159-
/>
160-
)}
171+
)}
161172
</>
162173
);
163174
}

web/src/shared/ValidationResult.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export interface ValidationResult<T> {
2+
value: T;
3+
success: boolean;
4+
validationMessages: string[];
5+
isError: boolean
6+
}
7+
8+
interface NameIndictator {
9+
name: string;
10+
scope: string;
11+
}
12+
13+
export interface NameIndicatorWithValue<T> extends NameIndictator {
14+
value: T;
15+
}
16+
17+
export type NamedValue<T> = T extends void ? NameIndictator : NameIndicatorWithValue<T>;
18+
19+
export const createValidatedFailurePayload = <T>(fieldScope: string, fieldName: string, message: string, fieldValue: T, isError = false): NamedValue<ValidationResult<T>> => ({
20+
scope: fieldScope,
21+
name: fieldName,
22+
value: {
23+
success: false,
24+
validationMessages: [message],
25+
value: fieldValue,
26+
isError: isError
27+
}
28+
})

0 commit comments

Comments
 (0)