I have been trying to implement Chris’ answer here: Can I make Firebase use a username login process? for the Facebook login but I can’t seem to get my head around it.
So far I’ve tried to set conditions on the textField but as Firebase observer works asynchronously, the conditions to check if the username exists in the database won’t work.
let usernameString = usernameTextField.text
let uid = FIRAuth.auth()?.currentUser?.uid
ref.runTransactionBlock({ (currentData: FIRMutableData) -> FIRTransactionResult in
if var post = currentData.value as? [String : AnyObject], let uid = FIRAuth.auth()?.currentUser?.uid {
let usernamesDictionary = post["usernames"] as! NSDictionary
for (key, _) in usernamesDictionary {
if key as? String == usernameString {
print("username not available: \(key)")
}
else if usernameString == "" {
print("Uh oh! Looks like you haven't set a username yet.")
}
else if key as? String != usernameString {
print("username available: \(key)")
print("All set to go!")
let setValue: NSDictionary = [usernameString!: uid]
post["usernames"] = setValue
currentData.value = post
}
}
return FIRTransactionResult.successWithValue(currentData)
}
return FIRTransactionResult.successWithValue(currentData)
}
Then I tried creating /usernames/ node in the database and set up rules as:
{
"rules": {
"usernames": {
".read": "auth != null",
".write": "newData.val() === auth.uid && !data.exists()"
}
}
}
Now that won’t let me set any username to the database. I get confused in creating rules but my whole point is that I need a sign up flow with the username data that’s unique for each user in the database.
While trying every answer I found in related posts, what worked for me the easy way i.e. without making Firebase rules play a part in it or creating a separate usernames node in the database was to not put an if/else condition inside the Firebase observer but instead to use the exists() method of FIRDataSnapshot.
Now here’s the trick, while I did try only the exists() method with a simple observer but that did not help me. What I did was first query usernames in order, then match the username with queryEqualToValue to filter the query:
refUsers.queryOrderedByChild("username").queryEqualToValue(usernameString).observeSingleEventOfType(.Value , withBlock: {
snapshot in
if !snapshot.exists() {
if usernameString == "" {
self.signupErrorAlert("Uh oh!", message: "Looks like you haven't set a username yet.")
}
else {
// Update database with a unique username.
}
}
else {
self.signupErrorAlert("Uh oh!", message: "\(usernameString!) is not available. Try another username.")
}
}) { error in
print(error.localizedDescription)
}
}
This is the first time out of most of the answers here that worked for me. But for now, I don’t know if this would scale. Post your experiences and best practices. They’ll be appreciated.
Related
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()
}
this is supposed to take the user ID from the result!.user.uid and store it a variable or function in order for me to use it again.
the problem is that I dont know how to get it to store the value outside of this function.
Ive tried to make it store to a variable outside of the initial button function, and Ive also tried to return it outside of the function by removing a part of the code which made it become a void. Im not sure where i need to go/what else I can try and do in order to fix this problem.
If anybody know how do I retrieve my document ID from this code your help would be greaty appreciated
#IBAction func NextButtonTapped(_ sender: Any) {
//validate the fileds
let Error = validateFields()
if Error != nil {
// there is somthing wrong with the fields show error message
showError(Error!)
}
else {
// create cleaned versions of the data
let Password = PasswordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let Email = EmailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let Firstname = FirstnameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let Lastname = LastnameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let Age = AgeTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
// create the user
Auth.auth().createUser(withEmail: Email, password: Password) { (results, Err) in
// check for errors
if Err != nil {
// there was an error creating the user
self.showError("Error creating user")
}
else {
// user was created succesfully store first and last name
let db = Firestore.firestore()
db.collection("users").document(results!.user.uid).setData(["first name":Firstname, "last name":Lastname, "age":Age, "uid":results!.user.uid]) { (Error) in
if Error != nil {
// show error message
self.showError("error saving user data")
}
//showing users document id
}
//transition to the home screen
self.transitionToHome()
}
}
}
}
I have no idea what to do any help would be amazing,
thank you very much!!!!
Define a uid variable outside of the IBAction function like so.
var uid: String? = nil
Then, within the createUser function
self.uid = results!.user.uid
I have tried a couple of different things, and at this point I am stumped. I simply want to be able to access the user's email to present it in a view. However I have not been able to successfully present, much less retrieve, this information. Here are the two pieces of code I have tried with:
func getUsername() -> String? {
if(self.isAuth) {
return AWSMobileClient.default().username
} else {
return nil
}
}
and
func getUserEmail() -> String {
var returnValue = String()
AWSMobileClient.default().getUserAttributes { (attributes, error) in
if(error != nil){
print("ERROR: \(String(describing: error))")
}else{
if let attributesDict = attributes{
//print(attributesDict["email"])
self.name = attributesDict["name"]!
returnValue = attributesDict["name"]!
}
}
}
print("return value: \(returnValue)")
return returnValue
}
Does anyone know why this is not working?
After sign in try this:
AWSMobileClient.default().getTokens { (tokens, error) in
if let error = error {
print("error \(error)")
} else if let tokens = tokens {
let claims = tokens.idToken?.claims
print("claims \(claims)")
print("email? \(claims?["email"] as? String ?? "No email")")
}
}
I've tried getting the user attributes using AWSMobileClient getUserAttributes with no success. Also tried using AWSCognitoIdentityPool getDetails With no success. Might be an error from AWS Mobile Client, but we can still get attributes from the id token, as seen above.
If you are using Hosted UI, remember to give your hosted UI the correct scopes, for example:
let hostedUIOptions = HostedUIOptions(scopes: ["openid", "email", "profile"], identityProvider: "Google")
It is because it is an async function so will return but later than when the function actually ends with the value. Only way I found to do it is placing a while loop and then using an if condition.
I am facing an issue in retrieving a data present in node
Dev->Regis->[userId("hjgsfgsdfghd8ydfsw3r")]->UserInfo->{userName:"", email:""}
I am trying the following query
ref.child(kChild).child(kRegistration).queryOrdered(byChild: kUserInfo).queryEqual(toValue: searchText).observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
}
}
I also tries by making rules but it does make result
"Development": {
"Registration": {
"$user_id": {
"UserInfo": {
".indexOn": ["userName"],
".read": "auth.uid == $user_id"
}
}
}
}
database structure
and ref means Database.refrence()
Please help to sort out this problem.
Thanks.
Assuming searchText is the username value that you want to grab the object for. Firebase requires an orderBy call.
let searchText = //username you wish to fetch
let ref = db.ref('Development/Registration/${userId}/userInfo')
ref.orderByChild('userName').equalTo(searchText).once("value", function(snapshot) {
if(snapshot.exists()){
//whatever you want to do here
}
}
I am currently trying to iterate through all user emails in firebase, however, whenever I run my code, and try to add the "test#gmail.com" user, there is an error. However, when I try to add my current user's email address, "test1#gmail.com", there is a success.
Below is a snippet of my code demonstrating this. B
Below is also an image showing the structure of my current database.
Note that each user's email is under a unique userID under the "users" part of the database.
Iterating through email snippet.
func searchEmails() {
var ref : DatabaseReference
let currentUserID = Auth.auth().currentUser?.uid
ref = Database.database().reference()
let userRef = ref.child("users")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot {
userRef.child(rest.key).child("email").observe(.value, with: { snapshot in
print(snapshot.value!)
// print("Other rest is this \(otherRest.value!) value")
if(snapshot.value as? String == self.shareEmail.text) {
SVProgressHUD.showSuccess(withStatus: "Request sent to user!")
}
else {
SVProgressHUD.showError(withStatus: "Email not valid.")
}
})
}
})
SVProgressHUD.dismiss()
}
Why don't you try this, Might turn out to be much less headache.. :-
if self.shareEmail.text != "" && self.shareEmail.text.isEmpty == false{
Database.database().reference().child("users").queryOrdered(byChild: "email").queryEqual(toValue: "somemail").observe(.value, with: {(Snapshot) in
if Snapshot.exists(){
// The email that you have been searching for exists in the
// database under some particular userID node which can
// be retrieved from ....
print(Snapshot)
}else{
// No such email found
}
}, withCancel: {(error) in
// Handle any error occurred while making the call to firebase
})
}else{
//Your textfield must not be empty;.... Handle that error
}
Note : This is only gonna work if Firebase Security rules allow it... so you might have to work on that on your console... Good luck!