mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Merge branch 'master' into master-ios
This commit is contained in:
@@ -162,16 +162,16 @@ func apiHideUser(_ userId: Int64, viewPwd: String) async throws -> User {
|
||||
try await setUserPrivacy_(.apiHideUser(userId: userId, viewPwd: viewPwd))
|
||||
}
|
||||
|
||||
func apiUnhideUser(_ userId: Int64, viewPwd: String?) async throws -> User {
|
||||
func apiUnhideUser(_ userId: Int64, viewPwd: String) async throws -> User {
|
||||
try await setUserPrivacy_(.apiUnhideUser(userId: userId, viewPwd: viewPwd))
|
||||
}
|
||||
|
||||
func apiMuteUser(_ userId: Int64, viewPwd: String?) async throws -> User {
|
||||
try await setUserPrivacy_(.apiMuteUser(userId: userId, viewPwd: viewPwd))
|
||||
func apiMuteUser(_ userId: Int64) async throws -> User {
|
||||
try await setUserPrivacy_(.apiMuteUser(userId: userId))
|
||||
}
|
||||
|
||||
func apiUnmuteUser(_ userId: Int64, viewPwd: String?) async throws -> User {
|
||||
try await setUserPrivacy_(.apiUnmuteUser(userId: userId, viewPwd: viewPwd))
|
||||
func apiUnmuteUser(_ userId: Int64) async throws -> User {
|
||||
try await setUserPrivacy_(.apiUnmuteUser(userId: userId))
|
||||
}
|
||||
|
||||
func setUserPrivacy_(_ cmd: ChatCommand) async throws -> User {
|
||||
|
||||
@@ -44,10 +44,12 @@ struct UserProfilesView: View {
|
||||
|
||||
private enum UserProfileAction: Identifiable {
|
||||
case deleteUser(user: User, delSMPQueues: Bool)
|
||||
case unhideUser(user: User)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case let .deleteUser(user, delSMPQueues): return "deleteUser \(user.userId) \(delSMPQueues)"
|
||||
case let .unhideUser(user): return "unhideUser \(user.userId)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,43 +209,58 @@ struct UserProfilesView: View {
|
||||
|
||||
@ViewBuilder private func profileActionView(_ action: UserProfileAction) -> some View {
|
||||
let passwordValid = actionPassword == actionPassword.trimmingCharacters(in: .whitespaces)
|
||||
switch action {
|
||||
case let .deleteUser(user, delSMPQueues):
|
||||
let actionEnabled = actionPassword != "" && passwordValid && correctPassword(user, actionPassword)
|
||||
List {
|
||||
Text("Delete user")
|
||||
.font(.title)
|
||||
.bold()
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
.listRowBackground(Color.clear)
|
||||
|
||||
Section() {
|
||||
ProfilePreview(profileOf: user).padding(.leading, -8)
|
||||
}
|
||||
|
||||
let passwordField = PassphraseField(key: $actionPassword, placeholder: "Profile password", valid: passwordValid)
|
||||
let actionEnabled: (User) -> Bool = { user in actionPassword != "" && passwordValid && correctPassword(user, actionPassword) }
|
||||
List {
|
||||
switch action {
|
||||
case let .deleteUser(user, delSMPQueues):
|
||||
actionHeader("Delete user", user)
|
||||
Section {
|
||||
PassphraseField(key: $actionPassword, placeholder: "Profile password", valid: passwordValid)
|
||||
passwordField
|
||||
settingsRow("trash") {
|
||||
Button("Delete user", role: .destructive) {
|
||||
profileAction = nil
|
||||
Task { await removeUser(user, delSMPQueues, viewPwd: actionPassword) }
|
||||
}
|
||||
.disabled(!actionEnabled)
|
||||
.disabled(!actionEnabled(user))
|
||||
}
|
||||
} footer: {
|
||||
if actionEnabled {
|
||||
if actionEnabled(user) {
|
||||
Text("All chats and messages will be deleted - this cannot be undone!")
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
case let .unhideUser(user):
|
||||
actionHeader("Unhide user", user)
|
||||
Section {
|
||||
passwordField
|
||||
settingsRow("lock.open") {
|
||||
Button("Unhide user") {
|
||||
profileAction = nil
|
||||
setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: actionPassword) }
|
||||
}
|
||||
.disabled(!actionEnabled(user))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func actionHeader(_ title: LocalizedStringKey, _ user: User) -> some View {
|
||||
Text(title)
|
||||
.font(.title)
|
||||
.bold()
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
.listRowBackground(Color.clear)
|
||||
Section() {
|
||||
ProfilePreview(profileOf: user).padding(.leading, -8)
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteModeButton(_ title: LocalizedStringKey, _ delSMPQueues: Bool) -> some View {
|
||||
Button(title, role: .destructive) {
|
||||
if let user = userToDelete {
|
||||
if user.hidden && user.activeUser && !correctPassword(user, searchTextOrPassword) {
|
||||
if passwordEntryRequired(user) {
|
||||
profileAction = .deleteUser(user: user, delSMPQueues: delSMPQueues)
|
||||
} else {
|
||||
alert = .deleteUser(user: user, delSMPQueues: delSMPQueues)
|
||||
@@ -252,6 +269,10 @@ struct UserProfilesView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func passwordEntryRequired(_ user: User) -> Bool {
|
||||
user.hidden && user.activeUser && !correctPassword(user, searchTextOrPassword)
|
||||
}
|
||||
|
||||
private func removeUser(_ user: User, _ delSMPQueues: Bool, viewPwd: String?) async {
|
||||
do {
|
||||
if user.activeUser {
|
||||
@@ -307,7 +328,11 @@ struct UserProfilesView: View {
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
if user.hidden {
|
||||
Button("Unhide") {
|
||||
setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: userViewPassword(user)) }
|
||||
if passwordEntryRequired(user) {
|
||||
profileAction = .unhideUser(user: user)
|
||||
} else {
|
||||
setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: searchTextOrPassword) }
|
||||
}
|
||||
}
|
||||
.tint(.green)
|
||||
} else {
|
||||
@@ -321,12 +346,12 @@ struct UserProfilesView: View {
|
||||
if user.showNtfs {
|
||||
Button("Mute") {
|
||||
setUserPrivacy(user, successAlert: showMuteProfileAlert ? .muteProfileAlert : nil) {
|
||||
try await apiMuteUser(user.userId, viewPwd: userViewPassword(user))
|
||||
try await apiMuteUser(user.userId)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Button("Unmute") {
|
||||
setUserPrivacy(user) { try await apiUnmuteUser(user.userId, viewPwd: userViewPassword(user)) }
|
||||
setUserPrivacy(user) { try await apiUnmuteUser(user.userId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ public enum ChatCommand {
|
||||
case listUsers
|
||||
case apiSetActiveUser(userId: Int64, viewPwd: String?)
|
||||
case apiHideUser(userId: Int64, viewPwd: String)
|
||||
case apiUnhideUser(userId: Int64, viewPwd: String?)
|
||||
case apiMuteUser(userId: Int64, viewPwd: String?)
|
||||
case apiUnmuteUser(userId: Int64, viewPwd: String?)
|
||||
case apiUnhideUser(userId: Int64, viewPwd: String)
|
||||
case apiMuteUser(userId: Int64)
|
||||
case apiUnmuteUser(userId: Int64)
|
||||
case apiDeleteUser(userId: Int64, delSMPQueues: Bool, viewPwd: String?)
|
||||
case startChat(subscribe: Bool, expire: Bool)
|
||||
case apiStopChat
|
||||
@@ -111,9 +111,9 @@ public enum ChatCommand {
|
||||
case .listUsers: return "/users"
|
||||
case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))"
|
||||
case let .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))"
|
||||
case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId)\(maybePwd(viewPwd))"
|
||||
case let .apiMuteUser(userId, viewPwd): return "/_mute user \(userId)\(maybePwd(viewPwd))"
|
||||
case let .apiUnmuteUser(userId, viewPwd): return "/_unmute user \(userId)\(maybePwd(viewPwd))"
|
||||
case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId) \(encodeJSON(viewPwd))"
|
||||
case let .apiMuteUser(userId): return "/_mute user \(userId)"
|
||||
case let .apiUnmuteUser(userId): return "/_unmute user \(userId)"
|
||||
case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return "/_delete user \(userId) del_smp=\(onOff(delSMPQueues))\(maybePwd(viewPwd))"
|
||||
case let .startChat(subscribe, expire): return "/_start subscribe=\(onOff(subscribe)) expire=\(onOff(expire))"
|
||||
case .apiStopChat: return "/_stop"
|
||||
@@ -332,10 +332,6 @@ public enum ChatCommand {
|
||||
return .apiHideUser(userId: userId, viewPwd: obfuscate(viewPwd))
|
||||
case let .apiUnhideUser(userId, viewPwd):
|
||||
return .apiUnhideUser(userId: userId, viewPwd: obfuscate(viewPwd))
|
||||
case let .apiMuteUser(userId, viewPwd):
|
||||
return .apiMuteUser(userId: userId, viewPwd: obfuscate(viewPwd))
|
||||
case let .apiUnmuteUser(userId, viewPwd):
|
||||
return .apiUnmuteUser(userId: userId, viewPwd: obfuscate(viewPwd))
|
||||
case let .apiDeleteUser(userId, delSMPQueues, viewPwd):
|
||||
return .apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: obfuscate(viewPwd))
|
||||
default: return self
|
||||
@@ -349,9 +345,8 @@ public enum ChatCommand {
|
||||
private func obfuscate(_ s: String?) -> String? {
|
||||
if let s = s {
|
||||
return obfuscate(s)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func onOff(_ b: Bool) -> String {
|
||||
|
||||
+30
-28
@@ -335,7 +335,7 @@ processChatCommand = \case
|
||||
tryError (withStore (`getUserIdByName` uName)) >>= \case
|
||||
Left _ -> throwChatError CEUserUnknown
|
||||
Right userId -> processChatCommand $ APISetActiveUser userId viewPwd_
|
||||
APIHideUser userId' (UserPwd viewPwd) -> withUser $ \_ -> do
|
||||
APIHideUser userId' (UserPwd viewPwd) -> withUser $ \user -> do
|
||||
user' <- privateGetUser userId'
|
||||
case viewPwdHash user' of
|
||||
Just _ -> throwChatError $ CEUserAlreadyHidden userId'
|
||||
@@ -344,34 +344,26 @@ processChatCommand = \case
|
||||
users <- withStore' getUsers
|
||||
unless (length (filter (isNothing . viewPwdHash) users) > 1) $ throwChatError $ CECantHideLastUser userId'
|
||||
viewPwdHash' <- hashPassword
|
||||
setUserPrivacy user' {viewPwdHash = viewPwdHash', showNtfs = False}
|
||||
setUserPrivacy user user' {viewPwdHash = viewPwdHash', showNtfs = False}
|
||||
where
|
||||
hashPassword = do
|
||||
salt <- drgRandomBytes 16
|
||||
let hash = B64UrlByteString $ C.sha512Hash $ encodeUtf8 viewPwd <> salt
|
||||
pure $ Just UserPwdHash {hash, salt = B64UrlByteString salt}
|
||||
APIUnhideUser userId' viewPwd_ -> withUser $ \user -> do
|
||||
APIUnhideUser userId' viewPwd@(UserPwd pwd) -> withUser $ \user -> do
|
||||
user' <- privateGetUser userId'
|
||||
case viewPwdHash user' of
|
||||
Nothing -> throwChatError $ CEUserNotHidden userId'
|
||||
_ -> do
|
||||
validateUserPassword user user' viewPwd_
|
||||
setUserPrivacy user' {viewPwdHash = Nothing, showNtfs = True}
|
||||
APIMuteUser userId' viewPwd_ -> withUser $ \user -> do
|
||||
user' <- privateGetUser userId'
|
||||
validateUserPassword user user' viewPwd_
|
||||
setUserPrivacy user' {showNtfs = False}
|
||||
APIUnmuteUser userId' viewPwd_ -> withUser $ \user -> do
|
||||
user' <- privateGetUser userId'
|
||||
case viewPwdHash user' of
|
||||
Just _ -> throwChatError $ CECantUnmuteHiddenUser userId'
|
||||
_ -> do
|
||||
validateUserPassword user user' viewPwd_
|
||||
setUserPrivacy user' {showNtfs = True}
|
||||
when (T.null pwd) $ throwChatError $ CEEmptyUserPassword userId'
|
||||
validateUserPassword user user' $ Just viewPwd
|
||||
setUserPrivacy user user' {viewPwdHash = Nothing, showNtfs = True}
|
||||
APIMuteUser userId' -> setUserNotifications userId' False
|
||||
APIUnmuteUser userId' -> setUserNotifications userId' True
|
||||
HideUser viewPwd -> withUser $ \User {userId} -> processChatCommand $ APIHideUser userId viewPwd
|
||||
UnhideUser -> withUser $ \User {userId} -> processChatCommand $ APIUnhideUser userId Nothing
|
||||
MuteUser -> withUser $ \User {userId} -> processChatCommand $ APIMuteUser userId Nothing
|
||||
UnmuteUser -> withUser $ \User {userId} -> processChatCommand $ APIUnmuteUser userId Nothing
|
||||
UnhideUser viewPwd -> withUser $ \User {userId} -> processChatCommand $ APIUnhideUser userId viewPwd
|
||||
MuteUser -> withUser $ \User {userId} -> processChatCommand $ APIMuteUser userId
|
||||
UnmuteUser -> withUser $ \User {userId} -> processChatCommand $ APIUnmuteUser userId
|
||||
APIDeleteUser userId' delSMPQueues viewPwd_ -> withUser $ \user -> do
|
||||
user' <- privateGetUser userId'
|
||||
validateUserPassword user user' viewPwd_
|
||||
@@ -1706,11 +1698,21 @@ processChatCommand = \case
|
||||
validPassword :: Text -> UserPwdHash -> Bool
|
||||
validPassword pwd UserPwdHash {hash = B64UrlByteString hash, salt = B64UrlByteString salt} =
|
||||
hash == C.sha512Hash (encodeUtf8 pwd <> salt)
|
||||
setUserPrivacy :: User -> m ChatResponse
|
||||
setUserPrivacy user = do
|
||||
asks currentUser >>= atomically . (`writeTVar` Just user)
|
||||
withStore' (`updateUserPrivacy` user)
|
||||
pure $ CRUserPrivacy user
|
||||
setUserNotifications :: UserId -> Bool -> m ChatResponse
|
||||
setUserNotifications userId' showNtfs = withUser $ \user -> do
|
||||
user' <- privateGetUser userId'
|
||||
case viewPwdHash user' of
|
||||
Just _ -> throwChatError $ CEHiddenUserAlwaysMuted userId'
|
||||
_ -> setUserPrivacy user user' {showNtfs}
|
||||
setUserPrivacy :: User -> User -> m ChatResponse
|
||||
setUserPrivacy user@User {userId} user'@User {userId = userId'}
|
||||
| userId == userId' = do
|
||||
asks currentUser >>= atomically . (`writeTVar` Just user')
|
||||
withStore' (`updateUserPrivacy` user')
|
||||
pure $ CRUserPrivacy {user = user', updatedUser = user'}
|
||||
| otherwise = do
|
||||
withStore' (`updateUserPrivacy` user')
|
||||
pure $ CRUserPrivacy {user, updatedUser = user'}
|
||||
checkDeleteChatUser :: User -> m ()
|
||||
checkDeleteChatUser user@User {userId} = do
|
||||
when (activeUser user) $ throwChatError (CECantDeleteActiveUser userId)
|
||||
@@ -4319,11 +4321,11 @@ chatCommandP =
|
||||
"/_user " *> (APISetActiveUser <$> A.decimal <*> optional (A.space *> jsonP)),
|
||||
("/user " <|> "/u ") *> (SetActiveUser <$> displayName <*> optional (A.space *> pwdP)),
|
||||
"/_hide user " *> (APIHideUser <$> A.decimal <* A.space <*> jsonP),
|
||||
"/_unhide user " *> (APIUnhideUser <$> A.decimal <*> optional (A.space *> jsonP)),
|
||||
"/_mute user " *> (APIMuteUser <$> A.decimal <*> optional (A.space *> jsonP)),
|
||||
"/_unmute user " *> (APIUnmuteUser <$> A.decimal <*> optional (A.space *> jsonP)),
|
||||
"/_unhide user " *> (APIUnhideUser <$> A.decimal <* A.space <*> jsonP),
|
||||
"/_mute user " *> (APIMuteUser <$> A.decimal),
|
||||
"/_unmute user " *> (APIUnmuteUser <$> A.decimal),
|
||||
"/hide user " *> (HideUser <$> pwdP),
|
||||
"/unhide user" $> UnhideUser,
|
||||
"/unhide user " *> (UnhideUser <$> pwdP),
|
||||
"/mute user" $> MuteUser,
|
||||
"/unmute user" $> UnmuteUser,
|
||||
"/_delete user " *> (APIDeleteUser <$> A.decimal <* " del_smp=" <*> onOffP <*> optional (A.space *> jsonP)),
|
||||
|
||||
@@ -190,11 +190,11 @@ data ChatCommand
|
||||
| APISetActiveUser UserId (Maybe UserPwd)
|
||||
| SetActiveUser UserName (Maybe UserPwd)
|
||||
| APIHideUser UserId UserPwd
|
||||
| APIUnhideUser UserId (Maybe UserPwd)
|
||||
| APIMuteUser UserId (Maybe UserPwd)
|
||||
| APIUnmuteUser UserId (Maybe UserPwd)
|
||||
| APIUnhideUser UserId UserPwd
|
||||
| APIMuteUser UserId
|
||||
| APIUnmuteUser UserId
|
||||
| HideUser UserPwd
|
||||
| UnhideUser
|
||||
| UnhideUser UserPwd
|
||||
| MuteUser
|
||||
| UnmuteUser
|
||||
| APIDeleteUser UserId Bool (Maybe UserPwd)
|
||||
@@ -421,7 +421,7 @@ data ChatResponse
|
||||
| CRFileTransferStatus User (FileTransfer, [Integer]) -- TODO refactor this type to FileTransferStatus
|
||||
| CRUserProfile {user :: User, profile :: Profile}
|
||||
| CRUserProfileNoChange {user :: User}
|
||||
| CRUserPrivacy {user :: User}
|
||||
| CRUserPrivacy {user :: User, updatedUser :: User}
|
||||
| CRVersionInfo {versionInfo :: CoreVersionInfo, chatMigrations :: [UpMigration], agentMigrations :: [UpMigration]}
|
||||
| CRInvitation {user :: User, connReqInvitation :: ConnReqInvitation}
|
||||
| CRSentConfirmation {user :: User}
|
||||
@@ -741,7 +741,7 @@ data ChatErrorType
|
||||
| CECantDeleteActiveUser {userId :: UserId}
|
||||
| CECantDeleteLastUser {userId :: UserId}
|
||||
| CECantHideLastUser {userId :: UserId}
|
||||
| CECantUnmuteHiddenUser {userId :: UserId}
|
||||
| CEHiddenUserAlwaysMuted {userId :: UserId}
|
||||
| CEEmptyUserPassword {userId :: UserId}
|
||||
| CEUserAlreadyHidden {userId :: UserId}
|
||||
| CEUserNotHidden {userId :: UserId}
|
||||
|
||||
@@ -116,7 +116,7 @@ responseToView user_ ChatConfig {logLevel, testView} liveItems ts = \case
|
||||
CRFileTransferStatus u ftStatus -> ttyUser u $ viewFileTransferStatus ftStatus
|
||||
CRUserProfile u p -> ttyUser u $ viewUserProfile p
|
||||
CRUserProfileNoChange u -> ttyUser u ["user profile did not change"]
|
||||
CRUserPrivacy u -> ttyUserPrefix u $ viewUserPrivacy u
|
||||
CRUserPrivacy u u' -> ttyUserPrefix u $ viewUserPrivacy u u'
|
||||
CRVersionInfo info _ _ -> viewVersionInfo logLevel info
|
||||
CRInvitation u cReq -> ttyUser u $ viewConnReqInvitation cReq
|
||||
CRSentConfirmation u -> ttyUser u ["confirmation sent!"]
|
||||
@@ -740,10 +740,11 @@ viewUserProfile Profile {displayName, fullName} =
|
||||
"(the updated profile will be sent to all your contacts)"
|
||||
]
|
||||
|
||||
viewUserPrivacy :: User -> [StyledString]
|
||||
viewUserPrivacy User {showNtfs, viewPwdHash} =
|
||||
[ "user messages are " <> if showNtfs then "shown" else "hidden (use /tail to view)",
|
||||
"user profile is " <> if isJust viewPwdHash then "hidden" else "visible"
|
||||
viewUserPrivacy :: User -> User -> [StyledString]
|
||||
viewUserPrivacy User {userId} User {userId = userId', localDisplayName = n', showNtfs, viewPwdHash} =
|
||||
[ (if userId == userId' then "current " else "") <> "user " <> plain n' <> ":",
|
||||
"messages are " <> if showNtfs then "shown" else "hidden (use /tail to view)",
|
||||
"profile is " <> if isJust viewPwdHash then "hidden" else "visible"
|
||||
]
|
||||
|
||||
-- TODO make more generic messages or split
|
||||
@@ -1241,8 +1242,8 @@ viewChatError logLevel = \case
|
||||
CECantDeleteActiveUser _ -> ["cannot delete active user"]
|
||||
CECantDeleteLastUser _ -> ["cannot delete last user"]
|
||||
CECantHideLastUser _ -> ["cannot hide the only not hidden user"]
|
||||
CECantUnmuteHiddenUser _ -> ["cannot unmute hidden user"]
|
||||
CEEmptyUserPassword _ -> ["cannot set empty password"]
|
||||
CEHiddenUserAlwaysMuted _ -> ["hidden user always muted when inactive"]
|
||||
CEEmptyUserPassword _ -> ["user password is required"]
|
||||
CEUserAlreadyHidden _ -> ["user is already hidden"]
|
||||
CEUserNotHidden _ -> ["user is not hidden"]
|
||||
CEChatNotStarted -> ["error: chat not started"]
|
||||
|
||||
+18
-17
@@ -1518,7 +1518,7 @@ testUserPrivacy =
|
||||
alice <# "bob> hey"
|
||||
-- hide user profile
|
||||
alice ##> "/hide user my_password"
|
||||
userHidden alice
|
||||
userHidden alice "current "
|
||||
-- shows messages when active
|
||||
bob #> "@alisa hello again"
|
||||
alice <# "bob> hello again"
|
||||
@@ -1555,13 +1555,15 @@ testUserPrivacy =
|
||||
]
|
||||
-- change profile password
|
||||
alice ##> "/unmute user"
|
||||
alice <## "cannot unmute hidden user"
|
||||
alice <## "hidden user always muted when inactive"
|
||||
alice ##> "/hide user password"
|
||||
alice <## "user is already hidden"
|
||||
alice ##> "/unhide user"
|
||||
userVisible alice
|
||||
alice ##> "/unhide user wrong_password"
|
||||
alice <## "user does not exist or incorrect password"
|
||||
alice ##> "/unhide user my_password"
|
||||
userVisible alice "current "
|
||||
alice ##> "/hide user new_password"
|
||||
userHidden alice
|
||||
userHidden alice "current "
|
||||
alice ##> "/_delete user 1 del_smp=on"
|
||||
alice <## "cannot delete last user"
|
||||
alice ##> "/_hide user 1 \"password\""
|
||||
@@ -1570,18 +1572,15 @@ testUserPrivacy =
|
||||
showActiveUser alice "alice (Alice)"
|
||||
-- change profile privacy for inactive user via API requires correct password
|
||||
alice ##> "/_unmute user 2"
|
||||
alice <## "cannot unmute hidden user"
|
||||
alice <## "hidden user always muted when inactive"
|
||||
alice ##> "/_hide user 2 \"password\""
|
||||
alice <## "user is already hidden"
|
||||
alice ##> "/_unhide user 2"
|
||||
alice <## "user does not exist or incorrect password"
|
||||
alice ##> "/_unhide user 2 \"wrong_password\""
|
||||
alice <## "user does not exist or incorrect password"
|
||||
alice ##> "/_unhide user 2 \"new_password\""
|
||||
userVisible alice
|
||||
userVisible alice ""
|
||||
alice ##> "/_hide user 2 \"another_password\""
|
||||
userHidden alice
|
||||
-- check new password
|
||||
userHidden alice ""
|
||||
alice ##> "/user alisa another_password"
|
||||
showActiveUser alice "alisa"
|
||||
alice ##> "/user alice"
|
||||
@@ -1594,12 +1593,14 @@ testUserPrivacy =
|
||||
alice <## "ok"
|
||||
alice <## "completed deleting user"
|
||||
where
|
||||
userHidden alice = do
|
||||
alice <## "user messages are hidden (use /tail to view)"
|
||||
alice <## "user profile is hidden"
|
||||
userVisible alice = do
|
||||
alice <## "user messages are shown"
|
||||
alice <## "user profile is visible"
|
||||
userHidden alice current = do
|
||||
alice <## (current <> "user alisa:")
|
||||
alice <## "messages are hidden (use /tail to view)"
|
||||
alice <## "profile is hidden"
|
||||
userVisible alice current = do
|
||||
alice <## (current <> "user alisa:")
|
||||
alice <## "messages are shown"
|
||||
alice <## "profile is visible"
|
||||
|
||||
testSetChatItemTTL :: HasCallStack => FilePath -> IO ()
|
||||
testSetChatItemTTL =
|
||||
|
||||
Reference in New Issue
Block a user