I am building an android app with Facebook login, once logged, it'll save information from Facebook to Parse like Facebook ID, name and profile picture. Is there a way to make a column unique so that it can only have 1 kind of FB ID? or check if FB ID exist in my User table? I know it might seem like a very general request, but I've been googling for hours and can't find any specific solution..
I've already faced this problem in iOS, and seems that Android can be solved in the same way.
So, you can mix the FB API with the parse function
ParseFacebookUtils.logIn(String facebookId, String accessToken, Date expirationDate, LogInCallback callback)
relying to facebook app you can obtain the facebookId, accessToken ( i think that for the expirationDate you can set a far date, like what i've done for iOS )
Another solution is:
ParseFacebookUtils.logIn(Arrays.asList(Permissions.User.EMAIL),
this, new LogInCallback() {
#Override
public void done(ParseUser user, ParseException err) {
Do your stuff...
}
});
it should automatically detect if an user with that facebook id already exist in your _User table or not ( i can confirm this on iOS, you can check if the user is new or still exist with user.isNew() ). If you need you could also save the retrieved facebookId in a separated field, but i think you could avoid it for this scope
Hope it helps
Related
I just started storing usertokens in my database for notification purposes. I have no problem storing and retrieving the data except with certain usertoken string values. But when I take a datasnapshot with a usertoken it returns null, only sometimes though.
My listener looks like this (just a regular listener):
udRef.addListenerForSingleValueEvent(new ValueEventListener () {
#Override
public void onDataChange(DataSnapshot datasnapshot) {
User user = datasnapshot.get value(User.class);
//do something with data here
//eg. Log.e("ERR", String.value Of(user));
//returns "null" for SOME users only, keep reading.
}
#Override
public void onCancelled (DatabaseError databaseError) { ... }
});
When a user creates an account, it automatically inserts a string token that looks like this:
"cL_AEuSZa1I:APA91bF9R-dDetqrSNMQMzAoHRmLOHXK2acK_zJLhFhZ5IhvNChPSx4P943GirVwsy5walc0RrvyGnMm49oLxNJ-uKsGRCELhJ-9kwnPmdHOyWJWz0Esm_Y0MB7BypNUuQ4qZQmoW5nO"
Tokens are used for push notifications using FCM. This is refreshed automatically as recommended by the FB guys. The token above isn't valid, in fact this very usertoken made my datasnapshot not work so I had to manually remove it! And yes, immediately I removed it I was able to capture the snapshot, it didn't return null anymore.
I do not know what I'm doing wrong, but when I look (with my eyes, not a script) at the usertokens in the database I can tell which ones will be troublesome. This is how:
All usertokens are too long, so Firebase's web console cuts them short like this "gftgvdgGhbgfc_fggg..."
All the troublesome tokens get cut also but without the 3 dots or closing quotes, they look like this "gftgvdgGhbgfc_fggg but when expanded they are normal like the working ones (length is same).
I have 30 users so I can easily go through them all :D
This is how my data looks (roughly):
"fhgfFghDfgghhfDDf" : {
"username" : "a_string_here"
"userPic" : "looong_string_link_to_fb_storage"
"usertoken" : "long_user_token_like the_one_in_question"
},...
Am I storing the tokens wrongly?, should I convert them to some other data type? Too many 'gotchas' in FB :(
Please note, I DO NOT have problems with anything else except the usertokens making my datasnapshot null, so any mistakes in the code are just typos (I typed this question from my phone referencing my laptop screen... don't ask)
I want to make a system which allows for username login. This process requires the following:
User must register with an email/password
The user can set a unique username
The user can sign in with either email or username
The user can recover their password via email or username
The functionality must work on a persistence enabled database
This question has been answered previously, but it disabled the functionality for the user to use password recovery. They also didn't address case-sensitivity, one person could register as "scooby" and another as "Scooby".
DISCLAIMER: This code is now over two years old. While this doesn't mean it's deprecated, I would strongly recommend investigating alternative methods before assuming this is the best approach. I personally wouldn't want the standard login process for Firebase to be dictated by my initial approach to a problem while Firebase wasn't as heavily adopted as it is now.
After multiple iterations of development I've come up with the following design to address this. I will post my code snippets in Swift, but they will typically be translatable directly into Android with ease.
Create a Firebase registration process for email/password.
This is required as the backbone of the user's sign-in experience. This can be implemented completely from the stock Firebase API documentation provided here
Prompt the user to enter their username
The username entry should be completed at registration, I'd recommend an additional field in the registration flow. I also recommend checking if the user has a username whenever they log in. If they don't, then display a SetUsername interface that prompts them to set a username before progressing further into the UI. A user might not have a username for a few reasons; it could be revoked for being rude or reserved, or they might have signed up prior to the username being required at registration.
Make sure that if you're using a persistence-enabled Firebase build that you use Firebase Transactions. The transactions are necessary, otherwise your app can make assumptions about the data in the username table, even though a username might have been set for a user only seconds earlier.
I would also advise enforcing the username to be nearly alphanumeric (I allow for some harmless punctuation). In Swift I can achieve this with the following code:
static var invalidCharacters:NSCharacterSet {
let chars = NSMutableCharacterSet.alphanumericCharacterSet()
// I add _ - and . to the valid characters.
chars.addCharactersInString("_-.")
return chars.invertedSet
}
if username.rangeOfCharacterFromSet(invalidCharacters) != nil {
// The username is valid
}
Saving the user data
The next important step is knowing how to save the user's data in a way that we can access it in the future. Below is a screenshot of the way I store my user data:
A few things to note:
The usernames are stored twice, once in usernames and again in details/[uid]/username. I recommend this as it allows you to be case sensitive with usernames (see the next point) and it also allows you to know the exact database reference to check a username (usernames/scooby) rather than having to query or check through the children of details to find a username that matches (which would only become more complicated when you have to factor in case-sensitivity)
the usernames reference is stored in lowercase. When I check the values in this reference, or when I save to this reference, I ensure that I only save data in lowercase. This means if anyone wants to check if the username 'scoobY' exists, it will fail because in lowercase it's the same username as the existing user "Scooby".
The details/[uid]/username field contains capitals. This allows for the username to display in the case of preference for the user, rather than enforcing a lowercase or Capitalised word, the user can specify their name as "NASA Fan" and not be converted over to "Nasa Fan", while also preventing anyone else from registering the username "NASA FAN" (or any other case iterations)
The emails are being stored in the user details. This might seem peculiar because you can retrieve the current user's email via Firebase.auth().currentUser.email?. The reason this is necessary is because we need references to the emails prior to logging in as the user.
Logging in with email or username
For this to work seamlessly, you need to incorporate a few checks at login.
Since I've disallowed the # character in usernames, I can assume that a login request containing an # is an email request. These requests get processed as normal, using Firebase's FIRAuth.auth().signInWithEmail(email, password, completion) method.
For all other requests, we will assume it's a username request. Note: The cast to lowercase.
let ref = FIRDatabase.database().reference()
let usernameRef = ref.child("users/usernames/\(username.lowercaseString)")
When you perform this retrieval, you should consider if you have persistence-enabled, and if there's a possibility that a username could be revoked. If a username could be revoked and you have persistence-enabled, you will want to ensure you retrieve the username value within a Transaction block, to make sure you don't get a cached value back.
When this retrieval succeeds, you get the value from username[username], which is the user's uid. With this value, you can now perform a retrieval on the user's email value:
let ref = FIRDatabase.database().reference()
let usernameRef = ref.child("users/details/[uid]/email")
Once this request succeeds, you can then perform the standard Firebase email login with the email string you just retrieved.
The exact same retrieval methods can be used to retrieve an email from a username to allow for password recovery.
A few points to be wary of for advanced functionality:
- If you allow the user to update their email using FIRUserProfileChangeRequest, make sure you update it both on the auth AND the details[uid]email field, otherwise you will break the username login functionality
- You can significantly reduce the code required to handle all the different failure cases in the retrieval methods by using success and failure blocks. Here's an example of my get email method:
static func getEmail(username:String, success:(email:String) -> Void, failure:(error:String!) -> Void) {
let usernameRef = FIRDatabase.database().reference().child("users/usernames/\(username.lowercaseString)")
usernameRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if let userId = snapshot.value as? String {
let emailRef = FIRDatabase.database().reference().child("users/details/\(userId)/email")
emailRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if let email = snapshot.value as? String {
success(email: email)
} else {
failure(error: "No email found for username '\(username)'.")
}
}) { (error) in
failure(error: "Email could not be found.")
}
} else {
failure(error: "No account found with username '\(username)'.")
}
}) { (error) in
failure(error: "Username could not be found.")
}
}
This success/failure block implementation allows the code I call in my ViewControllers to be much cleaner. Å login calls the following method:
if fieldText.containsString("#") {
loginWithEmail(fieldText)
} else {
// Attempt to get email for username.
LoginHelper.getEmail(fieldText, success: { (email) in
self.loginWithEmail(email)
}, failure: { error in
HUD.flash(.Error, delay: 0.5)
})
}
I'm working in an android app adding the following/follower system, I'm using the lines in the Parse.com, I have a user profile, and when I click on the follow button the app send the both users id (the current user and the other user) in a row. I'm using pointers targeting the _user class.
This is my current code :
Intent i = get intent();
String userId = i.getStringExtra("userid");
ParseObject follow = new ParseObject("Follow");
follow.put("from",ParseUser.getCurrentUser());
follow.put("to", userId);
follow.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
btn_follow.setImageDrawable(getResources().getDrawable(R.drawable.ic_following));
}
});
The btn_follow change the image. which means the following has been done. but my Follow class is empty.
I have changed my Follow table from :
...|from|to|
To :
...|from|to|
This worked well the both users id were saved, but I need pointers not string so I put from and to to pointers again and deleted the line
follow.put("to", userId);
This worked well also but it only saved the current user and not the user I'm trying to follow.
So my problem is with the ID of the user I want to follow.
In the guide they are saying
"ParseUser otheruser = ..."
(I don't no what to put in the 3 points to get the other user id)
You need to assign a ParseObject (or ParseUser) to create a pointer in database. For this you need to create ParseUser without data, just with id like this:
follow.put("to", ParseObject.createWithoutData("_User", userId));
Also, you should check the ParseException e in callback to see whats going on.
I am workin in an Android Project using parse.com and I have a table Requirement which has a column called "user_assigned" which is a relation to ParseUser. This user is in charge to modify the requirement even if he didn't create the Requirement, but when this user try to update the requirement values, it returns "Cannot save a ParseUser that is not authenticated"
P.D. All the ACL in the requirement are public, write and read.
// setting write permission
ParseACL postACL = new ParseACL(ParseUser.getCurrentUser());
postACL.setPublicWriteAccess(true);
postACL.setPublicReadAccess(true);
requirement.setACL(postACL);
requirement.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
callback.onFinish((Exception) e, requirement);
}
I don't want to change the user assigned I want to change other fields but the way I get and change the relation from the table is
ParseRelation<ParseUser> relation = requirement.getRelation("user_assigned");
relation.add(requirement.getUserAssigned());
I found a solution,
What is happening: 3 tables, Project, User and Requirements where a Requirement has a pointer to the Project and the Project has another pointer to an User called projectOwner. The problem happen when another user different to the projectOwner read some values of the projectOwner associated with the Requirements. If the new user wants to save some changes in the Requirements parse return the error "Cannot save a ParseUser that is not authenticated".
To fix it, i only read the objectId of the projectOwner and after I got the rest of the attributes of the ParseUser calling
ParseUser.getQuery();
query.get(objectId)
In other words, in my class Project, i have a method to get the attributes. When I want to get the projectUser (pointer) i got it like this:
User uTemp = (User) this.getParseUser("owner");
if(uTemp != null){
// this.setOwner(uTemp.extractAttributes());
uTemp = uTemp.getObjectId();
uTemp = UserDAO.getUserByObjectId(uTemp.getObjectId());
this.setOwner(uTemp);
}
I could fixed thanks to this, I hope it can be helpful.
I was using the Parse API for databases and trying to use the username service that it provides. I understand that from the tutorial that in order to login you do this :
ParseUser.logInInBackground("Jerry", "showmethemoney", new LogInCallback() {
public void done(ParseUser user, ParseException e) {
if (user != null) {
// Hooray! The user is logged in.
} else {
// Signup failed. Look at the ParseException to see what happened.
}
}
});
If the login failed, I was just wondering how I could tell whether it failed because the username typed in was invalid, or the password. I know that you can do e.getCode() to get the type of error that occurred, but from this site https://parse.com/docs/android/api/ I couldn't find any error codes pertaining to invalid username/password
Thank you
james
This is what I did to check username/password validity in one of my applications. This method submits a query against the ParseUser class and returns true if the passed username exists, if it does then you know the username is valid.
(check externally for ParseException.OBJECT_NOT_FOUND - in conjunction with this we can tell whether the user needs to register or has an invalid password.)
public boolean queryCredentials(String username) {
ParseQuery<ParseUser> queryuserlist = ParseUser.getQuery();
queryuserlist.whereEqualTo("username", username);
try {
//attempt to find a user with the specified credentials.
return (queryuserlist.count() != 0) ? true : false;
} catch (ParseException e) {
return false;
}
}
Hopefully this can help someone with this issue.
It seems to be a security risk to distinguish between invalid user and invalid password. This information would let a hacker test account names until the app gave an invalid password response, which would let the hacker know at least the username of a valid user. Therefore, I think Parse makes this difficult deliberately.
However, it may be possible to do this using a query that searches for users with the given username. If the query returns no users, the username is invalid. If the username returns a user, the password is invalid.
According to my own testing, it looks like it uses code 101 (ObjectNotFound) if the user's credentials are invalid. I do not recommend specifically checking if it's because of the username or password specifically, for security reasons that isaach1000 mentioned. However, if you must, see Spencer's answer.
Error codes for ParseExceptions are documented under ParseException in the Parse Android API reference docs.