how to handle an exception in change password? - swift

Function which change password in my app:
#IBAction func changePassword(_ sender: Any) {
let isMatched = NSPredicate(format:"SELF MATCHES %#", regexAllValidation).evaluate(with: newPasswordField.text)
if (isMatched == true){
let auth = Auth()
auth.changePassword(oldPassword: oldPasswordField.text!,newPassword: newPasswordField.text!)
displayAlert(title: "Correct password", message: "Password changed!")
}
}
Class Auth:
func changePassword(oldPassword: String, newPassword: String){
let user =
AppDelegate.defaultUserPool().currentUser()?.changePassword(oldPassword, proposedPassword: newPassword)
AppDelegate.defaultUserPool().currentUser()?.clearSession()
}
When I call this function with bad old password, I receive in console:
Response body:
{"__type":"NotAuthorizedException","message":"Incorrect username or password."}
"x-amzn-errormessage" = "Incorrect username or password.";
"x-amzn-errortype" = "NotAuthorizedException:";
I would like to display an alert informing the user of a wrong password.
how do it?

Not sure which framework you are using, but if you are using swift and cognito, then I recommend the amplify framework, more specifically AWSMobileClient.
It is filled with common functionalities and heavily documented with common scenario like what you mentioned.
Link: https://aws-amplify.github.io/docs/sdk/ios/authentication
In short, you should have completion closure where you could handle the different scenarios and display alert if needed. For example, using AWSMobileClient you can
AWSMobileClient.default().confirmForgotPassword(username: "my_username", newPassword: "MyNewPassword123!!", confirmationCode: "ConfirmationCode") { (forgotPasswordResult, error) in
if let forgotPasswordResult = forgotPasswordResult {
switch(forgotPasswordResult.forgotPasswordState) {
case .done:
print("Password changed successfully")
default:
print("Error: Could not change password.")
}
} else if let error = error {
print("Error occurred: \(error.localizedDescription)")
}
}

Related

Firebase Re-authentication error: Contextual closure type '(AuthDataResult?, Error?) -> Void' expects 2 arguments, but 1 was used in closure body

So, I tried to add a delete account function using Firebase Manage User service here, it's showing me error. Below is the code.
#IBAction func deleteAccount() {
let user = Auth.auth().currentUser
var credential: AuthCredential
user?.reauthenticate(with: credential) { error in
if let error = error {
self.redAlert(message: "Unable to authenticate. Please try again.")
} else {
Auth.auth().currentUser?.delete { error in
if let error = error {
self.redAlert(message: "An error happened. Please contact support to delete your account")
} else {
self.greenAlert(message: "Your account has been deleted")
}
}
}
}
Attached the screenshot as well.
It keeps showing "Contextual closure type '(AuthDataResult?, Error?) -> Void' expects 2 arguments, but 1 was used in closure body" which I really don't know how to fix it. Would you mind helping to pinpoint this?
Screenshot
EDIT: I actually found the solution. The right code should be:
#IBAction func deleteAccount(_send: Any) {
let user = Auth.auth().currentUser
let emailText: String = email.text!
let passwordText: String = password.text!
var credential: AuthCredential = EmailAuthProvider.credential(withEmail: emailText, password: passwordText)
user?.reauthenticate(with: credential, completion: { (result, error) in
if let error = error {
self.redAlert(message: "Unable to authenticate. Please try again.")
} else {
Auth.auth().currentUser?.delete { error in
if let error = error {
self.redAlert(message: "An error happened. Please contact support to delete your account")
} else {
self.greenAlert(message: "Your account has been deleted")
self.stopLoading()
self.logout()
}
}
}
})
}
Hope it helps anyone that is struggling!

SwiftUI: Check if Firebase RealtimeDatabase has a specific Child the register the value or return error

