From 8a6411ff215280a0a802003c4b15cbc7b33c0c6d Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Fri, 16 Dec 2022 12:02:12 -0800 Subject: [PATCH] support editing sealed messages in webapp --- net/web/src/api/setChannelTopicSubject.js | 4 +- net/web/src/context/useCardContext.hook.js | 56 +++++++++----- net/web/src/context/useChannelContext.hook.js | 74 ++++++++++++------- .../context/useConversationContext.hook.js | 14 +++- .../src/session/channels/useChannels.hook.js | 6 +- .../src/session/conversation/Conversation.jsx | 2 +- .../conversation/topicItem/TopicItem.jsx | 4 +- .../topicItem/useTopicItem.hook.js | 19 ++++- 8 files changed, 121 insertions(+), 58 deletions(-) diff --git a/net/web/src/api/setChannelTopicSubject.js b/net/web/src/api/setChannelTopicSubject.js index 3ef43810..22410919 100644 --- a/net/web/src/api/setChannelTopicSubject.js +++ b/net/web/src/api/setChannelTopicSubject.js @@ -1,9 +1,9 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function setChannelTopicSubject(token, channelId, topicId, data) { +export async function setChannelTopicSubject(token, channelId, topicId, datatype, data) { let subject = { data: JSON.stringify(data, (key, value) => { if (value !== null) return value - }), datatype: 'superbasictopic' }; + }), datatype }; let channel = await fetchWithTimeout(`/content/channels/${channelId}/topics/${topicId}/subject?agent=${token}&confirm=true`, { method: 'PUT', body: JSON.stringify(subject) }); diff --git a/net/web/src/context/useCardContext.hook.js b/net/web/src/context/useCardContext.hook.js index 7b121f24..79359170 100644 --- a/net/web/src/context/useCardContext.hook.js +++ b/net/web/src/context/useCardContext.hook.js @@ -278,22 +278,24 @@ export function useCardContext() { const channel = card.channels.get(channelId); const { subjectEncrypted, subjectIv, seals } = 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 iv = CryptoJS.enc.Hex.parse(subjectIv); - const key = CryptoJS.enc.Hex.parse(unsealedKey); - const enc = CryptoJS.enc.Base64.parse(subjectEncrypted); - let cipher = CryptoJS.lib.CipherParams.create({ ciphertext: enc, iv: iv }); - const dec = CryptoJS.AES.decrypt(cipher, key, { iv: iv }); - channel.data.unsealedChannel = JSON.parse(dec.toString(CryptoJS.enc.Utf8)); - card.channels.set(channel.id, { ...channel }); - cards.current.set(cardId, { ...card }); - updateState({ cards: cards.current }); - } - }); + if (seals?.length) { + seals.forEach(seal => { + if (seal.publicKey === sealKey.public) { + let crypto = new JSEncrypt(); + crypto.setPrivateKey(sealKey.private); + const unsealedKey = crypto.decrypt(seal.sealedKey); + const iv = CryptoJS.enc.Hex.parse(subjectIv); + const key = CryptoJS.enc.Hex.parse(unsealedKey); + const enc = CryptoJS.enc.Base64.parse(subjectEncrypted); + let cipher = CryptoJS.lib.CipherParams.create({ ciphertext: enc, iv: iv }); + const dec = CryptoJS.AES.decrypt(cipher, key, { iv: iv }); + channel.data.unsealedChannel = JSON.parse(dec.toString(CryptoJS.enc.Utf8)); + card.channels.set(channel.id, { ...channel }); + cards.current.set(cardId, { ...card }); + updateState({ cards: cards.current }); + } + }); + } }, removeChannel: async (cardId, channelId) => { let { cardProfile, cardDetail } = cards.current.get(cardId).data; @@ -318,7 +320,27 @@ export function useCardContext() { let { cardProfile, cardDetail } = cards.current.get(cardId).data; let token = cardProfile.guid + '.' + cardDetail.token; let node = cardProfile.node; - await setContactChannelTopicSubject(node, token, channelId, topicId, data); + await setContactChannelTopicSubject(node, token, channelId, topicId, 'superbasictopic', data); + try { + resync.current.push(cardId); + await setCards(null); + } + catch (err) { + console.log(err); + } + }, + setSealedChannelTopicSubject: async (cardId, channelId, topicId, data, sealKey) => { +console.log("SETTING:", data, sealKey); + + let { cardProfile, cardDetail } = cards.current.get(cardId).data; + let token = cardProfile.guid + '.' + cardDetail.token; + let node = cardProfile.node; + const iv = CryptoJS.lib.WordArray.random(128 / 8); + const key = CryptoJS.enc.Hex.parse(sealKey); + const encrypted = CryptoJS.AES.encrypt(JSON.stringify(data), key, { iv: iv }); + const messageEncrypted = encrypted.ciphertext.toString(CryptoJS.enc.Base64) + const messageIv = iv.toString(); + await setContactChannelTopicSubject(node, token, channelId, topicId, 'sealedtopic', { messageEncrypted, messageIv }); try { resync.current.push(cardId); await setCards(null); diff --git a/net/web/src/context/useChannelContext.hook.js b/net/web/src/context/useChannelContext.hook.js index 47db14f1..7d0ede2e 100644 --- a/net/web/src/context/useChannelContext.hook.js +++ b/net/web/src/context/useChannelContext.hook.js @@ -134,21 +134,23 @@ export function useChannelContext() { const channel = channels.current.get(channelId); const { subjectEncrypted, subjectIv, seals } = 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 iv = CryptoJS.enc.Hex.parse(subjectIv); - const key = CryptoJS.enc.Hex.parse(unsealedKey); - const enc = CryptoJS.enc.Base64.parse(subjectEncrypted); - let cipher = CryptoJS.lib.CipherParams.create({ ciphertext: enc, iv: iv }); - const dec = CryptoJS.AES.decrypt(cipher, key, { iv: iv }); - channel.data.unsealedChannel = JSON.parse(dec.toString(CryptoJS.enc.Utf8)); - channels.current.set(channel.id, { ...channel }); - updateState({ channels: channels.current }); - } - }); + if (seals?.length) { + seals.forEach(seal => { + if (seal.publicKey === sealKey.public) { + let crypto = new JSEncrypt(); + crypto.setPrivateKey(sealKey.private); + const unsealedKey = crypto.decrypt(seal.sealedKey); + const iv = CryptoJS.enc.Hex.parse(subjectIv); + const key = CryptoJS.enc.Hex.parse(unsealedKey); + const enc = CryptoJS.enc.Base64.parse(subjectEncrypted); + let cipher = CryptoJS.lib.CipherParams.create({ ciphertext: enc, iv: iv }); + const dec = CryptoJS.AES.decrypt(cipher, key, { iv: iv }); + channel.data.unsealedChannel = JSON.parse(dec.toString(CryptoJS.enc.Utf8)); + channels.current.set(channel.id, { ...channel }); + updateState({ channels: channels.current }); + } + }); + } }, setChannelSubject: async (channelId, subject) => { return await setChannelSubject(access.current, channelId, 'superbasic', { subject }); @@ -157,19 +159,21 @@ export function useChannelContext() { 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); + if (seals?.length) { + 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 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); }, @@ -192,7 +196,21 @@ export function useChannelContext() { } }, setChannelTopicSubject: async (channelId, topicId, data) => { - await setChannelTopicSubject(access.current, channelId, topicId, data); + await setChannelTopicSubject(access.current, channelId, topicId, 'superbasictopic', data); + try { + await setChannels(null); + } + catch (err) { + console.log(err); + } + }, + setSealedChannelTopicSubject: async (channelId, topicId, data, sealKey) => { + const iv = CryptoJS.lib.WordArray.random(128 / 8); + const key = CryptoJS.enc.Hex.parse(sealKey); + const encrypted = CryptoJS.AES.encrypt(JSON.stringify({ message: data }), key, { iv: iv }); + const messageEncrypted = encrypted.ciphertext.toString(CryptoJS.enc.Base64) + const messageIv = iv.toString(); + await setChannelTopicSubject(access.current, channelId, topicId, 'sealedtopic', { messageEncrypted, messageIv }); try { await setChannels(null); } diff --git a/net/web/src/context/useConversationContext.hook.js b/net/web/src/context/useConversationContext.hook.js index efa914ae..de145151 100644 --- a/net/web/src/context/useConversationContext.hook.js +++ b/net/web/src/context/useConversationContext.hook.js @@ -60,7 +60,7 @@ export function useConversationContext() { const getSeals = (conversation) => { try { - if (conversation.data.channelDetail.dataType === 'sealed') { + if (conversation?.data.channelDetail.dataType === 'sealed') { return JSON.parse(conversation.data.channelDetail.data).seals; } } @@ -226,10 +226,11 @@ export function useConversationContext() { if (curView === view.current) { let chan = getChannel(); + let contacts = getContacts(chan); let subject = getSubject(chan); let members = getMembers(chan); - const seals = getSeals(chan); + let seals = getSeals(chan); const enableImage = chan?.data?.channelDetail?.enableImage; const enableAudio = chan?.data?.channelDetail?.enableAudio; const enableVideo = chan?.data?.channelDetail?.enableVideo; @@ -391,6 +392,15 @@ export function useConversationContext() { return await channel.actions.setChannelTopicSubject(channelId, topicId, data); } }, + setSealedTopicSubject: async (topicId, data, sealKey) => { + const { cardId, channelId } = channelView.current; + if (cardId) { + return await card.actions.setSealedChannelTopicSubject(cardId, channelId, topicId, data, sealKey); + } + else { + return await channel.actions.setSealedChannelTopicSubject(channelId, topicId, data, sealKey); + } + }, resync: () => { updateState({ error: false }); events.current.push({ type: EVENT_RESYNC }); diff --git a/net/web/src/session/channels/useChannels.hook.js b/net/web/src/session/channels/useChannels.hook.js index ebffd744..f602b229 100644 --- a/net/web/src/session/channels/useChannels.hook.js +++ b/net/web/src/session/channels/useChannels.hook.js @@ -200,7 +200,7 @@ export function useChannels() { } const setMessage = (chan) => { - let message = ""; + let message; if (chan.data.channelSummary?.lastTopic?.dataType === 'superbasictopic') { try { message = JSON.parse(chan.data.channelSummary.lastTopic.data).text; @@ -210,7 +210,9 @@ export function useChannels() { } } - chan.message = message; + if (typeof message === 'string') { + chan.message = message; + } } useEffect(() => { diff --git a/net/web/src/session/conversation/Conversation.jsx b/net/web/src/session/conversation/Conversation.jsx index c44284a6..fb95d189 100644 --- a/net/web/src/session/conversation/Conversation.jsx +++ b/net/web/src/session/conversation/Conversation.jsx @@ -14,7 +14,7 @@ export function Conversation({ closeConversation, openDetails, cardId, channelId const thread = useRef(null); const topicRenderer = (topic) => { - return () + return () } // an unfortunate cludge for the mobile browser diff --git a/net/web/src/session/conversation/topicItem/TopicItem.jsx b/net/web/src/session/conversation/topicItem/TopicItem.jsx index 2fa9ddb8..64998d04 100644 --- a/net/web/src/session/conversation/topicItem/TopicItem.jsx +++ b/net/web/src/session/conversation/topicItem/TopicItem.jsx @@ -8,9 +8,9 @@ import { Space, Skeleton, Button, Modal, Input } from 'antd'; import { ExclamationCircleOutlined, DeleteOutlined, EditOutlined, FireOutlined, PictureOutlined } from '@ant-design/icons'; import { Carousel } from 'carousel/Carousel'; -export function TopicItem({ host, topic, sealKey }) { +export function TopicItem({ host, topic, sealed, sealKey }) { - const { state, actions } = useTopicItem(topic, sealKey); + const { state, actions } = useTopicItem(topic, sealed, sealKey); let name = state.name ? state.name : state.handle; let nameClass = state.name ? 'set' : 'unset'; diff --git a/net/web/src/session/conversation/topicItem/useTopicItem.hook.js b/net/web/src/session/conversation/topicItem/useTopicItem.hook.js index 34d2e0f2..0baefac5 100644 --- a/net/web/src/session/conversation/topicItem/useTopicItem.hook.js +++ b/net/web/src/session/conversation/topicItem/useTopicItem.hook.js @@ -3,7 +3,7 @@ import { ConversationContext } from 'context/ConversationContext'; import { ProfileContext } from 'context/ProfileContext'; import { CardContext } from 'context/CardContext'; -export function useTopicItem(topic, sealKey) { +export function useTopicItem(topic, sealed, sealKey) { const [state, setState] = useState({ init: false, @@ -60,7 +60,9 @@ export function useTopicItem(topic, sealKey) { if (dataType === 'superbasictopic') { try { message = JSON.parse(data); - text = message.text; + if (typeof message.text === 'string') { + text = message.text; + } if (message.textColor != null) { textColor = message.textColor; } @@ -84,7 +86,9 @@ export function useTopicItem(topic, sealKey) { } else if (dataType === 'sealedtopic') { if (topic.data.unsealedMessage) { - text = topic.data.unsealedMessage.message.text; +console.log("UNSEALED MESSAGE", topic.data.unsealedMessage); + + text = topic.data.unsealedMessage.message?.text; sealed = false; } else { @@ -141,8 +145,15 @@ export function useTopicItem(topic, sealKey) { if (!state.busy) { updateState({ busy: true }); try { - await conversation.actions.setTopicSubject(topic.id, + if (sealed) { +console.log("SET SEALED"); + await conversation.actions.setSealedTopicSubject(topic.id, {...state.message, text: editMessage.current }, sealKey); + } + else { +console.log("SET UNSEALED"); + await conversation.actions.setTopicSubject(topic.id, { ...state.message, text: editMessage.current, assets: state.assets }); + } updateState({ editing: false }); } catch (err) {