I want to create a system where the user can't sign up if the username is already taken by others. However, this seems to not work as even though the username is taken, the sign up process is still completed.
I am doing this by retrieving every username from Firebase and running it through it see if the username entered by the user is already used. If it is, it should stop the sign up process and tell user to enter another username.
Code:
let ref = Database.database().reference(fromURL: "https://heytest.firebaseio.com/")
let usernamesRef = ref.child("users")
usernamesRef.observeSingleEvent(of: .value, with: { (snapshot) in
let con = snapshot.value as! [String:[String:Any]]
var usernamesArray = [String]()
Array(con.keys).forEach {
if let res = con[$0] , let username = res["username"] as? String {
usernamesArray.append(username)
}
}
for storedUsername in usernamesArray {
if storedUsername == self.usernameTextField.text! {
self.usernameVerified = false
self.usernameLabel.textColor = UIColor.red
self.usernameLabel.text = "USERNAME TAKEN"
self.usernameTextField.layer.addBorder(edge: UIRectEdge.bottom, color: UIColor.red, thickness: 1.5)
return
}
}
}) { (error) in
print(error.localizedDescription)
}
print("TEST")
self.usernameVerified = true
print(self.usernameVerified)
self.usernameTextField.layer.addBorder(edge: UIRectEdge.bottom, color: UIColor.black, thickness: 1.5)
self.usernameLabel.textColor = UIColor.black
self.usernameLabel.text = "USERNAME"
On sign in button clicked:
#IBAction func onSignUp(_ sender: Any) {
print("Sign Up pressed")
isValidUsername(username: usernameTextField.text!)
print("[SIGN UP] - Username: \(usernameVerified)")
isValidEmail(email: emailTextField.text!)
print("[SIGN UP] - Email: \(emailVerified)")
isValidPassword(password: passwordTextField.text!)
print("[SIGN UP] - Password: \(passwordVerified)")
if passwordVerified && emailVerified && usernameVerified {
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordTextField.text!) { (authResult, error) in
if error != nil {
self.errorLabel.alpha = 1
self.errorLabel.text = error?.localizedDescription
self.shake(viewToShake: self.errorLabel)
return
}
guard let user = authResult?.user else {
return
}
//Successfully Authenticated User
let ref = Database.database().reference(fromURL: "https://heytest.firebaseio.com/")
let usersReference = ref.child("users").child(user.uid)
let values = ["username": self.usernameTextField.text!, "email": self.emailTextField.text!, "games-played": "0"]
usersReference.updateChildValues(values, withCompletionBlock: { (err, ref) in
if err != nil {
print(err!)
return
}
//Successfully registered user's data to database
print("[SIGN UP] - Successfully Signed Up")
self.errorLabel.alpha = 0
self.present((self.storyboard?.instantiateViewController(withIdentifier: "TabBarViewController"))!, animated: false, completion: nil)
})
}
} else {
errorLabel.alpha = 1
shake(viewToShake: errorLabel)
print("Password/Email/Username verification not complete!")
print("[SIGN UP] - Password: \(passwordVerified)")
print("[SIGN UP] - Username: \(usernameVerified)")
print("[SIGN UP] - Email: \(emailVerified)")
}
}
I think it will be easier for you to see if any matches are thrown from firebase for a specific userName.
let reference = Database.database().reference()
reference.child("users").queryOrdered(byChild: "username").queryEqual(toValue: "yourNewUserName").observeSingleEvent(of: .value) { (snapshot) in
// if there is data in the snapshot reject the registration else allow it
}
Otherwise you are downloading way too much data to the client. If you grow on users it will also take forever to download.
I am doing this by retrieving every username from Firebase and running it through it see if the username entered by the user is already used.
IMHO this is not the right approach, the logic should be handled server side. Imagine if you have 1 million user this will never work.
Related
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!
I have the issue that when a user logs in the homepage thereafter doesn't load properly, but when I stop the simulator and run it again (the the user is still logged in from the previous run) the page displays fine. I am wondering if this problem lies in the simulator or my code.
#IBAction func signInPressed(_ sender: Any) {
if let email = emailField.text, let password = passwordField.text {
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
Auth.auth().signIn(withEmail: email, password: password) { (user, error) in
if let userID = user?.uid {
KeychainWrapper.standard.set((userID), forKey: "uid")
self.performSegue(withIdentifier: "tohome", sender: nil) }
if error != nil{
print("Incorrect")
let alert = UIAlertController(title: "Error", message: "Incorrect Email or Password.", preferredStyle: UIAlertController.Style.alert)
let action = UIAlertAction(title: "Ok", style: .default, handler: nil)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
else {
if let userID = user?.uid {
KeychainWrapper.standard.set((userID), forKey: "uid")
self.performSegue(withIdentifier: "tohome", sender: nil)
let databaseRef = Database.database().reference() databaseRef.child("people").child(userID).child("users").setValue(self.emailField.text!)
databaseRef.child("people").child(userID).child("postID").setValue(userID)
I would advice you to read the documents of signin in with email and password again.
There are lot of mistakes in the code. When you create the user it signs in automatically, you don't have to use the sign in method agin. Secondly you navigating to new screen before saving the data into database. When we have Users Defaults present why use KeychainWrapper?
First Create a model class so it will be easy for you to access the data and save data.
class User {
var userId: String?
var email : String?
var password: String?
// All the other information you want
init(dictionary:[String: AnyObject]){
userId = dictionary["userId"] as? String // "this will be your firebase node //make no typos here you can create a constant if you want"
email = dictionary["email"] as? String
password = dictionary["password"] as? String
// All the other database
}
}
now our model class is done it will be easy for us to save data
func createUserWithEmailAndPassword(){
if ( emailField.text != "" && passwordField.text != "" {
let email = emailField.text
let password = passwordField.text
Auth.auth().createUser(withEmail: email, password: password, completion: { (user, error) in
if error != nil {
// create error alert how you want.
return
}
let user = user?.user
let uid = user?.uid
let email = user?.email
let password = user?.password
// saving the user data locally
let userDefaults = UserDefaults
userDefaults.standard.set(uid, forKey: "user_uid")
userDefaults.standard.set(email, forKey: "user_email")
// etc etc
// after that we will be saving this user database to firebase database
// calling firebase save user function here which we will create below
self.registerUserToDatabase(uid: uid!, email: email!, password: password!)
}
}
func registerUserToDatabase(uid: String, email: String, password: String){
let userDb = Database.database().reference.child("people").child("users").child(uid)
// Make sure the key value in the below dictionary matches the User model class you var you made
let userDictionary = ["userId": uid, "email": email,"password": password] as [String: AnyObject]
userDb.updateChildValues(userDictionary) {(err ,userDb) in
if err != nil {
// alert view for error
}
let users = User(dictionary: userDictionary)
// Below we will navigate to to home screen or which ever screen you want
self.navigateToApp()
}
}
create a navigate to controller function
func navigateToApp(){
// performe seque method
}
// PS what is wrong with GitHub formatter
this is my first question here, i hope you can help me.
I've been trying to get a specific path for the current user logged into my app.
I don't really now what i'm doing wrong so i'll paste my code here.
func userRoleListener() {
guard let user = Auth.auth().currentUser else { return } .
Firestore.firestore().collection("users").document("sjbD5SvKTYHWBLhCqojs").getDocument { (snapshot, error) in
if let data = snapshot?.data() {
guard let isAdmin = data["isAdmin"] as? Bool else { return }
if isAdmin {
self.applyButton.isHidden = true
} else {
self.applyButton.isHidden = false
}
}
}
}
This is my function to create a user.
private func createUser() {
guard let nameAndLastname = nameAndLastnameTextField.text , let email = emailTextField.text , let password = passwordTextField.text , !nameAndLastname.isEmpty , !email.isEmpty , !password.isEmpty else {
simpleAlert(title: "Error", msg: "Debe completar todos los campos")
return
}
let newUserReference = Firestore.firestore().collection("users").document()
newUserReference.setData([
"nameAndLastname": nameAndLastname,
"email": email,
"password": password,
"isAdmin": false,
"timestamp": Timestamp()
])
}
And this is my login action:
#IBAction func signUpButtonPressed(_ sender: Any) {
guard let email = emailTextField.text , let password = passwordTextField.text , !email.isEmpty , !password.isEmpty else { return }
self.signUpButton.animateButton(shouldLoad: true, withMessage: "")
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
if let error = error {
self.signUpButton.animateButton(shouldLoad: false, withMessage: "¡REGISTRAR!")
debugPrint(error.localizedDescription)
self.simpleAlert(title: "Error", msg: "Error al iniciar sesión, intente nuevamente en unos minutos")
return
}
self.signUpButton.animateButton(shouldLoad: false, withMessage: "")
self.createUser()
self.presentClientStoryboard()
}
}
And this is an image of my database:
Database image
If you create a user with Firebase Authentication, Authentication will create a user and a user id for you in Firebase Auth (Not Firestore)
So when you create your user in Firestore, you have to set the userID as document id, like below:
#IBAction func signUpButtonPressed(_ sender: Any) {
guard let email = emailTextField.text , let password = passwordTextField.text , !email.isEmpty , !password.isEmpty else { return }
self.signUpButton.animateButton(shouldLoad: true, withMessage: "")
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
if let error = error {
self.signUpButton.animateButton(shouldLoad: false, withMessage: "¡REGISTRAR!")
debugPrint(error.localizedDescription)
self.simpleAlert(title: "Error", msg: "Error al iniciar sesión, intente nuevamente en unos minutos")
return
}
self.signUpButton.animateButton(shouldLoad: false, withMessage: "")
self.createUser(user.uid) // <----- user id from Firebase Auth
self.presentClientStoryboard()
}
}
and
private func createUser(userId: String) {
guard let nameAndLastname = nameAndLastnameTextField.text , let email = emailTextField.text , let password = passwordTextField.text , !nameAndLastname.isEmpty , !email.isEmpty , !password.isEmpty else {
simpleAlert(title: "Error", msg: "Debe completar todos los campos")
return
}
let newUserReference = Firestore.firestore().collection("users").document(userId) // <-- create a document, with the user id from Firebase Auth
newUserReference.setData([
"nameAndLastname": nameAndLastname,
"email": email,
"password": password,
"isAdmin": false,
"timestamp": Timestamp()
])
}
You should get the user id from the current user, try this:
func userRoleListener() {
guard let userUid = Auth.auth().currentUser.uid else { return } .
Firestore.firestore().collection("users").document(userUid).getDocument { (snapshot, error) in
if let data = snapshot?.data() {
guard let isAdmin = data["isAdmin"] as? Bool else { return }
if isAdmin {
// And I believe here the true and false values should be switched as you are checking if the user IS an admin, if they are an admin, shouldn't you show the button?
self.applyButton.isHidden = false
} else {
self.applyButton.isHidden = true
}
}
}
}
This get's the current user logged into the app and then uses the users uid to search the database. Just make sure when you create an account you save the user data accordingly.
Hey guys actually i am trying two things here:- trying to create a new account and trying to open a screen like which appears after login but it is showing "email already exist error".
#IBAction func CreateAcccountButton(_ sender: AnyObject) {
guard let eventInterest = textBox.text,let email = EmailTestfield.text, let password = PasswordTestfield.text, let name = UsernameTestfield.text else {
print("Form is not valid")
return
}
Auth.auth().createUser(withEmail: email, password: password, completion: { (user, error) in
if let error = error {
print(error)
return
}
guard let uid = user?.uid else {
return
}
//successfully authenticated user
let imageName = UUID().uuidString
let storageRef = Storage.storage().reference().child("profile_images").child("\(imageName).png")
if let uploadData = UIImagePNGRepresentation(self.Profilepicture.image!) {
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if let error = error {
print(error)
return
}
print (metadata)
// let downloadURL = metadata?.downloadURL()
// print("URL ", downloadURL)
if let Profilepictureurl = metadata?.downloadURL()?.absoluteString {
let values = ["name": name, "email": email,"EventInterest":eventInterest,"Password":password,"Profilepictureurl": Profilepictureurl ]
let user = User(dictionary: values as [String : AnyObject])
let customViewController = MessagesController()
customViewController.setupNavBarWithUser(user)
customViewController.fetchUserAndSetupNavBarTitle()
// customViewController.navigationItem.title = values["name"] as? String
self.dismiss(animated: true, completion: nil)
self.registeruserintoDb(uid,values: values as [String : AnyObject])
}
})
}
}
)
}
fileprivate func registeruserintoDb(_ uid: String, values: [String: AnyObject]) {
let ref = Database.database().reference()
let usersReference = ref.child("users").child(uid)
usersReference.updateChildValues(values, withCompletionBlock: { (err, ref) in
if err != nil {
print(err!)
return
}
})
}
It's exactly what the error says, you already have a user with that email. Instead, use the auth.signIn method and check for currently signed in users.
I have a Registration Class that has 3 textfields for a username, email and password. When the user hits the Sign Up button, a handleRegister() function is called. This function takes the three values from these textfields.text and sends them to my Firebase database under a child node, containing their user id like in the image below:
My problem is that I want to be able to UPDATE any 3 of these values (email, name, password) OUTSIDE of the registration class. How do I do achieve this? Thank you. Here registration my code:
func handleRegister() {
guard let email = emailTextField.text, let password = passwordTextField.text, let name = usernameTextField.text else {
return
}
FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user: FIRUser?, error) in
if error != nil {
return
}
guard let uid = user?.uid else {
return
}
//successfully registered user.
let imageName = NSUUID().uuidString
let storageRef = FIRStorage.storage().reference().child("profile_images").child("\(imageName).png")
if let uploadData = UIImagePNGRepresentation(self.profileImageView.image!) {
storageRef.put(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
return
}
if let profileImageUrl = metadata?.downloadURL()?.absoluteString {
let values = ["name": name, "email": email, "password": password, "profileImageUrl": profileImageUrl]
self.registerUserIntoDatabaseWithUID(uid: uid, values: values as [String : AnyObject])
}
})
}
})
}
private func registerUserIntoDatabaseWithUID(uid: String, values: [String: AnyObject]) {
let ref = FIRDatabase.database().reference()
let usersReference = ref.child("users").child(uid)
usersReference.updateChildValues(values, withCompletionBlock: { (err, ref) in
if err != nil {
return
}
print("Successfully saved user to database.")
self.dismiss(animated: true, completion: nil)
})
}
You have two options:
Option 1: You need to save your user's database ID somewhere so you can use it later on in the app for situations just like this. You can save the ID in your Userdefaults or somewhere else that's a bit more secure.
Option 2: You can retrieve the ID of the logged in user by using Auth.auth()?.currentUser?.uid
guard let uid = Auth.auth()?.currentUser?.uid else { return }
When you have this ID you can update the value in the database like you are doing in registerUserIntoDatabaseWithUID().
func updateEmailAddress(text: String) {
guard let uid = Auth.auth()?.currentUser?.uid else { return }
let userReference = Database.database().reference.child("users/(uid)")
let values = ["email": text]
// Update the "email" value in the database for the logged in user
userReference.updateChildValues(values, withCompletionBlock: { (error, ref) in
if error != nil {
print(error.localizedDescription)
return
}
print("Successfully saved user to database.")
self.dismiss(animated: true, completion: nil)
})
}
Nota Bene
If you have any questions regarding this answer please add a comment.
The difference between this and the other answer is that I am
indicating how you can update in multiple locations as requested.
You'd want to look at using the data fan out approach. It deals with writing the data at mulitple locations. Here is a quick code sample:
let key = ref.child("posts").childByAutoId().key
let post = ["uid": userID,
"author": username,
"title": title,
"body": body]
let childUpdates = ["/posts/\(key)": post,
"/user-posts/\(userID)/\(key)/": post]
ref.updateChildValues(childUpdates)
To read more about this approach see the documentation at:
https://firebase.google.com/docs/database/ios/read-and-write#update_specific_fields