mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-20 12:22:23 +00:00
fixes EP review
This commit is contained in:
@@ -123,7 +123,7 @@ import Simplex.Messaging.Parsers (base64P)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), ErrorType (NAME), MsgFlags (..), NameRecord (..), NameRegistration (..), NameResponse (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), UserProtocol, userProtocol)
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
import Simplex.Messaging.ServiceScheme (ServiceScheme (..))
|
||||
import Simplex.Messaging.SystemTime (getSystemSeconds)
|
||||
import Simplex.Messaging.SystemTime (getSystemSeconds, roundedToUTCTime)
|
||||
import qualified Simplex.Messaging.TMap as TM
|
||||
import Simplex.Messaging.Transport.Client (defaultSocksProxyWithAuth)
|
||||
import Simplex.Messaging.Util
|
||||
@@ -1605,7 +1605,7 @@ processChatCommand cxt nm = \case
|
||||
UserContactLink {shortLinkDataSet, connLinkContact = CCLink _ sl_} <- withFastStore (`getUserAddress` user)
|
||||
case sl_ of
|
||||
Just sl | shortLinkDataSet -> do
|
||||
NameRecord {nrSimplexContact} <- resolveNameRecord user nm domain
|
||||
(NameRecord {nrSimplexContact}, _) <- resolveNameRecord user nm domain
|
||||
unless (nameResolvesTo sl nrSimplexContact) $ throwChatError $ CESimplexDomainNotReady domain SDENoValidLink
|
||||
pure $ Just (CLShort sl)
|
||||
_ -> throwCmdError "create the address short link and add it to name"
|
||||
@@ -1958,7 +1958,7 @@ processChatCommand cxt nm = \case
|
||||
(_, cData@(ContactLinkData _ UserContactData {relays = currentRelayLinks}), _) <- getShortLinkConnReq' nm user sLnk
|
||||
groupSLinkData_ <- liftIO $ decodeLinkUserData cData
|
||||
gInfo' <- case groupSLinkData_ of
|
||||
Just sLinkData -> fst <$> updateGroupFromLinkData user gInfo sLinkData Nothing
|
||||
Just sLinkData -> fst <$> updateGroupFromLinkData user gInfo sLinkData Nothing Nothing
|
||||
_ -> pure gInfo
|
||||
when (memberRole' (membership gInfo) /= GROwner && memberCurrent (membership gInfo)) $
|
||||
withGroupLock "syncSubscriberRelays" groupId $
|
||||
@@ -2410,21 +2410,21 @@ processChatCommand cxt nm = \case
|
||||
let connLink_ = preparedContact >>= \PreparedContact {connLinkToConnect = ACCL m (CCLink _ sLnk_)} -> ACSL m <$> sLnk_
|
||||
domain <- maybe (throwCmdError "contact has no name to verify") pure contactDomain
|
||||
(verified, reason) <- verifyEntityDomain user nm NTContact domain connLink_
|
||||
ct' <- maybe (pure ct) (\v -> withFastStore' $ \db -> setContactDomainVerified db user ct v) verified
|
||||
ct' <- maybe (pure ct) (\(v, expiresAt) -> withFastStore' $ \db -> setContactDomainVerified db user ct v expiresAt) verified
|
||||
pure $ CRContactDomainVerified user ct' reason
|
||||
APIVerifyGroupDomain groupId -> withUser $ \user -> do
|
||||
g@GroupInfo {groupProfile = GroupProfile {publicGroup}} <- withFastStore $ \db -> getGroupInfo db cxt user groupId
|
||||
PublicGroupProfile {groupLink, publicGroupAccess} <- maybe (throwCmdError "not a public group") pure publicGroup
|
||||
claim <- maybe (throwCmdError "group has no name to verify") pure $ publicGroupAccess >>= groupDomainClaim
|
||||
-- checks the profile link, not the link we joined through (which may have rotated)
|
||||
(verified, reason) <-
|
||||
(verified, reason, expiresAt) <-
|
||||
tryAllErrors (resolveNameRecord user nm (claimDomain claim)) >>= \case
|
||||
Right NameRecord {nrSimplexChannel}
|
||||
| nameResolvesTo groupLink nrSimplexChannel -> pure (True, Nothing)
|
||||
| otherwise -> pure (False, Just "the name does not resolve to the link in the group profile")
|
||||
Left (ChatErrorAgent {agentError = SMP _ (NAME SMP.NOT_FOUND)}) -> pure (False, Just "the name is not registered")
|
||||
Right (NameRecord {nrSimplexChannel}, expiresAt)
|
||||
| nameResolvesTo groupLink nrSimplexChannel -> pure (True, Nothing, expiresAt)
|
||||
| otherwise -> pure (False, Just "the name does not resolve to the link in the group profile", expiresAt)
|
||||
Left (ChatErrorAgent {agentError = SMP _ (NAME SMP.NOT_FOUND)}) -> pure (False, Just "the name is not registered", Nothing)
|
||||
Left e -> throwError e
|
||||
g' <- withFastStore' $ \db -> setGroupDomainVerified db user g verified
|
||||
g' <- withFastStore' $ \db -> setGroupDomainVerified db user g verified expiresAt
|
||||
pure $ CRGroupDomainVerified user g' reason
|
||||
APIConnectContactViaAddress userId incognito contactId -> withUserId userId $ \user -> do
|
||||
ct@Contact {profile = LocalProfile {contactLink}} <- withFastStore $ \db -> getContact db cxt user contactId
|
||||
@@ -3278,7 +3278,7 @@ processChatCommand cxt nm = \case
|
||||
processChatCommand cxt nm $ APIListGroups userId (contactId' <$> ct_) search_
|
||||
APIUpdateGroupProfile groupId p' -> withUser $ \user -> do
|
||||
gInfo <- withFastStore $ \db -> getGroupInfoKeys db cxt user groupId
|
||||
runUpdateGroupProfile user gInfo p' False
|
||||
runUpdateGroupProfile user gInfo p' Nothing
|
||||
UpdateGroupNames gName GroupProfile {displayName, fullName, shortDescr} ->
|
||||
updateGroupProfileByName gName $ \p -> p {displayName, fullName, shortDescr}
|
||||
ShowGroupProfile gName -> withUser $ \user ->
|
||||
@@ -3292,11 +3292,11 @@ processChatCommand cxt nm = \case
|
||||
case publicGroup of
|
||||
Just pg@PublicGroupProfile {groupLink, publicGroupAccess = existingAccess} -> do
|
||||
let domainChanged = (claimDomain <$> newClaim) /= (claimDomain <$> (existingAccess >>= groupDomainClaim))
|
||||
forM_ (claimDomain <$> newClaim) $ \newDomain ->
|
||||
when domainChanged $ do
|
||||
NameRecord {nrSimplexChannel} <- resolveNameRecord user nm newDomain
|
||||
unless (nameResolvesTo groupLink nrSimplexChannel) $ throwChatError $ CESimplexDomainNotReady newDomain SDENoValidLink
|
||||
runUpdateGroupProfile user gInfo p {publicGroup = Just pg {publicGroupAccess = Just access}} (isJust newClaim && domainChanged)
|
||||
verifiedExpiry_ <- forM (if domainChanged then claimDomain <$> newClaim else Nothing) $ \newDomain -> do
|
||||
(NameRecord {nrSimplexChannel}, expiresAt) <- resolveNameRecord user nm newDomain
|
||||
unless (nameResolvesTo groupLink nrSimplexChannel) $ throwChatError $ CESimplexDomainNotReady newDomain SDENoValidLink
|
||||
pure expiresAt
|
||||
runUpdateGroupProfile user gInfo p {publicGroup = Just pg {publicGroupAccess = Just access}} verifiedExpiry_
|
||||
Nothing -> throwChatError $ CECommandError "not a public group"
|
||||
APICreateGroupLink groupId mRole -> withUser $ \user -> withGroupLock "createGroupLink" groupId $ do
|
||||
gInfo@GroupInfo {groupProfile} <- withFastStore $ \db -> getGroupInfo db cxt user groupId
|
||||
@@ -4097,8 +4097,8 @@ processChatCommand cxt nm = \case
|
||||
void (sendDirectContactMessage user ct' $ XInfo p Nothing) `catchAllErrors` eToView
|
||||
lift . when (directOrUsed ct') $ createSndFeatureItems user ct ct'
|
||||
pure $ CRContactPrefsUpdated user ct ct'
|
||||
runUpdateGroupProfile :: User -> GroupInfoKeys -> GroupProfile -> Bool -> CM ChatResponse
|
||||
runUpdateGroupProfile user (GIK gInfo@GroupInfo {businessChat, groupProfile = p@GroupProfile {displayName = n}} gks) p'@GroupProfile {displayName = n', image = img', memberAdmission = ma'} domainVerified = do
|
||||
runUpdateGroupProfile :: User -> GroupInfoKeys -> GroupProfile -> Maybe (Maybe UTCTime) -> CM ChatResponse
|
||||
runUpdateGroupProfile user (GIK gInfo@GroupInfo {businessChat, groupProfile = p@GroupProfile {displayName = n}} gks) p'@GroupProfile {displayName = n', image = img', memberAdmission = ma'} verifiedExpiry_ = do
|
||||
assertUserGroupRole gInfo GROwner
|
||||
when (n /= n') $ checkValidName n'
|
||||
checkProfileImageSize img'
|
||||
@@ -4107,7 +4107,7 @@ processChatCommand cxt nm = \case
|
||||
-- updateGroupProfile clears domain verification; re-set it when the caller already re-resolved the name
|
||||
gInfo' <- withStore $ \db -> do
|
||||
g <- updateGroupProfile db user gInfo p'
|
||||
if domainVerified then liftIO $ setGroupDomainVerified db user g True else pure g
|
||||
maybe (pure g) (liftIO . setGroupDomainVerified db user g True) verifiedExpiry_
|
||||
msg <- case businessChat of
|
||||
Just BusinessChatInfo {businessId} -> do
|
||||
ms <- withStore' $ \db -> getGroupMembers db cxt user gInfo'
|
||||
@@ -4200,7 +4200,7 @@ processChatCommand cxt nm = \case
|
||||
applicable = if channel then groupFeatureInChannel feature else groupFeatureInRegularGroup feature
|
||||
unless applicable $
|
||||
throwCmdError $ T.unpack (groupFeatureNameText feature) <> " is not available in " <> (if channel then "channels" else "groups")
|
||||
runUpdateGroupProfile user gInfo (update p) False
|
||||
runUpdateGroupProfile user gInfo (update p) Nothing
|
||||
withCurrentCall :: ContactId -> (User -> Contact -> Call -> CM (Maybe Call)) -> CM ChatResponse
|
||||
withCurrentCall ctId action = do
|
||||
(user, ct) <- withStore $ \db -> do
|
||||
@@ -4411,6 +4411,24 @@ processChatCommand cxt nm = \case
|
||||
invitationReqAndPlan cReq sLnk_ cld ov = do
|
||||
plan <- invitationRequestPlan user cReq cld ov `catchAllErrors` (pure . CPError)
|
||||
pure (Just (ACCL SCMInvitation (CCLink cReq sLnk_)), Nothing, Nothing, plan)
|
||||
connectPlan user t@(ACTarget SCMContact ct) PRMUnknown sig_ Nothing
|
||||
| isName =
|
||||
tryAllErrors (connectPlan user t PRMNever sig_ Nothing) >>= \case
|
||||
Right r@(_, _, _, p) -> ifM (resolvedRecently p) (pure r) resolvePlan
|
||||
Left _ -> resolvePlan
|
||||
where
|
||||
isName = case ct of
|
||||
CTDomain _ -> True
|
||||
CTShortContact (CTName _) -> True
|
||||
_ -> False
|
||||
resolvePlan = connectPlan user t PRMAll sig_ Nothing
|
||||
resolvedRecently p = do
|
||||
resolution_ <- withFastStore' $ \db -> case p of
|
||||
CPContactAddress (CAPKnown ct') _ -> getContactDomainResolution db user ct'
|
||||
CPGroupLink (GLPKnown g _ _ _) _ -> getGroupDomainResolution db user g
|
||||
_ -> pure Nothing
|
||||
now <- liftIO getCurrentTime
|
||||
pure $ maybe False (\(resolvedAt, expiresAt_) -> diffUTCTime now resolvedAt < nominalDay && maybe True (now <) expiresAt_) resolution_
|
||||
connectPlan user (ACTarget SCMContact ct) resolveMode sig_ nameRec = case ct of
|
||||
CTDomain d
|
||||
-- local search only: look up #d then @d in the store, without online name resolution
|
||||
@@ -4466,7 +4484,7 @@ processChatCommand cxt nm = \case
|
||||
when (resolveMode == PRMNever) $ throwChatError CENotResolvedLocally
|
||||
l' <- resolveSLink
|
||||
case known_ of
|
||||
Just r | knownLinkOf r == Just l' -> pure r
|
||||
Just r@(_, p) | knownLinkOf r == Just l' -> r <$ when (isJust simplexName_) (setKnownVerified p)
|
||||
_ -> (if isJust known_ then second setAddressChanged else id) <$> resolvedPlan l'
|
||||
where
|
||||
resolvedPlan l' = do
|
||||
@@ -4476,7 +4494,7 @@ processChatCommand cxt nm = \case
|
||||
linkDomain_ = linkProfile_ >>= \Profile {contactDomain} -> claimDomain <$> contactDomain
|
||||
planDomain = case nl of CTName ni -> Just (nameDomain ni); _ -> Nothing
|
||||
refreshContact ct' = case (planDomain, linkProfile_) of
|
||||
(Just _, Just p) -> updateContactFromLinkData user ct' p
|
||||
(Just _, Just p) -> updateContactFromLinkData user ct' p expiresAt
|
||||
_ -> pure ct'
|
||||
forM_ planDomain $ \nameDomain ->
|
||||
unless (linkDomain_ == Just nameDomain) $ throwChatError $ CESimplexDomainNotReady nameDomain SDEUnknownDomain
|
||||
@@ -4519,6 +4537,11 @@ processChatCommand cxt nm = \case
|
||||
CTLink l' -> pure l'
|
||||
CTName n -> serverShortLink <$> resolveNameLink n
|
||||
con l' cReq = ACCL SCMContact $ CCLink cReq (Just l')
|
||||
expiresAt = nameExpiresAt =<< eitherToMaybe =<< nameRec
|
||||
setKnownVerified = \case
|
||||
CPContactAddress (CAPKnown ct') _ -> void $ withFastStore' $ \db -> setContactDomainVerified db user ct' True expiresAt
|
||||
CPGroupLink (GLPKnown g _ _ _) _ -> void $ withFastStore' $ \db -> setGroupDomainVerified db user g True expiresAt
|
||||
_ -> pure ()
|
||||
reResolveKnown (_, p) = resolveMode == PRMAll && case p of
|
||||
CPContactAddress (CAPKnown _) _ -> True
|
||||
CPGroupLink GLPKnown {} _ -> True
|
||||
@@ -4563,7 +4586,7 @@ processChatCommand cxt nm = \case
|
||||
-- data and mark it verified, so the check below passes and future by-name lookups match
|
||||
plan <- case (planDomain, plan0, groupSLinkData_) of
|
||||
(Just nameDomain, CPGroupLink (GLPKnown g u o os) _, Just sLinkData) ->
|
||||
(\(g', _) -> CPGroupLink (GLPKnown g' u o os) Nothing) <$> updateGroupFromLinkData user g sLinkData (Just nameDomain)
|
||||
(\(g', _) -> CPGroupLink (GLPKnown g' u o os) Nothing) <$> updateGroupFromLinkData user g sLinkData (Just nameDomain) expiresAt
|
||||
_ -> pure plan0
|
||||
forM_ planDomain $ \nameDomain ->
|
||||
let domain_ = (\GroupProfile {publicGroup} -> claimDomain <$> (publicGroup >>= publicGroupAccess >>= groupDomainClaim)) =<< case plan of
|
||||
@@ -4588,7 +4611,7 @@ processChatCommand cxt nm = \case
|
||||
let ov = verifyLinkOwner rk owners l' sig_
|
||||
glOwners = map (\OwnerAuth {ownerId, ownerKey} -> GroupLinkOwner {memberId = MemberId ownerId, memberKey = ownerKey}) owners
|
||||
(g', updated) <- case groupSLinkData_ of
|
||||
Just sLinkData -> updateGroupFromLinkData user g sLinkData Nothing
|
||||
Just sLinkData -> updateGroupFromLinkData user g sLinkData (nameDomain <$> simplexName_) expiresAt
|
||||
_ -> pure (g, False)
|
||||
pure (con l' cReq, CPGroupLink (GLPKnown g' updated ov (ListDef glOwners)) Nothing)
|
||||
-- resolve a name to its first contact/channel short link
|
||||
@@ -5111,10 +5134,10 @@ resolveNameRegistration user nm domain =
|
||||
registration <$> withAgent (\a -> resolveSimplexName a nm (aUserId user) domain)
|
||||
|
||||
-- the resolver now also reports names that are not registered, which stay the agent's NAME NOT_FOUND
|
||||
resolveNameRecord :: User -> NetworkRequestMode -> SimplexDomain -> CM NameRecord
|
||||
resolveNameRecord :: User -> NetworkRequestMode -> SimplexDomain -> CM (NameRecord, Maybe UTCTime)
|
||||
resolveNameRecord user nm domain =
|
||||
resolveNameRegistration user nm domain >>= \case
|
||||
NRRegistered {nameRecord} -> pure nameRecord
|
||||
reg@NRRegistered {nameRecord} -> pure (nameRecord, nameExpiresAt reg)
|
||||
_ -> throwError $ chatErrorAgent $ SMP "" (NAME SMP.NOT_FOUND)
|
||||
|
||||
nameExpired :: NameRegistration -> CM Bool
|
||||
@@ -5122,6 +5145,11 @@ nameExpired = \case
|
||||
NRRegistered {expires = Just expires} -> (expires <) <$> liftIO getSystemSeconds
|
||||
_ -> pure False
|
||||
|
||||
nameExpiresAt :: NameRegistration -> Maybe UTCTime
|
||||
nameExpiresAt = \case
|
||||
NRRegistered {expires} -> roundedToUTCTime <$> expires
|
||||
_ -> Nothing
|
||||
|
||||
nameHasLink :: SimplexNameType -> NameRegistration -> Bool
|
||||
nameHasLink nameType = \case
|
||||
NRRegistered {nameRecord = NameRecord {nrSimplexContact, nrSimplexChannel}} -> case nameType of
|
||||
@@ -5143,20 +5171,20 @@ setAddressChanged = \case
|
||||
CPGroupLink (GLPOk li gld ov _) nr -> CPGroupLink (GLPOk li gld ov True) nr
|
||||
p -> p
|
||||
|
||||
verifyEntityDomain :: User -> NetworkRequestMode -> SimplexNameType -> SimplexDomainClaim -> Maybe AConnShortLink -> CM (Maybe Bool, Maybe Text)
|
||||
verifyEntityDomain :: User -> NetworkRequestMode -> SimplexNameType -> SimplexDomainClaim -> Maybe AConnShortLink -> CM (Maybe (Bool, Maybe UTCTime), Maybe Text)
|
||||
verifyEntityDomain user nm nameType SimplexDomainClaim {domain = StrJSON domain, proof = proof_} connLink_ = case (proof_, connLink_) of
|
||||
(Nothing, _) -> pure (Nothing, Just "no name proof to verify")
|
||||
(_, Nothing) -> pure (Nothing, Just "no connection link to check the name against")
|
||||
(Just proof, Just (ACSL SCMContact profileSLnk)) -> do
|
||||
NameRecord {nrSimplexContact, nrSimplexChannel} <- resolveNameRecord user nm domain
|
||||
(NameRecord {nrSimplexContact, nrSimplexChannel}, expiresAt) <- resolveNameRecord user nm domain
|
||||
let resolvedLinks = case nameType of
|
||||
NTContact -> nrSimplexContact
|
||||
NTPublicGroup -> nrSimplexChannel
|
||||
if not (nameResolvesTo profileSLnk resolvedLinks)
|
||||
then pure (Just False, Just "the name does not resolve to this address")
|
||||
then pure (Just (False, expiresAt), Just "the name does not resolve to this address")
|
||||
else do
|
||||
ok <- verifyDomainProof proof profileSLnk
|
||||
pure (Just ok, if ok then Nothing else Just "the name proof was not signed by this address's owner")
|
||||
pure (Just (ok, expiresAt), if ok then Nothing else Just "the name proof was not signed by this address's owner")
|
||||
(Just _, Just _) -> pure (Nothing, Just "unexpected connection link type for name verification")
|
||||
where
|
||||
verifyDomainProof :: SimplexDomainProof -> ShortLinkContact -> CM Bool
|
||||
@@ -5777,7 +5805,7 @@ sendServiceRequestBytes nm user sendTarget requestTimeout signKey request = do
|
||||
_ -> throwCmdError "service request target must be a contact"
|
||||
CTDomain d -> resolveDomain d
|
||||
resolveDomain d = do
|
||||
nr <- resolveNameRecord user nm d
|
||||
(nr, _) <- resolveNameRecord user nm d
|
||||
case firstNameLink CCTContact (nrSimplexContact nr) of
|
||||
Just sLnk -> resolveShortLink sLnk
|
||||
Nothing -> throwChatError $ CESimplexDomainNotReady d SDENoValidLink
|
||||
|
||||
@@ -1611,8 +1611,8 @@ updatePublicGroupData user gInfo gks
|
||||
| otherwise = pure gInfo
|
||||
|
||||
-- must not resolve names here: a background link-data refresh would leak channel membership to the resolver
|
||||
updateGroupFromLinkData :: User -> GroupInfo -> GroupShortLinkData -> Maybe SimplexDomain -> CM (GroupInfo, Bool)
|
||||
updateGroupFromLinkData user gInfo@GroupInfo {groupProfile = p, groupSummary = GroupSummary {publicMemberCount = localCount}} GroupShortLinkData {groupProfile, publicGroupData} resolvedDomain_
|
||||
updateGroupFromLinkData :: User -> GroupInfo -> GroupShortLinkData -> Maybe SimplexDomain -> Maybe UTCTime -> CM (GroupInfo, Bool)
|
||||
updateGroupFromLinkData user gInfo@GroupInfo {groupProfile = p, groupSummary = GroupSummary {publicMemberCount = localCount}} GroupShortLinkData {groupProfile, publicGroupData} resolvedDomain_ expiresAt
|
||||
| profileChanged || countChanged || verifyResolved = do
|
||||
cxt <- chatStoreCxt
|
||||
withStore $ \db -> do
|
||||
@@ -1621,7 +1621,7 @@ updateGroupFromLinkData user gInfo@GroupInfo {groupProfile = p, groupSummary = G
|
||||
Just PublicGroupData {publicMemberCount} | countChanged ->
|
||||
setPublicMemberCount db cxt user g publicMemberCount
|
||||
_ -> pure g
|
||||
g'' <- if verifyResolved then liftIO $ setGroupDomainVerified db user g' True else pure g'
|
||||
g'' <- if verifyResolved then liftIO $ setGroupDomainVerified db user g' True expiresAt else pure g'
|
||||
pure (g'', profileChanged)
|
||||
| otherwise = pure (gInfo, False)
|
||||
where
|
||||
@@ -1633,13 +1633,13 @@ updateGroupFromLinkData user gInfo@GroupInfo {groupProfile = p, groupSummary = G
|
||||
newClaim = groupClaim groupProfile
|
||||
verifyResolved = isJust resolvedDomain_ && resolvedDomain_ == newClaim
|
||||
|
||||
updateContactFromLinkData :: User -> Contact -> Profile -> CM Contact
|
||||
updateContactFromLinkData user ct@Contact {profile = profile@LocalProfile {contactDomain = prevClaim, contactDomainVerified}} linkProfile@Profile {contactDomain = newClaim}
|
||||
updateContactFromLinkData :: User -> Contact -> Profile -> Maybe UTCTime -> CM Contact
|
||||
updateContactFromLinkData user ct@Contact {profile = profile@LocalProfile {contactDomain = prevClaim, contactDomainVerified}} linkProfile@Profile {contactDomain = newClaim} expiresAt
|
||||
| profileChanged || verifyChanged = do
|
||||
cxt <- chatStoreCxt
|
||||
withFastStore $ \db -> do
|
||||
ct' <- updateContactProfile db cxt user ct linkProfile
|
||||
if verifyChanged then liftIO $ setContactDomainVerified db user ct' True else pure ct'
|
||||
if verifyChanged then liftIO $ setContactDomainVerified db user ct' True expiresAt else pure ct'
|
||||
| otherwise = pure ct
|
||||
where
|
||||
profileChanged = fromLocalProfile profile /= linkProfile
|
||||
|
||||
@@ -52,6 +52,7 @@ module Simplex.Chat.Store.Direct
|
||||
getContactIdByName,
|
||||
updateContactProfile,
|
||||
setContactDomainVerified,
|
||||
getContactDomainResolution,
|
||||
updateContactUserPreferences,
|
||||
updateContactAlias,
|
||||
updateContactConnectionAlias,
|
||||
@@ -410,7 +411,7 @@ createPreparedContact db cxt user p connLinkToConnect welcomeSharedMsgId verifie
|
||||
let prepared = Just (connLinkToConnect, welcomeSharedMsgId)
|
||||
ctUserPreferences = newContactUserPrefs user p
|
||||
ct <- getContact db cxt user =<< createContact_ db cxt user p ctUserPreferences prepared "" currentTs
|
||||
liftIO $ maybe (pure ct) (setContactDomainVerified db user ct) verified_
|
||||
liftIO $ maybe (pure ct) (\v -> setContactDomainVerified db user ct v Nothing) verified_
|
||||
|
||||
updatePreparedContactUser :: DB.Connection -> StoreCxt -> User -> Contact -> User -> ExceptT StoreError IO Contact
|
||||
updatePreparedContactUser
|
||||
@@ -590,17 +591,26 @@ updateContactProfile db cxt user@User {userId} c p' = do
|
||||
clearVerificationIfClaimChanged
|
||||
pure $ Right c {localDisplayName = ldn, profile, mergedPreferences}
|
||||
|
||||
setContactDomainVerified :: DB.Connection -> User -> Contact -> Bool -> IO Contact
|
||||
setContactDomainVerified db User {userId} ct@Contact {contactId, profile = p} verified = do
|
||||
setContactDomainVerified :: DB.Connection -> User -> Contact -> Bool -> Maybe UTCTime -> IO Contact
|
||||
setContactDomainVerified db User {userId} ct@Contact {contactId, profile = p} verified expiresAt = do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE contact_profiles SET contact_domain_verified = ?
|
||||
UPDATE contact_profiles SET contact_domain_verified = ?, contact_domain_resolved_at = ?, contact_domain_expires_at = ?
|
||||
WHERE contact_profile_id IN (SELECT contact_profile_id FROM contacts WHERE user_id = ? AND contact_id = ?)
|
||||
|]
|
||||
(BI verified, userId, contactId)
|
||||
(BI verified, currentTs, expiresAt, userId, contactId)
|
||||
pure (ct {profile = p {contactDomainVerified = Just verified}} :: Contact)
|
||||
|
||||
getContactDomainResolution :: DB.Connection -> User -> Contact -> IO (Maybe (UTCTime, Maybe UTCTime))
|
||||
getContactDomainResolution db User {userId} Contact {profile = LocalProfile {profileId}} =
|
||||
maybeFirstRow id $
|
||||
DB.query
|
||||
db
|
||||
"SELECT contact_domain_resolved_at, contact_domain_expires_at FROM contact_profiles WHERE user_id = ? AND contact_profile_id = ? AND contact_domain_resolved_at IS NOT NULL"
|
||||
(userId, profileId)
|
||||
|
||||
updateContactUserPreferences :: DB.Connection -> User -> Contact -> Preferences -> IO Contact
|
||||
updateContactUserPreferences db user@User {userId} c@Contact {contactId} userPreferences = do
|
||||
updatedAt <- getCurrentTime
|
||||
|
||||
@@ -47,6 +47,7 @@ module Simplex.Chat.Store.Groups
|
||||
getGroupInfoByGroupLinkHash,
|
||||
updateGroupProfile,
|
||||
setGroupDomainVerified,
|
||||
getGroupDomainResolution,
|
||||
updateGroupPreferences,
|
||||
updateGroupProfileFromMember,
|
||||
getGroupIdByName,
|
||||
@@ -669,7 +670,7 @@ createPreparedGroup db gVar cxt user@User {userId, userContactId} groupProfile b
|
||||
-- a business has no domain in its profile, so set it out-of-band; a channel already has it (createGroup_), just verify
|
||||
g' <- liftIO $ case verifiedDomain of
|
||||
Just d | business -> setPreparedGroupDomain db user g d
|
||||
Just _ -> setGroupDomainVerified db user g True
|
||||
Just _ -> setGroupDomainVerified db user g True Nothing
|
||||
Nothing -> pure g
|
||||
pure (g', hostMember_)
|
||||
where
|
||||
@@ -2740,14 +2741,23 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName,
|
||||
(ldn, currentTs, userId, groupId)
|
||||
safeDeleteLDN db user localDisplayName
|
||||
|
||||
setGroupDomainVerified :: DB.Connection -> User -> GroupInfo -> Bool -> IO GroupInfo
|
||||
setGroupDomainVerified db User {userId} g@GroupInfo {groupId} verified = do
|
||||
setGroupDomainVerified :: DB.Connection -> User -> GroupInfo -> Bool -> Maybe UTCTime -> IO GroupInfo
|
||||
setGroupDomainVerified db User {userId} g@GroupInfo {groupId} verified expiresAt = do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE groups SET group_domain_verified = ? WHERE user_id = ? AND group_id = ?"
|
||||
(BI verified, userId, groupId)
|
||||
"UPDATE groups SET group_domain_verified = ?, group_domain_resolved_at = ?, group_domain_expires_at = ? WHERE user_id = ? AND group_id = ?"
|
||||
(BI verified, currentTs, expiresAt, userId, groupId)
|
||||
pure g {groupDomainVerified = Just verified}
|
||||
|
||||
getGroupDomainResolution :: DB.Connection -> User -> GroupInfo -> IO (Maybe (UTCTime, Maybe UTCTime))
|
||||
getGroupDomainResolution db User {userId} GroupInfo {groupId} =
|
||||
maybeFirstRow id $
|
||||
DB.query
|
||||
db
|
||||
"SELECT group_domain_resolved_at, group_domain_expires_at FROM groups WHERE user_id = ? AND group_id = ? AND group_domain_resolved_at IS NOT NULL"
|
||||
(userId, groupId)
|
||||
|
||||
-- A business group has no publicGroup claim, so the domain it was connected by (from its address) is written
|
||||
-- directly to group_domain and marked verified, so it is found by the local name search (getGroupToConnect).
|
||||
setPreparedGroupDomain :: DB.Connection -> User -> GroupInfo -> SimplexDomain -> IO GroupInfo
|
||||
@@ -2759,7 +2769,7 @@ setPreparedGroupDomain db user@User {userId} g@GroupInfo {groupId} domain = do
|
||||
WHERE group_profile_id IN (SELECT group_profile_id FROM groups WHERE user_id = ? AND group_id = ?)
|
||||
|]
|
||||
(domain, userId, groupId)
|
||||
setGroupDomainVerified db user g True
|
||||
setGroupDomainVerified db user g True Nothing
|
||||
|
||||
updateGroupPreferences :: DB.Connection -> User -> GroupInfo -> GroupPreferences -> IO GroupInfo
|
||||
updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p} ps = do
|
||||
|
||||
@@ -52,6 +52,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260828_file_expiry
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260904_file_badges
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260915_user_badges
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260918_badge_issue_errors
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260924_simplex_name_resolved
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Text, Maybe Text)]
|
||||
@@ -103,7 +104,8 @@ schemaMigrations =
|
||||
("20260828_file_expiry", m20260828_file_expiry, Just down_m20260828_file_expiry),
|
||||
("20260904_file_badges", m20260904_file_badges, Just down_m20260904_file_badges),
|
||||
("20260915_user_badges", m20260915_user_badges, Just down_m20260915_user_badges),
|
||||
("20260918_badge_issue_errors", m20260918_badge_issue_errors, Just down_m20260918_badge_issue_errors)
|
||||
("20260918_badge_issue_errors", m20260918_badge_issue_errors, Just down_m20260918_badge_issue_errors),
|
||||
("20260924_simplex_name_resolved", m20260924_simplex_name_resolved, Just down_m20260924_simplex_name_resolved)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.Postgres.Migrations.M20260924_simplex_name_resolved where
|
||||
|
||||
import Data.Text (Text)
|
||||
import Text.RawString.QQ (r)
|
||||
|
||||
m20260924_simplex_name_resolved :: Text
|
||||
m20260924_simplex_name_resolved =
|
||||
[r|
|
||||
ALTER TABLE contact_profiles ADD COLUMN contact_domain_resolved_at TIMESTAMPTZ;
|
||||
ALTER TABLE contact_profiles ADD COLUMN contact_domain_expires_at TIMESTAMPTZ;
|
||||
|
||||
ALTER TABLE groups ADD COLUMN group_domain_resolved_at TIMESTAMPTZ;
|
||||
ALTER TABLE groups ADD COLUMN group_domain_expires_at TIMESTAMPTZ;
|
||||
|]
|
||||
|
||||
down_m20260924_simplex_name_resolved :: Text
|
||||
down_m20260924_simplex_name_resolved =
|
||||
[r|
|
||||
ALTER TABLE contact_profiles DROP COLUMN contact_domain_resolved_at;
|
||||
ALTER TABLE contact_profiles DROP COLUMN contact_domain_expires_at;
|
||||
|
||||
ALTER TABLE groups DROP COLUMN group_domain_resolved_at;
|
||||
ALTER TABLE groups DROP COLUMN group_domain_expires_at;
|
||||
|]
|
||||
@@ -680,7 +680,9 @@ CREATE TABLE test_chat_schema.contact_profiles (
|
||||
contact_domain text,
|
||||
contact_domain_proof text,
|
||||
contact_domain_verified smallint,
|
||||
description text
|
||||
description text,
|
||||
contact_domain_resolved_at timestamp with time zone,
|
||||
contact_domain_expires_at timestamp with time zone
|
||||
);
|
||||
|
||||
|
||||
@@ -1168,7 +1170,9 @@ CREATE TABLE test_chat_schema.groups (
|
||||
roster_blob bytea,
|
||||
group_domain_verified smallint,
|
||||
stored_roster_version bigint,
|
||||
applied_complete_roster_version bigint
|
||||
applied_complete_roster_version bigint,
|
||||
group_domain_resolved_at timestamp with time zone,
|
||||
group_domain_expires_at timestamp with time zone
|
||||
);
|
||||
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260828_file_expiry
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260904_file_badges
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260915_user_badges
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260918_badge_issue_errors
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260924_simplex_name_resolved
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||
@@ -349,7 +350,8 @@ schemaMigrations =
|
||||
("20260828_file_expiry", m20260828_file_expiry, Just down_m20260828_file_expiry),
|
||||
("20260904_file_badges", m20260904_file_badges, Just down_m20260904_file_badges),
|
||||
("20260915_user_badges", m20260915_user_badges, Just down_m20260915_user_badges),
|
||||
("20260918_badge_issue_errors", m20260918_badge_issue_errors, Just down_m20260918_badge_issue_errors)
|
||||
("20260918_badge_issue_errors", m20260918_badge_issue_errors, Just down_m20260918_badge_issue_errors),
|
||||
("20260924_simplex_name_resolved", m20260924_simplex_name_resolved, Just down_m20260924_simplex_name_resolved)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.SQLite.Migrations.M20260924_simplex_name_resolved where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20260924_simplex_name_resolved :: Query
|
||||
m20260924_simplex_name_resolved =
|
||||
[sql|
|
||||
ALTER TABLE contact_profiles ADD COLUMN contact_domain_resolved_at TEXT;
|
||||
ALTER TABLE contact_profiles ADD COLUMN contact_domain_expires_at TEXT;
|
||||
|
||||
ALTER TABLE groups ADD COLUMN group_domain_resolved_at TEXT;
|
||||
ALTER TABLE groups ADD COLUMN group_domain_expires_at TEXT;
|
||||
|]
|
||||
|
||||
down_m20260924_simplex_name_resolved :: Query
|
||||
down_m20260924_simplex_name_resolved =
|
||||
[sql|
|
||||
ALTER TABLE contact_profiles DROP COLUMN contact_domain_resolved_at;
|
||||
ALTER TABLE contact_profiles DROP COLUMN contact_domain_expires_at;
|
||||
|
||||
ALTER TABLE groups DROP COLUMN group_domain_resolved_at;
|
||||
ALTER TABLE groups DROP COLUMN group_domain_expires_at;
|
||||
|]
|
||||
@@ -5392,7 +5392,7 @@ Plan:
|
||||
SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE contact_profiles SET contact_domain_verified = ?
|
||||
UPDATE contact_profiles SET contact_domain_verified = ?, contact_domain_resolved_at = ?, contact_domain_expires_at = ?
|
||||
WHERE contact_profile_id IN (SELECT contact_profile_id FROM contacts WHERE user_id = ? AND contact_id = ?)
|
||||
|
||||
Plan:
|
||||
@@ -7451,6 +7451,10 @@ Query: SELECT connection_id FROM connections WHERE user_id = ? AND group_member_
|
||||
Plan:
|
||||
SEARCH connections USING INDEX idx_connections_group_member_id (group_member_id=?)
|
||||
|
||||
Query: SELECT contact_domain_resolved_at, contact_domain_expires_at FROM contact_profiles WHERE user_id = ? AND contact_profile_id = ? AND contact_domain_resolved_at IS NOT NULL
|
||||
Plan:
|
||||
SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: SELECT contact_id FROM contacts WHERE user_id = ? AND chat_item_ttl > 0 OR chat_item_ttl IS NULL
|
||||
Plan:
|
||||
SCAN contacts
|
||||
@@ -7537,6 +7541,10 @@ Query: SELECT g.inv_queue_info FROM groups g WHERE g.group_id = ? AND g.user_id
|
||||
Plan:
|
||||
SEARCH g USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: SELECT group_domain_resolved_at, group_domain_expires_at FROM groups WHERE user_id = ? AND group_id = ? AND group_domain_resolved_at IS NOT NULL
|
||||
Plan:
|
||||
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: SELECT group_id FROM group_members WHERE group_member_id = ?
|
||||
Plan:
|
||||
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
|
||||
@@ -7861,6 +7869,14 @@ Query: UPDATE contact_profiles SET contact_domain = ?, updated_at = ? WHERE user
|
||||
Plan:
|
||||
SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE contact_profiles SET contact_domain_expires_at = datetime('now', '-1 hours')
|
||||
Plan:
|
||||
SCAN contact_profiles
|
||||
|
||||
Query: UPDATE contact_profiles SET contact_domain_resolved_at = datetime('now', '-2 days')
|
||||
Plan:
|
||||
SCAN contact_profiles
|
||||
|
||||
Query: UPDATE contact_profiles SET image = ? WHERE display_name = ?
|
||||
Plan:
|
||||
SEARCH contact_profiles USING INDEX contact_profiles_index (display_name=?)
|
||||
@@ -8093,7 +8109,11 @@ Query: UPDATE groups SET enable_ntfs = ?, send_rcpts = ?, favorite = ? WHERE use
|
||||
Plan:
|
||||
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE groups SET group_domain_verified = ? WHERE user_id = ? AND group_id = ?
|
||||
Query: UPDATE groups SET group_domain_resolved_at = datetime('now', '-2 days')
|
||||
Plan:
|
||||
SCAN groups
|
||||
|
||||
Query: UPDATE groups SET group_domain_verified = ?, group_domain_resolved_at = ?, group_domain_expires_at = ? WHERE user_id = ? AND group_id = ?
|
||||
Plan:
|
||||
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ CREATE TABLE contact_profiles(
|
||||
contact_domain TEXT,
|
||||
contact_domain_proof TEXT,
|
||||
contact_domain_verified INTEGER,
|
||||
description TEXT
|
||||
description TEXT,
|
||||
contact_domain_resolved_at TEXT,
|
||||
contact_domain_expires_at TEXT
|
||||
) STRICT;
|
||||
CREATE TABLE users(
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
@@ -209,7 +211,9 @@ CREATE TABLE groups(
|
||||
roster_blob BLOB,
|
||||
group_domain_verified INTEGER,
|
||||
stored_roster_version INTEGER,
|
||||
applied_complete_roster_version INTEGER, -- received
|
||||
applied_complete_roster_version INTEGER,
|
||||
group_domain_resolved_at TEXT,
|
||||
group_domain_expires_at TEXT, -- received
|
||||
FOREIGN KEY(user_id, local_display_name)
|
||||
REFERENCES display_names(user_id, local_display_name)
|
||||
ON DELETE CASCADE
|
||||
|
||||
@@ -74,7 +74,6 @@ import Simplex.Messaging.Encoding
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON)
|
||||
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType, BlockingInfo (..), BlockingReason (..), NameRegistration (..), NamePricing (..), NetworkError (..), ProtocolServer (..), ProtocolTypeI, SProtocolType (..), USDCents (..), UserProtocol)
|
||||
import Simplex.Messaging.SimplexName (fullDomainName)
|
||||
import Simplex.Messaging.SystemTime (roundedSeconds)
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
import Simplex.Messaging.Transport.Client (TransportHost (..))
|
||||
|
||||
Reference in New Issue
Block a user