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,28 +22,33 @@ 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; function onAddRequest(_user: string) {
setUserChosen(true);
constructor(props: PropsType) { const { etesync } = props;
super(props);
this.handleInputChange = handleInputChange(this); const creds = etesync.credentials;
this.onAddRequest = this.onAddRequest.bind(this); const apiBase = etesync.serviceApiUrl;
this.onOk = this.onOk.bind(this); const userInfoManager = new EteSync.UserInfoManager(creds, apiBase);
userInfoManager.fetch(addUser).then((userInfo) => {
setPublicKey(userInfo.publicKey);
}).catch((error) => {
setError(error);
});
}
function onOk() {
props.onOk(addUser, publicKey, readOnly);
} }
public render() { const { onClose } = props;
const { onClose } = this.props;
const { addUser, userChosen, publicKey, error } = this.state;
if (error) { if (error) {
return ( return (
@ -69,7 +73,7 @@ class JournalMemberAddDialog extends React.PureComponent<PropsType> {
title="Verify security fingerprint" title="Verify security fingerprint"
labelOk="OK" labelOk="OK"
open open
onOk={this.onOk} onOk={onOk}
onCancel={onClose} onCancel={onClose}
> >
<p> <p>
@ -88,7 +92,7 @@ class JournalMemberAddDialog extends React.PureComponent<PropsType> {
title="Add member" title="Add member"
labelOk="OK" labelOk="OK"
open={!userChosen} open={!userChosen}
onOk={this.onAddRequest} onOk={onAddRequest}
onCancel={onClose} onCancel={onClose}
> >
{userChosen ? {userChosen ?
@ -101,13 +105,13 @@ class JournalMemberAddDialog extends React.PureComponent<PropsType> {
placeholder="User email" placeholder="User email"
style={{ width: "100%" }} style={{ width: "100%" }}
value={addUser} value={addUser}
onChange={this.handleInputChange} onChange={(ev) => setAddUser(ev.target.value)}
/> />
<FormControlLabel <FormControlLabel
control={ control={
<Checkbox <Checkbox
checked={this.state.readOnly} checked={readOnly}
onChange={(event) => this.setState({ readOnly: event.target.checked })} onChange={(event) => setReadOnly(event.target.checked)}
/> />
} }
label="Read only?" label="Read only?"
@ -119,31 +123,3 @@ class JournalMemberAddDialog extends React.PureComponent<PropsType> {
); );
} }
} }
private onAddRequest(_user: string) {
this.setState({
userChosen: true,
});
const { etesync } = this.props;
const { addUser } = this.state;
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const userInfoManager = new EteSync.UserInfoManager(creds, apiBase);
userInfoManager.fetch(addUser).then((userInfo) => {
this.setState({
publicKey: userInfo.publicKey,
});
}).catch((error) => {
this.setState({ error });
});
}
private onOk() {
const { addUser, publicKey, readOnly } = this.state;
this.props.onOk(addUser, publicKey, readOnly);
}
}
export default JournalMemberAddDialog;

@ -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,28 +27,65 @@ 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);
function fetchMembers() {
const { etesync, syncJournal } = props;
const info = syncJournal.collection;
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, info.uid);
journalMembersManager.list().then((members) => {
setMembers(members);
});
} }
class JournalMembers extends React.PureComponent<PropsTypeInner> { React.useEffect(() => {
public state = { fetchMembers();
members: null as EteSync.JournalMemberJson[] | null, }, []);
revokeUser: null as string | null,
addMemberOpen: false,
};
constructor(props: PropsTypeInner) { function onRevokeRequest(user: string) {
super(props); setRevokeUser(user);
}
this.onRevokeRequest = this.onRevokeRequest.bind(this); function onRevokeDo() {
this.onRevokeDo = this.onRevokeDo.bind(this); const { etesync, syncJournal } = props;
this.onMemberAdd = this.onMemberAdd.bind(this); const info = syncJournal.collection;
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, info.uid);
journalMembersManager.delete({ user: revokeUser!, key: "" }).then(() => {
fetchMembers();
});
setRevokeUser(null);
} }
public render() { function onMemberAdd(user: string, publicKey: string, readOnly: boolean) {
const { syncJournal } = this.props; const { etesync, syncJournal, userInfo } = props;
const { members, revokeUser, addMemberOpen } = this.state; const journal = syncJournal.journal;
const derived = props.etesync.encryptionKey;
const keyPair = userInfo.getKeyPair(userInfo.getCryptoManager(derived));
const cryptoManager = journal.getCryptoManager(derived, keyPair);
const pubkeyBytes = sjcl.codec.bytes.fromBits(sjcl.codec.base64.toBits(publicKey));
const encryptedKey = sjcl.codec.base64.fromBits(sjcl.codec.bytes.toBits(cryptoManager.getEncryptedKey(keyPair, pubkeyBytes)));
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, journal.uid);
journalMembersManager.create({ user, key: encryptedKey, readOnly }).then(() => {
fetchMembers();
});
setAddMemberOpen(false);
}
const { syncJournal } = props;
const info = syncJournal.collection; const info = syncJournal.collection;
const sharingAllowed = syncJournal.journal.version > 1; const sharingAllowed = syncJournal.journal.version > 1;
@ -60,14 +96,14 @@ class JournalMembers extends React.PureComponent<PropsTypeInner> {
<Container style={{ maxWidth: "30rem" }}> <Container style={{ maxWidth: "30rem" }}>
{members ? {members ?
<List> <List>
<ListItem rightIcon={<IconMemberAdd />} onClick={() => this.setState({ addMemberOpen: true })}> <ListItem rightIcon={<IconMemberAdd />} onClick={() => setAddMemberOpen(true)}>
Add member Add member
</ListItem> </ListItem>
{(members.length > 0 ? {(members.length > 0 ?
members.map((member) => ( members.map((member) => (
<ListItem <ListItem
key={member.user} key={member.user}
onClick={() => this.onRevokeRequest(member.user)} onClick={() => onRevokeRequest(member.user)}
rightIcon={(member.readOnly) ? (<VisibilityIcon />) : undefined} rightIcon={(member.readOnly) ? (<VisibilityIcon />) : undefined}
> >
{member.user} {member.user}
@ -87,8 +123,8 @@ class JournalMembers extends React.PureComponent<PropsTypeInner> {
title="Remove member" title="Remove member"
labelOk="OK" labelOk="OK"
open={revokeUser !== null} open={revokeUser !== null}
onOk={this.onRevokeDo} onOk={onRevokeDo}
onCancel={() => this.setState({ revokeUser: null })} onCancel={() => setRevokeUser(null)}
> >
Would you like to revoke {revokeUser}'s access?<br /> 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. 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.
@ -97,18 +133,18 @@ class JournalMembers extends React.PureComponent<PropsTypeInner> {
{addMemberOpen && {addMemberOpen &&
(sharingAllowed ? (sharingAllowed ?
<JournalMemberAddDialog <JournalMemberAddDialog
etesync={this.props.etesync} etesync={props.etesync}
info={info} info={info}
onOk={this.onMemberAdd} onOk={onMemberAdd}
onClose={() => this.setState({ addMemberOpen: false })} onClose={() => setAddMemberOpen(false)}
/> />
: :
<ConfirmationDialog <ConfirmationDialog
title="Now Allowed" title="Now Allowed"
labelOk="OK" labelOk="OK"
open open
onOk={() => this.setState({ addMemberOpen: false })} onOk={() => setAddMemberOpen(false)}
onClose={() => this.setState({ addMemberOpen: 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. 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> </ConfirmationDialog>
@ -117,68 +153,3 @@ class JournalMembers extends React.PureComponent<PropsTypeInner> {
</> </>
); );
} }
public componentDidMount() {
this.fetchMembers();
}
private fetchMembers() {
const { etesync, syncJournal } = this.props;
const info = syncJournal.collection;
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, info.uid);
journalMembersManager.list().then((members) => {
this.setState({
members,
});
});
}
private onRevokeRequest(user: string) {
this.setState({
revokeUser: user,
});
}
private onRevokeDo() {
const { etesync, syncJournal } = this.props;
const { revokeUser } = this.state;
const info = syncJournal.collection;
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, info.uid);
journalMembersManager.delete({ user: revokeUser!, key: "" }).then(() => {
this.fetchMembers();
});
this.setState({
revokeUser: null,
});
}
private onMemberAdd(user: string, publicKey: string, readOnly: boolean) {
const { etesync, syncJournal, userInfo } = this.props;
const journal = syncJournal.journal;
const derived = this.props.etesync.encryptionKey;
const keyPair = userInfo.getKeyPair(userInfo.getCryptoManager(derived));
const cryptoManager = journal.getCryptoManager(derived, keyPair);
const pubkeyBytes = sjcl.codec.bytes.fromBits(sjcl.codec.base64.toBits(publicKey));
const encryptedKey = sjcl.codec.base64.fromBits(sjcl.codec.bytes.toBits(cryptoManager.getEncryptedKey(keyPair, pubkeyBytes)));
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, journal.uid);
journalMembersManager.create({ user, key: encryptedKey, readOnly }).then(() => {
this.fetchMembers();
});
this.setState({
addMemberOpen: false,
});
}
}
export default withTheme(JournalMembers);

Loading…
Cancel
Save