JournalMembers: change to be a function component.

master
Tom Hacohen 4 years ago
parent 6e4689f697
commit 6d18d494a7

@ -12,9 +12,8 @@ import ConfirmationDialog from "../widgets/ConfirmationDialog";
import PrettyFingerprint from "../widgets/PrettyFingerprint"; import PrettyFingerprint from "../widgets/PrettyFingerprint";
import * as EteSync from "etesync"; import * as EteSync from "etesync";
import { CredentialsData } from "../store";
import { handleInputChange } from "../helpers"; import { CredentialsData } from "../store";
interface PropsType { interface PropsType {
etesync: CredentialsData; etesync: CredentialsData;
@ -23,127 +22,104 @@ interface PropsType {
onClose: () => void; onClose: () => void;
} }
class JournalMemberAddDialog extends React.PureComponent<PropsType> { export default function JournalMemberAddDialog(props: PropsType) {
public state = { const [addUser, setAddUser] = React.useState("");
addUser: "", const [publicKey, setPublicKey] = React.useState("");
publicKey: "", const [readOnly, setReadOnly] = React.useState(false);
readOnly: false, const [userChosen, setUserChosen] = React.useState(false);
userChosen: false, const [error, setError] = React.useState<Error>();
error: undefined as Error | undefined,
};
private handleInputChange: any;
constructor(props: PropsType) {
super(props);
this.handleInputChange = handleInputChange(this);
this.onAddRequest = this.onAddRequest.bind(this);
this.onOk = this.onOk.bind(this);
}
public render() {
const { onClose } = this.props;
const { addUser, userChosen, publicKey, error } = this.state;
if (error) {
return (
<>
<ConfirmationDialog
title="Error adding member"
labelOk="OK"
open
onOk={onClose}
onCancel={onClose}
>
User ({addUser}) not found. Have they setup their encryption password from one of the apps?
</ConfirmationDialog>
</>
);
}
if (publicKey) { function onAddRequest(_user: string) {
return ( setUserChosen(true);
<>
<ConfirmationDialog
title="Verify security fingerprint"
labelOk="OK"
open
onOk={this.onOk}
onCancel={onClose}
>
<p>
Verify {addUser}'s security fingerprint to ensure the encryption is secure.
</p>
<div style={{ textAlign: "center" }}>
<PrettyFingerprint publicKey={publicKey} />
</div>
</ConfirmationDialog>
</>
);
} else {
return (
<>
<ConfirmationDialog
title="Add member"
labelOk="OK"
open={!userChosen}
onOk={this.onAddRequest}
onCancel={onClose}
>
{userChosen ?
<LoadingIndicator />
:
<>
<TextField
name="addUser"
type="email"
placeholder="User email"
style={{ width: "100%" }}
value={addUser}
onChange={this.handleInputChange}
/>
<FormControlLabel
control={
<Checkbox
checked={this.state.readOnly}
onChange={(event) => this.setState({ readOnly: event.target.checked })}
/>
}
label="Read only?"
/>
</>
}
</ConfirmationDialog>
</>
);
}
}
private onAddRequest(_user: string) {
this.setState({
userChosen: true,
});
const { etesync } = this.props; const { etesync } = props;
const { addUser } = this.state;
const creds = etesync.credentials; const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl; const apiBase = etesync.serviceApiUrl;
const userInfoManager = new EteSync.UserInfoManager(creds, apiBase); const userInfoManager = new EteSync.UserInfoManager(creds, apiBase);
userInfoManager.fetch(addUser).then((userInfo) => { userInfoManager.fetch(addUser).then((userInfo) => {
this.setState({ setPublicKey(userInfo.publicKey);
publicKey: userInfo.publicKey,
});
}).catch((error) => { }).catch((error) => {
this.setState({ error }); setError(error);
}); });
} }
private onOk() { function onOk() {
const { addUser, publicKey, readOnly } = this.state; props.onOk(addUser, publicKey, readOnly);
this.props.onOk(addUser, publicKey, readOnly);
} }
}
export default JournalMemberAddDialog; const { onClose } = props;
if (error) {
return (
<>
<ConfirmationDialog
title="Error adding member"
labelOk="OK"
open
onOk={onClose}
onCancel={onClose}
>
User ({addUser}) not found. Have they setup their encryption password from one of the apps?
</ConfirmationDialog>
</>
);
}
if (publicKey) {
return (
<>
<ConfirmationDialog
title="Verify security fingerprint"
labelOk="OK"
open
onOk={onOk}
onCancel={onClose}
>
<p>
Verify {addUser}'s security fingerprint to ensure the encryption is secure.
</p>
<div style={{ textAlign: "center" }}>
<PrettyFingerprint publicKey={publicKey} />
</div>
</ConfirmationDialog>
</>
);
} else {
return (
<>
<ConfirmationDialog
title="Add member"
labelOk="OK"
open={!userChosen}
onOk={onAddRequest}
onCancel={onClose}
>
{userChosen ?
<LoadingIndicator />
:
<>
<TextField
name="addUser"
type="email"
placeholder="User email"
style={{ width: "100%" }}
value={addUser}
onChange={(ev) => setAddUser(ev.target.value)}
/>
<FormControlLabel
control={
<Checkbox
checked={readOnly}
onChange={(event) => setReadOnly(event.target.checked)}
/>
}
label="Read only?"
/>
</>
}
</ConfirmationDialog>
</>
);
}
}

@ -6,7 +6,6 @@ import sjcl from "sjcl";
import { List, ListItem } from "../widgets/List"; import { List, ListItem } from "../widgets/List";
import { Theme, withTheme } from "@material-ui/core/styles";
import IconMemberAdd from "@material-ui/icons/PersonAdd"; import IconMemberAdd from "@material-ui/icons/PersonAdd";
import VisibilityIcon from "@material-ui/icons/Visibility"; import VisibilityIcon from "@material-ui/icons/Visibility";
@ -28,140 +27,48 @@ interface PropsType {
userInfo: UserInfoData; userInfo: UserInfoData;
} }
interface PropsTypeInner extends PropsType { export default function JournalMembers(props: PropsType) {
theme: Theme; const [members, setMembers] = React.useState<EteSync.JournalMemberJson[] | null>(null);
} const [revokeUser, setRevokeUser] = React.useState<string | null>(null);
const [addMemberOpen, setAddMemberOpen] = React.useState(false);
class JournalMembers extends React.PureComponent<PropsTypeInner> {
public state = {
members: null as EteSync.JournalMemberJson[] | null,
revokeUser: null as string | null,
addMemberOpen: false,
};
constructor(props: PropsTypeInner) {
super(props);
this.onRevokeRequest = this.onRevokeRequest.bind(this);
this.onRevokeDo = this.onRevokeDo.bind(this);
this.onMemberAdd = this.onMemberAdd.bind(this);
}
public render() {
const { syncJournal } = this.props;
const { members, revokeUser, addMemberOpen } = this.state;
const info = syncJournal.collection; function fetchMembers() {
const sharingAllowed = syncJournal.journal.version > 1; const { etesync, syncJournal } = props;
return (
<>
<AppBarOverride title={`${info.displayName} - Members`} />
<Container style={{ maxWidth: "30rem" }}>
{members ?
<List>
<ListItem rightIcon={<IconMemberAdd />} onClick={() => this.setState({ addMemberOpen: true })}>
Add member
</ListItem>
{(members.length > 0 ?
members.map((member) => (
<ListItem
key={member.user}
onClick={() => this.onRevokeRequest(member.user)}
rightIcon={(member.readOnly) ? (<VisibilityIcon />) : undefined}
>
{member.user}
</ListItem>
))
:
<ListItem>
No members
</ListItem>
)}
</List>
:
<LoadingIndicator />
}
</Container>
<ConfirmationDialog
title="Remove member"
labelOk="OK"
open={revokeUser !== null}
onOk={this.onRevokeDo}
onCancel={() => this.setState({ revokeUser: null })}
>
Would you like to revoke {revokeUser}'s access?<br />
Please be advised that a malicious user would potentially be able to retain access to encryption keys. Please refer to the FAQ for more information.
</ConfirmationDialog>
{addMemberOpen &&
(sharingAllowed ?
<JournalMemberAddDialog
etesync={this.props.etesync}
info={info}
onOk={this.onMemberAdd}
onClose={() => this.setState({ addMemberOpen: false })}
/>
:
<ConfirmationDialog
title="Now Allowed"
labelOk="OK"
open
onOk={() => this.setState({ addMemberOpen: false })}
onClose={() => this.setState({ addMemberOpen: false })}
>
Sharing of old-style journals is not allowed. In order to share this journal, create a new one, and copy its contents over using the "import" dialog. If you are experiencing any issues, please contact support.
</ConfirmationDialog>
)
}
</>
);
}
public componentDidMount() {
this.fetchMembers();
}
private fetchMembers() {
const { etesync, syncJournal } = this.props;
const info = syncJournal.collection; const info = syncJournal.collection;
const creds = etesync.credentials; const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl; const apiBase = etesync.serviceApiUrl;
const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, info.uid); const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, info.uid);
journalMembersManager.list().then((members) => { journalMembersManager.list().then((members) => {
this.setState({ setMembers(members);
members,
});
}); });
} }
private onRevokeRequest(user: string) { React.useEffect(() => {
this.setState({ fetchMembers();
revokeUser: user, }, []);
});
function onRevokeRequest(user: string) {
setRevokeUser(user);
} }
private onRevokeDo() { function onRevokeDo() {
const { etesync, syncJournal } = this.props; const { etesync, syncJournal } = props;
const { revokeUser } = this.state;
const info = syncJournal.collection; const info = syncJournal.collection;
const creds = etesync.credentials; const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl; const apiBase = etesync.serviceApiUrl;
const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, info.uid); const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, info.uid);
journalMembersManager.delete({ user: revokeUser!, key: "" }).then(() => { journalMembersManager.delete({ user: revokeUser!, key: "" }).then(() => {
this.fetchMembers(); fetchMembers();
});
this.setState({
revokeUser: null,
}); });
setRevokeUser(null);
} }
private onMemberAdd(user: string, publicKey: string, readOnly: boolean) { function onMemberAdd(user: string, publicKey: string, readOnly: boolean) {
const { etesync, syncJournal, userInfo } = this.props; const { etesync, syncJournal, userInfo } = props;
const journal = syncJournal.journal; const journal = syncJournal.journal;
const derived = this.props.etesync.encryptionKey; const derived = props.etesync.encryptionKey;
const keyPair = userInfo.getKeyPair(userInfo.getCryptoManager(derived)); const keyPair = userInfo.getKeyPair(userInfo.getCryptoManager(derived));
const cryptoManager = journal.getCryptoManager(derived, keyPair); const cryptoManager = journal.getCryptoManager(derived, keyPair);
@ -173,12 +80,76 @@ class JournalMembers extends React.PureComponent<PropsTypeInner> {
const apiBase = etesync.serviceApiUrl; const apiBase = etesync.serviceApiUrl;
const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, journal.uid); const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, journal.uid);
journalMembersManager.create({ user, key: encryptedKey, readOnly }).then(() => { journalMembersManager.create({ user, key: encryptedKey, readOnly }).then(() => {
this.fetchMembers(); fetchMembers();
});
this.setState({
addMemberOpen: false,
}); });
setAddMemberOpen(false);
} }
}
export default withTheme(JournalMembers); const { syncJournal } = props;
const info = syncJournal.collection;
const sharingAllowed = syncJournal.journal.version > 1;
return (
<>
<AppBarOverride title={`${info.displayName} - Members`} />
<Container style={{ maxWidth: "30rem" }}>
{members ?
<List>
<ListItem rightIcon={<IconMemberAdd />} onClick={() => setAddMemberOpen(true)}>
Add member
</ListItem>
{(members.length > 0 ?
members.map((member) => (
<ListItem
key={member.user}
onClick={() => onRevokeRequest(member.user)}
rightIcon={(member.readOnly) ? (<VisibilityIcon />) : undefined}
>
{member.user}
</ListItem>
))
:
<ListItem>
No members
</ListItem>
)}
</List>
:
<LoadingIndicator />
}
</Container>
<ConfirmationDialog
title="Remove member"
labelOk="OK"
open={revokeUser !== null}
onOk={onRevokeDo}
onCancel={() => setRevokeUser(null)}
>
Would you like to revoke {revokeUser}'s access?<br />
Please be advised that a malicious user would potentially be able to retain access to encryption keys. Please refer to the FAQ for more information.
</ConfirmationDialog>
{addMemberOpen &&
(sharingAllowed ?
<JournalMemberAddDialog
etesync={props.etesync}
info={info}
onOk={onMemberAdd}
onClose={() => setAddMemberOpen(false)}
/>
:
<ConfirmationDialog
title="Now Allowed"
labelOk="OK"
open
onOk={() => setAddMemberOpen(false)}
onClose={() => setAddMemberOpen(false)}
>
Sharing of old-style journals is not allowed. In order to share this journal, create a new one, and copy its contents over using the "import" dialog. If you are experiencing any issues, please contact support.
</ConfirmationDialog>
)
}
</>
);
}

Loading…
Cancel
Save