I am currently building an app with an account system.
Firebase is very new to me, that's why I watched a lot of tutorials, and now its working fine.
I want to implement that the user can choose a unique username at the registration. My problem is, I really don't know how to check if this name is already taken.
I found some code for that, but that's not working, I will show you the code for the RegistrationService file.
I hope someone can explain to me how to implement this username verification. It should return an error if the username is already taken and do continue the registration if its a valid username.
Thank you!
import Combine
import Firebase
import FirebaseDatabase
import Foundation
enum RegistrationKeys: String {
case firstName
case lastname
case info
case username
}
protocol RegisterService {
func register(with details: RegistrationDetails) -> AnyPublisher<Void, Error>
}
final class RegisterServiceImpl: RegisterService {
func register(with details: RegistrationDetails) -> AnyPublisher<Void, Error> {
Deferred {
Future { promise in
Auth.auth()
.createUser(
withEmail: details.email,
password: details.password
) { res, error in
if let err = error {
promise(.failure(err))
} else {
// Success on User creation
if let uid = res?.user.uid {
let values =
[
RegistrationKeys.firstName.rawValue: details.firstName,
RegistrationKeys.lastname.rawValue: details.lastName,
RegistrationKeys.info.rawValue: details.info,
] as [String: Any]
let db = Database.database(url: "theurl")
Database.database(url: "the url")
.reference()
.child("usernames")
.child("\([RegistrationKeys.info.rawValue: details.username] as [String : Any])")
// here should be the check and then continue if its valid
db
.reference()
.child("users")
.child(uid)
.updateChildValues(values) { error, ref in
if let err = error {
promise(.failure(err))
} else {
promise(.success(()))
}
}
} else {
promise(.failure(NSError(domain: "Invalid user ID", code: 0, userInfo: nil)))
}
}
}
}
}
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
}
I can see two possibilities to solve your problem:
If the e-mail can serve as the username
Firebase authentication already sends back an error message in case the e-mail (the one used when creating the user) already exists. If the e-mail passed in the following function is not unique, an error will be thrown:
Auth.auth()
.createUser(
withEmail: details.email,
password: details.password
) { res, error in
if let err = error {
promise(.failure(err))
If an additional username besides the e-mail is required
If you need usernames in addition to the e-mails, you can store them under a node "usernames", like we see in your example. Personally, I would hash them instead of storing them plain.
The structure could simply be:
{
usernames: {
username_1: true,
username_2: true,
...
username_n: true
}
}
The example below checks to see if a new username exists and stores the result in the variable isUsernameTaken:
let db = Database.database(url: "the url").reference()
let newUsername = "seeIfItIsTaken"
db.child("usernames").child(newUsername).getData() { error, snapshot in
guard error == nil else {
print("Found error \(error)")
return
}
let isUsernameTaken = snapshot.exists()
}

How to get auth error from sign in/sign up AWS Amplify?

I am currently trying to get what type of error is thrown when a user attempts to sign in/ registers but my switch case is not working because I don't know what enum I am supposed to compare to. Once the error can be determined the app displays a UIAlertController explaining the error. If I print(err.code) I just get an Int back. Can anyone steer me in the right direction? I couldn't find any docs on how to handle it.
func signIn(username: String, password: String) {
Amplify.Auth.signIn(username: username, password: password) { result in
switch result {
case .success:
print("Sign in succeeded")
//Go to root vc
case .failure(let error):
print("Sign in failed \(error)")
if let err = error as NSError?{
switch err.code {
case AWSCognitoIdentityProviderErrorType.unknown.rawValue:
self.presentAlert(errorTitle: "Unkown Error", errorMessage: "An unknown error has occured", buttonText: "Ok")
print("Unkown error")
case AWSCognitoIdentityProviderErrorType.invalidPassword.rawValue:
self.presentAlert(errorTitle: "Invalid Password", errorMessage: "You have entered an invalid password", buttonText: "Try Again")
case AWSCognitoIdentityProviderErrorType.tooManyFailedAttempts.rawValue:
self.presentAlert(errorTitle: "Excedded login trys", errorMessage: "You attempted to login too many times", buttonText: "Try Again Later")
case AWSCognitoIdentityProviderErrorType.userNotFound.rawValue:
self.presentAlert(errorTitle: "Unknown Credentials", errorMessage: "No user exists with the credentials you entered.", buttonText: "Try Again")
default:
break
}
}
}
}
}
The type of error thrown by the Amplify Authentication library is an AuthError type, which contains a more helpful embedded AWSCognitoAuthError type. The AWS documentation is confusing and misleading because AWS provides two SDKs at this time: the AWS Mobile SDK and the newer, dumber Amplify Libraries.
It looks like you are using the Amplify Libraries. If you break your code in the Xcode debugger on the .failure case, the debugger variable display shows error as Amplify.AuthError which requires a PhD in AWS to make sense of. But for purposes of illumination, if you slug this in under the .failure case
case .failure(let error):
if let actualError = error.underlyingError as NSError? {
print("Cast to nserror:", actualError)
}
you will get Cast to nserror: AWSCognitoAuthPlugin.AWSCognitoAuthError.xxx where the xxx might be usernameExists or some other useful error. It turns out AWSCognitoAuthError is a straightforward enum which you can peruse by typing AWSCognitoAuthError at some random spot in your source, then right-clicking and choosing Jump to Definition in the Xcode popup menu.
I prefer to keep all the voodoo Amplify knowledge isolated, so I created a User class to interact with AWS Authentication services. I include the error handling portions here in hopes it saves you some grief.
// At the top of the file, outside the class:
import Amplify
import AWSCognitoAuthPlugin
import AWSPluginsCore
import Foundation
// This typealias allows other modules to interpret error responses
// from the AWSCognitoAuthError enum, such as ".usernameExists" without
// needing to import Amplify everywhere.
typealias AWSError = AWSCognitoAuthError
// Amplify Auth methods return AuthError type errors, which are complex multilayer
// enums that require complex deciphering if you want to extract a simple error
// to guide the user.
//
// This custom Error type allows callers from other modules to receive a simple
// explanatory "message" string, which is pulled from the .errorDescription
// property of an Amplify AuthError. The "error" optional is the returned
// .underlyingError property cast to an AWSCognitoAuthError type, which is an
// enum of all the possible problems interacting with Amplify Auth such as
// .usernameExists, .userNotConfirmed, .codeMismatch, or .codeExpired.
struct UserError: Error {
let message: String
let error: AWSError?
init(message: String?, error: Error?) {
self.message = message ?? "Unknown error"
self.error = error as? AWSError
}
}
// Example of usage, within the class. In my case the class signIn()
// function includes an onCompletion callback parameter to allow the
// caller to inform the user and change the UI according to the results.
func signIn(username: String, password: String,
onCompletion: #escaping (Result<Bool, UserError>) -> Void) {
_ = Amplify.Auth.signIn(username: username, password: password) { result in
switch result {
case .success(let signInResult):
onCompletion(.success(signInResult.isSignedIn))
case .failure(let authError):
let awsError = authError.underlyingError as? AWSError ?? AWSError.userNotFound
let userError = UserError(message: authError.errorDescription,
error: awsError)
onCompletion(.failure(userError))
}
}
}
In your view controller, here is how you might process the response from the example signIn() User class method above:
// Inside e.g. an #IBAction function
User.sharedUser.signIn(email, password: password, onCompletion: { result in
switch result {
case .success:
// Go to root View Controller
case .failure(let userError):
if let authError = userError.error {
if (authError == .userNotConfirmed) {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "ShowConfirm", sender: nil)
}
return
}
}
let alert = UIAlertController(title: userError.message, message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
DispatchQueue.main.async {
self.present(alert, animated: true, completion: nil)
}
}
})

How to check if email already exists in Firestore db

I have implemented Auth method for the Firestore database, but when the user tries to register with the same email, the app crash. I'd like to implement a function to check if the email already exists (if it does, fire UIAlert, otherwise if it doesn't, create a new user).
I have so far:
Auth.auth().createUser(withEmail: email, password: password) { (Result, err) in
let db = Firestore.firestore()
let docRef = db.collection("email users").document("email")
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let emailAlreadyInUseAlert = UIAlertController(title: "Error", message: "Email already registered", preferredStyle: .alert)
emailAlreadyInUseAlert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(emailAlreadyInUseAlert, animated: true, completion: nil)
return
} else {
let db = Firestore.firestore()
db.collection("email users").addDocument(data: [
"firstName": firstName,
"lastName": lastName,
"email": email,
"created": Timestamp(date: Date()),
"uid": Result!.user.uid
])
}
self.transitionToHome()
}
}
}
}
func transitionToHome() {
let homeViewController = storyboard?.instantiateViewController(identifier: "HomeViewController") as? HomeViewController
view.window?.rootViewController = homeViewController
view.window?.makeKeyAndVisible()
}
}
At this code the UIAlert doesn't fire, and have an error at : "uid": Result!.user.uid - Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value. When creating new user with unique email, it works as it should, the user is created.
If I change if let document = document, document.exists to if error !=nil, I get the UIAlert when the email already exist and also if it doesnt exist, the code of creating user doesnt execute.
Even tried to implement addsnapshotlistener, no luck.
Any suggestions? Thank you
Regarding error: it's a common practice to return success code as error code instead of setting error to nil, and google docs seems mention it as well.
The other issue is because you are forcibly unwrapping items that can legitimately be nil.
Instead, use guard to isolate any invalid cases and exit:
guard error == nil || case FirestoreErrorCode.OK = error else {
// got error; process it and
return
}
guard let result = result else {
// got no error, but no result either
// fail and
return
}
//if you are here, it means you've got no error and `result` is not nil.
Also notice that result should not be capitalized in callback:
Auth.auth().createUser(withEmail: email, password: password) { (result, err) in ...
You may not need a custom function to check if an email already exists as that's a default error Firebase Auth will catch and allow you to handle when a user is created.
For example, this code will catch situations where the user is attempting to use an email that already exists.
func createUser() {
let email = "test#thing.com"
Auth.auth().createUser(withEmail: email, password: "password", completion: { authResult, error in
if let x = error {
let err = x as NSError
switch err.code {
case AuthErrorCode.wrongPassword.rawValue:
print("wrong password")
case AuthErrorCode.invalidEmail.rawValue:
print("invalid email")
case AuthErrorCode.accountExistsWithDifferentCredential.rawValue:
print("accountExistsWithDifferentCredential")
case AuthErrorCode.emailAlreadyInUse.rawValue:
print("email already in use")
default:
print("unknown error: \(err.localizedDescription)")
}
return
}
let x = authResult?.user.uid
print("successfully created user: \(x)")
})
}
There's a number of Authentication Error Codes so you can handle a wide variety of errors without any special error handling.
And the AuthErrorCode API has some more useful information which is demonstrated in the answer code.

Verification code always invalid using Swift Parse and Twilio

Im using SMS verification to verify users. My problem is that when I enter a code to verify I get invalid code. I can't for the life of me figure out why.
Calling cloud code function:
#IBAction func verifyCodeButtonTapped(sender: AnyObject) {
var verificationCode: String = verificationCodeTextField.text!
let textFieldText = verificationCodeTextField.text ?? ""
if verificationCode.utf16.count != 4 {
displayAlert("Error", message: "You must entert the 4 digit verification code sent yo your phone")
} else {
let params = ["verifyPhoneNumber" : textFieldText]
PFCloud.callFunctionInBackground("verifyPhoneNumber", withParameters: params, block: { (object: AnyObject?, error) -> Void in
if error == nil {
self.performSegueWithIdentifier("showVerifyCodeView", sender: self)
} else {
self.displayAlert("Sorry", message: "We couldnt verify you. Please check that you enterd the correct 4 digit code sent to your phone")
}
})
}
}
Cloud code to verify code:
Parse.Cloud.define("verifyPhoneNumber", function(request, response) {
var user = Parse.User.current();
var verificationCode = user.get("phoneVerificationCode");
if (verificationCode == request.params.phoneVerificationCode) {
user.set("phoneNumber", request.params.phoneNumber);
user.save();
response.success("Success");
} else {
response.error("Invalid verification code.");
}
});
Twilio developer evangelist here.
In the Parse code, you are expecting request.params.phoneVerificationCode but when you call the cloud function from iOS you let params = ["verifyPhoneNumber" : textFieldText].
So, either change that line to
let params = ["phoneVerificationCode" : textFieldText]
so that it matches the cloud code. Or change your cloud code to
if (verificationCode == request.params.verifyPhoneNumber) {
so that it matches the iOS code.