How to check if email already exist before creating an account (Swift) - swift

I know different variations of this question have been asked. However I seem to keep running into the same issue every time.
I want to check if an email already exist before the user pushes onto the next view. I will enter an email that exist in the database and the performSegue func is always called and pushes the user as if that email does not exist.
The only way I can check officially is when the user reaches the final sign up VC and the Auth.auth().createUser(withEmail: email as! String, password: password as! String ) { (user, error) in code will check for all errors.
However for good user experience I would hate for the user to have to click back three times to change the email address. Here is the code I have for the enter email view controller.
// Check if email is already taken
Auth.auth().fetchSignInMethods(forEmail: emailTextField.text!, completion: { (forEmail, error) in
// stop activity indicator
self.nextButton.setTitle("Continue", for: .normal)
self.activityIndicator.stopAnimating()
if let error = error {
print("Email Error: \(error.localizedDescription)")
print(error._code)
self.handleError(error)
return
} else {
print("Email is good")
self.performSegue(withIdentifier: "goToCreateUsernameVC", sender: self)
}
})
First off am I even entering the create property in the forEmail section? I added emailTextField.text because its the only way I know how even get the email the user typed. Does anyone know a better way I can do this?

How I create user accounts
This is an example of what I use. When a user provides credentials, FirebaseAuth checks if these credentials can be used to make a user account. The function returns two values, a boolean indicating whether the creation was successful, and an optional error, which is returned when the creation is unsuccessful. If the boolean returns true, we simply push to the next view controller. Otherwise, we present the error.
func createUserAcct(completion: #escaping (Bool, Error?) -> Void) {
//Try to create an account with the given credentials
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordConfirmTextField.text!) { (user, error) in
if error == nil {
//If the account is created without an error, then we will make a ProfileChangeRequest, i.e. update the user's photo and display name.
if let firebaseUser = Auth.auth().currentUser {
let changeRequest = firebaseUser.createProfileChangeRequest()
//If you have a URL for FirebaseStorage where the user has uploaded a profile picture, you'll pass the url here
changeRequest.photoURL = URL(string: "nil")
changeRequest.displayName = self.nameTextField.text!
changeRequest.commitChanges { error in
if let error = error {
// An error happened.
completion(false, error)
} else {
//If the change is committed successfully, then I create an object from the credentials. I store this object both on the FirebaseDatabase (so it is accessible by other users) and in my user defaults (so that the user doesn't have to remotely grab their own info
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
//Store the object as data in my user defaults
let data = NSKeyedArchiver.archivedData(withRootObject: userData)
UserDefaults.standard.set(data, forKey: "UserData")
UserDefaults.standard.set([Data](), forKey: "UserPhotos")
completion(true, nil)
}
}
}
} else {
// An error happened.
completion(false, error)
}
}
}
Here is an example of how I would use it. We can use the success boolean returned to determine if we should push to the next view controller, or present an error alert to the user.
createUserAcct { success, error in
//Handle the success
if success {
//Instantiate nextViewController
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let nextVC = storyboard.instantiateViewController(withIdentifier: "NextVC") as! NextViewController
//Push typeSelectVC
self.navigationController!.pushViewController(viewController: nextVC, animated: true, completion: {
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
})
} else {
//We now handle the error
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
//Create a UIAlertController with the error received as the message (ex. "A user with this email already exists.")
let alertController = UIAlertController(title: "Error", message: error!.localizedDescription, style: .alert)
let ok = UIAlertAction(title: "OK", style: .cancel, action: nil)
//Present the UIAlertController
alertController.addAction(ok)
self.present(alertController, animated: true, completion: nil)
}
}
Let me know if this all makes sense, I know there is a lot to it. I'm just considering things you'll maybe find you need done anyways that you may not be aware of (like making change requests, or storing a data object on FirebaseDatabase).
Now for checking if the email is already taken:
Remember when I said that I post a user object to FirebaseDatabase upon account creation? Well we can query for the given email to see if it already exists. If it doesn't we continue with the flow as normal, without having actually created the account. Otherwise, we simply tell the user to pick another email address.
Pushing a user object to your database (taken from the above code):
if let firebaseUser = Auth.auth().currentUser {
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
}
And now querying to see if somebody already has that email:
func checkIfEmailExists(email: String, completion: #escaping (Bool) -> Void ) {
Database.database().reference().child("Users").queryOrdered(byChild: "email").queryEqual(toValue: email).observeSingleEvent(of: .value, with: {(snapshot: DataSnapshot) in
if let result = snapshot.value as? [String:[String:Any]] {
completion(true)
} else {
completion(false)
}
}
}
Then we can call this like so:
checkIfEmailExists(email: emailTextField.text!, completion: {(exists) in
if exists {
//Present error that the email is already used
} else {
//Segue to next view controller
}
})

Related

How to Sign out and Delete Users Register By FirebaseUI Swift 5

I want to give my app the ability to Log out/delete the user and when I type this code
#IBAction func deleteTheAccountButtonHasBeenTapped(_ sender: Any) {
let user = Auth.auth().currentUser
var credential: AuthCredential
user?.reauthenticateAndRetrieveData(with: credential, completion: {(authResult, error) in
if let error = error {
// An error happened.
print(error)
}else{
//user re-authenticated
user?.delete { error in
if let error = error {
// An error happened.
print(error)
} else {
// Account deleted.
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
self.present(vc, animated:true, completion:nil)
}
}
}
})
}
I got this error:
Variable 'credential' used before being initialized
any one can help me?
The error message is pretty explicit: you're using credential before you initialized it.
In order to delete the user, you need to first reauthenticate them, as shown in the documentation on reauthenticating the user. Your version doesn't implement this comment from that code:
// Prompt the user to re-provide their sign-in credentials
How to reauthenticate the user depends on the provider. For example for email/password, you'd get them with:
credential = EmailAuthProvider.credential(withEmail: email, password: password)
A handy place to find similar snippets for other providers is in the documentation on linking accounts.

