|
|
|
@ -6,7 +6,6 @@ import sjcl from "sjcl";
|
|
|
|
|
|
|
|
|
|
import { List, ListItem } from "../widgets/List";
|
|
|
|
|
|
|
|
|
|
import { Theme, withTheme } from "@material-ui/core/styles";
|
|
|
|
|
import IconMemberAdd from "@material-ui/icons/PersonAdd";
|
|
|
|
|
import VisibilityIcon from "@material-ui/icons/Visibility";
|
|
|
|
|
|
|
|
|
@ -28,28 +27,65 @@ interface PropsType {
|
|
|
|
|
userInfo: UserInfoData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface PropsTypeInner extends PropsType {
|
|
|
|
|
theme: Theme;
|
|
|
|
|
}
|
|
|
|
|
export default function JournalMembers(props: PropsType) {
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
fetchMembers();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
function onRevokeRequest(user: string) {
|
|
|
|
|
setRevokeUser(user);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onRevokeDo() {
|
|
|
|
|
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.delete({ user: revokeUser!, key: "" }).then(() => {
|
|
|
|
|
fetchMembers();
|
|
|
|
|
});
|
|
|
|
|
setRevokeUser(null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onMemberAdd(user: string, publicKey: string, readOnly: boolean) {
|
|
|
|
|
const { etesync, syncJournal, userInfo } = props;
|
|
|
|
|
const journal = syncJournal.journal;
|
|
|
|
|
const derived = props.etesync.encryptionKey;
|
|
|
|
|
|
|
|
|
|
class JournalMembers extends React.PureComponent<PropsTypeInner> {
|
|
|
|
|
public state = {
|
|
|
|
|
members: null as EteSync.JournalMemberJson[] | null,
|
|
|
|
|
revokeUser: null as string | null,
|
|
|
|
|
addMemberOpen: false,
|
|
|
|
|
};
|
|
|
|
|
const keyPair = userInfo.getKeyPair(userInfo.getCryptoManager(derived));
|
|
|
|
|
const cryptoManager = journal.getCryptoManager(derived, keyPair);
|
|
|
|
|
|
|
|
|
|
constructor(props: PropsTypeInner) {
|
|
|
|
|
super(props);
|
|
|
|
|
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)));
|
|
|
|
|
|
|
|
|
|
this.onRevokeRequest = this.onRevokeRequest.bind(this);
|
|
|
|
|
this.onRevokeDo = this.onRevokeDo.bind(this);
|
|
|
|
|
this.onMemberAdd = this.onMemberAdd.bind(this);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public render() {
|
|
|
|
|
const { syncJournal } = this.props;
|
|
|
|
|
const { members, revokeUser, addMemberOpen } = this.state;
|
|
|
|
|
const { syncJournal } = props;
|
|
|
|
|
|
|
|
|
|
const info = syncJournal.collection;
|
|
|
|
|
const sharingAllowed = syncJournal.journal.version > 1;
|
|
|
|
@ -60,14 +96,14 @@ class JournalMembers extends React.PureComponent<PropsTypeInner> {
|
|
|
|
|
<Container style={{ maxWidth: "30rem" }}>
|
|
|
|
|
{members ?
|
|
|
|
|
<List>
|
|
|
|
|
<ListItem rightIcon={<IconMemberAdd />} onClick={() => this.setState({ addMemberOpen: true })}>
|
|
|
|
|
<ListItem rightIcon={<IconMemberAdd />} onClick={() => setAddMemberOpen(true)}>
|
|
|
|
|
Add member
|
|
|
|
|
</ListItem>
|
|
|
|
|
{(members.length > 0 ?
|
|
|
|
|
members.map((member) => (
|
|
|
|
|
<ListItem
|
|
|
|
|
key={member.user}
|
|
|
|
|
onClick={() => this.onRevokeRequest(member.user)}
|
|
|
|
|
onClick={() => onRevokeRequest(member.user)}
|
|
|
|
|
rightIcon={(member.readOnly) ? (<VisibilityIcon />) : undefined}
|
|
|
|
|
>
|
|
|
|
|
{member.user}
|
|
|
|
@ -87,8 +123,8 @@ class JournalMembers extends React.PureComponent<PropsTypeInner> {
|
|
|
|
|
title="Remove member"
|
|
|
|
|
labelOk="OK"
|
|
|
|
|
open={revokeUser !== null}
|
|
|
|
|
onOk={this.onRevokeDo}
|
|
|
|
|
onCancel={() => this.setState({ 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.
|
|
|
|
@ -97,18 +133,18 @@ class JournalMembers extends React.PureComponent<PropsTypeInner> {
|
|
|
|
|
{addMemberOpen &&
|
|
|
|
|
(sharingAllowed ?
|
|
|
|
|
<JournalMemberAddDialog
|
|
|
|
|
etesync={this.props.etesync}
|
|
|
|
|
etesync={props.etesync}
|
|
|
|
|
info={info}
|
|
|
|
|
onOk={this.onMemberAdd}
|
|
|
|
|
onClose={() => this.setState({ addMemberOpen: false })}
|
|
|
|
|
onOk={onMemberAdd}
|
|
|
|
|
onClose={() => setAddMemberOpen(false)}
|
|
|
|
|
/>
|
|
|
|
|
:
|
|
|
|
|
<ConfirmationDialog
|
|
|
|
|
title="Now Allowed"
|
|
|
|
|
labelOk="OK"
|
|
|
|
|
open
|
|
|
|
|
onOk={() => this.setState({ addMemberOpen: false })}
|
|
|
|
|
onClose={() => this.setState({ addMemberOpen: false })}
|
|
|
|
|
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>
|
|
|
|
@ -116,69 +152,4 @@ 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);
|
|
|
|
|