// // LoginController.swift // Befund // // Created by Artur Savitskiy on 27.07.22. // Copyright © 2022 MVZ Dr. Stein und Kollegen. All rights reserved. // import Foundation import LocalAuthentication import UIKit class LoginController: UIViewController, UITextFieldDelegate, UIAlertViewDelegate { public static var CurrentViewController: LoginController? = nil public static var Settings: Core.Models.Settings? = nil @IBOutlet weak var loginPassword_PassworArea: UIView! @IBOutlet weak var loginPassword_LaborLogoArea: UIView! @IBOutlet weak var loginPasswordView_LaborLogo: UIImageView! @IBOutlet weak var loginPasswordArea_InputArea: UIView! @IBOutlet weak var loginPasswordView_txtPassword: UITextField! @IBOutlet weak var loginPasswordArea_StatusArea: UIView! @IBOutlet weak var loginPasswordView_Status: UILabel! //MARK: *** PopupNewPGS - Buttons @IBOutlet weak var loginPasswordArea_ButtonsArea: UIView! @IBOutlet weak var loginPasswordView_BtnLogin: UIButton! @IBOutlet weak var loginPasswordView_BtnReset: UIButton! @IBOutlet weak var loginPasswordView_BtnSupport: UIButton! @IBOutlet weak var passwordReset_Overlay: UIView! @IBOutlet weak var passwordReset: UIView! //MARK: *** passwordReset - PGS @IBOutlet weak var passwordReset_Loading: UIActivityIndicatorView! @IBOutlet weak var passwordReset_Status: UILabel! //MARK: *** passwordReset - MasterPassword @IBOutlet weak var passwordReset_MasterPassword: UIView! @IBOutlet weak var passwordReset_PINView: UIView! @IBOutlet weak var passwordReset_PINVNewView: UIView! @IBOutlet weak var passwordReset_PasswordConfirmView: UIView! @IBOutlet weak var passwordReset_PasswordView: UIView! @IBOutlet weak var passwordReset_txtPassword: UITextField! @IBOutlet weak var passwordReset_txtPasswordConfirm: UITextField! @IBOutlet weak var passwordReset_txtPin: UITextField! @IBOutlet weak var passwordReset_txtPinNew: UITextField! @IBOutlet weak var passwordReset_lblPwdHinweis: UILabel! //MARK: *** passwordReset - Buttons @IBOutlet weak var passwordReset_BtnChange: UIButton! @IBOutlet weak var passwordReset_BtnPinRequest: UIButton! @IBOutlet weak var passwordReset_BtnCancel: UIButton! override func viewDidLoad() { super.viewDidLoad() self.Initialize() } override func loadView() { super.loadView() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.SetLabels() } /** * Initializes view */ private func Initialize() { LoginController.CurrentViewController = self LoginController.Settings = Core.Models.Settings.loadFromFile(atPath: Core.System.SettingsPath()) self.loginPasswordView_txtPassword.delegate = self self.loginPasswordView_txtPassword.enablePasswordToggle() self.passwordReset_Overlay.frame = self.view.frame self.view.addSubview(self.passwordReset_Overlay) self.passwordReset_txtPin.delegate = self self.passwordReset_txtPin.returnKeyType = .next self.passwordReset_txtPinNew.delegate = self self.passwordReset_txtPinNew.returnKeyType = .next self.passwordReset_txtPassword.delegate = self self.passwordReset_txtPassword.enablePasswordToggle() self.passwordReset_txtPassword.returnKeyType = .next self.passwordReset_txtPasswordConfirm.delegate = self self.passwordReset_txtPasswordConfirm.enablePasswordToggle() self.passwordReset_txtPasswordConfirm.returnKeyType = .done self.passwordReset_Overlay.isHidden = true self.passwordReset_lblPwdHinweis.text = "" self.passwordReset_lblPwdHinweis.isHidden = false self.passwordReset_txtPassword.addTarget(self, action: #selector(self.ShowPWDHinweis), for: .editingDidBegin) self.passwordReset_txtPassword.addTarget(self, action: #selector(self.HidePWDHinweis), for: .editingDidEnd) self.passwordReset_txtPasswordConfirm.addTarget(self, action: #selector(self.ShowPWDHinweis), for: .editingDidBegin) self.passwordReset_txtPasswordConfirm.addTarget(self, action: #selector(self.HidePWDHinweis), for: .editingDidEnd) self.passwordReset_txtPinNew.addTarget(self, action: #selector(self.ShowPinHinweis), for: .editingDidBegin) self.passwordReset_txtPinNew.addTarget(self, action: #selector(self.HidePWDHinweis), for: .editingDidEnd) self.addDoneToolbar([self.passwordReset_txtPin, self.passwordReset_txtPinNew, self.passwordReset_txtPassword, self.passwordReset_txtPasswordConfirm]) self.hideKeyboardWhenTappedAround() } @objc func ShowPinHinweis() { self.passwordReset_Status.text = "" self.passwordReset_lblPwdHinweis.text = Core.Lang.Get(key: "ERROR_PIN_LENGTH") self.passwordReset_lblPwdHinweis.isHidden = false } @objc func ShowPWDHinweis() { self.passwordReset_Status.text = "" self.passwordReset_lblPwdHinweis.text = Core.Lang.Get(key: "ERROR_ENTER_STRONG_PASSWORD") self.passwordReset_lblPwdHinweis.isHidden = false } @objc func HidePWDHinweis() { self.passwordReset_lblPwdHinweis.text = "" self.passwordReset_lblPwdHinweis.isHidden = true } private func SetLabels() { self.loginPasswordView_txtPassword.placeholder = Core.Lang.Get(key: "LBL_PASSWORD") self.loginPasswordView_BtnReset.setTitle(Core.Lang.Get(key: "BTN_FORGOT"), for: .normal) self.loginPasswordView_BtnLogin.setTitle(Core.Lang.Get(key: "BTN_LOGIN"), for: .normal) self.loginPasswordView_BtnSupport.setTitle(Core.Lang.Get(key: "BTN_SUPPORT"), for: .normal) self.passwordReset_BtnCancel.setTitle(Core.Lang.Get(key: "BTN_CANCEL"), for: .normal) self.passwordReset_BtnChange.setTitle(Core.Lang.Get(key: "BTN_CHANGE"), for: .normal) self.passwordReset_BtnPinRequest.setTitle(Core.Lang.Get(key: "BTN_PIN_REQUEST"), for: .normal) self.passwordReset_txtPin.placeholder = Core.Lang.Get(key: "LBL_PIN") self.passwordReset_txtPinNew.placeholder = Core.Lang.Get(key: "LBL_NEW_PIN") self.passwordReset_txtPassword.text = Core.Lang.Get(key: "LBL_NEW_PASSWORD") self.passwordReset_txtPasswordConfirm.text = Core.Lang.Get(key: "LBL_PASSWORD_CONFIRM") self.loginPasswordView_LaborLogo.image = LoginController.Settings?.labor?.logo } internal func textFieldShouldReturn(_ textField: UITextField) -> Bool { self.switchBasedNextTextField(textField) return true } private func switchBasedNextTextField(_ textField: UITextField) { switch textField { case self.passwordReset_txtPin: self.passwordReset_txtPinNew.becomeFirstResponder() case self.passwordReset_txtPinNew: self.passwordReset_txtPassword.becomeFirstResponder() case self.passwordReset_txtPassword: self.passwordReset_txtPasswordConfirm.becomeFirstResponder() default: self.view.endEditing(true) self.LoginPasswordView_BtnLoginClick("") } } @IBAction func ButtonSupportOpenClick(_ sender: Any) { let storyboard = UIStoryboard(name: "Main", bundle: nil) let settingsSupportController = storyboard.instantiateViewController(identifier: "SupportController") settingsSupportController.modalPresentationStyle = .fullScreen let transition = CATransition() transition.duration = 0.25 transition.type = .push transition.subtype = .fromRight self.view.window!.layer.add(transition, forKey: kCATransition) self.present(settingsSupportController, animated: false) } @IBAction func LoginPasswordView_BtnLoginClick(_ sender: Any) { self.loginPasswordView_Status.text = "" if(self.loginPasswordView_txtPassword.text?.isEmpty ?? true) { self.loginPasswordView_Status.text=Core.Lang.Get(key: "ERROR_INVALID_PASSWORD") } else { let password = self.loginPasswordView_txtPassword.text ?? "" let encrypted_pwd = Core.Security.AES.Decrypt(value: LoginController.Settings?.hashed_private_key ?? "", password: String( decoding:Core.Security.AES.GetKey(password: password), as: UTF8.self)) if(encrypted_pwd==nil) { self.loginPasswordView_Status.text=Core.Lang.Get(key: "ERROR_INVALID_PASSWORD") } else { AppDelegate.Session.DevicePassword = password self.view.endEditing(true) self.dismiss(animated: true, completion: nil) } } } private func PasswordActivity_HideLoading(message: String) { self.passwordReset_Loading.isHidden = true if(!message.isEmpty) { self.passwordReset_Status.isHidden = false self.passwordReset_Status.text = message } } @IBAction func LoginPasswordView_BtnResetClick(_ sender: Any) { self.passwordReset_txtPin.text = "" self.passwordReset_txtPinNew.text = "" self.passwordReset_txtPassword.text = "" self.passwordReset_txtPasswordConfirm.text = "" self.passwordReset_Status.text = "" self.passwordReset_Loading.isHidden = true self.passwordReset_Loading.stopAnimating() self.passwordReset.transform = CGAffineTransform(scaleX: 0.1, y: 0.1) self.passwordReset_Overlay.alpha = 0.0 self.passwordReset_Overlay.isHidden = false UIView.animate(withDuration: 0.24) { self.passwordReset.transform = CGAffineTransform.identity self.passwordReset_Overlay.alpha = 70.0 } } @IBAction func PasswordReset_BtnCancelClick(_ sender: Any) { self.HidePasswordResetPopup() } private func HidePasswordResetPopup() { self.view.endEditing(true) UIView.animate(withDuration: 0.24, animations: { self.passwordReset_Overlay.alpha = 0.0 self.passwordReset.transform = CGAffineTransform(scaleX: 0.1, y: 0.1) }) {_ in self.passwordReset_Overlay.isHidden = true } } @IBAction func PasswordReset_BtnChangeClick(_ sender: Any) { self.ChangePassword() } func ChangePassword() { self.ResetPassword_ShowLoading() let pin = (self.passwordReset_txtPin.text ?? "") let pinNew = (self.passwordReset_txtPinNew.text ?? "") if(pin.count == 0) { self.ResetPassword_HideLoading( message: Core.Lang.Get(key: "ERROR_ENTER_YOUR_PIN")) } else if(pin.count != 5) { self.ResetPassword_HideLoading( message: Core.Lang.Get(key: "ERROR_PIN_LENGTH")) } else if(pinNew.count == 0) { self.ResetPassword_HideLoading( message: Core.Lang.Get(key: "ERROR_ENTER_PIN")) } else if(pinNew.count != 5) { self.ResetPassword_HideLoading( message: Core.Lang.Get(key: "ERROR_PIN_LENGTH")) } else { let oldPasswordDecrypted = Core.Models.Request.ChangeVerificatorHashProvider.GetDecryptedOldPasswordByPin(settings: LoginController.Settings!, pin: pin) if(oldPasswordDecrypted == nil) { self.ResetPassword_HideLoading( message: Core.Lang.Get(key: "ERROR_INVALID_PASSWORD_RESET_PIN")) } else { let newPass = self.passwordReset_txtPassword?.text ?? "" let confirmPass = self.passwordReset_txtPasswordConfirm?.text ?? "" if(!Core.Models.Request.ChangeVerificatorHashProvider.IsPasswordStrong(password: newPass)) { self.ResetPassword_HideLoading(message: Core.Lang.Get(key: "ERROR_ENTER_STRONG_PASSWORD")) } else if(newPass != confirmPass) { self.ResetPassword_HideLoading(message: Core.Lang.Get(key: "ERROR_CONFIRM_PASSWORD")) } else { let changeVerificatorHash = Core.Models.Request.ChangeVerificatorHashProvider.PrepareChangeVerificatorHash(settings: LoginController.Settings!, oldPassword: oldPasswordDecrypted!, newPassword: newPass, newPin: pinNew) let requestKeyPair = Core.Security.Curve25519.GenerateKeyPair() Core.Https.Request.KeyExchangeAsync(host: (LoginController.Settings!.labor?.host ?? .DEVELOPMENT), keyPair: requestKeyPair, onSuccess: { publicKey in let sharedKey = requestKeyPair.GetSharedKey(peerPublicKeyBase64: publicKey.key) let encryptedRequest = Core.Models.Request.EncryptedRequest(descriptor: "ChangeVerificatorHash", contentObject: changeVerificatorHash, requestType: .REQUEST_VERIFICATOR_HASH, key: sharedKey!) let currentHost = ViewController.Settings!.labor?.host ?? .DEVELOPMENT Core.Https.Request.EncryptedRequestAsync(host: currentHost, controller: "results", action: "update_verificator_hash", request: encryptedRequest, serverPublicKey: publicKey, keyPair: requestKeyPair, onSuccess: { encryptedResponse in encryptedResponse.Decrypt(key: sharedKey!) DispatchQueue.main.async { if(encryptedResponse.descriptor!.lowercased() == "success") { var errMsg: String? = nil LoginController.Settings = Core.Models.Request.ChangeVerificatorHashProvider.SaveChangedVerificatorBySuccess(settings: LoginController.Settings!, oldPassword: oldPasswordDecrypted!, newPassword: newPass, pin: (changeVerificatorHash.pin ?? ""), errorMsg: &errMsg) if(errMsg != nil) { self.PasswordActivity_HideLoading(message: errMsg!) Core.Log.Critical(msg: "Could not save settings to the file", namespace: "SettingsController", method: "PasswordChange") } else { self.PasswordActivity_HideLoading(message: "") DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.HidePasswordResetPopup() self.ShowMessagePopup(title: "", message: Core.Lang.Get(key: "MSG_PASSWORD_HAS_BEEN_CHANGED").replacingOccurrences(of: "[PIN]", with: changeVerificatorHash.pin!)) } } } else { self.PasswordActivity_HideLoading(message: encryptedResponse.descriptor!) } } }, onError: { error in DispatchQueue.main.async { self.PasswordActivity_HideLoading(message: error) Core.Log.Critical(msg: "Server not reachable", namespace: "LoginController", method: "ChangePassword") } }) }, onError: { error in DispatchQueue.main.async { self.PasswordActivity_HideLoading(message: error) Core.Log.Critical(msg: "Server not reachable", namespace: "LoginController", method: "ChangePassword") } }) } } } } @IBAction func PasswordReset_BtnRequestPinClick(_ sender: Any) { self.RequestPIN() } func RequestPIN() { var authError: NSError? let localAuthContext = LAContext() if (localAuthContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError)) { localAuthContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: Core.Lang.Get(key: "MSG_TOUCHID_INFO")) { success, evaluateError in if success { DispatchQueue.main.async { self.ResetPassword_ShowLoading() } self.RequestPINFromServer() } else { //TODO: User did not authenticate successfully, look at error and take appropriate action guard let error = evaluateError else { return } DispatchQueue.main.async { self.ResetPassword_HideLoading(message: error.localizedDescription) } } } } else { guard let error = authError else { return } DispatchQueue.main.async { self.ResetPassword_HideLoading(message: Core.Lang.Get(key: "ERROR_BIOMETRICAL_AUTHENTICATION_COULD_NOT_ACTIVATED")) } } } func ResetPassword_ShowLoading() { self.passwordReset_Status.text = "" self.passwordReset_Status.isHidden = true self.passwordReset_Loading.isHidden = false self.passwordReset_Loading.startAnimating() } func ResetPassword_HideLoading(message: String) { self.passwordReset_Loading.isHidden = true if(!message.isEmpty) { self.passwordReset_Status.text = message self.passwordReset_Status.isHidden = false } } func RequestPINFromServer() { let requestKeyPair = Core.Security.Curve25519.GenerateKeyPair() Core.Https.Request.KeyExchangeAsync(host: (LoginController.Settings!.labor?.host ?? .DEVELOPMENT), keyPair: requestKeyPair, onSuccess: { publicKey in let getPIN = Core.Models.Request.GetPIN() getPIN.udid = LoginController.Settings!.udid; getPIN.verificator_hash = LoginController.Settings!.verificator_hash; let sharedKey = requestKeyPair.GetSharedKey(peerPublicKeyBase64: publicKey.key) let encryptedRequest = Core.Models.Request.EncryptedRequest(descriptor: "PIN", contentObject: getPIN, requestType: .REQUEST_GET_PIN, key: sharedKey!) let currentHost = ViewController.Settings!.labor?.host ?? .DEVELOPMENT Core.Https.Request.EncryptedRequestAsync(host: currentHost, controller: "results", action: "pin", request: encryptedRequest, serverPublicKey: publicKey, keyPair: requestKeyPair, onSuccess: { encryptedResponse in let response = encryptedResponse.Decrypt(key: sharedKey!) DispatchQueue.main.async { if(response != nil) { let pin = response as? Core.Models.Response.PIN; if(pin != nil && pin!.code!.count > 0) { self.ResetPassword_HideLoading(message: Core.Lang.Get(key: "PIN_HAS_BEEN_REQUESTED")) self.passwordReset_txtPin.text = pin!.code } else { let exception = response as? Core.Models.Response.Exception if(exception != nil) { self.ResetPassword_HideLoading(message: Core.Lang.Get(key: "ERROR_COULD_NOT_GET_PIN") + ": " + (exception!.message ?? "")) } else { self.ResetPassword_HideLoading(message: Core.Lang.Get(key: "ERROR_COULD_NOT_GET_PIN")) Core.Log.Error(msg: "Could not get pin from the server", namespace: "LoginActivity", method: "RequestPINFromServer") } } } else { self.ResetPassword_HideLoading(message: Core.Lang.Get(key: "ERROR_COULD_NOT_GET_PIN")) Core.Log.Error(msg: "Could not get pin from the server", namespace: "LoginActivity", method: "RequestPINFromServer") } } }, onError: { error in DispatchQueue.main.async { self.ResetPassword_HideLoading(message: Core.Lang.Get(key: "ERROR_COULD_NOT_GET_PIN")); Core.Log.Error(msg: error, namespace: "LoginActivity", method: "RequestPINFromServer"); } }) }, onError: { error in DispatchQueue.main.async { self.ResetPassword_HideLoading(message: Core.Lang.Get(key: "ERROR_COULD_NOT_GET_PIN")); Core.Log.Error(msg: error, namespace: "LoginActivity", method: "RequestPINFromServer"); } }) } }