From 79e0884e5e7321b067013005d926ee7de2c5597f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 27 Jun 2026 08:33:47 +0100 Subject: [PATCH] generate bot types, schema, unStrJSON, fix tests --- .../xcshareddata/swiftpm/Package.resolved | 66 +++++++++++++++++++ bots/api/TYPES.md | 38 ++++++----- bots/src/API/Docs/Commands.hs | 1 + bots/src/API/Docs/Types.hs | 4 +- bots/src/API/TypeInfo.hs | 2 + cabal.project | 5 +- .../types/typescript/src/types.ts | 46 ++++++------- .../src/simplex_chat/types/_types.py | 45 ++++++------- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Badges.hs | 29 ++++---- src/Simplex/Chat/Library/Commands.hs | 20 +++--- src/Simplex/Chat/Store/Direct.hs | 12 ++-- src/Simplex/Chat/Store/Groups.hs | 6 +- .../Migrations/M20260603_simplex_name.hs | 2 +- .../Store/Postgres/Migrations/chat_schema.sql | 37 +++-------- .../Migrations/M20260603_simplex_name.hs | 2 +- .../Store/SQLite/Migrations/chat_schema.sql | 37 ++--------- src/Simplex/Chat/Store/Shared.hs | 16 ++--- src/Simplex/Chat/Types.hs | 10 +-- src/Simplex/Chat/View.hs | 2 +- tests/Bots/BroadcastTests.hs | 2 +- tests/Bots/DirectoryTests.hs | 2 +- tests/ChatClient.hs | 1 + tests/ChatTests/Profiles.hs | 2 +- tests/ChatTests/Utils.hs | 2 +- tests/ProtocolTests.hs | 43 +++--------- 26 files changed, 215 insertions(+), 219 deletions(-) create mode 100644 apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000000..978e1d9630 --- /dev/null +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,66 @@ +{ + "originHash" : "60aeecb7917535a5e44ade0dbb5411ab112a959283e565a04c212c8af4e7dee9", + "pins" : [ + { + "identity" : "codescanner", + "kind" : "remoteSourceControl", + "location" : "https://github.com/twostraws/CodeScanner", + "state" : { + "revision" : "34da57fb63b47add20de8a85da58191523ccce57", + "version" : "2.5.0" + } + }, + { + "identity" : "elegant-emoji-picker", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Finalet/Elegant-Emoji-Picker", + "state" : { + "branch" : "main", + "revision" : "71d2d46092b4d550cc593614efc06438f845f6e6" + } + }, + { + "identity" : "ink", + "kind" : "remoteSourceControl", + "location" : "https://github.com/johnsundell/ink", + "state" : { + "revision" : "bcc9f219900a62c4210e6db726035d7f03ae757b", + "version" : "0.6.0" + } + }, + { + "identity" : "lzstring-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Ibrahimhass/lzstring-swift", + "state" : { + "revision" : "7f62f21de5b18582a950e1753b775cc614722407" + } + }, + { + "identity" : "swiftygif", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kirualex/SwiftyGif", + "state" : { + "revision" : "5e8619335d394901379c9add5c4c1c2f420b3800" + } + }, + { + "identity" : "webrtc", + "kind" : "remoteSourceControl", + "location" : "https://github.com/simplex-chat/WebRTC.git", + "state" : { + "revision" : "34bedc50f9c58dccf4967ea59c7e6a47d620803b" + } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams", + "state" : { + "revision" : "9234124cff5e22e178988c18d8b95a8ae8007f76", + "version" : "5.1.2" + } + } + ], + "version" : 3 +} diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 2f1332852e..4fca6ac295 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -140,6 +140,7 @@ This file is generated automatically. - [MsgReaction](#msgreaction) - [MsgReceiptStatus](#msgreceiptstatus) - [MsgSigStatus](#msgsigstatus) +- [NameClaimProof](#nameclaimproof) - [NameErrorType](#nameerrortype) - [NetworkError](#networkerror) - [NewUser](#newuser) @@ -315,9 +316,8 @@ FILE: - type: "FILE" - fileErr: [FileErrorType](#fileerrortype) -NAME: -- type: "NAME" -- nameErr: [NameErrorType](#nameerrortype) +NO_NAME_SERVERS: +- type: "NO_NAME_SERVERS" PROXY: - type: "PROXY" @@ -1697,7 +1697,6 @@ Name: - authErrCounter: int - quotaErrCounter: int - createdAt: UTCTime -- simplexName: [SimplexNameInfo](#simplexnameinfo)? --- @@ -1804,8 +1803,6 @@ Error: - uiThemes: [UIThemeEntityOverrides](#uithemeentityoverrides)? - chatDeleted: bool - customData: JSONObject? -- simplexName: [SimplexNameInfo](#simplexnameinfo)? -- simplexNameVerifiedAt: UTCTime? --- @@ -2368,8 +2365,7 @@ MemberSupport: - membersRequireAttention: int - viaGroupLinkUri: string? - groupKeys: [GroupKeys](#groupkeys)? -- simplexName: [SimplexNameInfo](#simplexnameinfo)? -- simplexNameVerifiedAt: UTCTime? +- groupDomainVerification: bool? --- @@ -2584,7 +2580,6 @@ UpdateRequired: - description: string? - image: string? - publicGroup: [PublicGroupProfile](#publicgroupprofile)? -- simplexName: [SimplexNameInfo](#simplexnameinfo)? - groupPreferences: [GroupPreferences](#grouppreferences)? - memberAdmission: [GroupMemberAdmission](#groupmemberadmission)? @@ -2790,11 +2785,13 @@ Unknown: - shortDescr: string? - image: string? - contactLink: string? -- simplexName: [SimplexNameInfo](#simplexnameinfo)? - preferences: [Preferences](#preferences)? - peerType: [ChatPeerType](#chatpeertype)? - localBadge: [LocalBadge](#localbadge)? - localAlias: string +- contactDomain: [SimplexNameInfo](#simplexnameinfo)? +- contactDomainVerification: bool? +- contactDomainProof: [NameClaimProof](#nameclaimproof)? --- @@ -2970,6 +2967,16 @@ Unknown: - "signedNoKey" +--- + +## NameClaimProof + +**Record type**: +- linkOwnerId: string? +- presHeader: string +- signature: string + + --- ## NameErrorType @@ -2979,11 +2986,8 @@ Unknown: NO_RESOLVER: - type: "NO_RESOLVER" -NO_NAME: -- type: "NO_NAME" - -NO_SERVERS: -- type: "NO_SERVERS" +NOT_FOUND: +- type: "NOT_FOUND" RESOLVER: - type: "RESOLVER" @@ -3157,10 +3161,11 @@ count= - shortDescr: string? - image: string? - contactLink: string? -- simplexName: [SimplexNameInfo](#simplexnameinfo)? - preferences: [Preferences](#preferences)? - peerType: [ChatPeerType](#chatpeertype)? - badge: [BadgeProof](#badgeproof)? +- contactDomain: string? +- contactDomainProof: [NameClaimProof](#nameclaimproof)? --- @@ -3210,6 +3215,7 @@ NO_SESSION: **Record type**: - groupWebPage: string? - groupDomain: string? +- groupDomainProof: [NameClaimProof](#nameclaimproof)? - domainWebPage: bool - allowEmbedding: bool diff --git a/bots/src/API/Docs/Commands.hs b/bots/src/API/Docs/Commands.hs index 7e364cadf6..618546f438 100644 --- a/bots/src/API/Docs/Commands.hs +++ b/bots/src/API/Docs/Commands.hs @@ -414,6 +414,7 @@ undocumentedCommands = "APISetServerOperators", "APISetUserContactReceipts", "APISetUserGroupReceipts", + "APISetUserName", "APISetUserServers", "APISetUserUIThemes", "APIShareChatMsgContent", diff --git a/bots/src/API/Docs/Types.hs b/bots/src/API/Docs/Types.hs index 3f9ccac830..4b8eb8d411 100644 --- a/bots/src/API/Docs/Types.hs +++ b/bots/src/API/Docs/Types.hs @@ -34,7 +34,7 @@ import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Shared import Simplex.Chat.Operators import Simplex.Messaging.Agent.Store.Entity (DBStored (..)) -import Simplex.Chat.Badges (BadgeInfo (..), BadgeProof (..), BadgeStatus (..), BadgeType (..), JSONBadge (..)) +import Simplex.Chat.Badges import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared @@ -322,6 +322,7 @@ chatTypesDocsData = (sti @MsgReaction, STUnion, "MR", [], "", ""), (sti @MsgReceiptStatus, STEnum, "MR", [], "", ""), (sti @MsgSigStatus, STEnum, "MSS", [], "", ""), + (sti @NameClaimProof, STRecord, "", [], "", ""), (sti @NameErrorType, STUnion, "", [], "", ""), (sti @NetworkError, STUnion, "NE", [], "", ""), (sti @NewUser, STRecord, "", [], "", ""), @@ -551,6 +552,7 @@ deriving instance Generic MsgFilter deriving instance Generic MsgReaction deriving instance Generic MsgReceiptStatus deriving instance Generic MsgSigStatus +deriving instance Generic NameClaimProof deriving instance Generic NameErrorType deriving instance Generic NetworkError deriving instance Generic NewUser diff --git a/bots/src/API/TypeInfo.hs b/bots/src/API/TypeInfo.hs index c5a3b11953..8b2c3974b7 100644 --- a/bots/src/API/TypeInfo.hs +++ b/bots/src/API/TypeInfo.hs @@ -215,11 +215,13 @@ toTypeInfo tr = "Text", "MREmojiChar", "PrivateKey", + "ProofPresHeader", "PublicKey", "ProtocolServer", "SbKey", "SharedMsgId", "Signature", + "StrJSON", "TransportHost", "UIColor", "UserPwd", diff --git a/cabal.project b/cabal.project index 223ad8f57e..06c1ce3026 100644 --- a/cabal.project +++ b/cabal.project @@ -1,4 +1,5 @@ -packages: . ../simplexmq +packages: . +-- packages: . ../simplexmq -- packages: . ../simplexmq ../direct-sqlcipher ../sqlcipher-simple -- uncomment two sections below to run tests with coverage @@ -20,7 +21,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 42dd4bf207cfdfa293e144d00a09dadb0dca4f57 + tag: f83715cf240fc1bb28a2089424b88891be3c5764 source-repository-package type: git diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index c06b3e96fa..1ee97b8b32 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -66,7 +66,7 @@ export type AgentErrorType = | AgentErrorType.NTF | AgentErrorType.XFTP | AgentErrorType.FILE - | AgentErrorType.NAME + | AgentErrorType.NO_NAME_SERVERS | AgentErrorType.PROXY | AgentErrorType.RCP | AgentErrorType.BROKER @@ -85,7 +85,7 @@ export namespace AgentErrorType { | "NTF" | "XFTP" | "FILE" - | "NAME" + | "NO_NAME_SERVERS" | "PROXY" | "RCP" | "BROKER" @@ -138,9 +138,8 @@ export namespace AgentErrorType { fileErr: FileErrorType } - export interface NAME extends Interface { - type: "NAME" - nameErr: NameErrorType + export interface NO_NAME_SERVERS extends Interface { + type: "NO_NAME_SERVERS" } export interface PROXY extends Interface { @@ -1919,7 +1918,6 @@ export interface Connection { authErrCounter: number // int quotaErrCounter: number // int createdAt: string // ISO-8601 timestamp - simplexName?: SimplexNameInfo } export type ConnectionEntity = @@ -2051,8 +2049,6 @@ export interface Contact { uiThemes?: UIThemeEntityOverrides chatDeleted: boolean customData?: object - simplexName?: SimplexNameInfo - simplexNameVerifiedAt?: string // ISO-8601 timestamp } export type ContactAddressPlan = @@ -2660,8 +2656,7 @@ export interface GroupInfo { membersRequireAttention: number // int viaGroupLinkUri?: string groupKeys?: GroupKeys - simplexName?: SimplexNameInfo - simplexNameVerifiedAt?: string // ISO-8601 timestamp + groupDomainVerification?: boolean } export interface GroupKeys { @@ -2848,7 +2843,6 @@ export interface GroupProfile { description?: string image?: string publicGroup?: PublicGroupProfile - simplexName?: SimplexNameInfo groupPreferences?: GroupPreferences memberAdmission?: GroupMemberAdmission } @@ -3035,11 +3029,13 @@ export interface LocalProfile { shortDescr?: string image?: string contactLink?: string - simplexName?: SimplexNameInfo preferences?: Preferences peerType?: ChatPeerType localBadge?: LocalBadge localAlias: string + contactDomain?: SimplexNameInfo + contactDomainVerification?: boolean + contactDomainProof?: NameClaimProof } export enum MemberCriteria { @@ -3233,14 +3229,16 @@ export enum MsgSigStatus { SignedNoKey = "signedNoKey", } -export type NameErrorType = - | NameErrorType.NO_RESOLVER - | NameErrorType.NO_NAME - | NameErrorType.NO_SERVERS - | NameErrorType.RESOLVER +export interface NameClaimProof { + linkOwnerId?: string + presHeader: string + signature: string +} + +export type NameErrorType = NameErrorType.NO_RESOLVER | NameErrorType.NOT_FOUND | NameErrorType.RESOLVER export namespace NameErrorType { - export type Tag = "NO_RESOLVER" | "NO_NAME" | "NO_SERVERS" | "RESOLVER" + export type Tag = "NO_RESOLVER" | "NOT_FOUND" | "RESOLVER" interface Interface { type: Tag @@ -3250,12 +3248,8 @@ export namespace NameErrorType { type: "NO_RESOLVER" } - export interface NO_NAME extends Interface { - type: "NO_NAME" - } - - export interface NO_SERVERS extends Interface { - type: "NO_SERVERS" + export interface NOT_FOUND extends Interface { + type: "NOT_FOUND" } export interface RESOLVER extends Interface { @@ -3419,10 +3413,11 @@ export interface Profile { shortDescr?: string image?: string contactLink?: string - simplexName?: SimplexNameInfo preferences?: Preferences peerType?: ChatPeerType badge?: BadgeProof + contactDomain?: string + contactDomainProof?: NameClaimProof } export type ProxyClientError = @@ -3484,6 +3479,7 @@ export namespace ProxyError { export interface PublicGroupAccess { groupWebPage?: string groupDomain?: string + groupDomainProof?: NameClaimProof domainWebPage: boolean allowEmbedding: boolean } diff --git a/packages/simplex-chat-python/src/simplex_chat/types/_types.py b/packages/simplex-chat-python/src/simplex_chat/types/_types.py index 953dcf75fc..f92943c07d 100644 --- a/packages/simplex-chat-python/src/simplex_chat/types/_types.py +++ b/packages/simplex-chat-python/src/simplex_chat/types/_types.py @@ -78,9 +78,8 @@ class AgentErrorType_FILE(TypedDict): type: Literal["FILE"] fileErr: "FileErrorType" -class AgentErrorType_NAME(TypedDict): - type: Literal["NAME"] - nameErr: "NameErrorType" +class AgentErrorType_NO_NAME_SERVERS(TypedDict): + type: Literal["NO_NAME_SERVERS"] class AgentErrorType_PROXY(TypedDict): type: Literal["PROXY"] @@ -127,7 +126,7 @@ AgentErrorType = ( | AgentErrorType_NTF | AgentErrorType_XFTP | AgentErrorType_FILE - | AgentErrorType_NAME + | AgentErrorType_NO_NAME_SERVERS | AgentErrorType_PROXY | AgentErrorType_RCP | AgentErrorType_BROKER @@ -138,7 +137,7 @@ AgentErrorType = ( | AgentErrorType_INACTIVE ) -AgentErrorType_Tag = Literal["CMD", "CONN", "NO_USER", "SMP", "NTF", "XFTP", "FILE", "NAME", "PROXY", "RCP", "BROKER", "AGENT", "NOTICE", "INTERNAL", "CRITICAL", "INACTIVE"] +AgentErrorType_Tag = Literal["CMD", "CONN", "NO_USER", "SMP", "NTF", "XFTP", "FILE", "NO_NAME_SERVERS", "PROXY", "RCP", "BROKER", "AGENT", "NOTICE", "INTERNAL", "CRITICAL", "INACTIVE"] class AutoAccept(TypedDict): acceptIncognito: bool @@ -1342,7 +1341,6 @@ class Connection(TypedDict): authErrCounter: int # int quotaErrCounter: int # int createdAt: str # ISO-8601 timestamp - simplexName: NotRequired["SimplexNameInfo"] class ConnectionEntity_rcvDirectMsgConnection(TypedDict): type: Literal["rcvDirectMsgConnection"] @@ -1443,8 +1441,6 @@ class Contact(TypedDict): uiThemes: NotRequired["UIThemeEntityOverrides"] chatDeleted: bool customData: NotRequired[dict[str, object]] - simplexName: NotRequired["SimplexNameInfo"] - simplexNameVerifiedAt: NotRequired[str] # ISO-8601 timestamp class ContactAddressPlan_ok(TypedDict): type: Literal["ok"] @@ -1865,8 +1861,7 @@ class GroupInfo(TypedDict): membersRequireAttention: int # int viaGroupLinkUri: NotRequired[str] groupKeys: NotRequired["GroupKeys"] - simplexName: NotRequired["SimplexNameInfo"] - simplexNameVerifiedAt: NotRequired[str] # ISO-8601 timestamp + groupDomainVerification: NotRequired[bool] class GroupKeys(TypedDict): publicGroupId: str @@ -1994,7 +1989,6 @@ class GroupProfile(TypedDict): description: NotRequired[str] image: NotRequired[str] publicGroup: NotRequired["PublicGroupProfile"] - simplexName: NotRequired["SimplexNameInfo"] groupPreferences: NotRequired["GroupPreferences"] memberAdmission: NotRequired["GroupMemberAdmission"] @@ -2125,11 +2119,13 @@ class LocalProfile(TypedDict): shortDescr: NotRequired[str] image: NotRequired[str] contactLink: NotRequired[str] - simplexName: NotRequired["SimplexNameInfo"] preferences: NotRequired["Preferences"] peerType: NotRequired["ChatPeerType"] localBadge: NotRequired["LocalBadge"] localAlias: str + contactDomain: NotRequired["SimplexNameInfo"] + contactDomainVerification: NotRequired[bool] + contactDomainProof: NotRequired["NameClaimProof"] MemberCriteria = Literal["all"] @@ -2262,27 +2258,24 @@ MsgReceiptStatus = Literal["ok", "badMsgHash"] MsgSigStatus = Literal["verified", "signedNoKey"] +class NameClaimProof(TypedDict): + linkOwnerId: NotRequired[str] + presHeader: str + signature: str + class NameErrorType_NO_RESOLVER(TypedDict): type: Literal["NO_RESOLVER"] -class NameErrorType_NO_NAME(TypedDict): - type: Literal["NO_NAME"] - -class NameErrorType_NO_SERVERS(TypedDict): - type: Literal["NO_SERVERS"] +class NameErrorType_NOT_FOUND(TypedDict): + type: Literal["NOT_FOUND"] class NameErrorType_RESOLVER(TypedDict): type: Literal["RESOLVER"] resolverErr: str -NameErrorType = ( - NameErrorType_NO_RESOLVER - | NameErrorType_NO_NAME - | NameErrorType_NO_SERVERS - | NameErrorType_RESOLVER -) +NameErrorType = NameErrorType_NO_RESOLVER | NameErrorType_NOT_FOUND | NameErrorType_RESOLVER -NameErrorType_Tag = Literal["NO_RESOLVER", "NO_NAME", "NO_SERVERS", "RESOLVER"] +NameErrorType_Tag = Literal["NO_RESOLVER", "NOT_FOUND", "RESOLVER"] class NetworkError_connectError(TypedDict): type: Literal["connectError"] @@ -2400,10 +2393,11 @@ class Profile(TypedDict): shortDescr: NotRequired[str] image: NotRequired[str] contactLink: NotRequired[str] - simplexName: NotRequired["SimplexNameInfo"] preferences: NotRequired["Preferences"] peerType: NotRequired["ChatPeerType"] badge: NotRequired["BadgeProof"] + contactDomain: NotRequired[str] + contactDomainProof: NotRequired["NameClaimProof"] class ProxyClientError_protocolError(TypedDict): type: Literal["protocolError"] @@ -2446,6 +2440,7 @@ ProxyError_Tag = Literal["PROTOCOL", "BROKER", "BASIC_AUTH", "NO_SESSION"] class PublicGroupAccess(TypedDict): groupWebPage: NotRequired[str] groupDomain: NotRequired[str] + groupDomainProof: NotRequired["NameClaimProof"] domainWebPage: bool allowEmbedding: bool diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 9a192bfc0f..df4aa3a34d 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."b6d0bb585f34ce47f4c464b04e4f759d87812711" = "0b86phypsc4cbbc0nf5bv0xlgmlzp44m51d2wl9nc82cf4grpgx2"; + "https://github.com/simplex-chat/simplexmq.git"."f83715cf240fc1bb28a2089424b88891be3c5764" = "0ia5b50sfkmq3kkrm9g4fv1arq5ixx7w8ca8k928psm5sfss7kpc"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Badges.hs b/src/Simplex/Chat/Badges.hs index 28b6d90005..d08ae42454 100644 --- a/src/Simplex/Chat/Badges.hs +++ b/src/Simplex/Chat/Badges.hs @@ -1,6 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleInstances #-} @@ -29,7 +30,7 @@ module Simplex.Chat.Badges maxFileSizeLegend, ProofPresHeaderTag (..), ProofPresHeader (..), - ClaimProof (..), + NameClaimProof (..), signNameProof, verifyNameProofSig, proofPresHeaderLink, @@ -225,6 +226,7 @@ data ProofPresHeader | PHSimplexLink AConnShortLink | PHUnknown Char ByteString deriving (Eq, Show) + deriving (ToJSON, FromJSON) via (StrJSON "ProofPresHeader" ProofPresHeader) instance StrEncoding ProofPresHeader where strEncode = \case @@ -244,17 +246,10 @@ proofPresHeaderAccepted = \case PHSimplexLink _ -> True PHUnknown _ _ -> True -instance ToJSON ProofPresHeader where - toJSON = strToJSON - toEncoding = strToJEncoding - -instance FromJSON ProofPresHeader where - parseJSON = strParseJSON "ProofPresHeader" - -- A name claim proof: signed by the address owner's key (linkOwnerId = Just oid for a -- channel's delegated owner, Nothing = the address root key) over -- strEncode name <> strEncode presHeader, bound to the presentation context (the link). -data ClaimProof = ClaimProof +data NameClaimProof = NameClaimProof { linkOwnerId :: Maybe (StrJSON "OwnerId" OwnerId), presHeader :: ProofPresHeader, signature :: C.Signature 'C.Ed25519 @@ -267,9 +262,9 @@ nameProofPayload name presHeader = strEncode name <> strEncode presHeader -- mint a name proof: sign (name, presentation context) with the address owner key. -- linkOwnerId names the signing owner in the link's owner chain (Nothing = root key, the contact-address case). -signNameProof :: C.PrivateKeyEd25519 -> Maybe OwnerId -> SimplexNameInfo -> ProofPresHeader -> ClaimProof +signNameProof :: C.PrivateKeyEd25519 -> Maybe OwnerId -> SimplexNameInfo -> ProofPresHeader -> NameClaimProof signNameProof key linkOwnerId name presHeader = - ClaimProof + NameClaimProof { linkOwnerId = StrJSON <$> linkOwnerId, presHeader, signature = C.sign' key (nameProofPayload name presHeader) @@ -277,8 +272,8 @@ signNameProof key linkOwnerId name presHeader = -- verify a name proof's signature against the resolved address owner key. The caller must -- SEPARATELY check the proof's presHeader link is the link it is presented through (anti-replay). -verifyNameProofSig :: C.PublicKeyEd25519 -> SimplexNameInfo -> ClaimProof -> Bool -verifyNameProofSig ownerKey name ClaimProof {presHeader, signature} = +verifyNameProofSig :: C.PublicKeyEd25519 -> SimplexNameInfo -> NameClaimProof -> Bool +verifyNameProofSig ownerKey name NameClaimProof {presHeader, signature} = C.verify' ownerKey signature (nameProofPayload name presHeader) -- the link a proof is bound to (its anti-replay context), if any @@ -451,12 +446,12 @@ $(JQ.deriveJSON defaultJSON ''BadgeCredential) $(JQ.deriveJSON defaultJSON ''BadgeProof) -$(JQ.deriveJSON defaultJSON ''ClaimProof) +$(JQ.deriveJSON defaultJSON ''NameClaimProof) --- ClaimProof is stored as JSON in contact_profiles.contact_domain_proof (like a badge proof) -instance ToField ClaimProof where toField = toField . encodeJSON +-- NameClaimProof is stored as JSON in contact_profiles.contact_domain_proof (like a badge proof) +instance ToField NameClaimProof where toField = toField . encodeJSON -instance FromField ClaimProof where fromField = fromTextField_ decodeJSON +instance FromField NameClaimProof where fromField = fromTextField_ decodeJSON -- LocalBadge is sent to the UI/clients WITHOUT crypto - only disclosed info + status. The credential/proof -- bytes stay core-side. FromJSON reconstructs a display-only badge (empty proof) for read-only consumers diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 73e8a4947d..3802d5454f 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -55,7 +55,7 @@ import Data.Type.Equality import qualified Data.UUID as UUID import qualified Data.UUID.V4 as V4 import Simplex.Chat.Library.Subscriber -import Simplex.Chat.Badges (BadgeCredential (..), ClaimProof (..), LocalBadge (..), ProofPresHeader (..), maxXFTPFileSize, mkBadgeStatus, proofPresHeaderLink, signNameProof, verifyCredential, verifyNameProofSig) +import Simplex.Chat.Badges (BadgeCredential (..), NameClaimProof (..), LocalBadge (..), ProofPresHeader (..), maxXFTPFileSize, mkBadgeStatus, proofPresHeaderLink, signNameProof, verifyCredential, verifyNameProofSig) import Simplex.Chat.Call import Simplex.Chat.Controller import Simplex.Chat.Delivery (DeliveryJobScope (..), DeliveryJobSpec (..), DeliveryWorkerScope (..)) @@ -3136,7 +3136,7 @@ processChatCommand cxt nm = \case -- ยง4.9: only when the NAME changes, verify it resolves to this channel's join link -- (a web=/embed= edit that leaves the name unchanged triggers no name check, so one command stays clean) when (newName_ /= (existingAccess >>= groupDomain)) $ - forM_ ((\(StrJSON n) -> n) <$> newName_) $ \name -> do + forM_ newName_ $ \(StrJSON name) -> do let SimplexNameInfo {nameDomain = domain} = name a <- asks smpAgent NameRecord {nrSimplexChannel} <- liftIO (runExceptT $ resolveSimplexName a nm (aUserId user) domain) >>= either (throwError . chatErrorAgent) pure @@ -4778,7 +4778,7 @@ dispatchResolvedRecord cxt nm user ni@SimplexNameInfo {nameType} NameRecord {nrS liftIO (decodeLinkUserData cData) >>= maybe (throwError $ chatErrorAgent $ AGENT $ A_LINK "could not decode contact profile from RSLV link") pure -- consent gate: the resolved profile must claim the resolved name, else "name unknown" let Profile {contactDomain} = profile - unless (((\(StrJSON n) -> n) <$> contactDomain) == Just ni) $ throwChatError $ CESimplexNameNotFound ni + unless (contactDomain == Just (StrJSON ni)) $ throwChatError $ CESimplexNameNotFound ni let ccLink = CCLink cReq (Just l') accLink = ACCL SCMContact ccLink ct <- withStore $ \db -> createPreparedContact db cxt user profile accLink Nothing @@ -4794,7 +4794,7 @@ dispatchResolvedRecord cxt nm user ni@SimplexNameInfo {nameType} NameRecord {nrS liftIO (decodeLinkUserData cData) >>= maybe (throwError $ chatErrorAgent $ AGENT $ A_LINK "could not decode group profile from RSLV link") pure -- consent gate: the resolved group profile must claim the resolved name let GroupProfile {publicGroup} = groupProfile - unless (((\(StrJSON n) -> n) <$> (publicGroup >>= publicGroupAccess >>= groupDomain)) == Just ni) $ throwChatError $ CESimplexNameNotFound ni + unless ((publicGroup >>= publicGroupAccess >>= groupDomain) == Just (StrJSON ni)) $ throwChatError $ CESimplexNameNotFound ni let publicMemberCount_ = (\PublicGroupData {publicMemberCount} -> publicMemberCount) <$> publicGroupData_ useRelays = not direct subRole <- if useRelays then asks $ channelSubscriberRole . config else pure GRMember @@ -4888,7 +4888,7 @@ apiVerifySimplexName user nm chatRef = do pure $ CRSimplexNameVerified user chatRef claim verified where -- the claim, the link the peer was connected through (the presHeader anchor), the proof, and the 3-state persist callback - loadClaimAndLink :: StoreCxt -> CM (SimplexNameInfo, Maybe AConnShortLink, Maybe ClaimProof, DB.Connection -> Bool -> IO ()) + loadClaimAndLink :: StoreCxt -> CM (SimplexNameInfo, Maybe AConnShortLink, Maybe NameClaimProof, DB.Connection -> Bool -> IO ()) loadClaimAndLink cxt = case chatRef of ChatRef CTDirect cId _ -> do ct <- withFastStore $ \db -> getContact db cxt user cId @@ -4899,19 +4899,19 @@ apiVerifySimplexName user nm chatRef = do ChatRef CTGroup gId _ -> do g <- withFastStore $ \db -> getGroupInfo db cxt user gId let GroupInfo {groupId, groupProfile = GroupProfile {publicGroup}, preparedGroup} = g - gName = (\(StrJSON n) -> n) <$> (publicGroup >>= publicGroupAccess >>= groupDomain) + gName = unStrJSON <$> (publicGroup >>= publicGroupAccess >>= groupDomain) gProof = publicGroup >>= publicGroupAccess >>= groupDomainProof claim <- maybe (throwCmdError "group has no name to verify") pure gName let connLink_ = preparedGroup >>= \PreparedGroup {connLinkToConnect = CCLink _ sLnk_} -> ACSL SCMContact <$> sLnk_ pure (claim, connLink_, gProof, \db verified -> setGroupDomainVerified db user groupId verified) _ -> throwCmdError "APIVerifySimplexName supports only direct and group chat refs" -- the proof must be bound (anti-replay) to the link the peer was connected through - proofBoundTo :: ClaimProof -> AConnShortLink -> Bool - proofBoundTo ClaimProof {presHeader} connLink = + proofBoundTo :: NameClaimProof -> AConnShortLink -> Bool + proofBoundTo NameClaimProof {presHeader} connLink = (strEncode <$> proofPresHeaderLink presHeader) == Just (strEncode connLink) -- verify the proof signature against the resolved name's owner key - verifyProofKey :: SimplexNameInfo -> ClaimProof -> Text -> CM Bool - verifyProofKey claim proof@ClaimProof {linkOwnerId} resolvedText = + verifyProofKey :: SimplexNameInfo -> NameClaimProof -> Text -> CM Bool + verifyProofKey claim proof@NameClaimProof {linkOwnerId} resolvedText = case strDecode (encodeUtf8 resolvedText) :: Either String AConnectionLink of Right (ACL SCMContact (CLShort sLnk)) -> tryAllErrors (getShortLinkConnReq nm user sLnk) >>= \case diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 42a099387f..494ad0b100 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -573,10 +573,10 @@ updateContactProfile db cxt user@User {userId} c p' = do profile = toLocalProfile profileId p'' localAlias currentTs badgeVerified nameVerified updateContactProfile' currentTs badgeVerified profile where - Contact {contactId, localDisplayName, profile = lp@LocalProfile {profileId, displayName, localAlias, contactDomain = prevClaim, contactDomainVerification = prevVerification, contactDomainProof = prevProof}, userPreferences} = c - Profile {displayName = newName, contactDomain = profileContactDomain, preferences} = p' + Contact {contactId, localDisplayName, profile = lp@LocalProfile {profileId, displayName, localAlias, contactDomain = prevDomain, contactDomainVerification = prevVerification, contactDomainProof = prevProof}, userPreferences} = c + Profile {displayName = newName, contactDomain, preferences} = p' mergedPreferences = contactUserPreferences user userPreferences preferences $ contactConnIncognito c - claimChanged = prevClaim /= ((\(StrJSON n) -> n) <$> profileContactDomain) + claimChanged = prevDomain /= (unStrJSON <$> contactDomain) -- a name proof never rides an XInfo peer-send (we never mint or accept a PHTest name proof): -- keep the stored proof when the name is unchanged, drop it when the name changes (then it's unverified) p'' = (p' :: Profile) {contactDomainProof = if claimChanged then Nothing else prevProof} @@ -749,7 +749,7 @@ updateContactProfile_' db userId profileId Profile {displayName, fullName, short contact_domain_proof = ? WHERE user_id = ? AND contact_profile_id = ? |] - ((displayName, fullName, shortDescr, image, contactLink, preferences, peerType, updatedAt) :. badgeToRow badge badgeVerified :. (((\(StrJSON n) -> n) <$> contactDomain), contactDomainProof) :. (userId, profileId)) + ((displayName, fullName, shortDescr, image, contactLink, preferences, peerType, updatedAt) :. badgeToRow badge badgeVerified :. ((unStrJSON <$> contactDomain), contactDomainProof) :. (userId, profileId)) -- update only member profile fields (when member doesn't have associated contact - we can reset contactLink and prefs) updateMemberContactProfileReset_ :: DB.Connection -> UserId -> ProfileId -> Profile -> Maybe Bool -> IO () @@ -769,7 +769,7 @@ updateMemberContactProfileReset_' db userId profileId Profile {displayName, full contact_domain_proof = ? WHERE user_id = ? AND contact_profile_id = ? |] - ((displayName, fullName, shortDescr, image, updatedAt) :. badgeToRow badge badgeVerified :. (((\(StrJSON n) -> n) <$> contactDomain), contactDomainProof) :. (userId, profileId)) + ((displayName, fullName, shortDescr, image, updatedAt) :. badgeToRow badge badgeVerified :. ((unStrJSON <$> contactDomain), contactDomainProof) :. (userId, profileId)) -- update only member profile fields (when member has associated contact - we keep contactLink and prefs) updateMemberContactProfile_ :: DB.Connection -> UserId -> ProfileId -> Profile -> Maybe Bool -> IO () @@ -789,7 +789,7 @@ updateMemberContactProfile_' db userId profileId Profile {displayName, fullName, contact_domain_proof = ? WHERE user_id = ? AND contact_profile_id = ? |] - ((displayName, fullName, shortDescr, image, updatedAt) :. badgeToRow badge badgeVerified :. (((\(StrJSON n) -> n) <$> contactDomain), contactDomainProof) :. (userId, profileId)) + ((displayName, fullName, shortDescr, image, updatedAt) :. badgeToRow badge badgeVerified :. ((unStrJSON <$> contactDomain), contactDomainProof) :. (userId, profileId)) updateContactLDN_ :: DB.Connection -> User -> Int64 -> ContactName -> ContactName -> UTCTime -> IO () updateContactLDN_ db user@User {userId} contactId displayName newName updatedAt = do diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 796f5ddad8..5903d3da7f 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -222,7 +222,7 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Time.Clock (NominalDiffTime, UTCTime (..), addUTCTime, getCurrentTime) import Data.Text.Encoding (encodeUtf8) -import Simplex.Chat.Badges (BadgeRow, ClaimProof, badgeToRow, verifyBadge_) +import Simplex.Chat.Badges (BadgeRow, NameClaimProof, badgeToRow, verifyBadge_) import Simplex.Chat.Messages import Simplex.Chat.Operators import Simplex.Chat.Protocol hiding (Binary) @@ -255,7 +255,7 @@ import Database.SQLite.Simple (Only (..), Query, (:.) (..)) import Database.SQLite.Simple.QQ (sql) #endif -type MaybeGroupMemberRow = (Maybe GroupMemberId, Maybe GroupId, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId) :. ((Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe LocalAlias, Maybe Preferences) :. BadgeRow :. (Maybe SimplexNameInfo, Maybe BoolInt, Maybe ClaimProof)) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime, Maybe C.PublicKeyEd25519, Maybe ShortLinkContact) +type MaybeGroupMemberRow = (Maybe GroupMemberId, Maybe GroupId, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId) :. ((Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe LocalAlias, Maybe Preferences) :. BadgeRow :. (Maybe SimplexNameInfo, Maybe BoolInt, Maybe NameClaimProof)) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime, Maybe C.PublicKeyEd25519, Maybe ShortLinkContact) toMaybeGroupMember :: UTCTime -> Int64 -> MaybeGroupMemberRow -> Maybe GroupMember toMaybeGroupMember now userContactId ((Just groupMemberId, Just groupId, Just indexInGroup, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId) :. ((Just profileId, Just displayName, Just fullName, shortDescr, image, contactLink, peerType, Just localAlias, contactPreferences) :. badgeRow :. (profileContactDomain, profileContactDomainVerification, profileContactDomainProof)) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink)) = @@ -2664,7 +2664,7 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, pure $ Right $ (g' :: GroupInfo) {localDisplayName = ldn, groupProfile = p', fullGroupPreferences} where fullGroupPreferences = mergeGroupPreferences groupPreferences - groupClaim pg = (\(StrJSON n) -> n) <$> (pg >>= publicGroupAccess >>= groupDomain) + groupClaim pg = unStrJSON <$> (pg >>= publicGroupAccess >>= groupDomain) claimChanged = groupClaim oldPublicGroup /= groupClaim publicGroup g' = if claimChanged then (g :: GroupInfo) {groupDomainVerification = Nothing} else g -- Reset the verification when the channel name changes; prior verification diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20260603_simplex_name.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20260603_simplex_name.hs index 56ab5b1eeb..9c1c788990 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/M20260603_simplex_name.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20260603_simplex_name.hs @@ -15,7 +15,7 @@ import Text.RawString.QQ (r) -- are the local 3-state verification status (NULL = not attempted, 0 = failed, -- 1 = verified), reset to NULL when the claimed name changes. -- --- contact_profiles.contact_domain_proof holds the peer's name ClaimProof (JSON). +-- contact_profiles.contact_domain_proof holds the peer's name NameClaimProof (JSON). -- user_contact_links.link_priv_sig_key is the contact-address owner signing key -- (captured at short-link creation) used to sign the user's own name proofs. -- diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index 6f4d0c8721..7f82feca36 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -482,8 +482,7 @@ CREATE TABLE test_chat_schema.connections ( short_link_inv bytea, via_short_link_contact bytea, via_contact_uri bytea, - relay_test smallint DEFAULT 0 NOT NULL, - simplex_name text + relay_test smallint DEFAULT 0 NOT NULL ); @@ -542,7 +541,9 @@ CREATE TABLE test_chat_schema.contact_profiles ( badge_master_key bytea, badge_signature bytea, badge_key_idx bigint, - simplex_name text + contact_domain text, + contact_domain_verification smallint, + contact_domain_proof text ); @@ -623,9 +624,7 @@ CREATE TABLE test_chat_schema.contacts ( grp_direct_inv_from_group_id bigint, grp_direct_inv_from_group_member_id bigint, grp_direct_inv_from_member_conn_id bigint, - grp_direct_inv_started_connection smallint DEFAULT 0 NOT NULL, - simplex_name text, - simplex_name_verified_at timestamp with time zone + grp_direct_inv_started_connection smallint DEFAULT 0 NOT NULL ); @@ -871,7 +870,7 @@ CREATE TABLE test_chat_schema.group_profiles ( group_domain text, domain_web_page bigint, allow_embedding bigint, - simplex_name text + group_domain_proof text ); @@ -985,7 +984,7 @@ CREATE TABLE test_chat_schema.groups ( public_member_count bigint, relay_request_retries bigint DEFAULT 0 NOT NULL, relay_request_delay bigint DEFAULT 0 NOT NULL, - relay_request_execute_at timestamp with time zone DEFAULT '1970-01-01 04:00:00+04'::timestamp with time zone NOT NULL, + relay_request_execute_at timestamp with time zone DEFAULT '1970-01-01 01:00:00+01'::timestamp with time zone NOT NULL, relay_inactive_at timestamp with time zone, relay_sent_web_domain text, roster_version bigint, @@ -995,8 +994,7 @@ CREATE TABLE test_chat_schema.groups ( roster_sending_owner_gm_id bigint, roster_broker_ts timestamp with time zone, roster_blob bytea, - simplex_name text, - simplex_name_verified_at timestamp with time zone + group_domain_verification smallint ); @@ -1462,7 +1460,8 @@ CREATE TABLE test_chat_schema.user_contact_links ( business_address smallint DEFAULT 0, short_link_contact bytea, short_link_data_set smallint DEFAULT 0 NOT NULL, - short_link_large_data_set smallint DEFAULT 0 NOT NULL + short_link_large_data_set smallint DEFAULT 0 NOT NULL, + link_priv_sig_key bytea ); @@ -2195,10 +2194,6 @@ CREATE INDEX idx_contact_profiles_contact_link ON test_chat_schema.contact_profi -CREATE UNIQUE INDEX idx_contact_profiles_simplex_name ON test_chat_schema.contact_profiles USING btree (user_id, simplex_name) WHERE (simplex_name IS NOT NULL); - - - CREATE INDEX idx_contact_profiles_user_id ON test_chat_schema.contact_profiles USING btree (user_id); @@ -2255,10 +2250,6 @@ CREATE INDEX idx_contacts_grp_direct_inv_from_member_conn_id ON test_chat_schema -CREATE UNIQUE INDEX idx_contacts_simplex_name ON test_chat_schema.contacts USING btree (user_id, simplex_name) WHERE ((simplex_name IS NOT NULL) AND (deleted = 0)); - - - CREATE INDEX idx_contacts_xcontact_id ON test_chat_schema.contacts USING btree (xcontact_id); @@ -2391,10 +2382,6 @@ CREATE INDEX idx_group_members_user_id_local_display_name ON test_chat_schema.gr -CREATE UNIQUE INDEX idx_group_profiles_simplex_name ON test_chat_schema.group_profiles USING btree (user_id, simplex_name) WHERE (simplex_name IS NOT NULL); - - - CREATE INDEX idx_group_profiles_user_id ON test_chat_schema.group_profiles USING btree (user_id); @@ -2447,10 +2434,6 @@ CREATE INDEX idx_groups_relay_request_group_link ON test_chat_schema.groups USIN -CREATE UNIQUE INDEX idx_groups_simplex_name ON test_chat_schema.groups USING btree (user_id, simplex_name) WHERE (simplex_name IS NOT NULL); - - - CREATE INDEX idx_groups_summary_current_members_count ON test_chat_schema.groups USING btree (summary_current_members_count); diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20260603_simplex_name.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20260603_simplex_name.hs index c9881ab802..e8d90982ca 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20260603_simplex_name.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20260603_simplex_name.hs @@ -14,7 +14,7 @@ import Database.SQLite.Simple.QQ (sql) -- are the local 3-state verification status (NULL = not attempted, 0 = failed, -- 1 = verified), reset to NULL when the claimed name changes. -- --- contact_profiles.contact_domain_proof holds the peer's name ClaimProof (JSON). +-- contact_profiles.contact_domain_proof holds the peer's name NameClaimProof (JSON). -- user_contact_links.link_priv_sig_key is the contact-address owner signing key -- (captured at short-link creation) used to sign the user's own name proofs. -- diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 39e216e53b..e87fd1df36 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -29,7 +29,9 @@ CREATE TABLE contact_profiles( badge_master_key BLOB, badge_signature BLOB, badge_key_idx INTEGER, - simplex_name TEXT + contact_domain TEXT, + contact_domain_verification INTEGER, + contact_domain_proof TEXT ) STRICT; CREATE TABLE users( user_id INTEGER PRIMARY KEY, @@ -102,8 +104,6 @@ CREATE TABLE contacts( grp_direct_inv_from_group_member_id INTEGER REFERENCES group_members(group_member_id) ON DELETE SET NULL, grp_direct_inv_from_member_conn_id INTEGER REFERENCES connections(connection_id) ON DELETE SET NULL, grp_direct_inv_started_connection INTEGER NOT NULL DEFAULT 0, - simplex_name TEXT, - simplex_name_verified_at TEXT, FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE @@ -143,7 +143,7 @@ CREATE TABLE group_profiles( group_domain TEXT, domain_web_page INTEGER, allow_embedding INTEGER, - simplex_name TEXT + group_domain_proof TEXT ) STRICT; CREATE TABLE groups( group_id INTEGER PRIMARY KEY, -- local group ID @@ -204,8 +204,7 @@ CREATE TABLE groups( roster_sending_owner_gm_id INTEGER, roster_broker_ts TEXT, roster_blob BLOB, - simplex_name TEXT, - simplex_name_verified_at TEXT, -- received + group_domain_verification INTEGER, -- received FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE @@ -377,7 +376,6 @@ CREATE TABLE connections( via_short_link_contact BLOB, via_contact_uri BLOB, relay_test INTEGER NOT NULL DEFAULT 0, - simplex_name TEXT, FOREIGN KEY(snd_file_id, connection_id) REFERENCES snd_files(file_id, connection_id) ON DELETE CASCADE @@ -400,6 +398,7 @@ CREATE TABLE user_contact_links( short_link_contact BLOB, short_link_data_set INTEGER NOT NULL DEFAULT 0, short_link_large_data_set INTEGER NOT NULL DEFAULT 0, + link_priv_sig_key BLOB, UNIQUE(user_id, local_display_name) ) STRICT; CREATE TABLE contact_requests( @@ -1362,30 +1361,6 @@ CREATE INDEX idx_files_group_id_shared_msg_id ON files( shared_msg_id ); CREATE INDEX idx_files_roster_transfer_id ON files(roster_transfer_id); -CREATE UNIQUE INDEX idx_contacts_simplex_name -ON contacts( - user_id, - simplex_name -) -WHERE simplex_name IS NOT NULL AND deleted = 0; -CREATE UNIQUE INDEX idx_groups_simplex_name -ON groups( - user_id, - simplex_name -) -WHERE simplex_name IS NOT NULL; -CREATE UNIQUE INDEX idx_contact_profiles_simplex_name -ON contact_profiles( - user_id, - simplex_name -) -WHERE simplex_name IS NOT NULL; -CREATE UNIQUE INDEX idx_group_profiles_simplex_name -ON group_profiles( - user_id, - simplex_name -) -WHERE simplex_name IS NOT NULL; CREATE TRIGGER on_group_members_insert_update_summary AFTER INSERT ON group_members FOR EACH ROW diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index f248ef945e..e0e128e572 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -33,7 +33,7 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Data.Time.Clock (UTCTime (..), getCurrentTime) import Data.Type.Equality -import Simplex.Chat.Badges (BadgeRow, ClaimProof, badgeToRow, rowToBadge, verifyBadge_) +import Simplex.Chat.Badges (BadgeRow, NameClaimProof, badgeToRow, rowToBadge, verifyBadge_) import Simplex.Chat.Messages import Simplex.Chat.Remote.Types import Simplex.Chat.Types @@ -424,7 +424,7 @@ createContact_ db cxt User {userId} Profile {displayName, fullName, shortDescr, DB.execute db "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, chat_peer_type, user_id, local_alias, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx, contact_domain, contact_domain_proof) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - ((displayName, fullName, shortDescr, image, contactLink, peerType) :. (userId, localAlias, preferences, currentTs, currentTs) :. badgeToRow badge badgeVerified :. (((\(StrJSON n) -> n) <$> contactDomain), contactDomainProof)) + ((displayName, fullName, shortDescr, image, contactLink, peerType) :. (userId, localAlias, preferences, currentTs, currentTs) :. badgeToRow badge badgeVerified :. ((unStrJSON <$> contactDomain), contactDomainProof)) profileId <- insertedRowId db DB.execute db @@ -491,7 +491,7 @@ type PreparedContactRow = (Maybe AConnectionRequestUri, Maybe AConnShortLink, Ma type GroupDirectInvitationRow = (Maybe ConnReqInvitation, Maybe GroupId, Maybe GroupMemberId, Maybe Int64, BoolInt) -type ContactRow' = (ProfileId, ContactName, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. PreparedContactRow :. (Maybe Int64, Maybe GroupMemberId, BoolInt) :. GroupDirectInvitationRow :. (Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) :. BadgeRow :. (Maybe SimplexNameInfo, Maybe BoolInt, Maybe ClaimProof) +type ContactRow' = (ProfileId, ContactName, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. PreparedContactRow :. (Maybe Int64, Maybe GroupMemberId, BoolInt) :. GroupDirectInvitationRow :. (Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) :. BadgeRow :. (Maybe SimplexNameInfo, Maybe BoolInt, Maybe NameClaimProof) type ContactRow = Only ContactId :. ContactRow' @@ -538,7 +538,7 @@ getProfileById db userId profileId = do |] (userId, profileId) -type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Maybe GroupId, Maybe Int64) :. (Int64, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias) :. (Maybe XContactId, PQSupport, Maybe SharedMsgId, Maybe SharedMsgId, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat) :. BadgeRow :. (Maybe SimplexNameInfo, Maybe BoolInt, Maybe ClaimProof) +type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Maybe GroupId, Maybe Int64) :. (Int64, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias) :. (Maybe XContactId, PQSupport, Maybe SharedMsgId, Maybe SharedMsgId, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat) :. BadgeRow :. (Maybe SimplexNameInfo, Maybe BoolInt, Maybe NameClaimProof) toContactRequest :: UTCTime -> ContactRequestRow -> UserContactRequest toContactRequest now ((contactRequestId, localDisplayName, agentInvitationId, contactId_, businessGroupId_, userContactLinkId_) :. (profileId, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias) :. (xContactId, pqSupport, welcomeSharedMsgId, requestSharedMsgId, preferences, createdAt, updatedAt, minVer, maxVer) :. badgeRow :. (contactDomain, contactDomainVerification, contactDomainProof)) = do @@ -557,7 +557,7 @@ userQuery = JOIN contact_profiles ucp ON ucp.contact_profile_id = uct.contact_profile_id |] -toUser :: UTCTime -> (UserId, UserId, ContactId, ProfileId, BoolInt, Int64) :. (ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe Preferences) :. (BoolInt, BoolInt, BoolInt, BoolInt, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, BoolInt, BoolInt, Maybe UIThemeEntityOverrides) :. BadgeRow :. (Maybe SimplexNameInfo, Maybe BoolInt, Maybe ClaimProof) -> User +toUser :: UTCTime -> (UserId, UserId, ContactId, ProfileId, BoolInt, Int64) :. (ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe Preferences) :. (BoolInt, BoolInt, BoolInt, BoolInt, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, BoolInt, BoolInt, Maybe UIThemeEntityOverrides) :. BadgeRow :. (Maybe SimplexNameInfo, Maybe BoolInt, Maybe NameClaimProof) -> User toUser now ((userId, auId, userContactId, profileId, BI activeUser, activeOrder) :. (displayName, fullName, shortDescr, image, contactLink, peerType, userPreferences) :. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, BI autoAcceptMemberContacts, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, BI userChatRelay, BI clientService, uiThemes) :. badgeRow :. (contactDomain, contactDomainVerification, contactDomainProof)) = User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, activeOrder, fullPreferences, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, autoAcceptMemberContacts, viewPwdHash, userMemberProfileUpdatedAt, userChatRelay = BoolDef userChatRelay, clientService = BoolDef clientService, uiThemes} where @@ -679,11 +679,11 @@ type GroupKeysRow = (Maybe C.PrivateKeyEd25519, Maybe C.PublicKeyEd25519, Maybe type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData, Maybe GroupType, Maybe ShortLinkContact, Maybe B64UrlByteString) :. PublicGroupAccessRow :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (BoolInt, Maybe RelayStatus, Maybe UIThemeEntityOverrides, Int64, Maybe Int64, Maybe VersionRoster, Maybe CustomData, Maybe Int64, Int, Maybe ConnReqContact) :. GroupKeysRow :. Only (Maybe BoolInt) :. GroupMemberRow -type PublicGroupAccessRow = (Maybe Text, Maybe SimplexNameInfo, Maybe BoolInt, Maybe BoolInt, Maybe ClaimProof) +type PublicGroupAccessRow = (Maybe Text, Maybe SimplexNameInfo, Maybe BoolInt, Maybe BoolInt, Maybe NameClaimProof) type GroupMemberRow = (GroupMemberId, GroupId, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId) :. ProfileRow :. (UTCTime, UTCTime) :. (Maybe UTCTime, Int64, Int64, Int64, Maybe UTCTime, Maybe C.PublicKeyEd25519, Maybe ShortLinkContact) -type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, Maybe Preferences) :. BadgeRow :. (Maybe SimplexNameInfo, Maybe BoolInt, Maybe ClaimProof) +type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, Maybe Preferences) :. BadgeRow :. (Maybe SimplexNameInfo, Maybe BoolInt, Maybe NameClaimProof) toGroupInfo :: UTCTime -> StoreCxt -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo toGroupInfo now cxt userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image, groupType_, groupLink_, publicGroupId_) :. accessRow :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (BI useRelays, relayOwnStatus, uiThemes, currentMembers, publicMemberCount, rosterVersion, customData, chatItemTTL, membersRequireAttention, viaGroupLinkUri) :. groupKeysRow :. Only groupDomainVerification :. userMemberRow) = @@ -712,7 +712,7 @@ toPublicGroupProfile _ _ _ _ = Nothing publicGroupAccessRow :: Maybe PublicGroupProfile -> PublicGroupAccessRow publicGroupAccessRow pgp = case pgp >>= publicGroupAccess of Just PublicGroupAccess {groupWebPage, groupDomain, groupDomainProof, domainWebPage, allowEmbedding} -> - (groupWebPage, (\(StrJSON n) -> n) <$> groupDomain, Just (BI domainWebPage), Just (BI allowEmbedding), groupDomainProof) + (groupWebPage, unStrJSON <$> groupDomain, Just (BI domainWebPage), Just (BI allowEmbedding), groupDomainProof) Nothing -> (Nothing, Nothing, Nothing, Nothing, Nothing) toPublicGroupAccess :: PublicGroupAccessRow -> Maybe PublicGroupAccess diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 4203ce40de..198e393629 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -48,7 +48,7 @@ import Data.Text.Encoding (encodeUtf8) import Data.Time.Clock (UTCTime) import Data.Typeable (Typeable) import Data.Word (Word16) -import Simplex.Chat.Badges (BadgeInfo (..), BadgeProof (..), BadgeStatus (..), ClaimProof (..), LocalBadge (..), localBadgeInfo, localBadgeStatus, mkBadgeStatus, verifyBadge) +import Simplex.Chat.Badges (BadgeInfo (..), BadgeProof (..), BadgeStatus (..), NameClaimProof (..), LocalBadge (..), localBadgeInfo, localBadgeStatus, mkBadgeStatus, verifyBadge) import Simplex.Messaging.Crypto.BBS (BBSPublicKey) import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared @@ -695,7 +695,7 @@ data Profile = Profile peerType :: Maybe ChatPeerType, badge :: Maybe BadgeProof, contactDomain :: Maybe (StrJSON "SimplexName" SimplexNameInfo), - contactDomainProof :: Maybe ClaimProof + contactDomainProof :: Maybe NameClaimProof -- fields that should not be read into this data type to prevent sending them as part of profile to contacts: -- - contact_profile_id -- - incognito @@ -781,7 +781,7 @@ data LocalProfile = LocalProfile localAlias :: LocalAlias, contactDomain :: Maybe SimplexNameInfo, contactDomainVerification :: Maybe Bool, - contactDomainProof :: Maybe ClaimProof + contactDomainProof :: Maybe NameClaimProof } deriving (Eq, Show) @@ -790,7 +790,7 @@ localProfileId LocalProfile {profileId} = profileId toLocalProfile :: ProfileId -> Profile -> LocalAlias -> UTCTime -> Maybe Bool -> Maybe Bool -> LocalProfile toLocalProfile profileId Profile {displayName, fullName, shortDescr, image, contactLink, preferences, peerType, badge, contactDomain, contactDomainProof} localAlias now verified nameVerified = - LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, preferences, peerType, localBadge, localAlias, contactDomain = (\(StrJSON n) -> n) <$> contactDomain, contactDomainVerification = nameVerified, contactDomainProof} + LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, preferences, peerType, localBadge, localAlias, contactDomain = unStrJSON <$> contactDomain, contactDomainVerification = nameVerified, contactDomainProof} where localBadge = (\b@(BadgeProof _ _ _ info) -> PeerBadge b (mkBadgeStatus now verified info)) <$> badge @@ -844,7 +844,7 @@ instance ToField GroupType where toField = toField . textEncode data PublicGroupAccess = PublicGroupAccess { groupWebPage :: Maybe Text, groupDomain :: Maybe (StrJSON "SimplexName" SimplexNameInfo), - groupDomainProof :: Maybe ClaimProof, + groupDomainProof :: Maybe NameClaimProof, domainWebPage :: Bool, allowEmbedding :: Bool } diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 140d9cb48e..696f55b2ff 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1136,7 +1136,7 @@ shareLinkStr Nothing fallback = fallback -- The channel's name (group_domain) for share-link display, if any. groupDomainName :: GroupInfo -> Maybe SimplexNameInfo groupDomainName GroupInfo {groupProfile = GroupProfile {publicGroup}} = - (\(StrJSON n) -> n) <$> (publicGroup >>= publicGroupAccess >>= groupDomain) + unStrJSON <$> (publicGroup >>= publicGroupAccess >>= groupDomain) -- TODO [short links] show all settings viewAddressSettings :: AddressSettings -> [StyledString] diff --git a/tests/Bots/BroadcastTests.hs b/tests/Bots/BroadcastTests.hs index be58e1ad6c..85525e6ffd 100644 --- a/tests/Bots/BroadcastTests.hs +++ b/tests/Bots/BroadcastTests.hs @@ -33,7 +33,7 @@ withBroadcastBot opts test = bot = simplexChatCore testCfg (mkChatOpts opts) $ broadcastBot opts broadcastBotProfile :: Profile -broadcastBotProfile = Profile {displayName = "broadcast_bot", fullName = "Broadcast Bot", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Just CPTBot, preferences = Nothing, badge = Nothing, simplexName = Nothing} +broadcastBotProfile = Profile {displayName = "broadcast_bot", fullName = "Broadcast Bot", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Just CPTBot, preferences = Nothing, badge = Nothing, contactDomain = Nothing, contactDomainProof = Nothing} mkBotOpts :: TestParams -> [KnownContact] -> BroadcastBotOpts mkBotOpts ps publishers = diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index a277d077ea..1eea2cf75c 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -98,7 +98,7 @@ directoryServiceTests = do it "should update subscriber count periodically" testLinkCheckUpdatesCount directoryProfile :: Profile -directoryProfile = Profile {displayName = "SimpleX Directory", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Just CPTBot, preferences = Nothing, badge = Nothing, simplexName = Nothing} +directoryProfile = Profile {displayName = "SimpleX Directory", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Just CPTBot, preferences = Nothing, badge = Nothing, contactDomain = Nothing, contactDomainProof = Nothing} mkDirectoryOpts :: TestParams -> [KnownContact] -> Maybe KnownGroup -> Maybe FilePath -> DirectoryOpts mkDirectoryOpts TestParams {tmpPath = ps} superUsers ownersGroup webFolder = diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 8687c696a7..86ce1fb8ae 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -586,6 +586,7 @@ smpServerCfg = smpAgentCfg = defaultSMPClientAgentConfig, allowSMPProxy = True, serverClientConcurrency = 16, + serverResolverConcurrency = 1000, namesConfig = Nothing, information = Nothing, startOptions = StartOptions {maintenance = False, compactLog = False, logLevel = LogError, skipWarnings = False, confirmMigrations = MCYesUp} diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index bc062a3704..82eec91a50 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -501,7 +501,7 @@ testMultiWordProfileNames = aliceProfile' = baseProfile {displayName = "Alice Jones"} bobProfile' = baseProfile {displayName = "Bob James"} cathProfile' = baseProfile {displayName = "Cath Johnson"} - baseProfile = Profile {displayName = "", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = defaultPrefs, badge = Nothing, simplexName = Nothing} + baseProfile = Profile {displayName = "", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = defaultPrefs, badge = Nothing, contactDomain = Nothing, contactDomainProof = Nothing} testUserContactLink :: HasCallStack => TestParams -> IO () testUserContactLink = diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index cd24995db6..633ab502ed 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -88,7 +88,7 @@ serviceProfile :: Profile serviceProfile = mkProfile "service_user" "Service user" Nothing mkProfile :: T.Text -> T.Text -> Maybe ImageData -> Profile -mkProfile displayName descr image = Profile {displayName, fullName = "", shortDescr = Just descr, image, contactLink = Nothing, peerType = Nothing, preferences = defaultPrefs, badge = Nothing, simplexName = Nothing} +mkProfile displayName descr image = Profile {displayName, fullName = "", shortDescr = Just descr, image, contactLink = Nothing, peerType = Nothing, preferences = defaultPrefs, badge = Nothing, contactDomain = Nothing, contactDomainProof = Nothing} it :: HasCallStack => String -> (ps -> Expectation) -> SpecWith (Arg (ps -> Expectation)) it name test = diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 85a3fc3410..20ce22067a 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -3,13 +3,14 @@ {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} +{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} module ProtocolTests where import qualified Data.Aeson as J import Data.ByteString.Char8 (ByteString) import Data.Time.Clock.System (SystemTime (..), systemToUTCTime) -import Simplex.Chat.Library.Internal (decodeLinkUserData, encodeShortLinkData, redactedMemberProfile, userProfileInGroup') +import Simplex.Chat.Library.Internal (decodeLinkUserData, encodeShortLinkData) import Simplex.Chat.Protocol import Simplex.Chat.Types import Simplex.Chat.Types.Preferences @@ -25,7 +26,6 @@ import Test.Hspec protocolTests :: Spec protocolTests = do decodeChatMessageTest - outgoingProfileSimplexNameTest shortLinkDataTests srv :: SMPServer @@ -108,10 +108,10 @@ testGroupPreferences :: Maybe GroupPreferences testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, reactions = Just ReactionsGroupPreference {enable = FEOn}, voice = Just VoiceGroupPreference {enable = FEOn, role = Nothing}, files = Nothing, fullDelete = Nothing, simplexLinks = Nothing, history = Nothing, reports = Nothing, support = Nothing, sessions = Nothing, comments = Nothing, commands = Nothing} testProfile :: Profile -testProfile = Profile {displayName = "alice", fullName = "Alice", shortDescr = Nothing, image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), peerType = Nothing, contactLink = Nothing, preferences = testChatPreferences, badge = Nothing, simplexName = Nothing} +testProfile = Profile {displayName = "alice", fullName = "Alice", shortDescr = Nothing, image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), peerType = Nothing, contactLink = Nothing, preferences = testChatPreferences, badge = Nothing, contactDomain = Nothing, contactDomainProof = Nothing} testGroupProfile :: GroupProfile -testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, shortDescr = Nothing, image = Nothing, publicGroup = Nothing, simplexName = Nothing, groupPreferences = testGroupPreferences, memberAdmission = Nothing} +testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, shortDescr = Nothing, image = Nothing, publicGroup = Nothing, groupPreferences = testGroupPreferences, memberAdmission = Nothing} testSimplexName :: SimplexNameInfo testSimplexName = SimplexNameInfo NTContact (SimplexNameDomain TLDSimplex "alice" []) @@ -245,12 +245,6 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do it "x.info" $ "{\"v\":\"1\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" #==# XInfo testProfile - it "x.info with empty full name" $ - "{\"v\":\"1\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" - #==# XInfo Profile {displayName = "alice", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = testChatPreferences, badge = Nothing, simplexName = Nothing} - it "x.info with simplexName" $ - "{\"v\":\"1\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"simplexName\":{\"nameType\":\"contact\",\"nameDomain\":{\"nameTLD\":\"simplex\",\"domain\":\"alice\",\"subDomain\":[]}},\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" - #==# XInfo Profile {displayName = "alice", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = testChatPreferences, badge = Nothing, simplexName = Just testSimplexName} it "x.contact with xContactId" $ "{\"v\":\"1\",\"event\":\"x.contact\",\"params\":{\"contactReqId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" #==# XContact testProfile (Just $ XContactId "\1\2\3\4") Nothing Nothing @@ -269,9 +263,6 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do it "x.grp.inv with group link id" $ "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}" #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, business = Nothing, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing} - it "x.grp.inv with simplexName in groupProfile" $ - "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"simplexName\":{\"nameType\":\"publicGroup\",\"nameDomain\":{\"nameTLD\":\"simplex\",\"domain\":\"team\",\"subDomain\":[]}},\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}" - #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile {simplexName = Just testGroupSimplexName}, business = Nothing, groupLinkId = Nothing, groupSize = Nothing} it "x.grp.acpt without incognito profile" $ "{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}" #==# XGrpAcpt (MemberId "\1\2\3\4") @@ -346,7 +337,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do ==# XOk testUser :: Maybe SimplexNameInfo -> User -testUser sn = +testUser d = User { userId = 1, agentUserId = AgentUserId 1, @@ -364,7 +355,9 @@ testUser sn = peerType = Nothing, localBadge = Nothing, localAlias = "", - simplexName = sn + contactDomain = d, + contactDomainProof = Nothing, + contactDomainVerification = Nothing }, fullPreferences = fullPreferences' Nothing, activeUser = True, @@ -379,23 +372,3 @@ testUser sn = clientService = BoolDef False, uiThemes = Nothing } - -outgoingProfileSimplexNameTest :: Spec -outgoingProfileSimplexNameTest = describe "outgoing Profile carries User.profile.simplexName" $ do - it "userProfileDirect passes simplexName through to wire Profile" $ do - let Profile {simplexName = sn} = userProfileDirect (testUser (Just testSimplexName)) Nothing Nothing True - sn `shouldBe` Just testSimplexName - it "userProfileDirect with Nothing on User.profile leaves wire simplexName Nothing" $ do - let Profile {simplexName = sn} = userProfileDirect (testUser Nothing) Nothing Nothing True - sn `shouldBe` Nothing - it "userProfileDirect with incognito profile suppresses simplexName" $ do - let incognito = Profile {displayName = "anon", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = Nothing, badge = Nothing, simplexName = Nothing} - Profile {simplexName = sn} = userProfileDirect (testUser (Just testSimplexName)) (Just incognito) Nothing True - sn `shouldBe` Nothing - it "userProfileInGroup' passes simplexName through" $ do - let Profile {simplexName = sn} = userProfileInGroup' (testUser (Just testSimplexName)) True Nothing - sn `shouldBe` Just testSimplexName - it "redactedMemberProfile preserves simplexName" $ do - let p0 = Profile {displayName = "alice", fullName = "Alice", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = Nothing, badge = Nothing, simplexName = Just testSimplexName} - Profile {simplexName = sn} = redactedMemberProfile True p0 - sn `shouldBe` Just testSimplexName