Swift 5 & Firebase: Creating a shared sign in and sign-out button - swift

I thought I'd return to StackOverflow with another question because you guys helped me significantly with my last issue
Anyway, I currently have my authentication system setup so that the sign in and signup button are shared. I am looking to have firebase reference storage when an email is entered to have it checked against other accounts in the database. As of right now, a user can enter an email address for their account and then if they enter the wrong password it just sends them right to the sign up even though they currently have an account. This is a serious problem as it will cause confusion
I want it to work like so:
If the email address is taken, I want an alert to be displayed for the user says "Incorrect password"
If the email address is not taken, I want it to tell the user that they need to enter a password with at least 10 characters, 1 number, and 1 special character, which I have already figured out using
I only want it to segue to create a new user if the email is not taken and the password and email field meet the criteria fields that I have already set within my code. I just need help preventing it from switching to the create new user VC if the email is already taken, and I need to to say
func isValidPassword(_ email: String) -> Bool {
let emailRegEx = "##$%^&+=^.*(?=.{10,})(?=.*d)(?=.*[a-z])(?=.*[A-Z])(?=.*[##$%^&+=]).*$"
let emailPred = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailPred.evaluate(with: email)
}
Anyway, here is the code so far attached to the IBAction
if let email = emailField.text, let password = passwordField.text {
Auth.auth().signIn(withEmail: email, password: password, completion:
{ (user,error) in
if error == nil {
if let user = user {
self.userUid = user.user.uid
self.goToFeedVC()
}
} else {
self.goToCreateUserVC()
Here is a picture of the interface
I want it to be intuitive but I have been unable to code this myself so if anyone is able to help advise me on how to finish this block it would be incredibly appreciated

Firebase gives pretty detailed error responses for their Auth call:
So you can check to see what the error is inside of your call:
Below are the two error that they give (I only added the two scenarios that you mentioned)
Description: The password is invalid or the user does not have a password.
FIRAuthErrorUserInfoNameKey: ERROR_WRONG_PASSWORD
&
There is no user record corresponding to this identifier. The user may have been deleted.
FIRAuthErrorUserInfoNameKey: ERROR_USER_NOT_FOUND
Auth.auth().signIn(withEmail: email, password: password, completion:
{ (user,error) in
if error == nil {
if let user = user {
self.userUid = user.user.uid
self.goToFeedVC()
}
} else {
guard let error = error?.localizedDescription else { return } // but actually handle this
print(error)
if error == wrong password {
// show alert for email taken/wrong password
} else if error == user doesnt exists {
// self.goToCreateUserVC()
}
}
}
Just replace the if and else if conditions with the actual errors. I'd avoid comparing the strings and use the key/code in case the strings change in the future.
Official list of error codes can be found here
And if you print the full error instead of the error?.localizedDescription you'll get the full details, as can be seen here:
Optional(Error Domain=FIRAuthErrorDomain Code=17011 "There is no user record corresponding to this identifier. The user may have been deleted." UserInfo={NSLocalizedDescription=There is no user record corresponding to this identifier. The user may have been deleted., FIRAuthErrorUserInfoNameKey=ERROR_USER_NOT_FOUND})

Related

How can you add a user phone number in the same child as email?

Use Case: App build around email (with three childs of importance here). Now I just want cell phone login users to have the same Childs.
So something like this below must not produce an error(it currently does). I can do everything else with phone login (even create a user with phone number), but I want him to have these Childs too, for when the user uses phone and not email.
Auth.auth().createUser(withEmail: ResultString, password: remainingPart) { (user, error) in
let databaseRef = Database.database().reference()
guard error == nil else { return }
guard let user = user else { return }
let userObject =
[
"users": ResultString,
"postID": user.user.uid,
"e2": remainingPart,
] as [String: Any]
databaseRef.child("people").child(user.user.uid).setValue(userObject)
print("YESSSSS")
}
It seems that you're trying to pass a phone number to createUser(withEmail:,password:). Since a phone number is not a valid email address, the API rejects it.
To sign a user in with their phone number, follow the documentation for phone number sign-in.
After singing the user in with their phone number, you can still write their details to the database, same as you're doing now.

Firebase sendEmailVerification prior to createUser

Is there a way in Firebase to verify a user's email prior to adding the user to the database?
My ultimate goal is to create a SignUp form where the user first enters the email address, then presses a "Verify Email address" button. At this point the verification email will be sent and the user will follow the link from within to confirm the email address. Now, when going back to the app, the user will have a continue button, and if the the email is not verified, the user will not be able to register, if contrary, the user will be able to register.
How do I go about this?
So far all the documentation says that I must createUser so then I can use currentUser.sendEmailVerification, but obviously, I do not want create a user, before verification.
I also thought about using a completion block, but I am not sure how to work that out, because the registration would have to be postponed until the user presses the continue button.
Thanks
Auth.auth().createUser(withEmail: email, password: password, completion: {(user: User?, error) in
if error != nil {
print(error!)
return
}
guard let uid = user?.uid else {
return
}
let ref = Database.database().reference(fromURL: "https://project/")
let values = [
"email": email,
"userName": userName,
"name": name,
"birthDate": birthDate,
"phoneNumber": phoneNumber]
let userReference = ref.child("users").child(uid)
userReference.updateChildValues(values, withCompletionBlock: {(err, ref) in
if err != nil{
print(err!)
return
}
print("Successfully added user to database")
})
let usedUserReference = ref.child("users-Used").child(userName)
usedUserReference.setValue(uid, withCompletionBlock: {(err, ref) in
if err != nil{
print(err!)
return
}
print("Successfully added user to cannot be used-again list")
})
})
You can't do this with Firebase Auth. The user needs to be created first, because that record is the "container" that gives the email verification the appropriate context about which user has actually been verified when they respond to the email.
In your app, you can check to see if a particular user account has been verified by using the emailVerified property. This allows you to give the end user different experience with respect to their verification.

swift 3 store code on the cloud

I just started working with servers in swift and I'm using parse server hosted by AWS to store a database of users when they create an account for my app. I have this function called sign up that is linked to a sign up button which works fine and properly stores user's info into my parse database.:
#IBAction func signUp(_ sender: AnyObject) {
//I first check to see if the users left any of the fields blank
if firstName.text == "" || lastName.text == "" || email.text == "" || userName.text == "" || password.text == "" {
createAlert(title: "Error in form", message: "Please fill in all text fields")
//If everything is filled in
}else{
let user = PFUser()
user.username = userName.text
user["firstname"] = firstName.text
user["lastname"] = lastName.text
user.email = email.text
user.password = password.text
user.signUpInBackground(block: { (success, error) in
if error != nil {
var displayErrorMessage = "Please try again later."
if let errorMessage = (error! as NSError).userInfo["error"] as? String {
displayErrorMessage = errorMessage
}
self.createAlert(title: "Signup error", message: displayErrorMessage)
}else{
print("User signed up")
}
})
}
}
Now i need help storing this code on the cloud.
The reason why I ask this is because I had previously posted a question a little different on stack overflow: Deleting PFUser objects from database in swift
And in the response, I was told to store this exact function in cloud code (rather than locally) and I don't know how to do that.
Can anyone show me how it's done?
Thanks a lot in advance!
I'm assuming what it means when you want to "store your code in the cloud" is that all personal info (name, email, password) should be handled from the backend, not that you actually store the code in the cloud. You should handle sensitive personal data from the server-side and not the client-side. This involves server-side encryption and handling requests with SSL exchanges. This is beyond the scope of my expertise and this question, and I highly encourage that you do more research before you try to tackle this problem in your app. Here are some links I found:
http://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html
https://www.raywenderlich.com/92667/securing-ios-data-keychain-touch-id-1password
What they told you is to implement that code in the server, the server should be the one to process the function you ask for in the past question, this one, I think you have already done it because you said the info is stored successfully, but what the answer above this says is that to implement that code in an actual app you should investigate more about security and encryption to protect users information.

compare textfield.text to firebase string swift

I have a database in Firebase that will have individual user nodes. In each user's node will be data pertaining to them and will be private. In addition to that I want to create a node that is JUST a collection of registered emails. The reason is when a user is on the Sign In VC and the user types an email in..If the email is already registered an image view will turn green. However, if the email is not in the database (or if it doesn't match email address format) the image will be red.
A previous answer on my previous question(s) illustrated that I need to change the '.' to a ',' in email addresses. So #gmail.com would be stored as gmail,com
I have that down.
FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user, error) in
if error == nil {
let email = firstContainerTextField.text ?? ""
let newString = email.replacingOccurrences(of: ".", with: ",", options: .literal, range: nil)
self.ref.child("users").child("allUsers").child(newString).setValue(true)
self.ref.child("users").child((user?.uid)!).setValue(["Email": email])
FIRAuth.auth()!.signIn(withEmail: email,
password: password)
} else {
//registration failure
}
This is the code from the New User VC (partial).
So the node that says "users" and "allUsers" looks like this on Firebase Console
users
allUsers
bob#bob,com: true
ted#ted,com: true
The 'true' part was just so I could get the bob#bob,com onto the database...the true part will never be used for anything.
On the log in VC I honestly cannot figure out what to do
A previous answer said to use
hasChildren()
And I used that and then googled what to do with that
and I tried using something like this
ref.child("users").child("allUsers").queryEqual(toValue: newString)
.observe(.value, with: { snapshot in
if snapshot.hasChildren() {
for child in snapshot.children.allObjects as! [FIRDataSnapshot] {
....
}
});
But I just cannot seem to get anywhere with it.
How can I simply see if a textfield.text == an email already stored in firebase?
(I did convert the '.' to ',' when comparing)
Please don't use email addresses as keys. Email addresses are dynamic and may change (as in if the users wants to change it) and if they do, you'll have a mess on your hands as every node that directly used that email would have be deleted and re-created.
Best practice is to disassociate key's from the data they contain.
Here's the structure to use
emails
-Yiaisjpa90is
email: "dude#test.com"
-Yijs9a9js09a
email: "thing#test.com"
then you simply query the email node for the email you are looking for, and handle accordingly if it exists.
And some code
emailsRef.queryOrdered(byChild: "email").queryEqual(toValue: "dude#test.com")
.observe(.value, with: { snapshot in
if snapshot.value is NSNull {
print("the snapshot was null, no email found")
} else {
print("email was found, YIPEE")
}
})
For completeness it would be a little more Swifty to use
if snapshot.exists() {
print("found it")
} else {
print("no email found")
}

How to check if user needs to re-authenticate using Firebase Authentication

I am using Firebase to log in users into my app, but when I am adding the capability to manage their account like changing their email, password and so on. The documentation says that if the user have not recently signed in they need to re-authenticate, but my question is: How can I check if the user have signed in recently or not? According to the docs the error will return FIRAuthErrorCodeCredentialTooOld, but how can I check this?
Swift 3
I had to do this yesterday when trying to delete a user. One thing to note is FIRAuthErrorCodeCredentialTooOld is now FIRAuthErrorCode.errorCodeRequiresRecentLogin
What I did was trigger a UIView to ask for log in details if that error is thrown. Since I was using email and password, that's what I collected from the user in my example.
private func deleteUser() {
//get the current user
guard let currentUser = FIRAuth.auth()?.currentUser else { return }
currentUser.delete { (error) in
if error == nil {
//currentUser is deleted
} else {
//this gets the error code
guard let errorCode = FIRAuthErrorCode(rawValue: error!._code) else { return }
if errorCode == FIRAuthErrorCode.errorCodeRequiresRecentLogin {
//create UIView to get user login information
let loginView = [yourLoginUIViewController]
self.present(loginView, animated: true, completion: nil)
}
}
}
Once I had the login information I ran this function to reauthenticate the user. In my case I ran it the loginView in the above code if the login in was successful:
func reauthenticateUserWith(email: String, password: String) {
FIRAuth.auth()?.signIn(withEmail: email, password: password) { (user, error) in
if error == nil {
//display UIView to delete user again
let deleteUserView = deleteUserView()
present(deleteUserView, animated: true, completion: nil)
} else {
//handle error
print(error!.localizedDescription)
}
}
}
The deleteUserView in my case calls deleteUser() on a button tap from the user. You can also use UIAlertController in place of the custom UIViews, but that's up to you.
Hope this helps.
Update for current Swift 5
let user = Auth.auth().currentUser
user?.delete { error in
if let error = error {
let authErr = AuthErrorCode(rawValue: error.code)
if authErr == .requiresRecentLogin {
// reauthenticate
}
// other error
} else {
// delete success
}
}
According to the documents, there is currently no way to check FIRAuthErrorCodeCredentialTooOld other than going through the deleting of the account or the other sensitive cases mentioned.
If you are like me and ended up here because you are trying to figure out how to handle removing someone from Auth and removing other user data in Cloud Firestore, Realtime Database, and/or Cloud Storage, then there is a better solution.
Check out the Delete User Data Extension from Firebase to handle this. In short, when a user profile is deleted from Auth, you can use this also to delete data associated with the uid from those other Firebase data storage tools.