Add popup for updating password

This commit is contained in:
Benoit Martins 2025-07-08 13:27:01 +02:00
parent 2b79b95079
commit fe261f3127
9 changed files with 235 additions and 11 deletions

View file

@ -251,15 +251,39 @@ class CoreContext: ObservableObject {
}
}
}
}, onAuthenticationRequested: { (_: Core, authInfo: AuthInfo, method: AuthMethod) in
guard let username = authInfo.username, let server = authInfo.authorizationServer, !server.isEmpty else {
}, onAuthenticationRequested: { (core: Core, authInfo: AuthInfo, method: AuthMethod) in
guard let username = authInfo.username, let domain = authInfo.domain, let realm = authInfo.realm else {
Log.error("Authentication requested but either username [\(String(describing: authInfo.username))], domain [\(String(describing: authInfo.domain))] or server [\(String(describing: authInfo.authorizationServer))] is nil or empty!")
return
}
if method == .Bearer {
Log.info("Authentication requested method is Bearer, starting Single Sign On activity with server URL \(server) and username \(username)")
self.bearerAuthInfoPendingPasswordUpdate = authInfo
SingleSignOnManager.shared.setUp(ssoUrl: server, user: username)
if let server = authInfo.authorizationServer, !server.isEmpty {
Log.info("Authentication requested method is Bearer, starting Single Sign On activity with server URL \(server) and username \(username)")
self.bearerAuthInfoPendingPasswordUpdate = authInfo
SingleSignOnManager.shared.setUp(ssoUrl: server, user: username)
}
}
if method == .HttpDigest {
guard let accountFound = core.accountList.first(where: {
$0.params?.identityAddress?.username == authInfo.username &&
$0.params?.identityAddress?.domain == authInfo.domain
}) else {
Log.info("[CoreContext] Failed to find account matching auth info, aborting auth dialog")
return
}
let identity = "\(authInfo.username ?? "username")@\(authInfo.domain ?? "domain")"
Log.info("[CoreContext] Authentication requested method is HttpDigest, showing dialog asking user for password for identity [\(identity)]")
DispatchQueue.main.async {
NotificationCenter.default.post(
name: NSNotification.Name("PasswordUpdate"),
object: nil,
userInfo: ["address": "sip:" + identity]
)
}
}
}, onTransferStateChanged: { (_: Core, transferred: Call, callState: Call.State) in
Log.info("[CoreContext] Transferred call \(transferred.remoteAddress!.asStringUriOnly()) state changed \(callState)")

View file

@ -22,6 +22,9 @@
"account_settings_avpf_title" = "AVPF";
"account_settings_conference_factory_uri_title" = "URI konfiguračního serveru konference";
"account_settings_cpim_in_basic_conversations_title" = "Použít CPIM v \"základních\" konverzacích";
"account_settings_dialog_invalid_password_title" = "Vyžadováno ověření";
"account_settings_dialog_invalid_password_message" = "Připojení se nezdařilo, protože chybí nebo je neplatné ověření účtu\n%@.\n\nMůžete znovu zadat heslo nebo zkontrolovat nastavení účtu v konfiguraci.";
"account_settings_dialog_invalid_password_hint" = "Nové heslo";
"account_settings_enable_ice_title" = "Povolit ICE";
"account_settings_enable_turn_title" = "Povolit TURN";
"account_settings_expire_title" = "Platnost (v sekundách)";

View file

@ -38,6 +38,9 @@
"account_settings_ccmp_server_url_title" = "CCMP server URL";
"account_settings_conference_factory_uri_title" = "Conference factory URI";
"account_settings_cpim_in_basic_conversations_title" = "Use CPIM in \"basic\" conversations";
"account_settings_dialog_invalid_password_title" = "Authentication needed";
"account_settings_dialog_invalid_password_message" = "Connection failed because authentication is missing or invalid for account \n%@.\n\nYou can provide password again, or check your account configuration in the settings.";
"account_settings_dialog_invalid_password_hint" = "Password";
"account_settings_enable_ice_title" = "Enable ICE";
"account_settings_enable_turn_title" = "Enable TURN";
"account_settings_expire_title" = "Expire (in seconds)";

View file

@ -38,6 +38,9 @@
"account_settings_ccmp_server_url_title" = "URL du serveur CCMP";
"account_settings_conference_factory_uri_title" = "URI du serveur de conversations";
"account_settings_cpim_in_basic_conversations_title" = "Utiliser CPIM dans les conversations \"basiques\"";
"account_settings_dialog_invalid_password_title" = "Autentification requise";
"account_settings_dialog_invalid_password_message" = "La connexion a échoué pour le compte \n%@.\n\nVous pouvez renseigner votre mot de passe à nouveau ou bien vérifier les options de configuration de votre compte.";
"account_settings_dialog_invalid_password_hint" = "Mot de passe";
"account_settings_enable_ice_title" = "Activer ICE";
"account_settings_enable_turn_title" = "Activer TURN";
"account_settings_expire_title" = "Expiration (en secondes)";

View file

@ -298,6 +298,9 @@
"account_settings_bundle_mode_title" = "Режим об'єднання";
"account_settings_ccmp_server_url_title" = "URL-адреса сервера CCMP";
"account_settings_conference_factory_uri_title" = "URI ресурсу конференцій";
"account_settings_dialog_invalid_password_title" = "Потрібна автентифікація";
"account_settings_dialog_invalid_password_message" = "З’єднання не вдалося, оскільки автентифікація відсутня або недійсна для облікового запису\n%@.\n\nВи можете ввести пароль ще раз або перевірити конфігурацію облікового запису в налаштуваннях.";
"account_settings_dialog_invalid_password_hint" = "Новий пароль";
"account_settings_enable_ice_title" = "Увімкнути ICE";
"account_settings_enable_turn_title" = "Увімкнути TURN";
"account_settings_expire_title" = "Спливає (в секундах)";

View file

@ -82,6 +82,9 @@ struct ContentView: View {
@State var isShowConversationInfoPopup: Bool = false
@State var conversationInfoPopupText: String = ""
@State var isShowUpdatePasswordPopup: Bool = false
@State var passwordUpdateAddress: String = ""
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
@ -1293,6 +1296,18 @@ struct ContentView: View {
}
}
if isShowUpdatePasswordPopup {
PopupUpdatePassword(
isShowUpdatePasswordPopup: $isShowUpdatePasswordPopup,
passwordUpdateAddress: $passwordUpdateAddress
)
.background(.black.opacity(0.65))
.zIndex(3)
.onTapGesture {
self.isShowUpdatePasswordPopup.toggle()
}
}
if telecomManager.meetingWaitingRoomDisplayed {
MeetingWaitingRoomFragment()
.zIndex(3)
@ -1353,6 +1368,10 @@ struct ContentView: View {
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CoreStarted"))) { _ in
accountProfileViewModel.setAvatarModel()
}
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("PasswordUpdate")).compactMap { $0.userInfo?["address"] as? String }) { address in
passwordUpdateAddress = address
isShowUpdatePasswordPopup = true
}
}
.overlay {
if isMenuOpen {

View file

@ -0,0 +1,153 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import SwiftUI
struct PopupUpdatePassword: View {
@ObservedObject var sharedMainViewModel = SharedMainViewModel.shared
@Binding var isShowUpdatePasswordPopup: Bool
@Binding var passwordUpdateAddress: String
@State private var passwordPopupText: String = ""
@State private var isSecured: Bool = true
@FocusState var isPasswordFocused: Bool
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
Text("account_settings_dialog_invalid_password_title")
.default_text_style_800(styleSize: 16)
.frame(alignment: .leading)
.padding(.bottom, 2)
Text(String(format: String(localized: "account_settings_dialog_invalid_password_message"), passwordUpdateAddress))
.default_text_style(styleSize: 15)
.padding(.bottom, 20)
ZStack(alignment: .trailing) {
Group {
if isSecured {
SecureField("account_settings_dialog_invalid_password_hint", text: $passwordPopupText)
.default_text_style(styleSize: 15)
.frame(height: 25)
.focused($isPasswordFocused)
} else {
TextField("account_settings_dialog_invalid_password_hint", text: $passwordPopupText)
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.frame(height: 25)
.focused($isPasswordFocused)
}
}
Button(action: {
isSecured.toggle()
}, label: {
Image(self.isSecured ? "eye-slash" : "eye")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 20, height: 20)
})
}
.padding(.horizontal, 20)
.padding(.vertical, 15)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(isPasswordFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.padding(.bottom)
Button(action: {
isShowUpdatePasswordPopup = false
}, label: {
Text("dialog_cancel")
.default_text_style_orange_600(styleSize: 20)
.frame(height: 35)
.frame(maxWidth: .infinity)
})
.padding(.horizontal, 20)
.padding(.vertical, 10)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(Color.orangeMain500, lineWidth: 1)
)
.padding(.bottom, 10)
Button(action: {
setNewPassword()
isShowUpdatePasswordPopup = false
}, label: {
Text("dialog_ok")
.default_text_style_white_600(styleSize: 20)
.frame(height: 35)
.frame(maxWidth: .infinity)
})
.padding(.horizontal, 20)
.padding(.vertical, 10)
.background(passwordPopupText.isEmpty ? Color.orangeMain100 : Color.orangeMain500)
.cornerRadius(60)
.disabled(passwordPopupText.isEmpty)
}
.padding(.horizontal, 20)
.padding(.vertical, 20)
.background(.white)
.cornerRadius(20)
.padding(.horizontal)
.frame(maxHeight: .infinity)
.shadow(color: Color.orangeMain500, radius: 0, x: 0, y: 2)
.frame(maxWidth: SharedMainViewModel.shared.maxWidth)
.position(x: geometry.size.width / 2, y: geometry.size.height / 2)
}
}
func setNewPassword() {
CoreContext.shared.doOnCoreQueue { core in
Log.info("[SetNewPassword] ---- \(core.defaultAccount?.params?.identityAddress?.asStringUriOnly() ?? "No account found") \(passwordUpdateAddress)")
if let account = core.accountList.first { $0.params?.identityAddress?.asStringUriOnly() == passwordUpdateAddress } {
let authInfo = account.findAuthInfo()
if (authInfo != nil) {
Log.info(
"[SetNewPassword] Updating password for username \(authInfo!.username) using auth info \(authInfo!)"
)
authInfo!.password = passwordPopupText
core.addAuthInfo(info: authInfo!)
core.refreshRegisters()
} else {
Log.warn(
"[SetNewPassword] Failed to find auth info for account \(account.params?.identityAddress?.asStringUriOnly())"
)
}
}
}
}
}
#Preview {
PopupUpdatePassword(isShowUpdatePasswordPopup: .constant(true), passwordUpdateAddress: .constant("example@sip.linphone.org"))
}

