From 40dc395dffa9f1980b1340142543a83441e949e4 Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Wed, 14 Dec 2022 12:18:48 -0800 Subject: [PATCH] support updating subject on sealed channels in webapp --- net/web/src/api/setChannelSubject.js | 5 +- net/web/src/context/useChannelContext.hook.js | 24 ++++++++- net/web/src/session/details/Details.jsx | 31 +++++++++--- net/web/src/session/details/Details.styled.js | 3 -- .../src/session/details/useDetails.hook.js | 50 ++++++++++++++++--- 5 files changed, 90 insertions(+), 23 deletions(-) diff --git a/net/web/src/api/setChannelSubject.js b/net/web/src/api/setChannelSubject.js index 49c95e04..8dd6a080 100644 --- a/net/web/src/api/setChannelSubject.js +++ b/net/web/src/api/setChannelSubject.js @@ -1,8 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function setChannelSubject(token, channelId, subject ) { - let data = { subject }; - let params = { dataType: 'superbasic', data: JSON.stringify(data) }; +export async function setChannelSubject(token, channelId, dataType, data ) { + let params = { dataType, data: JSON.stringify(data) }; let channel = await fetchWithTimeout(`/content/channels/${channelId}/subject?agent=${token}`, { method: 'PUT', body: JSON.stringify(params)} ); checkResponse(channel); return await channel.json(); diff --git a/net/web/src/context/useChannelContext.hook.js b/net/web/src/context/useChannelContext.hook.js index d6f0d42f..5d59e211 100644 --- a/net/web/src/context/useChannelContext.hook.js +++ b/net/web/src/context/useChannelContext.hook.js @@ -48,7 +48,7 @@ export function useChannelContext() { else { let detail = await getChannelDetail(access.current, channel.id); cur.data.channelDetail = detail; - cur.data.unsealedSubject = null; + cur.data.unsealedChannel = null; } cur.data.detailRevision = channel.data.detailRevision; } @@ -151,7 +151,27 @@ export function useChannelContext() { }); }, setChannelSubject: async (channelId, subject) => { - return await setChannelSubject(access.current, channelId, subject); + return await setChannelSubject(access.current, channelId, 'superbasic', { subject }); + }, + setChannelSealedSubject: async (channelId, subject, sealKey) => { + const channel = channels.current.get(channelId); + + let { seals, subjectEncrypted, subjectIv } = JSON.parse(channel.data.channelDetail.data); + seals.forEach(seal => { + if (seal.publicKey === sealKey.public) { + let crypto = new JSEncrypt(); + crypto.setPrivateKey(sealKey.private); + const unsealedKey = crypto.decrypt(seal.sealedKey); + const key = CryptoJS.enc.Hex.parse(unsealedKey); + + const iv = CryptoJS.lib.WordArray.random(128 / 8); + const encrypted = CryptoJS.AES.encrypt(JSON.stringify({ subject }), key, { iv: iv }); + subjectEncrypted = encrypted.ciphertext.toString(CryptoJS.enc.Base64) + subjectIv = iv.toString(); + } + }); + const data = { subjectEncrypted, subjectIv, seals }; + return await setChannelSubject(access.current, channelId, 'sealed', data); }, setChannelCard: async (channelId, cardId) => { return await setChannelCard(access.current, channelId, cardId); diff --git a/net/web/src/session/details/Details.jsx b/net/web/src/session/details/Details.jsx index a1633fc2..0b6df911 100644 --- a/net/web/src/session/details/Details.jsx +++ b/net/web/src/session/details/Details.jsx @@ -7,6 +7,7 @@ import { EditOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; import { CardSelect } from '../cardSelect/CardSelect'; import { EditSubject } from './editSubject/EditSubject'; import { EditMembers } from './editMembers/EditMembers'; +import { UnlockOutlined, LockFilled } from '@ant-design/icons'; export function Details({ cardId, channelId, closeDetails, closeConversation, openContact }) { @@ -117,15 +118,31 @@ export function Details({ cardId, channelId, closeDetails, closeConversation, op
{ state.host && ( -
- -
{ state.subject }
- -
+
+ { state.locked && !state.unlocked && ( + + )} + { state.locked && state.unlocked && ( + + )} + { state.subject } + { state.editable && ( + + + + )}
)} { !state.host && ( -
{ state.subject }
+
+ { state.locked && !state.unlocked && ( + + )} + { state.locked && state.unlocked && ( + + )} + { state.subject } +
)} { state.host && (
host
@@ -139,7 +156,7 @@ export function Details({ cardId, channelId, closeDetails, closeConversation, op { state.host && (
Delete Topic
)} - { state.host && ( + { state.host && !state.locked && (
Edit Membership
)} { !state.host && ( diff --git a/net/web/src/session/details/Details.styled.js b/net/web/src/session/details/Details.styled.js index b23d6a2f..72c9dd13 100644 --- a/net/web/src/session/details/Details.styled.js +++ b/net/web/src/session/details/Details.styled.js @@ -90,9 +90,6 @@ export const DetailsWrapper = styled.div` padding-left: 16px; .edit { - display: flex; - flex-direction: row; - align-items: center; cursor: pointer; } diff --git a/net/web/src/session/details/useDetails.hook.js b/net/web/src/session/details/useDetails.hook.js index a0222bfe..74ba9e7e 100644 --- a/net/web/src/session/details/useDetails.hook.js +++ b/net/web/src/session/details/useDetails.hook.js @@ -1,6 +1,7 @@ import { useContext, useState, useEffect } from 'react'; import { CardContext } from 'context/CardContext'; import { ChannelContext } from 'context/ChannelContext'; +import { AccountContext } from 'context/AccountContext'; import { ViewportContext } from 'context/ViewportContext'; export function useDetails(cardId, channelId) { @@ -20,9 +21,13 @@ export function useDetails(cardId, channelId) { subjectUpdate: null, unknown: 0, display: null, + locked: false, + unlocked: false, + editable: false, }); const card = useContext(CardContext); + const account = useContext(AccountContext); const channel = useContext(ChannelContext); const viewport = useContext(ViewportContext); @@ -35,7 +40,7 @@ export function useDetails(cardId, channelId) { }, [viewport]); useEffect(() => { - let img, subject, subjectUpdate, host, started, contacts + let img, subject, subjectUpdate, host, started, contacts, locked, unlocked, editable; let chan; if (cardId) { const cardChan = card.state.cards.get(cardId); @@ -60,10 +65,34 @@ export function useDetails(cardId, channelId) { img = 'team'; subject = 'Conversation' } - const parsed = JSON.parse(chan.data.channelDetail.data); - if (parsed.subject) { - subject = parsed.subject; - subjectUpdate = subject; + if (chan.data.channelDetail.dataType === 'sealed') { + editable = false; + try { + const seals = JSON.parse(chan.data.channelDetail.data).seals; + seals.forEach(seal => { + if (account.state.sealKey.public === seal.publicKey) { + editable = true; + } + }); + } + catch (err) { + console.log(err); + } + locked = true; + unlocked = chan.data.unsealedChannel != null; + if (chan.data.unsealedChannel?.subject) { + subject = chan.data.unsealedChannel.subject; + subjectUpdate = subject; + } + } + else { + locked = false; + editable = (chan.cardId == null); + const parsed = JSON.parse(chan.data.channelDetail.data); + if (parsed.subject) { + subject = parsed.subject; + subjectUpdate = subject; + } } const date = new Date(chan.data.channelDetail.created * 1000); const now = new Date(); @@ -96,8 +125,8 @@ export function useDetails(cardId, channelId) { } }); - updateState({ img, subject, host, started, contacts, members, unknown, subjectUpdate }); - }, [cardId, channelId, card, channel]); + updateState({ img, subject, host, started, contacts, members, unknown, subjectUpdate, locked, unlocked, editable }); + }, [cardId, channelId, card, channel, account]); const actions = { setEditSubject: () => { @@ -113,7 +142,12 @@ export function useDetails(cardId, channelId) { if (!state.busy) { try { updateState({ busy: true }); - channel.actions.setChannelSubject(channelId, state.subjectUpdate); + if (state.locked) { + channel.actions.setChannelSealedSubject(channelId, state.subjectUpdate, account.state.sealKey); + } + else { + channel.actions.setChannelSubject(channelId, state.subjectUpdate); + } updateState({ busy: false }); } catch(err) {