Firebase Functions in swift does not return anything if there is no Internet

There is a function that is triggered after the AccountKit authorization, it calls the Firebase Function to validate the token on Facebook and returns a userId if everything is confirmed, and registers the user if he has not yet been registered.
It works fine when Internet is available, but while offline - Firebase function does not return or throw any errors or at least nil results, and I would like it to return an error such as No internet connection or ANYTHING that could be catched.
Digging web and APIReference brought no results. Does the call of firebase function really not return anything in such cases (offline)?
func checkUserCredentials(phoneNumber: String, FBId: String, Token: String) {
functions.httpsCallable("checkUserCredentials").call(["phone":"\(phoneNumber)", "FBId":"\(FBId)", "Token":"\(Token)"])
{ (result, error) in
if let error = error as NSError?
{
if error.domain == FunctionsErrorDomain
{
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
}
}
if let userDoc = (result?.data as? [String: Any])?["userID"] as? String
{
DispatchQueue.main.async(execute: { self.performSegue(withIdentifier: "StartTheApp", sender: self) })
}
} }
I recommend checking for a network connection before making any network request. That way you're not dependent on the vagaries of whichever library you're using to talk to the network.
I use Reachability to check for a network connection before performing any requests (which I then perform using Alamofire). Below is a sample function to check for network:
import Reachability
...
func networkIsReachable(shouldShowAlert: Bool) -> Bool {
if let reachability: Reachability = Reachability(), reachability.connection != .none {
return true
}
if shouldShowAlert {
let alertController = UIAlertController(title: "Error", message: "No internet connection.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
present(alertController, animated: true, completion: nil)
}
return false
}
Since I'm using this function all throughout my codebase, I even moved it into an extension so as not to violate DRY principle.
Updating your code to use this function would look like this:
func checkUserCredentials(phoneNumber: String, FBId: String, Token: String) {
guard let networkIsReachable(shouldShowAlert: true) else {
// network is not reachable, and user has been shown an error message
return
}
// now perform network request
// ...
}

Swift Firebase Check if user exists

What am i doing wrong? I have a database structure like the one shown in this image.
In appleDelegate.swift i just want to check if a certain user token actually exists under the "users" node. that is, if "users" has the child currentUserID (a string token). I understand observeSingleEvent is executed asynchronously.I get this error in swift: 'Application windows are expected to have a root view controller at the end of application launch'. in "func application(_ application: UIApplication" i have this code. I also have my completion handler function below.
if let user = Auth.auth().currentUser{
let currentUserID = user.uid
ifUserIsMember(userId:currentUserID){(exist)->() in
if exist == true{
print("user is member")
self.window?.rootViewController = CustomTabBarController()
} else {
self.window?.rootViewController = UINavigationController(rootViewController: LoginController())
}
}
return true
} else {
self.window?.rootViewController = UINavigationController(rootViewController: LoginController())
return true
}
}
func ifUserIsMember(userId:String,completionHandler:#escaping((_ exists : Bool)->Void)){
print("ifUserIsMember")
let ref = Database.database().reference()
ref.child("users").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild(userId) {
print("user exists")
completionHandler(true)
} else {
print("user doesn't exist")
completionHandler(false)
}
})
}
I would suggest moving the code out of the app delegate and into an initial viewController. From there establish if this is an existing user and send the user to the appropriate UI.
.observeSingleEvent loads all of the nodes at a given location - one use would be to iterate over them to populate a datasource. If there were 10,000 users they would all be loaded in if you observe the /users node.
In this case it's really not necessary. It would be better to just observe the single node you are interested in and if it exists, send the user to a UI for existing users.
here's the code to do that
if let user = Auth.auth().currentUser {
let ref = self.ref.child("users").child(user.uid)
ref.observeSingleEvent(of: .value, with: { snapshot in
self.presentUserViewController(existing: snapshot.exists() )
})
}
snapshot.exists will be either true if the user node exists or false if not so the function presentUserViewController would accept a bool to then set up the UI depending on the user type.