View file

@ -1,9 +1,21 @@
//
// PopupViewWithTextField.swift
// Linphone
//
// Created by Benoît Martins on 12/11/2024.
//
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import SwiftUI

View file

@ -192,6 +192,7 @@
D7E6D0552AEBFCCE00A57AAF /* ContactsInnerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */; };
D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */; };
D7EFD1E42CD11F70005E67CD /* EphemeralFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EFD1E32CD11F53005E67CD /* EphemeralFragment.swift */; };
D7F01ADE2E1D0DA3006942C0 /* PopupUpdatePassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F01ADD2E1D0D92006942C0 /* PopupUpdatePassword.swift */; };
D7F4D9CB2B5FD27200CDCD76 /* CallsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F4D9CA2B5FD27200CDCD76 /* CallsListFragment.swift */; };
D7F5F6412C359F3B007FCF2F /* SipAddressesPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F5F6402C359F3B007FCF2F /* SipAddressesPopup.swift */; };
D7FB55112AD447FD00A5AB15 /* RegisterFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */; };
@ -408,6 +409,7 @@
D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsInnerFragment.swift; sourceTree = "<group>"; };
D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsFragment.swift; sourceTree = "<group>"; };
D7EFD1E32CD11F53005E67CD /* EphemeralFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralFragment.swift; sourceTree = "<group>"; };
D7F01ADD2E1D0D92006942C0 /* PopupUpdatePassword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupUpdatePassword.swift; sourceTree = "<group>"; };
D7F4D9CA2B5FD27200CDCD76 /* CallsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallsListFragment.swift; sourceTree = "<group>"; };
D7F5F6402C359F3B007FCF2F /* SipAddressesPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SipAddressesPopup.swift; sourceTree = "<group>"; };
D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterFragment.swift; sourceTree = "<group>"; };
@ -782,6 +784,7 @@
D74C9CFD2ACAEC150021626A /* Fragments */ = {
isa = PBXGroup;
children = (
D7F01ADD2E1D0D92006942C0 /* PopupUpdatePassword.swift */,
D74C9CFE2ACAEC5E0021626A /* PopupView.swift */,
D72343352AD037AF009AA24E /* ToastView.swift */,
D750D3382AD3E6EE00EC99C5 /* PopupLoadingView.swift */,
@ -1342,6 +1345,7 @@
D7C48DF62AFCDF4700D938CB /* ContactInnerActionsFragment.swift in Sources */,
D72343322ACEFF58009AA24E /* QRScannerController.swift in Sources */,
662B69D92B25DE18007118BF /* TelecomManager.swift in Sources */,
D7F01ADE2E1D0DA3006942C0 /* PopupUpdatePassword.swift in Sources */,
D72343342ACEFFC3009AA24E /* QRScanner.swift in Sources */,
66246C6A2C622AE900973E97 /* TimeZoneExtension.swift in Sources */,
66C491FB2B24D32600CEA16D /* CoreExtension.swift in Sources */,