How to change email address in Firebase? - swift

I have some problems with changing email address in firebase authentication.
My code looks like this now:
func changeEmail(withEmail email: String, completion: #escaping ((Bool) -> Void)) {
guard let currentUser = Auth.auth().currentUser, let email = mail else { return }
currentUser.updateEmail(to: email) { [weak self]
error in
guard let self = self else { return }
let title: String
let message: String
if let error = error {
title = "alert.error.title".localized()
message = error.localizedDescription
} else {
title = email
message = "auth.confirm.email.popup".localized()
currentUser.sendEmailVerification()
}
self.navigator.showAlert(title: title,
message: message,
bottomLeftTitle: "general.got.it".localized(),
bottomLeftHandler: { completion(error == nil)
})
}
}
So it is okey, and working, and user can actually change email.
But problem occurs when user stayed too long and needs to re-login. Everyone knows that it is disturbing user experience in app.
Auth.auth().reload() //not working in this situation.
So how to change email, without asking user to logout and login again?

There is a reauthenticate method exactly for this purpose.
https://firebase.google.com/docs/auth/ios/manage-users#re-authenticate_a_user
What you need to do is ask the user for its login credentials again. No logout - login needed.
Possible code for that:
if (self.newPassword == self.newPasswordConfirm) && (!(self.newPassword.isEmpty) || !(self.newUserName.isEmpty)) {
reauthenticate(email: self.accountEmail, password: self.oldPassword) { isSucceeded in
//Successfully authenticated
if isSucceeded == true {
if !self.newUserName.isEmpty {
// update username
}
Auth.auth().currentUser?.updatePassword(to: self.newPassword) { (error) in
// Alert user that it didn't work
}
self.editProfile.toggle()
}
// Failed to reauthenticate
else if isSucceeded == false {
// Alert User
}
}
}

Related

Facebook Auth "An account already exists with the same email address but different sign-in credentials."