AWS Mobile Hub states user is not signed-in after custom UI User Pool sign-in/authentication

I'm currently using AWS Mobile Hub for an iOS application that utilizes Cognito and Cloud Logic.
I decided to replace the default AuthUIViewController because I didn't like how it looked. I used this sample project to help me implement sign up through User Pools: https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift .
Here is my implementation:
Starting in my AppDelegate, I set the UserPool I want to sign into to a commonly accessible constant variable. One idea I have to why AWSMobileClient doesn't think my user is signed in is because it defines its own service configuration/pool, but I'm not sure:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
AWSDDLog.add(AWSDDTTYLogger.sharedInstance)
AWSDDLog.sharedInstance.logLevel = .verbose
// setup service configuration
let serviceConfiguration = AWSServiceConfiguration(region: Constants.AWS.CognitoIdentityUserPoolRegion, credentialsProvider: nil)
// create pool configuration
let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: Constants.AWS.CognitoIdentityUserPoolAppClientId,
clientSecret: Constants.AWS.CognitoIdentityUserPoolAppClientSecret,
poolId: Constants.AWS.CognitoIdentityUserPoolId)
// initialize user pool client
AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)
// fetch the user pool client we initialized in above step
Constants.AWS.pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
return AWSMobileClient.sharedInstance().interceptApplication(
application, didFinishLaunchingWithOptions:
launchOptions)
}
After the AppDelegate is finished, the application goes to its root view controller named InitialViewController. Here, I allow the user to click a facebook sign in or regular (user pool) sign in.
class InitialViewController:UIViewController {
#objc func regLogin() {
//Set a shared constants variable "user" to the current user
if (Constants.AWS.user == nil) {
Constants.AWS.user = Constants.AWS.pool?.currentUser()
}
Constants.AWS.pool?.delegate = self
//This function calls the delegate function startPasswordAuthentication() in the extension below to initiate login
Constants.AWS.user?.getDetails().continueOnSuccessWith { (task) -> AnyObject? in
DispatchQueue.main.async(execute: {
//called after details for user are successfully retrieved after login
print(AWSSignInManager.sharedInstance().isLoggedIn)// false
print(AWSSignInManager.init().isLoggedIn)// false
print(AWSCognitoUserPoolsSignInProvider.init().isLoggedIn())// false
print(Constants.AWS.user?.isSignedIn) // true
AppDelegate.del().signIn()
})
return nil
}
}
}
extension InitialViewController: AWSCognitoIdentityInteractiveAuthenticationDelegate {
func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
self.present(loginVC, animated: true, completion: nil)
return self.loginVC
}
}
As you can see, the functions do their job and the user is successfully logged in according to (Constants.AWS.user?.isSignedIn) as well as the fact that I am successfully able to retrieve the user details. However, when I ask AWSSignInManager or the UserPoolsSignInProvider whether my user is logged in, it returns false. This is a problem because without AWSMobileHub seeing my user as logged in, I cannot access my Cloud Logic functions etc.
Can someone please help me shed light on how I can notify MobileHub and the sign in manager that my user is logged into the user pool so that my application can work right?
Thank You!
Jonathan's answer is a good starting point for this, but it requires to include AWSAuthUI to your project.
A better solution is to implement directly the functions in AWSUserPoolsUIOperations.m. In particular, the function triggered when pressing the sign in button, should look like this:
#IBAction func signInPressed(_ sender: AnyObject) {
if (self.usernameTextField.text != nil && self.passwordTextField.text != nil) {
self.userName = self.usernameTextField.text!
self.password = self.passwordTextField.text!
AWSCognitoUserPoolsSignInProvider.sharedInstance().setInteractiveAuthDelegate(self)
AWSSignInManager.sharedInstance().login(
signInProviderKey: AWSCognitoUserPoolsSignInProvider.sharedInstance().identityProviderName,
completionHandler: { (provider: Any?, error: Error?) in
print(AWSSignInManager.sharedInstance().isLoggedIn)
})
} else {
let alertController = UIAlertController(title: "Missing information",
message: "Please enter a valid user name and password",
preferredStyle: .alert)
let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
alertController.addAction(retryAction)
}
}
Then include the following functions as an extension of your SignIn view controller:
public func handleUserPoolSignInFlowStart() {
let authDetails = AWSCognitoIdentityPasswordAuthenticationDetails(username: self.userName!, password: self.password!)
self.passwordAuthenticationCompletion?.set(result: authDetails)
}
public func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
return self
}
public func getDetails(_ authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>) {
self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource
}
public func didCompleteStepWithError(_ error: Error?) {
DispatchQueue.main.async {
if let error = error as NSError? {
let alertController = UIAlertController(title: error.userInfo["__type"] as? String,
message: error.userInfo["message"] as? String,
preferredStyle: .alert)
let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
alertController.addAction(retryAction)
self.present(alertController, animated: true, completion: nil)
} else {
self.usernameTextField.text = nil
self.dismiss(animated: true, completion: nil)
}
}
}
After digging more through the AWS code, I found my answer in the pod AWSAuthUI in AWSSignInViewController.m (The view controller used in the default authentication process for mobile hub/cognito).
The code is:
- (void)handleUserPoolSignIn {
Class awsUserPoolsUIOperations = NSClassFromString(USERPOOLS_UI_OPERATIONS);
AWSUserPoolsUIOperations *userPoolsOperations = [[awsUserPoolsUIOperations alloc] initWithAuthUIConfiguration:self.config];
[userPoolsOperations loginWithUserName:[self.tableDelegate getValueForCell:self.userNameRow forTableView:self.tableView]
password:[self.tableDelegate getValueForCell:self.passwordRow forTableView:self.tableView]
navigationController:self.navigationController
completionHandler:self.completionHandler];
}
and getting to just the parts that matter... in Swift!!
userPoolsOperations.login(withUserName: "foo", password: "bar", navigationController: self.navigationController!, completionHandler: { (provider: Any?, error: Error?) in
print(AWSSignInManager.sharedInstance().isLoggedIn) // true
print(AWSSignInManager.init().isLoggedIn) // false
print(AWSCognitoUserPoolsSignInProvider.init().isLoggedIn()) // false
print(Constants.AWS.user?.isSignedIn) // nil
})
}
lesson learned: Reading through AWS's code is helpful even though it sucks

