From 674a545a3354a472130a35066e0d73ee5f8da634 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 24 Mar 2026 20:05:09 +0000 Subject: [PATCH] core: signed messages status (#6699) * XOcore: signed messages status * remove empty lines Co-authored-by: Evgeny * EOL * only mark verified as verified * update API types * todos --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- bots/api/EVENTS.md | 14 ++++---- bots/api/TYPES.md | 12 ++++++- bots/src/API/Docs/Types.hs | 2 ++ .../types/typescript/src/events.ts | 14 ++++---- .../types/typescript/src/types.ts | 7 +++- src/Simplex/Chat/Controller.hs | 14 ++++---- src/Simplex/Chat/Library/Internal.hs | 11 +++--- src/Simplex/Chat/Library/Subscriber.hs | 26 +++++++------- src/Simplex/Chat/Messages.hs | 8 ++--- src/Simplex/Chat/Messages/Batch.hs | 2 +- src/Simplex/Chat/Protocol.hs | 10 +++--- src/Simplex/Chat/Store/Delivery.hs | 3 +- src/Simplex/Chat/Store/Groups.hs | 4 +-- src/Simplex/Chat/Store/Messages.hs | 22 ++++++------ .../Migrations/M20260222_chat_relays.hs | 2 +- .../Store/Postgres/Migrations/chat_schema.sql | 2 +- .../Migrations/M20260222_chat_relays.hs | 2 +- .../Store/SQLite/Migrations/chat_schema.sql | 2 +- src/Simplex/Chat/Types/Shared.hs | 18 ++++++++++ src/Simplex/Chat/View.hs | 34 +++++++++++-------- 20 files changed, 124 insertions(+), 85 deletions(-) diff --git a/bots/api/EVENTS.md b/bots/api/EVENTS.md index 1dcdf5163c..bc99f68c4b 100644 --- a/bots/api/EVENTS.md +++ b/bots/api/EVENTS.md @@ -301,7 +301,7 @@ Group profile or preferences updated. - fromGroup: [GroupInfo](./TYPES.md#groupinfo) - toGroup: [GroupInfo](./TYPES.md#groupinfo) - member_: [GroupMember](./TYPES.md#groupmember)? -- msgSigned: bool +- msgSigned: [MsgSigStatus](./TYPES.md#msgsigstatus)? --- @@ -331,7 +331,7 @@ Member (or bot user's) group role changed. - member: [GroupMember](./TYPES.md#groupmember) - fromRole: [GroupMemberRole](./TYPES.md#groupmemberrole) - toRole: [GroupMemberRole](./TYPES.md#groupmemberrole) -- msgSigned: bool +- msgSigned: [MsgSigStatus](./TYPES.md#msgsigstatus)? --- @@ -347,7 +347,7 @@ Another member is removed from the group. - byMember: [GroupMember](./TYPES.md#groupmember) - deletedMember: [GroupMember](./TYPES.md#groupmember) - withMessages: bool -- msgSigned: bool +- msgSigned: [MsgSigStatus](./TYPES.md#msgsigstatus)? --- @@ -361,7 +361,7 @@ Another member left the group. - user: [User](./TYPES.md#user) - groupInfo: [GroupInfo](./TYPES.md#groupinfo) - member: [GroupMember](./TYPES.md#groupmember) -- msgSigned: bool +- msgSigned: [MsgSigStatus](./TYPES.md#msgsigstatus)? --- @@ -376,7 +376,7 @@ Bot user was removed from the group. - groupInfo: [GroupInfo](./TYPES.md#groupinfo) - member: [GroupMember](./TYPES.md#groupmember) - withMessages: bool -- msgSigned: bool +- msgSigned: [MsgSigStatus](./TYPES.md#msgsigstatus)? --- @@ -390,7 +390,7 @@ Group was deleted by the owner (not bot user). - user: [User](./TYPES.md#user) - groupInfo: [GroupInfo](./TYPES.md#groupinfo) - member: [GroupMember](./TYPES.md#groupmember) -- msgSigned: bool +- msgSigned: [MsgSigStatus](./TYPES.md#msgsigstatus)? --- @@ -434,7 +434,7 @@ Another member blocked for all members. - byMember: [GroupMember](./TYPES.md#groupmember) - member: [GroupMember](./TYPES.md#groupmember) - blocked: bool -- msgSigned: bool +- msgSigned: [MsgSigStatus](./TYPES.md#msgsigstatus)? --- diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index e097205aa0..c1d2cbb4ba 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -125,6 +125,7 @@ This file is generated automatically. - [MsgFilter](#msgfilter) - [MsgReaction](#msgreaction) - [MsgReceiptStatus](#msgreceiptstatus) +- [MsgSigStatus](#msgsigstatus) - [NetworkError](#networkerror) - [NewUser](#newuser) - [NoteFolder](#notefolder) @@ -781,7 +782,7 @@ Group: - editable: bool - forwardedByMember: int64? - showGroupAsSender: bool -- msgSigned: bool +- msgSigned: [MsgSigStatus](#msgsigstatus)? - createdAt: UTCTime - updatedAt: UTCTime @@ -2725,6 +2726,15 @@ Unknown: - "badMsgHash" +--- + +## MsgSigStatus + +**Enum type**: +- "verified" +- "signedNoKey" + + --- ## NetworkError diff --git a/bots/src/API/Docs/Types.hs b/bots/src/API/Docs/Types.hs index 7c526ec58a..19a33a678c 100644 --- a/bots/src/API/Docs/Types.hs +++ b/bots/src/API/Docs/Types.hs @@ -307,6 +307,7 @@ chatTypesDocsData = (sti @MsgFilter, STEnum, "MF", [], "", ""), (sti @MsgReaction, STUnion, "MR", [], "", ""), (sti @MsgReceiptStatus, STEnum, "MR", [], "", ""), + (sti @MsgSigStatus, STEnum, "MSS", [], "", ""), (sti @NetworkError, STUnion, "NE", [], "", ""), (sti @NewUser, STRecord, "", [], "", ""), (sti @NoteFolder, STRecord, "", [], "", ""), @@ -507,6 +508,7 @@ deriving instance Generic MsgErrorType deriving instance Generic MsgFilter deriving instance Generic MsgReaction deriving instance Generic MsgReceiptStatus +deriving instance Generic MsgSigStatus deriving instance Generic NetworkError deriving instance Generic NewUser deriving instance Generic NoteFolder diff --git a/packages/simplex-chat-client/types/typescript/src/events.ts b/packages/simplex-chat-client/types/typescript/src/events.ts index 72d1b5e0b9..b0b9b31fb4 100644 --- a/packages/simplex-chat-client/types/typescript/src/events.ts +++ b/packages/simplex-chat-client/types/typescript/src/events.ts @@ -215,7 +215,7 @@ export namespace CEvt { fromGroup: T.GroupInfo toGroup: T.GroupInfo member_?: T.GroupMember - msgSigned: boolean + msgSigned?: T.MsgSigStatus } export interface JoinedGroupMember extends Interface { @@ -233,7 +233,7 @@ export namespace CEvt { member: T.GroupMember fromRole: T.GroupMemberRole toRole: T.GroupMemberRole - msgSigned: boolean + msgSigned?: T.MsgSigStatus } export interface DeletedMember extends Interface { @@ -243,7 +243,7 @@ export namespace CEvt { byMember: T.GroupMember deletedMember: T.GroupMember withMessages: boolean - msgSigned: boolean + msgSigned?: T.MsgSigStatus } export interface LeftMember extends Interface { @@ -251,7 +251,7 @@ export namespace CEvt { user: T.User groupInfo: T.GroupInfo member: T.GroupMember - msgSigned: boolean + msgSigned?: T.MsgSigStatus } export interface DeletedMemberUser extends Interface { @@ -260,7 +260,7 @@ export namespace CEvt { groupInfo: T.GroupInfo member: T.GroupMember withMessages: boolean - msgSigned: boolean + msgSigned?: T.MsgSigStatus } export interface GroupDeleted extends Interface { @@ -268,7 +268,7 @@ export namespace CEvt { user: T.User groupInfo: T.GroupInfo member: T.GroupMember - msgSigned: boolean + msgSigned?: T.MsgSigStatus } export interface ConnectedToGroupMember extends Interface { @@ -294,7 +294,7 @@ export namespace CEvt { byMember: T.GroupMember member: T.GroupMember blocked: boolean - msgSigned: boolean + msgSigned?: T.MsgSigStatus } export interface GroupMemberUpdated extends Interface { diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index c388072846..e55904644c 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -795,7 +795,7 @@ export interface CIMeta { editable: boolean forwardedByMember?: number // int64 showGroupAsSender: boolean - msgSigned: boolean + msgSigned?: MsgSigStatus createdAt: string // ISO-8601 timestamp updatedAt: string // ISO-8601 timestamp } @@ -3030,6 +3030,11 @@ export enum MsgReceiptStatus { BadMsgHash = "badMsgHash", } +export enum MsgSigStatus { + Verified = "verified", + SignedNoKey = "signedNoKey", +} + export type NetworkError = | NetworkError.ConnectError | NetworkError.TLSError diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 3e8c7550dc..f1205e3dfb 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -861,17 +861,17 @@ data ChatEvent | CEvtJoinedGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} -- there is the same command response | CEvtJoinedGroupMemberConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember, member :: GroupMember} | CEvtMemberAcceptedByOther {user :: User, groupInfo :: GroupInfo, acceptingMember :: GroupMember, member :: GroupMember} - | CEvtMemberRole {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, member :: GroupMember, fromRole :: GroupMemberRole, toRole :: GroupMemberRole, msgSigned :: Bool} - | CEvtMemberBlockedForAll {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, member :: GroupMember, blocked :: Bool, msgSigned :: Bool} + | CEvtMemberRole {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, member :: GroupMember, fromRole :: GroupMemberRole, toRole :: GroupMemberRole, msgSigned :: Maybe MsgSigStatus} + | CEvtMemberBlockedForAll {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, member :: GroupMember, blocked :: Bool, msgSigned :: Maybe MsgSigStatus} | CEvtConnectedToGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember, memberContact :: Maybe Contact} - | CEvtDeletedMember {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, deletedMember :: GroupMember, withMessages :: Bool, msgSigned :: Bool} - | CEvtDeletedMemberUser {user :: User, groupInfo :: GroupInfo, member :: GroupMember, withMessages :: Bool, msgSigned :: Bool} - | CEvtLeftMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember, msgSigned :: Bool} + | CEvtDeletedMember {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, deletedMember :: GroupMember, withMessages :: Bool, msgSigned :: Maybe MsgSigStatus} + | CEvtDeletedMemberUser {user :: User, groupInfo :: GroupInfo, member :: GroupMember, withMessages :: Bool, msgSigned :: Maybe MsgSigStatus} + | CEvtLeftMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember, msgSigned :: Maybe MsgSigStatus} | CEvtUnknownMemberCreated {user :: User, groupInfo :: GroupInfo, forwardedByMember :: GroupMember, member :: GroupMember} | CEvtUnknownMemberBlocked {user :: User, groupInfo :: GroupInfo, blockedByMember :: GroupMember, member :: GroupMember} | CEvtUnknownMemberAnnounced {user :: User, groupInfo :: GroupInfo, announcingMember :: GroupMember, unknownMember :: GroupMember, announcedMember :: GroupMember} - | CEvtGroupDeleted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, msgSigned :: Bool} - | CEvtGroupUpdated {user :: User, fromGroup :: GroupInfo, toGroup :: GroupInfo, member_ :: Maybe GroupMember, msgSigned :: Bool} -- there is the same command response + | CEvtGroupDeleted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, msgSigned :: Maybe MsgSigStatus} + | CEvtGroupUpdated {user :: User, fromGroup :: GroupInfo, toGroup :: GroupInfo, member_ :: Maybe GroupMember, msgSigned :: Maybe MsgSigStatus} -- there is the same command response | CEvtAcceptingGroupJoinRequestMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} | CEvtNoMemberContactCreating {user :: User, groupInfo :: GroupInfo, member :: GroupMember} -- only used in CLI | CEvtNewMemberContactReceivedInv {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember} diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 6af078dbe6..de8511bce9 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -2321,10 +2321,9 @@ saveSndChatItems user cd showGroupAsSender itemsData itemTimed live = do createItem :: DB.Connection -> UTCTime -> NewSndChatItemData c -> IO (Either ChatError (ChatItem c 'MDSnd)) createItem db createdAt NewSndChatItemData {msg = msg@SndMessage {sharedMsgId, signedMsg_}, content, itemTexts, itemMentions, ciFile, quotedItem, itemForwarded} = do let hasLink_ = ciContentHasLink content (snd itemTexts) - signed = isJust signedMsg_ ciId <- createNewSndChatItem db user cd showGroupAsSender msg content quotedItem itemForwarded itemTimed live hasLink_ createdAt forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt - let ci = mkChatItem_ cd showGroupAsSender ciId content itemTexts ciFile quotedItem (Just sharedMsgId) itemForwarded itemTimed live False hasLink_ createdAt Nothing signed createdAt + let ci = mkChatItem_ cd showGroupAsSender ciId content itemTexts ciFile quotedItem (Just sharedMsgId) itemForwarded itemTimed live False hasLink_ createdAt Nothing (MSSVerified <$ signedMsg_) createdAt Right <$> case cd of CDGroupSnd g _scope | not (null itemMentions) -> createGroupCIMentions db g ci itemMentions _ -> pure ci @@ -2383,9 +2382,9 @@ mkChatItem :: (ChatTypeI c, MsgDirectionI d) => ChatDirection c d -> ShowGroupAs mkChatItem cd showGroupAsSender ciId content file quotedItem sharedMsgId itemForwarded itemTimed live userMention itemTs forwardedByMember currentTs = let ts@(_, ft_) = ciContentTexts content hasLink_ = ciContentHasLink content ft_ - in mkChatItem_ cd showGroupAsSender ciId content ts file quotedItem sharedMsgId itemForwarded itemTimed live userMention hasLink_ itemTs forwardedByMember False currentTs + in mkChatItem_ cd showGroupAsSender ciId content ts file quotedItem sharedMsgId itemForwarded itemTimed live userMention hasLink_ itemTs forwardedByMember Nothing currentTs -mkChatItem_ :: (ChatTypeI c, MsgDirectionI d) => ChatDirection c d -> ShowGroupAsSender -> ChatItemId -> CIContent d -> (Text, Maybe MarkdownList) -> Maybe (CIFile d) -> Maybe (CIQuote c) -> Maybe SharedMsgId -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> Bool -> Bool -> ChatItemTs -> Maybe GroupMemberId -> Bool -> UTCTime -> ChatItem c d +mkChatItem_ :: (ChatTypeI c, MsgDirectionI d) => ChatDirection c d -> ShowGroupAsSender -> ChatItemId -> CIContent d -> (Text, Maybe MarkdownList) -> Maybe (CIFile d) -> Maybe (CIQuote c) -> Maybe SharedMsgId -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> Bool -> Bool -> ChatItemTs -> Maybe GroupMemberId -> Maybe MsgSigStatus -> UTCTime -> ChatItem c d mkChatItem_ cd showGroupAsSender ciId content (itemText, formattedText) file quotedItem sharedMsgId itemForwarded itemTimed live userMention hasLink_ itemTs forwardedByMember msgSigned currentTs = let itemStatus = ciCreateStatus content meta = mkCIMeta ciId content itemText itemStatus Nothing sharedMsgId itemForwarded Nothing False itemTimed (justTrue live) userMention hasLink_ currentTs itemTs forwardedByMember showGroupAsSender msgSigned currentTs currentTs @@ -2696,9 +2695,9 @@ createLocalChatItems user cd itemsData createdAt = do createItem :: DB.Connection -> (CIContent 'MDSnd, Maybe (CIFile 'MDSnd), Maybe CIForwardedFrom, (Text, Maybe MarkdownList)) -> IO (ChatItem 'CTLocal 'MDSnd) createItem db (content, ciFile, itemForwarded, ts@(_, ft_)) = do let hasLink_ = ciContentHasLink content ft_ - ciId <- createNewChatItem_ db user cd False Nothing Nothing content (Nothing, Nothing, Nothing, Nothing, Nothing) itemForwarded Nothing False False hasLink_ createdAt Nothing False createdAt + ciId <- createNewChatItem_ db user cd False Nothing Nothing content (Nothing, Nothing, Nothing, Nothing, Nothing) itemForwarded Nothing False False hasLink_ createdAt Nothing Nothing createdAt forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt - pure $ mkChatItem_ cd False ciId content ts ciFile Nothing Nothing itemForwarded Nothing False False hasLink_ createdAt Nothing False createdAt + pure $ mkChatItem_ cd False ciId content ts ciFile Nothing Nothing itemForwarded Nothing False False hasLink_ createdAt Nothing Nothing createdAt withUser' :: (User -> CM ChatResponse) -> CM ChatResponse withUser' action = diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 294531e0f8..650b554857 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -2506,7 +2506,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateBusinessChatProfile g@GroupInfo {businessChat} = case businessChat of Just bc | isMainBusinessMember bc m -> do g' <- withStore $ \db -> updateGroupProfileFromMember db user g p' - toView $ CEvtGroupUpdated user g g' (Just m) False + toView $ CEvtGroupUpdated user g g' (Just m) Nothing _ -> pure () isMainBusinessMember BusinessChatInfo {chatType, businessId, customerId} GroupMember {memberId} = case chatType of BCBusiness -> businessId == memberId @@ -3076,7 +3076,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | memberRole < GROwner = messageError "x.grp.prefs with insufficient member permissions" $> Nothing | otherwise = updateGroupPrefs_ msgSigned g m ps' $> Just DJSGroup {jobSpec = DJDeliveryJob {includePending = True}} - updateGroupPrefs_ :: Bool -> GroupInfo -> GroupMember -> GroupPreferences -> CM () + updateGroupPrefs_ :: Maybe MsgSigStatus -> GroupInfo -> GroupMember -> GroupPreferences -> CM () updateGroupPrefs_ msgSigned g@GroupInfo {groupProfile = p} m ps' = unless (groupPreferences p == Just ps') $ do g' <- withStore' $ \db -> updateGroupPreferences db user g ps' @@ -3222,25 +3222,25 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Nothing -> messageError $ "x.grp.msg.forward: event " <> tshow tag <> " requires author" withVerifiedMsg :: MsgEncodingI e => GroupInfo -> Maybe GroupChatScopeInfo -> GroupMember -> ParsedMsg e -> UTCTime -> (VerifiedMsg e -> CM a) -> CM (Maybe a) - withVerifiedMsg gInfo@GroupInfo {membership} scopeInfo member (ParsedMsg _ signedMsg_ chatMsg@ChatMessage {chatMsgEvent}) ts action - | verified = Just <$> action verifiedMsg - | otherwise = do + withVerifiedMsg gInfo@GroupInfo {membership} scopeInfo member (ParsedMsg _ signedMsg_ chatMsg@ChatMessage {chatMsgEvent}) ts action = + case verified of + Just verifiedMsg -> Just <$> action verifiedMsg + Nothing -> do createInternalChatItem user (CDGroupRcv gInfo scopeInfo member) (CIRcvGroupEvent RGEMsgBadSignature) (Just ts) pure Nothing where - verifiedMsg = case signedMsg_ of - Nothing -> VMUnsigned chatMsg - Just sm -> VMSigned sm chatMsg verified = case signedMsg_ of - Just SignedMsg {chatBinding, signatures, signedBody} + Just sm@SignedMsg {chatBinding, signatures, signedBody} | GroupMember {memberPubKey = Just pubKey, memberId} <- member -> case chatBinding of CBGroup | Just GroupKeys {groupRootKey} <- groupKeys gInfo -> let prefix = smpEncode chatBinding <> smpEncode (groupRootPubKey groupRootKey, memberId) - in all (\(MsgSignature KRMember sig) -> C.verify (C.APublicVerifyKey C.SEd25519 pubKey) sig (prefix <> signedBody)) signatures - _ -> signatureOptional - | otherwise -> signatureOptional || unverifiedAllowed membership member tag - Nothing -> signatureOptional + in signed MSSVerified <$ guard (all (\(MsgSignature KRMember sig) -> C.verify (C.APublicVerifyKey C.SEd25519 pubKey) sig (prefix <> signedBody)) signatures) + _ -> signed MSSSignedNoKey <$ guard signatureOptional + | otherwise -> signed MSSSignedNoKey <$ guard (signatureOptional || unverifiedAllowed membership member tag) + where + signed status = VMSigned status sm chatMsg + Nothing -> VMUnsigned chatMsg <$ guard signatureOptional where tag = toCMEventTag chatMsgEvent signatureOptional = not (useRelays' gInfo) || not (requiresSignature tag) diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index ef91caaea5..e404388d8d 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -512,7 +512,7 @@ data CIMeta (c :: ChatType) (d :: MsgDirection) = CIMeta editable :: Bool, forwardedByMember :: Maybe GroupMemberId, showGroupAsSender :: ShowGroupAsSender, - msgSigned :: Bool, + msgSigned :: Maybe MsgSigStatus, createdAt :: UTCTime, updatedAt :: UTCTime } @@ -520,7 +520,7 @@ data CIMeta (c :: ChatType) (d :: MsgDirection) = CIMeta type ShowGroupAsSender = Bool -mkCIMeta :: forall c d. ChatTypeI c => ChatItemId -> CIContent d -> Text -> CIStatus d -> Maybe Bool -> Maybe SharedMsgId -> Maybe CIForwardedFrom -> Maybe (CIDeleted c) -> Bool -> Maybe CITimed -> Maybe Bool -> Bool -> Bool -> UTCTime -> ChatItemTs -> Maybe GroupMemberId -> Bool -> Bool -> UTCTime -> UTCTime -> CIMeta c d +mkCIMeta :: forall c d. ChatTypeI c => ChatItemId -> CIContent d -> Text -> CIStatus d -> Maybe Bool -> Maybe SharedMsgId -> Maybe CIForwardedFrom -> Maybe (CIDeleted c) -> Bool -> Maybe CITimed -> Maybe Bool -> Bool -> Bool -> UTCTime -> ChatItemTs -> Maybe GroupMemberId -> Bool -> Maybe MsgSigStatus -> UTCTime -> UTCTime -> CIMeta c d mkCIMeta itemId itemContent itemText itemStatus sentViaProxy itemSharedMsgId itemForwarded itemDeleted itemEdited itemTimed itemLive userMention hasLink_ currentTs itemTs forwardedByMember showGroupAsSender msgSigned createdAt updatedAt = let deletable = deletable' itemContent itemDeleted itemTs nominalDay currentTs editable = deletable && isNothing itemForwarded @@ -556,7 +556,7 @@ dummyMeta itemId ts itemText = editable = False, forwardedByMember = Nothing, showGroupAsSender = False, - msgSigned = False, + msgSigned = Nothing, createdAt = ts, updatedAt = ts } @@ -1166,7 +1166,7 @@ data RcvMessage = RcvMessage { msgId :: MessageId, chatMsgEvent :: AChatMsgEvent, sharedMsgId_ :: Maybe SharedMsgId, - msgSigned :: Bool, + msgSigned :: Maybe MsgSigStatus, forwardedByMember :: Maybe GroupMemberId } diff --git a/src/Simplex/Chat/Messages/Batch.hs b/src/Simplex/Chat/Messages/Batch.hs index 4684fb0261..a9e835a83e 100644 --- a/src/Simplex/Chat/Messages/Batch.hs +++ b/src/Simplex/Chat/Messages/Batch.hs @@ -98,7 +98,7 @@ batchDeliveryTasks1 _vr maxLen = toResult . foldl' addToBatch ([], [], [], 0, 0) encodeFwdElement :: GrpMsgForward -> VerifiedMsg 'Json -> ByteString encodeFwdElement fwd verifiedMsg = ">" <> smpEncode fwd <> encodeBatchElement signedMsg_ msgBody where - (signedMsg_, msgBody) = verifiedMsgParts verifiedMsg + (_, signedMsg_, msgBody) = verifiedMsgParts verifiedMsg encodeBatch :: BatchMode -> [ByteString] -> ByteString encodeBatch _ [] = mempty diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index 6d785264a4..2e921425b9 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -336,7 +336,7 @@ data SignedMsg = SignedMsg -- accept only VerifiedMsg, preventing unverified messages from being persisted. data VerifiedMsg e = VMUnsigned (ChatMessage e) - | VMSigned SignedMsg (ChatMessage e) + | VMSigned MsgSigStatus SignedMsg (ChatMessage e) data ParsedMsg e = ParsedMsg (Maybe GrpMsgForward) (Maybe SignedMsg) (ChatMessage e) @@ -1392,15 +1392,15 @@ chatMsgToBody chatMsg = case encoding @e of verifiedChatMsg :: VerifiedMsg e -> ChatMessage e verifiedChatMsg = \case VMUnsigned cm -> cm - VMSigned _ cm -> cm + VMSigned _ _ cm -> cm -- | Canonical bytes to store/forward, with optional signature. -- Signed: original bytes (re-encoding would invalidate signature). -- Unsigned: re-encoded from parsed ChatMessage (sanitizes stored content). -verifiedMsgParts :: MsgEncodingI e => VerifiedMsg e -> (Maybe SignedMsg, ByteString) +verifiedMsgParts :: MsgEncodingI e => VerifiedMsg e -> (Maybe MsgSigStatus, Maybe SignedMsg, ByteString) verifiedMsgParts = \case - VMUnsigned chatMsg -> (Nothing, chatMsgToBody chatMsg) - VMSigned sm@SignedMsg {signedBody} _ -> (Just sm, signedBody) + VMUnsigned chatMsg -> (Nothing, Nothing, chatMsgToBody chatMsg) + VMSigned s sm@SignedMsg {signedBody} _ -> (Just s, Just sm, signedBody) instance ToJSON (ChatMessage 'Json) where diff --git a/src/Simplex/Chat/Store/Delivery.hs b/src/Simplex/Chat/Store/Delivery.hs index 5b060f5ca8..393e008835 100644 --- a/src/Simplex/Chat/Store/Delivery.hs +++ b/src/Simplex/Chat/Store/Delivery.hs @@ -39,6 +39,7 @@ import Simplex.Chat.Delivery import Simplex.Chat.Protocol hiding (Binary) import Simplex.Chat.Store.Shared import Simplex.Chat.Types +import Simplex.Chat.Types.Shared (MsgSigStatus (..)) import Simplex.Messaging.Agent.Store.AgentStore (getWorkItem, getWorkItems, maybeFirstRow) import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..)) import qualified Simplex.Messaging.Agent.Store.DB as DB @@ -157,7 +158,7 @@ getMsgDeliveryTask_ db taskId = -- Signed: original bytes preserved (re-encoding would invalidate signature). -- Unsigned: re-encoded from parsed ChatMessage on forward (sanitizes content). verifiedMsg = case (chatBinding_, decodeSigs sigs_) of - (Just cb, Just sigs) -> VMSigned (SignedMsg cb sigs msgBody) chatMsg + (Just cb, Just sigs) -> VMSigned MSSVerified (SignedMsg cb sigs msgBody) chatMsg _ -> VMUnsigned chatMsg in Right $ MessageDeliveryTask {taskId = taskId', jobScope, senderGMId, fwdSender, brokerTs, verifiedMsg} (Nothing, _) -> Left $ SEInvalidDeliveryTask taskId' diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index d40d294ee1..8698b1718c 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -451,9 +451,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ ((profileId, localDisplayName, connRequest, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business) insertedRowId db let hostVRange = adjustedMemberVRange vr peerChatVRange - -- TODO [member keys] inviting host should generate its keys in public groups GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing Nothing currentTs hostVRange - -- TODO [member keys] relay should pass key received via XMember membership <- createContactMemberInv_ db user groupId (Just groupMemberId) user invitedMember GCUserMember GSMemInvited (IBContact contactId) incognitoProfileId Nothing currentTs vr let chatSettings = ChatSettings {enableNtfs = MFAll, sendRcpts = Nothing, favorite = False} pure @@ -813,7 +811,7 @@ createGroupViaLink' hostMemberId <- insertHost_ currentTs groupId liftIO $ DB.execute db "UPDATE connections SET conn_type = ?, group_member_id = ?, updated_at = ? WHERE connection_id = ?" (ConnMember, hostMemberId, currentTs, connId) -- using IBUnknown since host is created without contact - -- TODO [member keys] can this be used with public groups? if yes member keys need to be added + -- TODO [member keys] this is currently not used with public groups. If it needs to be used, member keys need to be added void $ createContactMemberInv_ db user groupId (Just hostMemberId) user invitedMember GCUserMember membershipStatus IBUnknown customUserProfileId Nothing currentTs vr liftIO $ setViaGroupLinkUri db groupId connId (,) <$> getGroupInfo db vr user groupId <*> getGroupMemberById db vr user hostMemberId diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 26c9d5c60a..465ee2b1b4 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -328,8 +328,8 @@ createNewRcvMessage db connOrGroupId NewRcvMessage {chatMsgEvent, verifiedMsg, b ((MDRcv, toCMEventTag chatMsgEvent, DB.Binary msgBody, chatBinding <$> signedMsg_, smpEncode . signatures <$> signedMsg_, brokerTs, currentTs, currentTs, connId_, groupId_) :. (sharedMsgId_, authorMember, forwardedByMember)) msgId <- insertedRowId db - pure RcvMessage {msgId, chatMsgEvent = ACME (encoding @e) chatMsgEvent, sharedMsgId_, msgSigned = isJust signedMsg_, forwardedByMember} - (signedMsg_, msgBody) = verifiedMsgParts verifiedMsg + pure RcvMessage {msgId, chatMsgEvent = ACME (encoding @e) chatMsgEvent, sharedMsgId_, msgSigned, forwardedByMember} + (msgSigned, signedMsg_, msgBody) = verifiedMsgParts verifiedMsg updateSndMsgDeliveryStatus :: DB.Connection -> Int64 -> AgentMsgId -> MsgDeliveryStatus 'MDSnd -> IO () updateSndMsgDeliveryStatus db connId agentMsgId sndMsgDeliveryStatus = do @@ -535,7 +535,7 @@ setSupportChatMemberAttention db vr user g m memberAttention = do createNewSndChatItem :: DB.Connection -> User -> ChatDirection c 'MDSnd -> ShowGroupAsSender -> SndMessage -> CIContent 'MDSnd -> Maybe (CIQuote c) -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> Bool -> UTCTime -> IO ChatItemId createNewSndChatItem db user chatDirection showGroupAsSender SndMessage {msgId, sharedMsgId, signedMsg_} ciContent quotedItem itemForwarded timed live hasLink createdAt = - createNewChatItem_ db user chatDirection showGroupAsSender createdByMsgId (Just sharedMsgId) ciContent quoteRow itemForwarded timed live False hasLink createdAt Nothing (isJust signedMsg_) createdAt + createNewChatItem_ db user chatDirection showGroupAsSender createdByMsgId (Just sharedMsgId) ciContent quoteRow itemForwarded timed live False hasLink createdAt Nothing (MSSVerified <$ signedMsg_) createdAt where createdByMsgId = if msgId == 0 then Nothing else Just msgId quoteRow :: NewQuoteRow @@ -571,12 +571,12 @@ createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent, msgS createNewChatItemNoMsg :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> ShowGroupAsSender -> CIContent d -> Maybe SharedMsgId -> Bool -> UTCTime -> UTCTime -> IO ChatItemId createNewChatItemNoMsg db user chatDirection showGroupAsSender ciContent sharedMsgId_ hasLink itemTs = - createNewChatItem_ db user chatDirection showGroupAsSender Nothing sharedMsgId_ ciContent quoteRow Nothing Nothing False False hasLink itemTs Nothing False + createNewChatItem_ db user chatDirection showGroupAsSender Nothing sharedMsgId_ ciContent quoteRow Nothing Nothing False False hasLink itemTs Nothing Nothing where quoteRow :: NewQuoteRow quoteRow = (Nothing, Nothing, Nothing, Nothing, Nothing) -createNewChatItem_ :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> ShowGroupAsSender -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> Bool -> Bool -> UTCTime -> Maybe GroupMemberId -> Bool -> UTCTime -> IO ChatItemId +createNewChatItem_ :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> ShowGroupAsSender -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> Bool -> Bool -> UTCTime -> Maybe GroupMemberId -> Maybe MsgSigStatus -> UTCTime -> IO ChatItemId createNewChatItem_ db User {userId} chatDirection showGroupAsSender msgId_ sharedMsgId ciContent quoteRow itemForwarded timed live userMention hasLink itemTs forwardedByMember msgSigned createdAt = do DB.execute db @@ -598,8 +598,8 @@ createNewChatItem_ db User {userId} chatDirection showGroupAsSender msgId_ share forM_ msgId_ $ \msgId -> insertChatItemMessage_ db ciId msgId createdAt pure ciId where - itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, Text, CIStatus d, Maybe MsgContentTag, Maybe SharedMsgId, Maybe GroupMemberId, BoolInt) :. (UTCTime, UTCTime, Maybe BoolInt, BoolInt, BoolInt, BoolInt, BoolInt) :. (Maybe Int, Maybe UTCTime) - itemRow = (msgDirection @d, itemTs, ciContent, toCIContentTag ciContent, ciContentToText ciContent, ciCreateStatus ciContent, msgContentTag <$> ciMsgContent ciContent, sharedMsgId, forwardedByMember, BI includeInHistory) :. (createdAt, createdAt, BI <$> justTrue live, BI userMention, BI hasLink, BI showGroupAsSender, BI msgSigned) :. ciTimedRow timed + itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, Text, CIStatus d, Maybe MsgContentTag, Maybe SharedMsgId, Maybe GroupMemberId, BoolInt) :. (UTCTime, UTCTime, Maybe BoolInt, BoolInt, BoolInt, BoolInt, Maybe MsgSigStatus) :. (Maybe Int, Maybe UTCTime) + itemRow = (msgDirection @d, itemTs, ciContent, toCIContentTag ciContent, ciContentToText ciContent, ciCreateStatus ciContent, msgContentTag <$> ciMsgContent ciContent, sharedMsgId, forwardedByMember, BI includeInHistory) :. (createdAt, createdAt, BI <$> justTrue live, BI userMention, BI hasLink, BI showGroupAsSender, msgSigned) :. ciTimedRow timed quoteRow' = let (a, b, c, d, e) = quoteRow in (a, b, c, BI <$> d, e) idsRow :: (Maybe ContactId, Maybe GroupId, Maybe GroupMemberId, Maybe NoteFolderId) idsRow = case chatDirection of @@ -1064,7 +1064,7 @@ getLocalChatPreview_ db user (LocalChatPD _ noteFolderId lastItemId_ stats) = do -- this function can be changed so it never fails, not only avoid failure on invalid json toLocalChatItem :: UTCTime -> ChatItemRow -> Either StoreError (CChatItem 'CTLocal) -toLocalChatItem currentTs ((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sentViaProxy, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. forwardedFromRow :. (timedTTL, timedDeleteAt, itemLive, BI userMention, BI hasLink, BI msgSigned) :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_)) = +toLocalChatItem currentTs ((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sentViaProxy, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. forwardedFromRow :. (timedTTL, timedDeleteAt, itemLive, BI userMention, BI hasLink, msgSigned) :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_)) = chatItem $ fromRight invalid $ dbParseACIContent itemContentText where invalid = ACIContent msgDir $ CIInvalidJSON itemContentText @@ -2218,7 +2218,7 @@ updateLocalChatItemsRead db User {userId} noteFolderId = do type MaybeCIFIleRow = (Maybe Int64, Maybe String, Maybe Integer, Maybe FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe ACIFileStatus, Maybe FileProtocol) -type ChatItemModeRow = (Maybe Int, Maybe UTCTime, Maybe BoolInt, BoolInt, BoolInt, BoolInt) +type ChatItemModeRow = (Maybe Int, Maybe UTCTime, Maybe BoolInt, BoolInt, BoolInt, Maybe MsgSigStatus) type ChatItemForwardedFromRow = (Maybe CIForwardedFromTag, Maybe Text, Maybe MsgDirection, Maybe Int64, Maybe Int64, Maybe Int64) @@ -2242,7 +2242,7 @@ toQuote (quotedItemId, quotedSharedMsgId, quotedSentAt, quotedMsgContent, _) dir -- this function can be changed so it never fails, not only avoid failure on invalid json toDirectChatItem :: UTCTime -> ChatItemRow :. QuoteRow -> Either StoreError (CChatItem 'CTDirect) -toDirectChatItem currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sentViaProxy, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. forwardedFromRow :. (timedTTL, timedDeleteAt, itemLive, BI userMention, BI hasLink, BI msgSigned) :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_)) :. quoteRow) = +toDirectChatItem currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sentViaProxy, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. forwardedFromRow :. (timedTTL, timedDeleteAt, itemLive, BI userMention, BI hasLink, msgSigned) :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_)) :. quoteRow) = chatItem $ fromRight invalid $ dbParseACIContent itemContentText where invalid = ACIContent msgDir $ CIInvalidJSON itemContentText @@ -2313,7 +2313,7 @@ toGroupChatItem ( ( (itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sentViaProxy, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. forwardedFromRow - :. (timedTTL, timedDeleteAt, itemLive, BI userMention, BI hasLink, BI msgSigned) + :. (timedTTL, timedDeleteAt, itemLive, BI userMention, BI hasLink, msgSigned) :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_) ) :. (forwardedByMember, BI showGroupAsSender) diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs index 11e1270e75..9c78fb595c 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs @@ -68,7 +68,7 @@ ALTER TABLE group_members ALTER TABLE messages ADD COLUMN msg_chat_binding TEXT; ALTER TABLE messages ADD COLUMN msg_signatures BYTEA; -ALTER TABLE chat_items ADD COLUMN msg_signed SMALLINT NOT NULL DEFAULT 0; +ALTER TABLE chat_items ADD COLUMN msg_signed TEXT; |] down_m20260222_chat_relays :: Text diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index e80bc0ef9a..7e021c241f 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -344,7 +344,7 @@ CREATE TABLE test_chat_schema.chat_items ( group_scope_group_member_id bigint, show_group_as_sender smallint DEFAULT 0 NOT NULL, has_link smallint DEFAULT 0 NOT NULL, - msg_signed smallint DEFAULT 0 NOT NULL + msg_signed text ); diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs index f0f0e151dc..cc3706081a 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs @@ -80,7 +80,7 @@ ALTER TABLE group_members ADD COLUMN member_pub_key BLOB; ALTER TABLE messages ADD COLUMN msg_chat_binding TEXT; ALTER TABLE messages ADD COLUMN msg_signatures BLOB; -ALTER TABLE chat_items ADD COLUMN msg_signed INTEGER NOT NULL DEFAULT 0; +ALTER TABLE chat_items ADD COLUMN msg_signed TEXT; |] down_m20260222_chat_relays :: Query diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index ecdde196d4..9782ce056d 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -462,7 +462,7 @@ CREATE TABLE chat_items( group_scope_group_member_id INTEGER REFERENCES group_members(group_member_id) ON DELETE CASCADE, show_group_as_sender INTEGER NOT NULL DEFAULT 0, has_link INTEGER NOT NULL DEFAULT 0, - msg_signed INTEGER NOT NULL DEFAULT 0 + msg_signed TEXT ) STRICT; CREATE TABLE sqlite_sequence(name,seq); CREATE TABLE chat_item_messages( diff --git a/src/Simplex/Chat/Types/Shared.hs b/src/Simplex/Chat/Types/Shared.hs index dfc932c35d..22cb73f325 100644 --- a/src/Simplex/Chat/Types/Shared.hs +++ b/src/Simplex/Chat/Types/Shared.hs @@ -110,3 +110,21 @@ instance FromField RelayStatus where fromField = fromTextField_ textDecode instance ToField RelayStatus where toField = toField . textEncode $(JQ.deriveJSON (enumJSON $ dropPrefix "RS") ''RelayStatus) + +data MsgSigStatus = MSSVerified | MSSSignedNoKey + deriving (Eq, Show) + +instance TextEncoding MsgSigStatus where + textEncode = \case + MSSVerified -> "verified" + MSSSignedNoKey -> "no_key" + textDecode = \case + "verified" -> Just MSSVerified + "no_key" -> Just MSSSignedNoKey + _ -> Nothing + +instance ToField MsgSigStatus where toField = toField . textEncode + +instance FromField MsgSigStatus where fromField = fromTextField_ textDecode + +$(JQ.deriveJSON (enumJSON $ dropPrefix "MSS") ''MsgSigStatus) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index b94c8c5924..22d136c59e 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -240,7 +240,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte CRMemberSupportChatDeleted u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " support chat deleted"] CRMembersRoleUser u g members r' signed -> ttyUser u $ viewMemberRoleUserChanged g members r' signed CRMembersBlockedForAllUser u g members blocked signed -> ttyUser u $ viewMembersBlockedForAllUser g members blocked signed - CRGroupUpdated u g g' m signed -> ttyUser u $ viewGroupUpdated g g' m signed + CRGroupUpdated u g g' m signed -> ttyUser u $ viewGroupUpdated g g' m (if signed then Just MSSVerified else Nothing) CRGroupProfile u g -> ttyUser u $ viewGroupProfile g CRGroupDescription u g -> ttyUser u $ viewGroupDescription g CRGroupLinkCreated u g gLink -> ttyUser u $ groupLink_ "Group link is created!" g gLink @@ -360,7 +360,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte testViewItem :: CChatItem c -> Maybe GroupMember -> Text testViewItem (CChatItem _ ci@ChatItem {meta = CIMeta {itemText, msgSigned}}) membership_ = let deleted_ = maybe "" (\t -> " [" <> t <> "]") (chatItemDeletedText ci membership_) - in itemText <> signedStr msgSigned <> deleted_ + in itemText <> sigStatusStr msgSigned <> deleted_ unmuted :: User -> ChatInfo c -> ChatItem c d -> [StyledString] -> [StyledString] unmuted u chat ci@ChatItem {chatDir} = unmuted' u chat chatDir $ isUserMention ci unmutedReaction :: User -> ChatInfo c -> CIReaction c d -> [StyledString] -> [StyledString] @@ -372,6 +372,12 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte | otherwise = [] withMessages wm = if wm then " with all messages" else "" +sigStatusStr :: IsString a => Maybe MsgSigStatus -> a +sigStatusStr = \case + Just MSSVerified -> " (signed)" + Just MSSSignedNoKey -> " (signed, no key to verify)" + Nothing -> "" + signedStr :: IsString a => Bool -> a signedStr signed = if signed then " (signed)" else "" @@ -476,10 +482,10 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView} CEvtMemberAcceptedByOther u g acceptingMember m -> ttyUser u $ viewMemberAcceptedByOther g acceptingMember m CEvtMemberRole u g by m r r' signed -> ttyUser u $ viewMemberRoleChanged g by m r r' signed CEvtMemberBlockedForAll u g by m blocked signed -> ttyUser u $ viewMemberBlockedForAll g by m blocked signed - CEvtDeletedMemberUser u g by wm signed -> ttyUser u $ [ttyGroup' g <> ": " <> ttyMember by <> " removed you from the group" <> withMessages wm <> signedStr signed] <> groupPreserved g - CEvtDeletedMember u g by m wm signed -> ttyUser u [ttyGroup' g <> ": " <> ttyMember by <> " removed " <> ttyMember m <> " from the group" <> withMessages wm <> signedStr signed] - CEvtLeftMember u g m signed -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " left the group" <> signedStr signed] - CEvtGroupDeleted u g m signed -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " deleted the group" <> signedStr signed, "use " <> highlight ("/d #" <> viewGroupName g) <> " to delete the local copy of the group"] + CEvtDeletedMemberUser u g by wm signed -> ttyUser u $ [ttyGroup' g <> ": " <> ttyMember by <> " removed you from the group" <> withMessages wm <> sigStatusStr signed] <> groupPreserved g + CEvtDeletedMember u g by m wm signed -> ttyUser u [ttyGroup' g <> ": " <> ttyMember by <> " removed " <> ttyMember m <> " from the group" <> withMessages wm <> sigStatusStr signed] + CEvtLeftMember u g m signed -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " left the group" <> sigStatusStr signed] + CEvtGroupDeleted u g m signed -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " deleted the group" <> sigStatusStr signed, "use " <> highlight ("/d #" <> viewGroupName g) <> " to delete the local copy of the group"] CEvtGroupUpdated u g g' m signed -> ttyUser u $ viewGroupUpdated g g' m signed CEvtAcceptingGroupJoinRequestMember _ g m -> [ttyFullMember m <> ": accepting request to join group " <> ttyGroup' g <> "..."] CEvtNoMemberContactCreating u g m -> ttyUser u ["member " <> ttyGroup' g <> " " <> ttyMember m <> " does not have direct connection, creating"] @@ -732,8 +738,8 @@ viewChatItem chat ci@ChatItem {chatDir, meta = meta@CIMeta {itemForwarded, forwa ("", Just _, []) -> [] ("", Just CIFile {fileName}, _) -> view dir context (MCText $ T.pack fileName) ts tz meta _ -> view dir context mc ts tz meta - showSndItem to = showItem $ sentWithTime_ ts tz [to <> plainContent content <> signedStr msgSigned] meta - showRcvItem from = showItem $ receivedWithTime_ ts tz from [] meta [plainContent content <> signedStr msgSigned] False + showSndItem to = showItem $ sentWithTime_ ts tz [to <> plainContent content <> sigStatusStr msgSigned] meta + showRcvItem from = showItem $ receivedWithTime_ ts tz from [] meta [plainContent content <> sigStatusStr msgSigned] False showSndItemProhibited to = showItem $ sentWithTime_ ts tz [to <> plainContent content <> " " <> prohibited] meta showRcvItemProhibited from = showItem $ receivedWithTime_ ts tz from [] meta [plainContent content <> " " <> prohibited] False showItem ss = if doShow then ss else [] @@ -1305,7 +1311,7 @@ connectedMember m = case memberCategory m of GCPostMember -> "new member " <> ttyMember m -- without fullName as as it was shown in joinedGroupMemberConnecting _ -> "member " <> ttyMember m -- these case is not used -viewMemberRoleChanged :: GroupInfo -> GroupMember -> GroupMember -> GroupMemberRole -> GroupMemberRole -> Bool -> [StyledString] +viewMemberRoleChanged :: GroupInfo -> GroupMember -> GroupMember -> GroupMemberRole -> GroupMemberRole -> Maybe MsgSigStatus -> [StyledString] viewMemberRoleChanged g@GroupInfo {membership} by m r r' signed | r == r' = [ttyGroup' g <> ": member role did not change"] | groupMemberId' membership == memId = view "your role" @@ -1313,16 +1319,16 @@ viewMemberRoleChanged g@GroupInfo {membership} by m r r' signed | otherwise = view $ "the role of " <> ttyMember m where memId = groupMemberId' m - view s = [ttyGroup' g <> ": " <> ttyMember by <> " changed " <> s <> " from " <> showRole r <> " to " <> showRole r' <> signedStr signed] + view s = [ttyGroup' g <> ": " <> ttyMember by <> " changed " <> s <> " from " <> showRole r <> " to " <> showRole r' <> sigStatusStr signed] viewMemberRoleUserChanged :: GroupInfo -> [GroupMember] -> GroupMemberRole -> Bool -> [StyledString] viewMemberRoleUserChanged g members r signed = case members of [m] -> [ttyGroup' g <> ": you changed the role of " <> ttyMember m <> " to " <> showRole r <> signedStr signed] mems' -> [ttyGroup' g <> ": you changed the role of " <> sShow (length mems') <> " members to " <> showRole r <> signedStr signed] -viewMemberBlockedForAll :: GroupInfo -> GroupMember -> GroupMember -> Bool -> Bool -> [StyledString] +viewMemberBlockedForAll :: GroupInfo -> GroupMember -> GroupMember -> Bool -> Maybe MsgSigStatus -> [StyledString] viewMemberBlockedForAll g by m blocked signed = - [ttyGroup' g <> ": " <> ttyMember by <> " " <> (if blocked then "blocked" else "unblocked") <> " " <> ttyMember m <> signedStr signed] + [ttyGroup' g <> ": " <> ttyMember by <> " " <> (if blocked then "blocked" else "unblocked") <> " " <> ttyMember m <> sigStatusStr signed] viewMembersBlockedForAllUser :: GroupInfo -> [GroupMember] -> Bool -> Bool -> [StyledString] viewMembersBlockedForAllUser g members blocked signed = case members of @@ -1894,7 +1900,7 @@ countactUserPrefText cup = case cup of CUPUser p -> "default (" <> preferenceText p <> ")" CUPContact p -> preferenceText p -viewGroupUpdated :: GroupInfo -> GroupInfo -> Maybe GroupMember -> Bool -> [StyledString] +viewGroupUpdated :: GroupInfo -> GroupInfo -> Maybe GroupMember -> Maybe MsgSigStatus -> [StyledString] viewGroupUpdated GroupInfo {localDisplayName = n, groupProfile = GroupProfile {fullName, shortDescr, description, image, groupPreferences = gps, memberAdmission = ma}} g'@GroupInfo {localDisplayName = n', groupProfile = GroupProfile {fullName = fullName', shortDescr = shortDescr', description = description', image = image', groupPreferences = gps', memberAdmission = ma'}} @@ -1904,7 +1910,7 @@ viewGroupUpdated then [] else memberUpdated <> update where - memberUpdated = maybe [] (\m' -> [ttyMember m' <> " updated group " <> ttyGroup n <> ":" <> signedStr signed]) m + memberUpdated = maybe [] (\m' -> [ttyMember m' <> " updated group " <> ttyGroup n <> ":" <> sigStatusStr signed]) m groupProfileUpdated = ["changed to " <> ttyFullGroup g' | n /= n'] <> ["full name " <> if T.null fullName' || fullName' == n' then "removed" else "changed to: " <> plain fullName' | n == n' && fullName /= fullName']