I have multiple login options for the user to choose from: email, google, and facebook. If Firebase already has the email stored as a user (ie. the user previously signed up with test#gmail.com), an alert that tells the user that an account with that email already exists. This works perfectly for Google sign in, as shown in this screenshot. However, nothing visually happens when the user clicks the Facebook button (doesn't even switch screens), and I get this error in the debugger:
Error Domain=FIRAuthErrorDomain Code=17012 "An account already exists with
the same email address but different sign-in credentials. Sign in using a
provider associated with this email address." UserInfo={FIRAuthErrorUserInfoNameKey=ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL,
FIRAuthErrorUserInfoEmailKey=318junkjabr#gmail.com,
FIRAuthErrorUserInfoUpdatedCredentialKey=<FIROAuthCredential:
0x6000005f3200>, NSLocalizedDescription=An account already exists with the
same email address but different sign-in credentials. Sign in using a
provider associated with this email address.}
This is my code for the Log In View Controller:
override func viewDidLoad() {
super.viewDidLoad()
setUpFBButton()
setUpGoogleButton()
setUpEmailButton()
GIDSignIn.sharedInstance()?.presentingViewController = self
}
// MARK: - SIGN UP WITH GOOGLE
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let err = error {
print("Failed to log into Google: ", err)
return
}
print("Successfully logged into Google")
guard let authentication = user.authentication else { return }
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)
// sign user in with Firebase
Auth.auth().signIn(with: credential, completion: { (user, error) in
let firstName = user?.user.displayName
let email = user?.user.email
let lastName = ""
let uid = user?.user.uid
if let err = error {
print("Failed to create a Firebase User with Google account: ", err)
return
} else {
// Successfully logged in
print("Successfully logged into Firebase with Google email: ", email ?? "", "Now add user to Firestore if user is new.")
// check if user already exists
self.addUserToFirestore(firstName ?? "", lastName, email ?? "", uid ?? "", "Google")
}
})
}
fileprivate func setUpGoogleButton() {
Utilities.styleLightFilledButton(signInGoogleButton)
signInGoogleButton!.addTarget(self, action:
#selector(handleCustomGoogleSignIn), for: .touchUpInside)
GIDSignIn.sharedInstance()?.delegate = self
}
#objc func handleCustomGoogleSignIn() {
GIDSignIn.sharedInstance().signIn()
}
// MARK: - SIGN UP WITH FACEBOOK
// design the facebook button and assign #selector to facebook button actions
fileprivate func setUpFBButton() {
Utilities.styleHollowButton(signInFacebookButton)
signInFacebookButton.addTarget(self, action: #selector(handleCustomFBButton), for: .touchUpInside)
}
// handle the facebook button actions
#objc func handleCustomFBButton() {
LoginManager().logIn(permissions: ["email", "public_profile"], from: self) { (result, err) in
if err != nil {
print("Custom FB login failed:", err!)
return
}
self.getUserInfo()
}
}
// grab id, name, and email of user
func getUserInfo() {
print("Successfully logged in with facebook...")
GraphRequest(graphPath: "/me", parameters: ["fields": "id, name, email"]).start {
(connection, result, err) in
guard let Info = result as? [String: Any] else { return }
let name = Info["name"] as? String
let email = Info["email"] as? String
let uid = Info["id"] as? String
if err != nil {
print("Failed to start graph request:", err!)
return
}
print(result!)
self.signIntoFirebase(name ?? "", email ?? "", uid ?? "")
}
}
// connect the user to firebase
func signIntoFirebase(_ name:String, _ email:String, _ uid:String) {
let credential = FacebookAuthProvider.credential(withAccessToken: AccessToken.current!.tokenString)
Auth.auth().signIn(with: credential) { (user, error) in
if let err = error {
print(err)
return
} else {
print("Facebook user successfully authenticated with Firebase. Now run through Firestore.")
// check if user already exists. if user exists, go to chats screen. if it does not exist, create a new user and redirect to chat screen.
self.addUserToFirestore(name, "", email, uid, "Facebook")
}
}
}
func loginButtonDidLogOut(_ loginButton: FBLoginButton) {
print("Logged out of facebook")
}
// MARK: - Other functions
func addUserToFirestore(_ firstName:String, _ lastName:String, _ email:String, _ uid:String, _ signInMethod:String) {
let db = Firestore.firestore()
let docRef = db.collection("users").document(uid)
// check if user exists in firestore
docRef.getDocument { (document, error) in
if let document = document {
if document.exists {
let message = "Good news! You already have a Coal account that uses " + email + ".\nPlease sign in to your existing account. Then you will be able to link your " + signInMethod + " profile from your Account Settings page."
// user exists. send to chats screen.
print("User already exists. Document data: \(String(describing: document.data()))")
self.showError("You're already a member!", message)
} else {
// user does not exist. create a new user
print("Document does not exist. Create new user.")
docRef.setData(["firstname":firstName, "lastname":lastName, "email":email]) { err in
if err != nil {
// Show error message
print("Error saving user data to Firestore")
} else {
print("New user created in Firestore")
self.transitionToConvo()
}
}
}
}
}
}
func showError(_ title:String, _ message:String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
func transitionToConvo() {
let tabBarC = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController") as! TabBarController
tabBarC.modalPresentationStyle = .fullScreen
self.present(tabBarC, animated: true, completion: nil)
print("Switched to TabBarController")
}
func setUpEmailButton() {
Utilities.styleDarkFilledButton(signInEmailButton)
}
} // end
I think the reason why it's not getting an alert is because the Facebook uid doesn't match the email uid saved in Firestore (while Google uid does match). The way I'm getting the alert to show up is if the uid matches that in Firestore, so consequently, the alert doesn't show. Does anyone know how I can get the alert to show an how to not get this error?
I know my code is a bit messy, so please let me know if you need further explanation. Any help is appreciated!!
After hours of trying to figure out my problem, I decided to post a question... but almost immediately after, found the answer (i know right :/).
Since one account per email is already enabled in the project settings, Auth.auth().signIn automatically scans if a user exists. If a user exists, it'll return an error after if error != nil. Because it returned an error before my addUserToFirestore function was called (to check if the user exists and if not, add user to Firestore), the alert was never shown.
Now that we know what if error != nil means, we can just insert the alert there:
Auth.auth().signIn(with: credential) { (user, error) in
if error != nil {
let message = "Good news! You already have a Coal account that uses " + email + ".\nPlease sign in to your existing account. Then you will be able to link your Facebook profile from your Account Settings page."
// user exists. send to chats screen.
print("User already exists. Let user know.")
self.showError("You're already a member!", message)
return
}
I'm not sure why it worked for Google Auth, but it ended up that this is what worked for Facebook Auth.

Stop one type of user from logging into app firebase

I have two apps that share the same backend but want presented in the app space as two separate apps. I have one for the users and the other for admin of my company. Both apps have completely different functions but need to access info from the same database. I am storying my users in my firebase database like:
{
"Users" : {
"Admin" : {
"OStVNPELMvVlu9JIQ3UttsDMpJK2" : {
"Name" : "Dave",
},
"ZtfDN0gou8Qe6csrwcaKaVzgeUT2" : {
"Name" : "Matthew",
}
},
"People" : {
"ED2RLbhJJrhX4CTl4iVRUjo1VkM2" : {
"Name" : "Kathy",
},
"arBssUBJaHXyU6G7roWI6miWri22" : {
"Name" : "Kate",
}
}
}
}
I then have a function for logging them in that isn't working due to ambiguity but I think that it is a decent start to figuring out my problem. It looks like:
#IBAction func LogInButtonTapped(_ sender: Any) {
// TODO: Validate Text Fields
// Create cleaned versions of the text field
let email = EmailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = PasswordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
// Signing in the user
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
// Couldn't sign iner
self.ErrorLabel.text = error!.localizedDescription
self.ErrorLabel.alpha = 1
}
else {
if Auth.auth().currentUser?.uid == Database.database().reference().child("Users").child("Admin").child(Auth.auth().currentUser!.uid) {
self.performSegue(withIdentifier: "GoToMainTBC", sender: nil)
}
else {
self.ErrorLabel.alpha = 1
self.ErrorLabel.text = "No account found"
}
}
}
}
I really need to apps to be separate as I have spent months with both being separated and want to keep it that way. I also don't want to necessarily change my database because I have various functions set up based on the above JSON tree. There has to be a way to restrict the one type of users from accessing the one app. Thank you for all the help!
I was able to figure out the answer to my problem. One problem I was running into after figuring out how to verify if the user was admin, was when they clicked on forgot password. It would take them to another view controller and then when presented to the login view controller it would keep them logged in and give them access to the app. My following code was able to fix the problem while maintaining my database the way I wanted it:
#IBAction func LogInButtonTapped(_ sender: Any) {
// TODO: Validate Text Fields
// Create cleaned versions of the text field
let email = EmailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = PasswordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
// Signing in the user
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
// Couldn't sign iner
self.ErrorLabel.text = error!.localizedDescription
self.ErrorLabel.alpha = 1
}
else {
let UID = Auth.auth().currentUser!.uid as String
var currentUser = ""
Database.database().reference().child("Users").child("Admin").child(UID).observe(.value) { (snapshot) in
let user = UserSignIn(from: snapshot)
let dictionary = snapshot.value as? [String: Any]
user?.UID = dictionary?["UID"] as? String
currentUser.append((user?.UID) ?? "")
if UID == currentUser {
self.performSegue(withIdentifier: "GoToMainTBC", sender: nil)
}
else {
self.ErrorLabel.alpha = 1
self.ErrorLabel.text = "No account found"
do{
try Auth.auth().signOut()
} catch let logoutError {
print(logoutError)
}
}
}
}
}
}
I needed to also create a class that could download the UID with nil so it could be checked for my if else statement. The way I fixed the still logging in when reopened or transitioning back to the login view controller was by forcing the app to sign the user out on the device so it wouldn't recognize them.
Here is the class:
class UserSignIn: NSObject {
var UID: String?
init?(from snapshot: DataSnapshot) {
let dictionary = snapshot.value as? [String: Any]
self.UID = dictionary?["UID"] as? String
}
}
I had a great time figuring this out and I hope that it helps someone in the future!