Swift Firebase Login Registration auth

So when I first the page up I've kept it simple 2 text fields (email & password) 1 lbl 2 buttons (login register). Initially when I started out testing it everything worked when there was a problem an alert would pop up and tell you the problem, or you would be segue to next page based on button press and auth being complete.
But now every time I open the app to run code deeper inside no matter what I type I can sign in with the wrong password, or sign in with a previous email address even though previously these would both present errors. I have rebuilt this page numerous times over the week and decided to also add a get photo function from the users profile page.
Whenever I run the app and press a button I get this in the console:
Warning: Attempt to present on whose view is not in the window hierarchy!
I figure this is the problem because there is an error with the registration but the user is allowed to continue regardless. I've looked it up googled it read all the documentation I can find and even messed about trying and failing to put in a task structure.
PS I moved away from error label to use alerts hoping they would force a stop in the code
#IBAction func registerBtn(_ sender: Any) {
if emailTextField.text! == "" || passwordTextField.text == "" {
displayAlert(title: "Error", message: "Please Enter Your EMail Address & Choose A Password")
} else {
if let email = emailTextField.text {
if let password = passwordTextField.text {
//Create User
Auth.auth().createUser(withEmail: email, password: password, completion: { (user, error) in
// Auth.auth().createUserAndRetrieveData(withEmail: email, password: password, completion: { (user, error) in
if error != nil {
self.displayAlert(title: "Error", message: error!.localizedDescription)
} else {
print("Registration Successful")
self.performSegue(withIdentifier: "registrationSegue", sender: nil)
}
self.login()
})
}
}
}
}
if error != nil {
// show your alert
}
You don’t need an else statement on authenticating your users. so delete that line
Also, if you’re presenting a view controller that’s supposed to be accessible by authenticated users.
Please try:
let HomePage = HomePageController() // Change this to your viewcontroller name
self.present(HomePage, animated: true, completion: nil)
swift 4 - Xcode 9.1
func signUP(){
Auth.auth().createUser(withEmail: emailIdTextField.text!, password: passworTextField.text!) { (user, error) in
print(user?.email)
}
}
func signIn(){
Auth.auth().signIn(withEmail: emailTextField.text!, password: passwordTextField.text!) { (user, error) in
print(user?.uid)
}
}
Swift 4.2
//creating user with email password authentication
Auth.auth().createUser(withEmail: emailTextfield.text!, password: passwordTextfield.text!) { (user, error) in
// completion handler
if error != nil {
print(error!)
} else {
//success
print("Registration Sucessful")
self.performSegue(withIdentifier: "goToChat", sender: self)
}
}