mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-03-30 14:15:55 +00:00
core: api to change user of pending connections (#4681)
* core: add api that enables change of owner user id for pending connections * old user sends request, incognito handling and coverage * call agent inside set connection api * only set user id if servers match * simplify * reduce test noise * return invitation when a newone is created * add test for profile on different server * refactor namings * update simplexmq * refactor * test improvements and simplify * remove fdescribes * simplify and reduce vars scope * put if back * refactor, change error * refactor view * refactor --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: 571d148bdf9e29403b9bcec6f7daeff815a4db38
|
||||
tag: 1cbf8c0015a8014bcc9d1894055d734947176684
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."571d148bdf9e29403b9bcec6f7daeff815a4db38" = "1p7m7i4wdyvd2wypm04cx4w960vnyqa9wkfnlpv72cz83k0xirxf";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."1cbf8c0015a8014bcc9d1894055d734947176684" = "01x8fvwzahz32fmxc2mjvxl1z6agwr3952hl4qjswym76r1dpc1s";
|
||||
"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";
|
||||
|
||||
@@ -1655,6 +1655,40 @@ processChatCommand' vr = \case
|
||||
case conn'_ of
|
||||
Just conn' -> pure $ CRConnectionIncognitoUpdated user conn'
|
||||
Nothing -> throwChatError CEConnectionIncognitoChangeProhibited
|
||||
APIChangeConnectionUser connId newUserId -> withUser $ \user@User {userId} -> do
|
||||
conn <- withFastStore $ \db -> getPendingContactConnection db userId connId
|
||||
let PendingContactConnection {pccConnStatus, connReqInv} = conn
|
||||
case (pccConnStatus, connReqInv) of
|
||||
(ConnNew, Just cReqInv) -> do
|
||||
newUser <- privateGetUser newUserId
|
||||
conn' <- ifM (canKeepLink cReqInv newUser) (updateConnRecord user conn newUser) (recreateConn user conn newUser)
|
||||
pure $ CRConnectionUserChanged user conn conn' newUser
|
||||
_ -> throwChatError CEConnectionUserChangeProhibited
|
||||
where
|
||||
canKeepLink :: ConnReqInvitation -> User -> CM Bool
|
||||
canKeepLink (CRInvitationUri crData _) newUser = do
|
||||
let ConnReqUriData {crSmpQueues = q :| _} = crData
|
||||
SMPQueueUri {queueAddress = SMPQueueAddress {smpServer}} = q
|
||||
cfg <- asks config
|
||||
newUserServers <- L.map (\ServerCfg {server} -> protoServer server) . useServers cfg SPSMP <$> withFastStore' (`getProtocolServers` newUser)
|
||||
pure $ smpServer `elem` newUserServers
|
||||
updateConnRecord user@User {userId} conn@PendingContactConnection {customUserProfileId} newUser = do
|
||||
withAgent $ \a -> changeConnectionUser a (aUserId user) (aConnId' conn) (aUserId newUser)
|
||||
withFastStore' $ \db -> do
|
||||
conn' <- updatePCCUser db userId conn newUserId
|
||||
forM_ customUserProfileId $ \profileId ->
|
||||
deletePCCIncognitoProfile db user profileId
|
||||
pure conn'
|
||||
recreateConn user conn@PendingContactConnection {customUserProfileId} newUser = do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
(agConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOn subMode
|
||||
conn' <- withFastStore' $ \db -> do
|
||||
deleteConnectionRecord db user connId
|
||||
forM_ customUserProfileId $ \profileId ->
|
||||
deletePCCIncognitoProfile db user profileId
|
||||
createDirectConnection db newUser agConnId cReq ConnNew Nothing subMode initialChatVersion PQSupportOn
|
||||
deleteAgentConnectionAsync user (aConnId' conn)
|
||||
pure conn'
|
||||
APIConnectPlan userId cReqUri -> withUserId userId $ \user ->
|
||||
CRConnectionPlan user <$> connectPlan user cReqUri
|
||||
APIConnect userId incognito (Just (ACR SCMInvitation cReq)) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do
|
||||
@@ -7790,6 +7824,7 @@ chatCommandP =
|
||||
"/_connect " *> (APIConnect <$> A.decimal <*> incognitoOnOffP <* A.space <*> ((Just <$> strP) <|> A.takeByteString $> Nothing)),
|
||||
"/_connect " *> (APIAddContact <$> A.decimal <*> incognitoOnOffP),
|
||||
"/_set incognito :" *> (APISetConnectionIncognito <$> A.decimal <* A.space <*> onOffP),
|
||||
"/_set conn user :" *> (APIChangeConnectionUser <$> A.decimal <* A.space <*> A.decimal),
|
||||
("/connect" <|> "/c") *> (Connect <$> incognitoP <* A.space <*> ((Just <$> strP) <|> A.takeTill isSpace $> Nothing)),
|
||||
("/connect" <|> "/c") *> (AddContact <$> incognitoP),
|
||||
ForwardMessage <$> chatNameP <* " <- @" <*> displayName <* A.space <*> msgTextP,
|
||||
|
||||
@@ -403,6 +403,7 @@ data ChatCommand
|
||||
| APIAddContact UserId IncognitoEnabled
|
||||
| AddContact IncognitoEnabled
|
||||
| APISetConnectionIncognito Int64 IncognitoEnabled
|
||||
| APIChangeConnectionUser Int64 UserId -- new user id to switch connection to
|
||||
| APIConnectPlan UserId AConnectionRequestUri
|
||||
| APIConnect UserId IncognitoEnabled (Maybe AConnectionRequestUri)
|
||||
| Connect IncognitoEnabled (Maybe AConnectionRequestUri)
|
||||
@@ -628,6 +629,7 @@ data ChatResponse
|
||||
| CRVersionInfo {versionInfo :: CoreVersionInfo, chatMigrations :: [UpMigration], agentMigrations :: [UpMigration]}
|
||||
| CRInvitation {user :: User, connReqInvitation :: ConnReqInvitation, connection :: PendingContactConnection}
|
||||
| CRConnectionIncognitoUpdated {user :: User, toConnection :: PendingContactConnection}
|
||||
| CRConnectionUserChanged {user :: User, fromConnection :: PendingContactConnection, toConnection :: PendingContactConnection, newUser :: User}
|
||||
| CRConnectionPlan {user :: User, connectionPlan :: ConnectionPlan}
|
||||
| CRSentConfirmation {user :: User, connection :: PendingContactConnection}
|
||||
| CRSentInvitation {user :: User, connection :: PendingContactConnection, customUserProfile :: Maybe Profile}
|
||||
@@ -1191,6 +1193,7 @@ data ChatErrorType
|
||||
| CEAgentCommandError {message :: String}
|
||||
| CEInvalidFileDescription {message :: String}
|
||||
| CEConnectionIncognitoChangeProhibited
|
||||
| CEConnectionUserChangeProhibited
|
||||
| CEPeerChatVRangeIncompatible
|
||||
| CEInternalError {message :: String}
|
||||
| CEException {message :: String}
|
||||
|
||||
@@ -64,6 +64,7 @@ module Simplex.Chat.Store.Direct
|
||||
createAcceptedContact,
|
||||
getUserByContactRequestId,
|
||||
getPendingContactConnections,
|
||||
updatePCCUser,
|
||||
getContactConnections,
|
||||
getConnectionById,
|
||||
getConnectionsContacts,
|
||||
@@ -424,6 +425,19 @@ updatePCCIncognito db User {userId} conn customUserProfileId = do
|
||||
(customUserProfileId, updatedAt, userId, pccConnId conn)
|
||||
pure (conn :: PendingContactConnection) {customUserProfileId, updatedAt}
|
||||
|
||||
updatePCCUser :: DB.Connection -> UserId -> PendingContactConnection -> UserId -> IO PendingContactConnection
|
||||
updatePCCUser db userId conn newUserId = do
|
||||
updatedAt <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE connections
|
||||
SET user_id = ?, custom_user_profile_id = NULL, updated_at = ?
|
||||
WHERE user_id = ? AND connection_id = ?
|
||||
|]
|
||||
(newUserId, updatedAt, userId, pccConnId conn)
|
||||
pure (conn :: PendingContactConnection) {customUserProfileId = Nothing, updatedAt}
|
||||
|
||||
deletePCCIncognitoProfile :: DB.Connection -> User -> ProfileId -> IO ()
|
||||
deletePCCIncognitoProfile db User {userId} profileId =
|
||||
DB.execute
|
||||
|
||||
@@ -172,6 +172,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
CRVersionInfo info _ _ -> viewVersionInfo logLevel info
|
||||
CRInvitation u cReq _ -> ttyUser u $ viewConnReqInvitation cReq
|
||||
CRConnectionIncognitoUpdated u c -> ttyUser u $ viewConnectionIncognitoUpdated c
|
||||
CRConnectionUserChanged u c c' nu -> ttyUser u $ viewConnectionUserChanged u c nu c'
|
||||
CRConnectionPlan u connectionPlan -> ttyUser u $ viewConnectionPlan connectionPlan
|
||||
CRSentConfirmation u _ -> ttyUser u ["confirmation sent!"]
|
||||
CRSentInvitation u _ customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView
|
||||
@@ -1498,6 +1499,20 @@ viewConnectionIncognitoUpdated PendingContactConnection {pccConnId, customUserPr
|
||||
| isJust customUserProfileId = ["connection " <> sShow pccConnId <> " changed to incognito"]
|
||||
| otherwise = ["connection " <> sShow pccConnId <> " changed to non incognito"]
|
||||
|
||||
viewConnectionUserChanged :: User -> PendingContactConnection -> User -> PendingContactConnection -> [StyledString]
|
||||
viewConnectionUserChanged User {localDisplayName = n} PendingContactConnection {pccConnId, connReqInv} User {localDisplayName = n'} PendingContactConnection {connReqInv = connReqInv'} =
|
||||
case (connReqInv, connReqInv') of
|
||||
(Just cReqInv, Just cReqInv')
|
||||
| cReqInv /= cReqInv' -> [userChangedStr <> ", new link:"] <> newLink cReqInv'
|
||||
_ -> [userChangedStr]
|
||||
where
|
||||
userChangedStr = "connection " <> sShow pccConnId <> " changed from user " <> plain n <> " to user " <> plain n'
|
||||
newLink cReqInv =
|
||||
[ "",
|
||||
(plain . strEncode) (simplexChatInvitation cReqInv),
|
||||
""
|
||||
]
|
||||
|
||||
viewConnectionPlan :: ConnectionPlan -> [StyledString]
|
||||
viewConnectionPlan = \case
|
||||
CPInvitationLink ilp -> case ilp of
|
||||
@@ -2025,6 +2040,7 @@ viewChatError isCmd logLevel testView = \case
|
||||
CEAgentCommandError e -> ["agent command error: " <> plain e]
|
||||
CEInvalidFileDescription e -> ["invalid file description: " <> plain e]
|
||||
CEConnectionIncognitoChangeProhibited -> ["incognito mode change prohibited"]
|
||||
CEConnectionUserChangeProhibited -> ["incognito mode change prohibited for user"]
|
||||
CEPeerChatVRangeIncompatible -> ["peer chat protocol version range incompatible"]
|
||||
CEInternalError e -> ["internal chat error: " <> plain e]
|
||||
CEException e -> ["exception: " <> plain e]
|
||||
|
||||
@@ -63,6 +63,11 @@ chatProfileTests = do
|
||||
describe "contact aliases" $ do
|
||||
it "set contact alias" testSetAlias
|
||||
it "set connection alias" testSetConnectionAlias
|
||||
describe "pending connection users" $ do
|
||||
it "change user for pending connection" testChangePCCUser
|
||||
it "change from incognito profile connects as new user" testChangePCCUserFromIncognito
|
||||
it "change user for pending connection and later set incognito connects as incognito in changed profile" testChangePCCUserAndThenIncognito
|
||||
it "change user for user without matching servers creates new connection" testChangePCCUserDiffSrv
|
||||
describe "preferences" $ do
|
||||
it "set contact preferences" testSetContactPrefs
|
||||
it "feature offers" testFeatureOffers
|
||||
@@ -1557,6 +1562,126 @@ testSetAlias = testChat2 aliceProfile bobProfile $
|
||||
alice ##> "/contacts"
|
||||
alice <## "bob (Bob)"
|
||||
|
||||
testChangePCCUser :: HasCallStack => FilePath -> IO ()
|
||||
testChangePCCUser = testChat2 aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
-- Create a new invite
|
||||
alice ##> "/connect"
|
||||
inv <- getInvitation alice
|
||||
-- Create new user and go back to original user
|
||||
alice ##> "/create user alisa"
|
||||
showActiveUser alice "alisa"
|
||||
alice ##> "/create user alisa2"
|
||||
showActiveUser alice "alisa2"
|
||||
alice ##> "/user alice"
|
||||
showActiveUser alice "alice (Alice)"
|
||||
-- Change connection to newly created user
|
||||
alice ##> "/_set conn user :1 2"
|
||||
alice <## "connection 1 changed from user alice to user alisa"
|
||||
alice ##> "/user alisa"
|
||||
showActiveUser alice "alisa"
|
||||
-- Change connection back to other user
|
||||
alice ##> "/_set conn user :1 3"
|
||||
alice <## "connection 1 changed from user alisa to user alisa2"
|
||||
alice ##> "/user alisa2"
|
||||
showActiveUser alice "alisa2"
|
||||
-- Connect
|
||||
bob ##> ("/connect " <> inv)
|
||||
bob <## "confirmation sent!"
|
||||
concurrently_
|
||||
(alice <## "bob (Bob): contact is connected")
|
||||
(bob <## "alisa2: contact is connected")
|
||||
|
||||
testChangePCCUserFromIncognito :: HasCallStack => FilePath -> IO ()
|
||||
testChangePCCUserFromIncognito = testChat2 aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
-- Create a new invite and set as incognito
|
||||
alice ##> "/connect"
|
||||
inv <- getInvitation alice
|
||||
alice ##> "/_set incognito :1 on"
|
||||
alice <## "connection 1 changed to incognito"
|
||||
-- Create new user and go back to original user
|
||||
alice ##> "/create user alisa"
|
||||
showActiveUser alice "alisa"
|
||||
alice ##> "/user alice"
|
||||
showActiveUser alice "alice (Alice)"
|
||||
-- Change connection to newly created user
|
||||
alice ##> "/_set conn user :1 2"
|
||||
alice <## "connection 1 changed from user alice to user alisa"
|
||||
alice `hasContactProfiles` ["alice"]
|
||||
alice ##> "/user alisa"
|
||||
showActiveUser alice "alisa"
|
||||
-- Change connection back to initial user
|
||||
alice ##> "/_set conn user :1 1"
|
||||
alice <## "connection 1 changed from user alisa to user alice"
|
||||
alice ##> "/user alice"
|
||||
showActiveUser alice "alice (Alice)"
|
||||
-- Connect
|
||||
bob ##> ("/connect " <> inv)
|
||||
bob <## "confirmation sent!"
|
||||
concurrently_
|
||||
(alice <## "bob (Bob): contact is connected")
|
||||
(bob <## "alice (Alice): contact is connected")
|
||||
|
||||
testChangePCCUserAndThenIncognito :: HasCallStack => FilePath -> IO ()
|
||||
testChangePCCUserAndThenIncognito = testChat2 aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
-- Create a new invite and set as incognito
|
||||
alice ##> "/connect"
|
||||
inv <- getInvitation alice
|
||||
-- Create new user and go back to original user
|
||||
alice ##> "/create user alisa"
|
||||
showActiveUser alice "alisa"
|
||||
alice ##> "/user alice"
|
||||
showActiveUser alice "alice (Alice)"
|
||||
-- Change connection to newly created user
|
||||
alice ##> "/_set conn user :1 2"
|
||||
alice <## "connection 1 changed from user alice to user alisa"
|
||||
alice ##> "/user alisa"
|
||||
showActiveUser alice "alisa"
|
||||
-- Change connection to incognito and make sure it's attached to the newly created user profile
|
||||
alice ##> "/_set incognito :1 on"
|
||||
alice <## "connection 1 changed to incognito"
|
||||
bob ##> ("/connect " <> inv)
|
||||
bob <## "confirmation sent!"
|
||||
alisaIncognito <- getTermLine alice
|
||||
concurrentlyN_
|
||||
[ bob <## (alisaIncognito <> ": contact is connected"),
|
||||
do
|
||||
alice <## ("bob (Bob): contact is connected, your incognito profile for this contact is " <> alisaIncognito)
|
||||
alice <## ("use /i bob to print out this incognito profile again")
|
||||
]
|
||||
|
||||
testChangePCCUserDiffSrv :: HasCallStack => FilePath -> IO ()
|
||||
testChangePCCUserDiffSrv = testChat2 aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
-- Create a new invite
|
||||
alice ##> "/connect"
|
||||
_ <- getInvitation alice
|
||||
alice ##> "/_set incognito :1 on"
|
||||
alice <## "connection 1 changed to incognito"
|
||||
-- Create new user with different servers
|
||||
alice ##> "/create user alisa"
|
||||
showActiveUser alice "alisa"
|
||||
alice #$> ("/smp smp://2345-w==@smp2.example.im smp://3456-w==@smp3.example.im:5224", id, "ok")
|
||||
alice ##> "/user alice"
|
||||
showActiveUser alice "alice (Alice)"
|
||||
-- Change connection to newly created user and use the newly created connection
|
||||
alice ##> "/_set conn user :1 2"
|
||||
alice <## "connection 1 changed from user alice to user alisa, new link:"
|
||||
alice <## ""
|
||||
inv <- getTermLine alice
|
||||
alice <## ""
|
||||
alice `hasContactProfiles` ["alice"]
|
||||
alice ##> "/user alisa"
|
||||
showActiveUser alice "alisa"
|
||||
-- Connect
|
||||
bob ##> ("/connect " <> inv)
|
||||
bob <## "confirmation sent!"
|
||||
concurrently_
|
||||
(alice <## "bob (Bob): contact is connected")
|
||||
(bob <## "alisa: contact is connected")
|
||||
|
||||
testSetConnectionAlias :: HasCallStack => FilePath -> IO ()
|
||||
testSetConnectionAlias = testChat2 aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
|
||||
Reference in New Issue
Block a user