AWS Cognito GetDetails() Method not calling

I'm using AWS Cognito for my app Sign In and Sign up. In my app first the user register with email and phone number. After that, I'm redirecting to Verification Screen(Here OTP is sending by Cognito) After Verifying the OTP user will create some stores and then enter into the Dashboard. In this flow, I want to get the User details Attribute from Cognito in Verification code success. I've implemented the getDetails() method to get the userAttributes in Verification code success but it is not calling. I need the userAttributes when the time of store creation. Any help appreciated.
Here is my code:
#IBAction func submitButtonAction(_ sender: GradientButton) {
let code = firstChar+secondChar+thirdChar+fourthChar+fifthChar+sixthChar
guard code.count == 6 else{
self.showAlert(message: ErrorMessages.kEnterValidOTP)
return
}
let currentUser = self.userPool?.getUser("xxxx#gmail.com")
currentUser?.confirmSignUp(code, forceAliasCreation: true).continueWith(block: { [weak self] (task) -> Any? in
guard let strongSelf = self else { return nil }
DispatchQueue.main.async {
if let error = task.error as NSError? {
if let message = error.userInfo["message"] as? String{
self?.showAlert(message: message, onOkAction: {
strongSelf.clearTextFieldData()
})
}else{
self?.showAlert(message: error.localizedDescription, onOkAction: {
strongSelf.clearTextFieldData()
})
}
}else{
print(task.result)
strongSelf.clearTextFieldData()
print(AWSUserDetails.shared.userPool.currentUser()?.username)
let user = AWSUserDetails.shared.userPool.currentUser()
//I've tried the above `user` and `currentUser`. But not working.
user?.getDetails().continueOnSuccessWith(block: { (task) -> Any? in
DispatchQueue.main.async {
if task.error == nil{
print(task.error)
}else{
print(task.result)
}
}
})
// strongSelf.performSegue(withIdentifier: SegueIdentifiers.createStoreSegue, sender: self)
}
}
return nil
})
}

