I am working on an app that accesses a REST API Webservice. Everything is working great, except I recently started working on the the ability to logout and switch users and I've run into a strange situation. If I log out, and then click login again without entering the password it's working. I've even debugged the code and see that the password is blank, but the authentication is still working. Here is the code:
import UIKit
import LocalAuthentication
var userName = String()
var password = String()
var server = String()
var port = String()
var myUser = User()
var myExtensions = [ExtensionListItem]()
var myDevices = [Device]()
class LoginViewController: UIViewController, NSURLSessionDelegate, UITextFieldDelegate {
let authContext: LAContext = LAContext()
var logOutUser = Bool()
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
#IBOutlet weak var serverNameField: UITextField!
#IBOutlet weak var usernameField: UITextField!
#IBOutlet weak var passwordField: UITextField!
#IBOutlet var loginEnable: UIButton!
var userPasswordString = NSString()
let userRequest = NSMutableURLRequest()
var userSession = NSURLSession()
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(logoff), name: "logoff", object: nil)
if logOutUser {
NSUserDefaults.standardUserDefaults().setValue("", forKey: "password")
NSURLCache.sharedURLCache().removeAllCachedResponses()
userPasswordString = NSString()
}
//Determine if the user has a stored Username and populate the usernameField if possible
if NSUserDefaults.standardUserDefaults().objectForKey("userName") != nil{
usernameField.text = NSUserDefaults.standardUserDefaults().objectForKey("userName") as? String}
//Determine if the user has a stored ServerName and populate the serverNameField if possible.
if NSUserDefaults.standardUserDefaults().objectForKey("serverName") != nil{
serverNameField.text = NSUserDefaults.standardUserDefaults().objectForKey("serverName") as? String}
//Determin if the user has requested to use Touch ID
if (NSUserDefaults.standardUserDefaults().objectForKey("useTouchID") != nil) {
if NSUserDefaults.standardUserDefaults().valueForKey("useTouchID") as! Bool == true && CheckTouchIDCapable(){
//Trigger Touch ID
usernameField.enabled = false
passwordField.enabled = false
serverNameField.enabled = false
activityIndicator.startAnimating()
TouchIDCall()
}
}
// Do any additional setup after loading the view.
}
func logoff(){
NSURLCache.sharedURLCache().removeAllCachedResponses()
userSession.invalidateAndCancel()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
NSURLCache.sharedURLCache().removeAllCachedResponses()
// Dispose of any resources that can be recreated.
}
#IBAction func loginButton(sender: AnyObject) {
NSUserDefaults.standardUserDefaults().setObject(usernameField.text, forKey: "userName")
NSUserDefaults.standardUserDefaults().setObject(passwordField.text, forKey: "password")
NSUserDefaults.standardUserDefaults().setObject(serverNameField.text, forKey: "serverName")
if NSUserDefaults.standardUserDefaults().valueForKey("touchIDPreferenceSet") == nil && CheckTouchIDCapable() {
DisplayTouchIDQuestion("Use Touch ID?", message: "Would you like to use touch ID to login?")
}else{
usernameField.enabled = false
passwordField.enabled = false
serverNameField.enabled = false
activityIndicator.startAnimating()
CheckUser()
}
print("Password: \(password)")
print("Stored Password: \(NSUserDefaults.standardUserDefaults().valueForKey("password"))")
print("?? \(NSUserDefaults.standardUserDefaults().objectForKey("password"))")
}
func CheckUser(){
userName = (NSUserDefaults.standardUserDefaults().objectForKey("userName") as? String)!
if !logOutUser{
password = (NSUserDefaults.standardUserDefaults().objectForKey("password") as? String)!
}
server = (NSUserDefaults.standardUserDefaults().objectForKey("serverName") as? String)!
port = "8443"
// set up the base64-encoded credentials
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
userPasswordString = NSString(format: "%#:%#", userName, password)
let userPasswordData = userPasswordString.dataUsingEncoding(NSUTF8StringEncoding)
let base64EncodedCredential = userPasswordData!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
let authString = "Basic \(base64EncodedCredential)"
config.HTTPAdditionalHeaders?.removeAll()
config.HTTPAdditionalHeaders = ["Authorization" : authString]
config.timeoutIntervalForRequest = 10.0
// create the user request
let userUrlString = NSString(format: "https://%#:%#/webserver/user/%#", server, port, userName)
let userUrl = NSURL(string: userUrlString as String)
userRequest.cachePolicy = .ReloadIgnoringLocalAndRemoteCacheData
userRequest.URL = userUrl!
userRequest.HTTPMethod = "GET"
userRequest.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
userSession = NSURLSession(configuration: config, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
//Send User Request to the server and populate labels with response.
_ = userSession.dataTaskWithRequest(userRequest) { (data, response, error) in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if error?.code != nil{
print("ERROR: \(error!.localizedDescription)")
self.DisplayAlert("Error", message: error!.localizedDescription)
}else{
_ = NSString (data: data!, encoding: NSUTF8StringEncoding)
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
let accessDenied = Bool(dataString?.rangeOfString("HTTP Status 403").location != NSNotFound)
let authFailure = Bool(dataString?.rangeOfString("HTTP Status 401").location != NSNotFound)
if (authFailure || accessDenied) {
print("\(NSDate()): Unsuccessful Password Authentication Attempt for user: \(NSUserDefaults.standardUserDefaults().valueForKey("userName")!)")
self.DisplayAlert("Access Denied", message: "Please Verify Your Credentials")
}else{
print("\(NSDate()): Successful Password Authentication for user: \(NSUserDefaults.standardUserDefaults().valueForKey("userName")!)")
self.performSegueWithIdentifier("authenticated", sender: self)
}
}
})
}.resume()
}
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))
}
override func prefersStatusBarHidden() -> Bool {
return true
}
// MARK: - Keyboard Functions
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.view.endEditing(true)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
if textField == passwordField && usernameField.text != "" && serverNameField.text != ""{
loginButton(self)
}
return true
}
func ReEnableLogin(){
self.activityIndicator.hidesWhenStopped = true
self.activityIndicator.stopAnimating()
self.usernameField.enabled = true
self.passwordField.enabled = true
self.serverNameField.enabled = true
}
func DisplayAlert(title: String, message: String){
let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
self.ReEnableLogin()
}
func DisplayTouchIDQuestion(title: String, message: String){
let alertControllerQuestion = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertControllerQuestion.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.Default, handler: { (action:UIAlertAction) in
NSUserDefaults.standardUserDefaults().setValue(true, forKey: "useTouchID")
NSUserDefaults.standardUserDefaults().setValue(true, forKey: "touchIDPreferenceSet")
NSUserDefaults.standardUserDefaults().setValue(self.passwordField.text, forKey: "touchIDCachedCredential")
self.CheckUser()
}))
alertControllerQuestion.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.Default, handler: { (action:UIAlertAction) in
NSUserDefaults.standardUserDefaults().setValue(false, forKey: "useTouchID")
NSUserDefaults.standardUserDefaults().setValue(true, forKey: "touchIDPreferenceSet")
self.CheckUser()
}))
self.presentViewController(alertControllerQuestion, animated: true, completion: nil)
}
func CheckTouchIDCapable()-> Bool {
var error: NSError?
var touchEnabledDevice: Bool = false
if authContext.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error){
touchEnabledDevice = true
}
return touchEnabledDevice
}
func TouchIDCall(){
authContext.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: "Place your finger on the Home button to log into Collaboration User Tools", reply: { (wasSuccessful, error) in
if wasSuccessful{
print("\(NSDate()): Successful Biometric Authentication for user: \(NSUserDefaults.standardUserDefaults().valueForKey("userName")!)")
NSUserDefaults.standardUserDefaults().setValue(NSUserDefaults.standardUserDefaults().valueForKey("touchIDCachedCredential"), forKey: "password")
self.CheckUser()
}else{
print("\(NSDate()): Error: \(error!.code)")
print("\(NSDate()): Unsuccessful Biometric Authentication for user: \(NSUserDefaults.standardUserDefaults().valueForKey("userName")!)")
let qualityOfServiceClass = QOS_CLASS_USER_INTERACTIVE
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.activityIndicator.stopAnimating()
})
})
self.ReEnableLogin()
}
})
}
}
I've tried:
NSURLCache.sharedURLCache().removeAllCachedResponses()
userSession.invalidatedAndCancel()
The logout table view controller calls this method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
switch indexPath.row{
case 0:
myUser = User()
myExtensions.removeAll()
myDevices.removeAll()
NSUserDefaults.standardUserDefaults().setObject("", forKey: "password")
userName = ""
password = ""
NSURLCache.sharedURLCache().removeAllCachedResponses()
NSNotificationCenter.defaultCenter().postNotificationName("logoff", object: nil)
performSegueWithIdentifier("logout", sender: self)
default:
break
}
}
I don't know where the password is being cached. Any ideas?
I think your issue lies on part of your logic when handling your password. First, in your func CheckUser() you assign the value of var password if !logOutUser from your user defaults, however this property (password) is not getting cleared at any point, so if !logOutUser is false (which it looks like always is, as its not getting set anywhere), and your input fields are empty, then it will use the previous value that you had store in there.
So if I am correct, and the culprit here is that the password field is storing its data, a quick, dirty fix would be to simply clear these fields (username and password) after you perform your request. A better fix would be to directly pass the usernameField.text value directly and clear it after its done. (I'm unsure why you are storing your user's password in your app, which I would recommend reconsidering, if you need it for validating a session you should be using authentication tokens)
// set up the base64-encoded credentials
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
userPasswordString = NSString(format: "%#:%#", userName, password)
userName = ""
password = ""
I would also like to point out that you should be using optionals for these fields, you should be using optional unwrapping for your user defaults and also adding a validation mechanism so that you do not submit requests with empty fields, ideally your button would be disabled or you would alert your user of an invalid field.
Good Luck!
EDIT
Try this approach: rather than using a class property for the values of password username and server. Use method variables to set your values, this way these values will get disposed when the method terminates. (This is a bit of a shot in the dark as i dont have a compiler at hand so there might be some minor syntax errors). - note the method signature
func CheckUser(username: String, password: String, server: String){
let port = "8443"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let userPasswordString = NSString(format: "%#:%#", username, password)
let userPasswordData = userPasswordString.dataUsingEncoding(NSUTF8StringEncoding)
let base64EncodedCredential = userPasswordData!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
let authString = "Basic \(base64EncodedCredential)"
config.HTTPAdditionalHeaders?.removeAll()
config.HTTPAdditionalHeaders = ["Authorization" : authString]
config.timeoutIntervalForRequest = 10.0
// create the user request
let userUrlString = NSString(format: "https://%#:%#/webserver/user/%#", server, port, username)
let userUrl = NSURL(string: userUrlString as String)
userRequest.cachePolicy = .ReloadIgnoringLocalAndRemoteCacheData
userRequest.URL = userUrl!
userRequest.HTTPMethod = "GET"
userRequest.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
userSession = NSURLSession(configuration: config, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
//Send User Request to the server and populate labels with response.
let task = userSession.dataTaskWithRequest(userRequest) { (data, response, error) in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if error?.code != nil{
print("ERROR: \(error!.localizedDescription)")
self.DisplayAlert("Error", message: error!.localizedDescription)
}else{
_ = NSString (data: data!, encoding: NSUTF8StringEncoding)
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
let accessDenied = Bool(dataString?.rangeOfString("HTTP Status 403").location != NSNotFound)
let authFailure = Bool(dataString?.rangeOfString("HTTP Status 401").location != NSNotFound)
if (authFailure || accessDenied) {
print("\(NSDate()): Unsuccessful Password Authentication Attempt for user: \(NSUserDefaults.standardUserDefaults().valueForKey("userName")!)")
self.DisplayAlert("Access Denied", message: "Please Verify Your Credentials")
}else{
print("\(NSDate()): Successful Password Authentication for user: \(NSUserDefaults.standardUserDefaults().valueForKey("userName")!)")
self.performSegueWithIdentifier("authenticated", sender: self)
}
}
})
}.resume()
}
Then you can call your method as so:
if NSUserDefaults.standardUserDefaults().valueForKey("touchIDPreferenceSet") == nil && CheckTouchIDCapable() {
DisplayTouchIDQuestion("Use Touch ID?", message: "Would you like to use touch ID to login?")
}else{
usernameField.enabled = false
passwordField.enabled = false
serverNameField.enabled = false
activityIndicator.startAnimating()
CheckUser(usernameField.text, password: passwordField.text, server: serverNameField.text)
}
My problem was never related to password caching. Most likely due to my relatively amateur experience level, I never considered the fact that it might be COOKIES keeping the session up. That's exactly what it ended up being. I solved the situation simply by deleting all cookies during the logout procedure. I added the following to my logout code and it's working perfectly.
print("\(NSDate()): Logout Requested, Deleting Cookies")
let cookieStorage = NSHTTPCookieStorage.sharedHTTPCookieStorage()
let cookies = cookieStorage.cookies! as [NSHTTPCookie]
print("\(NSDate()): Cookie count: \(cookies.count)")
for cookie in cookies{
print("\(NSDate()): Deleting Cookie name: \(cookie.name) value: \(cookie.value)")
NSHTTPCookieStorage.sharedHTTPCookieStorage().deleteCookie(cookie)
}
Related
Hello I'm currently working on ActionExtension using Firebase in order to share information through firebase realtime database.
My problem is Whenever I declare "Database.database().reference()" Application doesn't let me open the extension.
I have a code and error log underneath
import UIKit
import MobileCoreServices
import Firebase
import FirebaseAuth
import FirebaseCore
import FirebaseDatabase
class TextToClipViewController: UIViewController {
#IBOutlet weak var TextToClip: UITextView!
var convertedString: String?
var uid: String?
var ref = Database.database().reference() //<-- this is problem
override func viewDidLoad() {
super.viewDidLoad()
let textItem = self.extensionContext!.inputItems[0] as! NSExtensionItem
let textItemProvider = textItem.attachments![0]
if textItemProvider.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
textItemProvider.loadItem(forTypeIdentifier: kUTTypeText as String,
options: nil,
completionHandler: {
(result, error) in
self.convertedString = result as? String
if self.convertedString != nil {
//self.convertedString = self.convertedString!.appending(uid)
DispatchQueue.main.async {
self.TextToClip.text = self.convertedString!
}
}
})
}
FirebaseApp.configure()
let user = Auth.auth().currentUser
let defaults = UserDefaults(suiteName: "group.JS.TossSync.share")
let email = defaults?.string(forKey: "email") ?? "nothing"
let password = defaults?.string(forKey: "password") ?? "nothing"
if let user = user {
uid = user.uid
if textItemProvider.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
textItemProvider.loadItem(forTypeIdentifier: kUTTypeText as String,
options: nil,
completionHandler: {
(result, error) in
self.convertedString = result as? String
if self.convertedString != nil {
//self.convertedString = self.convertedString!.appending(uid)
DispatchQueue.main.async {
self.TextToClip.text = self.convertedString!
}
}
})
}
}
else{
if(email == "nothing" && password == "nothing"){
TextToClip.text = "login to main app"
}
else{
Auth.auth().signIn(withEmail: email, password: password) { user, error in
if let error = error, user == nil {
let alert = UIAlertController(title: "Sign In Failed",
message: error.localizedDescription,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true, completion: nil)
}
else{}
}
}
}
}
#IBAction func done() {
let returnProvider =
NSItemProvider(item: convertedString as NSSecureCoding?,
typeIdentifier: kUTTypeText as String)
let returnItem = NSExtensionItem()
returnItem.attachments = [returnProvider]
self.extensionContext!.completeRequest(
returningItems: [returnItem], completionHandler: nil)
}
#IBAction func share(_ sender: Any) {
let pusher: [String : String] = ["content/10": convertedString!, "flag": "0"]
ref.child(uid!).child("clipboard").updateChildValues(pusher)
}
}
2020-04-18 01:12:51.284690+0900 TossSync_ForApple[8976:2834488] [lifecycle ] [u EFC68F17-B2CF-4CB0-8994-C50A12180DA3:m (null)] [JS.TossSync-Apple.Copy(1.0)] Connection to plugin interrupted while in use.
2020-04-18 01:12:51.285353+0900 TossSync_ForApple[8976:2834488] [lifecycle ] [u EFC68F17-B2CF-4CB0-8994-C50A12180DA3:m (null)] [JS.TossSync-Apple.Copy(1.0)] Connection to plugin invalidated while in use.
2020-04-18 01:12:51.425523+0900 TossSync_ForApple[8976:2834495] [assertion] Error acquiring assertion: <NSError: 0x283b3a580; domain: RBSAssertionErrorDomain; code: 2; reason: "Specified target process does not exist">
2020-04-18 01:12:51.484291+0900 TossSync_ForApple[8976:2834495] [lifecycle ] [u EFC68F17-B2CF-4CB0-8994-C50A12180DA3:m (null)] [JS.TossSync-Apple.Copy(1.0)] Connection to plugin interrupted while in use.
2020-04-18 01:12:51.484772+0900 TossSync_ForApple[8976:2834319] [ShareSheet] cancelled request - error: Extension cancelled by host.
2020-04-18 01:12:51.485174+0900 TossSync_ForApple[8976:2834319] [ShareSheet] Cannot connect to view controller in JS.TossSync-Apple.Copy - Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service on pid 8983 named JS.TossSync-Apple.Copy.viewservice was interrupted, but the message was sent over an additional proxy and therefore this proxy has become invalid." UserInfo={NSDebugDescription=The connection to service on pid 8983 named JS.TossSync-Apple.Copy.viewservice was interrupted, but the message was sent over an additional proxy and therefore this proxy has become invalid.} info {
NSDebugDescription = "The connection to service on pid 8983 named JS.TossSync-Apple.Copy.viewservice was interrupted, but the message was sent over an additional proxy and therefore this proxy has become invalid.";
}
As the title is saying, im working on a login. The tokenHandler is already working and im using a KeychainAccess.
Here my tokenHandler class:
import KeychainAccess
class TokenHandler {
func saveUsernameToKeyChain(username: String) {
do {
try keychain.set(username, key: "myUsername")
} catch let error {
print(error)
}
}
func getUsernameFromKeyChain() -> String? {
return keychain[string: "myUsername" ]
}
func saveUserPasswordToKeyChain(password: String) {
do {
try keychain.set(password, key: "UserPassword")
} catch let error {
print(error)
}
}
func getUserPasswordFromKeyChain() -> String? {
return keychain[string: "UserPassword"]
}
let keychain = Keychain(service: "com.mybackendpage")
func getTokenFromKeyChain() -> String? {
return keychain[string: "myToken"]
}
func saveTokenToKeyChain(token: String) {
do {
try keychain.set(token, key: "myToken")
}
catch let error {
print(error)
}
}
func saveRefreshTokenToKeyChain(refreshToken: String) {
do {
try keychain.set(refreshToken, key: "myRefreshToken")
}
catch let error {
print(error)
}
}
func loginToAPI(username: String, password: String) -> Any {
guard let url = URL(string: "https:mypage.com") else
{
return ""
}
let jsonData = try? JSONSerialization.data(withJSONObject: [
"email": username,
"password": password
])
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
// insert json data to the request
request.httpBody = jsonData
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else { print(error!.localizedDescription); return }
guard let data = data else { print("Empty data"); return }
if let str = String(data: data, encoding: .utf8) {
print(str)
}
}.resume()
return "TOKENSTRING"
}
}
And here my LoginVC class:
class LoginViewController: UIViewController {
let tokenHandler = TokenHandler()
#IBOutlet weak var usernameTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
let username = tokenHandler.getUsernameFromKeyChain()
let userPassword = tokenHandler.getUserPasswordFromKeyChain()
#IBAction func unwindToLogin(_ unwindSegue: UIStoryboardSegue) {
print("-unwinding success-")
}
// func for the login button:
#IBAction func loginButton(_ sender: UIButton) {
activityIndicator.startAnimating()
loginWithCredentials()
let token = tokenHandler.getTokenFromKeyChain()
if token != nil {
performSegue(withIdentifier: "segueToNavigation", sender: self)
} else if ( token == nil ){
// create the alert:
let alert = UIAlertController(title: "Wrong login data", message: "Please try again.", preferredStyle: UIAlertController.Style.alert)
// add an action to the button:
alert.addAction(UIAlertAction( title: "Ok", style: UIAlertAction.Style.default, handler: nil ))
// show the alert:
self.presentingViewController
print("-Token could not be created.-")
}
else {
// create the alert:
let alert = UIAlertController(title: "Wrong login data", message: "Please try again.", preferredStyle: UIAlertController.Style.alert)
// add an action to the button:
alert.addAction(UIAlertAction( title: "Ok", style: UIAlertAction.Style.default, handler: nil ))
// show the alert:
self.presentingViewController
print("-Token could not be created.-")
}
}
func loginWithCredentials() {
let username: String = usernameTextField.text!
let password: String = passwordTextField.text!
let authResponse = tokenHandler.loginToAPI(username: username, password: password)
}
}
Im still not skilled swift programmer, so I will be happy if any of you could give me some good advices. I was reading and trying to work with the delegate principle, but frankly, my guts are telling me, that this is not what I need.
I was reading about
PerformSegueWithIdentifier
but not really understand how to transform it into my code...
The segues which I have included storyboardwise are working, but unfortunately also, if the test user didn't do the login. So, im pressing the login button w/o any username and userpwd and im getting anyway to the next View. Not cool, so help me please :)
EDIT: I changed performSegue to shouldPerformSegue but im still getting access to the next View w/o any permission.
EDIT: Im getting:
-Token could not be created.-
{"message":"The given data was invalid.","errors":{"email":["The email field is required."],"password":["The password field is required."]}}
So the error is correct, but by pressing on the "Login" Button im still getting to the next View.
EDIT:
Ive tried a few changes, now I have for eg:
if tokenHandler.getTokenFromKeyChain() != nil
instead of
let token = tokenHandler.getTokenFromKeyChain()
if token != nil
Apparently, nothing what im doing in this IBAction for the LoginButton does anything different. What am I missing?
Well, it looks to me, that if you did once call saveTokenToKeyChain() ever since you've been running the app on your device/simulator, then the KeyChain will hold some string there, as I can't see where you set it to nil (I can't see where you set it at all, but let's suppose that you deleted the code that saves the token). So what your current logic does is that it performs the segue if you have some string saved as token (no matter if it's empty string, random string or an expired token). Whatever was left there, getTokenFromKeyChain() will return you a string so your if token != nil will always evaluate to true.
On the other hand, if you clear the KeyChain data, as I can't find any piece of code that saves the token, the UI will always say that the login fails, even if it actually succeeds.
So you should handle the login success/failure with properly writing/deleting the token to/from KeyChain.
I'm trying to allow a user to report a user (in a Tinder-like app). To report, this button takes the user to a new VC to elaborate the issue as an email.
What I'm missing:
How can I add the reporter's and reportee's Firebase unique ID to the email (or whatever form of communication)? (So then I can investigate and take action as needed)
Here's what I have:
The code to properly send an email...
func configureMailController() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self
mailComposerVC.setToRecipients(["RadiusAppHelp#gmail.com"])
mailComposerVC.setSubject("Reporting user")
mailComposerVC.setMessageBody("Please include as much detail as possible:", isHTML: false)
return mailComposerVC
}
func showMailError() {
let sendMailErrorAlert = showAlert(withTitle: "Could not send message", message: "Please try again")
let dismiss = UIAlertAction(title: "Okay", style: .default, handler: nil)
// sendMailErrorAlert.addAction(dismiss)
// self.present(sendMailErrorAlert, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}
The code to pull the other user's ID in the swipe view VC...
var otherUsersId = ""
var currentlyViewedUserId: String?
firebaseServer.fetchUsers {[weak self] (usersDict) in
self?.usersDict = usersDict
let fetchedUsers = Array(usersDict.values)
self?.filterBlockedUsers(from: fetchedUsers)
self?.loadFirstUser()
self?.cardView.reloadData()
}
func loadFirstUser() {
if users.count > 0 {
let imageView = UIImageView()
let storage = Storage.storage()
let storageRef = storage.reference(withPath:
"\(users[0].userId!)/photos/\(0)")
currentlyViewedUserId = users[0].userId
PhotoUploader.downloadImageUrl(from:
storageRef) { (url) in
guard let url = url else { return }
imageView.downloaded(from: url,
contentMode: .scaleAspectFill)
}
nameLbl.text = users[0].firstName
setupDetailsFor(user: users[0])
infoCollectionView.reloadData()
}
}
As well as the code to block a user (but blocking / reporting functions work independently).
Any help is greatly appreciated!
Okay, I found at least a temporary fix...
(Not using Firestore however, which I'll eventually need to implement — https://www.youtube.com/watch?v=Ofux_4c94FI)
In FirebaseFunctions.swift...
// Report Someone
func reportSomeone(with userId: String, completion:
#escaping (Error?) -> Void) {
let usersRef = db.child("users")
if let uid = Auth.auth().currentUser?.uid {
usersRef.child("\(uid)/report/\ .
(userId)").setValue(true) { (error, dbref) in
completion(error)
}
}
}
// Set Preferences for Reporting
func reportPreferences(with userId: String,
completion: #escaping (Error?) -> Void) {
let usersRef = db.child("users")
if let uid = Auth.auth().currentUser?.uid {
usersRef.child("\(uid)/preferences/\ .
(userId)").setValue(true) { (error, dbref) in
completion(error)
}
}
}
In User.swift...
var report: [String: Bool]? = [:]
func makeDictionary() -> [String: Any] {
print("")
return [
"report": report ?? [:]
]
static func makeObjectFrom(_ dictionary: [String:
Any]) -> LocalUser {
let report = dictionary["report"] as?
[String:Bool] ?? [:]
}
let localUser = LocalUser(report: report)
In the View Controller...
import Firebase
var currentUserId = Auth.auth().currentUser?.uid
var otherUsersId = ""
// Report Button
var isCurrentUserReported = false
var isOtherUserReported = false
var currentlyViewedUserId: String?
// FILTER REPORTED USERS
func filterReportUsers(from users: [LocalUser]) {
var notReportUsers = users
var notReportUsersDict = self.usersDict
var reportUsers = newUser.report ?? [:]
if let currentUserId =
Auth.auth().currentUser?.uid {
reportUsers[currentUserId] = true
}
for (userId, report) in reportUsers ?? [:] {
print("UserId...", userId)
print("UserHere...", usersDict[userId])
if let user = usersDict[userId] {
notReportUsersDict.removeValue(forKey:
userId)
}
}
let notReport = Array(notReportUsersDict.values)
self.users = notReport
}
This isn't perfect, but it works!
I have several UITextField where a user can insert phone numbers into it. When I click the send button, it sends an automated message to the numbers listed. Everything works well but what I want to do is that when I click on the Send Button, I want it to check if the UITextFields that has text in it has a + symbol in front of the phone number listed before connecting with my server to send the automated message. How do I go about sending an alert to the user if the phone number listed does not have a + symbol?
ViewController:
class ViewController: UIViewController {
#IBOutlet weak var scrollviewcontact: UIScrollView!
#IBOutlet weak var viewcontact: UIView!
#IBOutlet weak var phonenumber: UITextField!
#IBOutlet weak var phonenumber1: UITextField!
#IBOutlet weak var phonenumber2: UITextField!
#IBOutlet weak var phonenumber3: UITextField!
var currentTextField: UITextField?
private let contactPicker = CNContactPickerViewController()
override func viewDidLoad() {
super.viewDidLoad()
phonenumber.textContentType = .telephoneNumber
phonenumber1.textContentType = .telephoneNumber
phonenumber2.textContentType = .telephoneNumber
phonenumber3.textContentType = .telephoneNumber
}
#IBAction func sendbutton(_ sender: Any) {
var numArray: Array<Any>
numArray = [phonenumber.text!, phonenumber1.text!, phonenumber2.text!, phonenumber3.text!]
let Url = String(format: "//URL")
guard let serviceUrl = URL(string: Url) else { return }
var request = URLRequest(url: serviceUrl)
request.httpMethod = "POST"
request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
guard let httpBody = try? JSONSerialization.data(withJSONObject: numArray, options:[]) else {
return
}
request.httpBody = httpBody
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
print("json ", json)
} catch {
print(error)
}
}
}.resume()
}
extension ViewController: CNContactPickerDelegate {
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
let phoneNumberCount = contact.phoneNumbers.count
guard phoneNumberCount > 0 else {
dismiss(animated: true)
return
}
if phoneNumberCount == 1 {
setNumberFromContact(contactNumber: contact.phoneNumbers[0].value.stringValue)
}else{
let alertController = UIAlertController(title: "Select one of the numbers", message: nil, preferredStyle: .alert)
for i in 0...phoneNumberCount-1 {
let phoneAction = UIAlertAction(title: contact.phoneNumbers[i].value.stringValue, style: .default, handler: {
alert -> Void in
self.setNumberFromContact(contactNumber: contact.phoneNumbers[i].value.stringValue)
})
alertController.addAction(phoneAction)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: {
alert -> Void in
})
alertController.addAction(cancelAction)
dismiss(animated: true)
self.present(alertController, animated: true, completion: nil)
}
}
func setNumberFromContact(contactNumber: String) {
var contactNumber = contactNumber.replacingOccurrences(of: "-", with: "")
contactNumber = contactNumber.replacingOccurrences(of: "(", with: "")
contactNumber = contactNumber.replacingOccurrences(of: ")", with: "")
contactNumber = contactNumber.replacingOccurrences(of: " ", with: "")
currentTextField?.text = String(contactNumber)
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
}
}
The goal that I'm trying to achieve is that when the Send Button is clicked, it checks which UITextField has text in it, and if it doesn't have a + as a prefix, an alert message should pop up.
You can try
let numArray = [phonenumber.text!, phonenumber1.text!, phonenumber2.text!, phonenumber3.text!]
guard numArray.filter { $0.hasPrefix("+") }.count == numArray.count else {
// alert with message make sure all textfields has + prefix
return
}
You can use the hasPrefix method. For example:
for num in numArray {
if num.hasPrefix("+") {
// do something
} else {
// do something else
}
}
I have a main View Controller than has a "Sign In" button if a user is not logged in. I then have another view controller "Login View Controller" that checks if user exists and then logs them in. Once a user has logged in via the login View Controller. I want the text of my Sign In Button on my main view controller to change to their email that they used to login. I am using a struct to try and accomplish this. My struct email variable seems to work within my Login View Controller but then does not change the string value of my button on the main view controller. Any help would be greatly appreciated.
LoginViewController.swift
class LoginViewController: NSViewController {
#IBOutlet weak var userEmailTextField: NSTextField!
struct GloablUserEmail {
static var user_status = Bool()
static var user_email = String()
}
#IBAction func LoginPushed(_ sender: AnyObject) {
GloablUserEmail.user_email = userEmailTextField.stringValue
let requestURL: NSURL = NSURL(string: "myurl")!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(url: requestURL as URL)
urlRequest.httpMethod = "POST"
let postString = "user_email=\(GloablUserEmail.user_email)&user_password=\(user_password)"
urlRequest.httpBody = postString.data(using: String.Encoding.utf8);
let session = URLSession.shared
let task = session.dataTask(with: urlRequest as URLRequest) {
(data, response, error) -> Void in
let httpResponse = response as! HTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
do{
let json = try JSONSerialization.jsonObject(with: data!, options:.mutableContainers) as? NSDictionary
if let parseJSON = json {
let resultValue = parseJSON["Status"] as? String
print("result: \(resultValue)")
self.isUserRegistered = false
if(resultValue=="Success"){
GloablUserEmail.user_status = true;
UserDefaults.standard.set(true, forKey: "isUserLoggedIn");
UserDefaults.standard.synchronize();
}
}
}catch{
print("Error parsing the JSON: \(error)")
}
}
}
task.resume()
}
}
Once the result is successful and the user is verified to exist then I assign the global user_status to true.
MainViewController.swift
class ViewController: NSViewController {
#IBOutlet weak var SignInButton: NSButton!
override func viewDidLoad() {
super.viewDidLoad()
if(LoginViewController.GloablUserEmail.user_status == false){
SignInButton.title = "Sign In"
}else if(LoginViewController.GloablUserEmail.user_status == true){
SignInButton.title = LoginViewController.GloablUserEmail.user_email
}
}
}
You can user a simple UserDefaults extension:
extension UserDefaults{
var isLoggedIn: Bool{
return bool(forKey: "loggedIn")
}
func set(loggedIn value: Bool){
set(value, forKey: "loggedIn")
synchronize()
}
}
To set login status:
let defaults = UserDefaults.standard
defaults.set(loggedIn: true)
And check its value:
let defaults = UserDefaults.standard
defaults.isLoggedIn