Swift: Using credentials with Firebase and a additional textfield for username

I have a few things I'm trying to accomplish. First, my email and password don't matter if they're filled or not to login. I would like the app to check if email and password is filled and correct before logging in. Secondly, I would like to put in a username when they register so it would show up on the profile page and tell other users who they're without revealing an email address. I'm using Firebase and I thought this would do the trick, but it doesn't. I looked over this Stack overFlow Post and have everything correct I think, but its still letting you login without credentials.
#IBAction func loginRegisterBtnPressed(_ sender: AnyObject) {
performSegue(withIdentifier: "profileVC", sender: self)
if let email = emailTextField.text, let password = passwordTextField.text {
FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error ) in
if error == nil {
print("DAW: User Created")
} else {
FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user, error ) in
if error != nil {
print ("DAW: User failed to authenticate with Firebase")
} else {
print ("DAW: Successfully")
if let user = user {
self.completeSignIn(id: user.uid)
}
}
})
}
})
}
}
#IBAction func loginRegisterBtnPressed(_ sender: AnyObject) {
if let email = emailTextField.text, let password = passwordTextField.text {
if email != "" && password != ""{
FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error ) in
if error == nil {
performSegue(withIdentifier: "profileVC", sender: self)
} else {
FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user, error ) in
if error != nil {
print ("DAW: User failed to authenticate with Firebase")
} else {
print ("DAW: Successfully")
if let user = user {
self.completeSignIn(id: user.uid)
}
}
})
}
})
}
}
}

Get Facebook user details with swift and parse

i need to get the Facebook user information details when i login a new user through parse. at present i am logging in the new user below. can't seem to get the user details though. I've seen some code written on objective - c. most of the functions don't work anymore
The Facebook iOS sdks i am running is v4.3.0.
#IBAction func facebookButton(sender: AnyObject) {
PFFacebookUtils.logInInBackgroundWithReadPermissions(permissions) {
(user: PFUser?, error: NSError?) -> Void in
if let user = user {
if user.isNew {
println("User signed up and logged in through Facebook!")
} else {
println("User logged in through Facebook!")
}
} else {
println("Uh oh. The user cancelled the Facebook login.")
}
}
}
To get the user details you have to send a FBSDKGraphRequest after the login request.
This can be done inside the if let user = user {...} block.
// Create request for user's Facebook data
let request = FBSDKGraphRequest(graphPath:"me", parameters:nil)
// Send request to Facebook
request.startWithCompletionHandler {
(connection, result, error) in
if error != nil {
// Some error checking here
}
else if let userData = result as? [String:AnyObject] {
// Access user data
let username = userData["name"] as? String
// ....
}
}
For the new Facebook API and version of Swift 3.1 you can do something like this:
if((FBSDKAccessToken.current()) != nil) {
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id,name,first_name,last_name,email"]).start(completionHandler: {(connection, result, error) -> Void in
if(error != nil) {
print("Some error occurred.");
} else {
print(result!)
}
})
}