So I'm trying to create a login screen for my app in java on Android Studio using Firebase. Currently, in creating accounts, I am doing it this way:
if (password.getText().toString().equals(confirm.getText().toString())) {
Map map = new HashMap<String, String>();
map.put(username.getText().toString(), password.getText().toString());
myFirebaseRef.push().setValue(map);
error.setText("Account created!");
}
else {
error.setText("Passwords do not match");
}
But it saves the data on my Firebase like
this
So now on my login screen, once a user types in their username, I wanna search my Firebase for the username, and make sure it exists, then see if the password matches what they entered. How Would I do that? Thanks!
As Amy already said: no matter what else you do, immediately change your code to stop storing passwords in plain text. That is an incredibly bad practice. In general authentication is better left to dedicated tools, such as Firebase Authentication.
Aside from that, you'll want to change the way you store the users a bit. It seems you identify users by their name, store them under their name:
Map map = new HashMap<String, String>();
map.put(username.getText().toString(), password.getText().toString());
myFirebaseRef.updateChildren(map);
Now you can look up a user by their name with:
Firebase userRef = myFirebaseRef.child(username.getText().toString());
userRef.addListenerForSingleValueEvent(...
See the Firebase guide on reading data for more on that.
Related
In my main page I have a list of users and i'd like to choose and open a channel to chat with one of them.
I am thinking if use the id is the best way and control an access of a channel like USERID1-USERID2.
But of course, user 2 can open the same channel too, so I'd like to find something more easy to control.
Please, if you want to help me, give me an example in javascript using a firebase url/array.
Thank you!
A common way to handle such 1:1 chat rooms is to generate the room URL based on the user ids. As you already mention, a problem with this is that either user can initiate the chat and in both cases they should end up in the same room.
You can solve this by ordering the user ids lexicographically in the compound key. For example with user names, instead of ids:
var user1 = "Frank"; // UID of user 1
var user2 = "Eusthace"; // UID of user 2
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
user1 = "Eusthace";
user2 = "Frank";
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
A common follow-up questions seems to be how to show a list of chat rooms for the current user. The above code does not address that. As is common in NoSQL databases, you need to augment your data model to allow this use-case. If you want to show a list of chat rooms for the current user, you should model your data to allow that. The easiest way to do this is to add a list of chat rooms for each user to the data model:
"userChatrooms" : {
"Frank" : {
"Eusthace_Frank": true
},
"Eusthace" : {
"Eusthace_Frank": true
}
}
If you're worried about the length of the keys, you can consider using a hash codes of the combined UIDs instead of the full UIDs.
This last JSON structure above then also helps to secure access to the room, as you can write your security rules to only allow users access for whom the room is listed under their userChatrooms node:
{
"rules": {
"chatrooms": {
"$chatroomid": {
".read": "
root.child('userChatrooms').child(auth.uid).child(chatroomid).exists()
"
}
}
}
}
In a typical database schema each Channel / ChatGroup has its own node with unique $key (created by Firebase). It shouldn't matter which user opened the channel first but once the node (& corresponding $key) is created, you can just use that as channel id.
Hashing / MD5 strategy of course is other way to do it but then you also have to store that "route" info as well as $key on the same node - which is duplication IMO (unless Im missing something).
We decided on hashing users uid's, which means you can look up any existing conversation,if you know the other persons uid.
Each conversation also stores a list of the uids for their security rules, so even if you can guess the hash, you are protected.
Hashing with js-sha256 module worked for me with directions of Frank van Puffelen and Eduard.
import SHA256 from 'crypto-js/sha256'
let agentId = 312
let userId = 567
let chatHash = SHA256('agent:' + agentId + '_user:' + userId)
I've read the Firebase docs on Stucturing Data. Data storage is cheap, but the user's time is not. We should optimize for get operations, and write in multiple places.
So then I might store a list node and a list-index node, with some duplicated data between the two, at very least the list name.
I'm using ES6 and promises in my javascript app to handle the async flow, mainly of fetching a ref key from firebase after the first data push.
let addIndexPromise = new Promise( (resolve, reject) => {
let newRef = ref.child('list-index').push(newItem);
resolve( newRef.key()); // ignore reject() for brevity
});
addIndexPromise.then( key => {
ref.child('list').child(key).set(newItem);
});
How do I make sure the data stays in sync in all places, knowing my app runs only on the client?
For sanity check, I set a setTimeout in my promise and shut my browser before it resolved, and indeed my database was no longer consistent, with an extra index saved without a corresponding list.
Any advice?
Great question. I know of three approaches to this, which I'll list below.
I'll take a slightly different example for this, mostly because it allows me to use more concrete terms in the explanation.
Say we have a chat application, where we store two entities: messages and users. In the screen where we show the messages, we also show the name of the user. So to minimize the number of reads, we store the name of the user with each chat message too.
users
so:209103
name: "Frank van Puffelen"
location: "San Francisco, CA"
questionCount: 12
so:3648524
name: "legolandbridge"
location: "London, Prague, Barcelona"
questionCount: 4
messages
-Jabhsay3487
message: "How to write denormalized data in Firebase"
user: so:3648524
username: "legolandbridge"
-Jabhsay3591
message: "Great question."
user: so:209103
username: "Frank van Puffelen"
-Jabhsay3595
message: "I know of three approaches, which I'll list below."
user: so:209103
username: "Frank van Puffelen"
So we store the primary copy of the user's profile in the users node. In the message we store the uid (so:209103 and so:3648524) so that we can look up the user. But we also store the user's name in the messages, so that we don't have to look this up for each user when we want to display a list of messages.
So now what happens when I go to the Profile page on the chat service and change my name from "Frank van Puffelen" to just "puf".
Transactional update
Performing a transactional update is the one that probably pops to mind of most developers initially. We always want the username in messages to match the name in the corresponding profile.
Using multipath writes (added on 20150925)
Since Firebase 2.3 (for JavaScript) and 2.4 (for Android and iOS), you can achieve atomic updates quite easily by using a single multi-path update:
function renameUser(ref, uid, name) {
var updates = {}; // all paths to be updated and their new values
updates['users/'+uid+'/name'] = name;
var query = ref.child('messages').orderByChild('user').equalTo(uid);
query.once('value', function(snapshot) {
snapshot.forEach(function(messageSnapshot) {
updates['messages/'+messageSnapshot.key()+'/username'] = name;
})
ref.update(updates);
});
}
This will send a single update command to Firebase that updates the user's name in their profile and in each message.
Previous atomic approach
So when the user change's the name in their profile:
var ref = new Firebase('https://mychat.firebaseio.com/');
var uid = "so:209103";
var nameInProfileRef = ref.child('users').child(uid).child('name');
nameInProfileRef.transaction(function(currentName) {
return "puf";
}, function(error, committed, snapshot) {
if (error) {
console.log('Transaction failed abnormally!', error);
} else if (!committed) {
console.log('Transaction aborted by our code.');
} else {
console.log('Name updated in profile, now update it in the messages');
var query = ref.child('messages').orderByChild('user').equalTo(uid);
query.on('child_added', function(messageSnapshot) {
messageSnapshot.ref().update({ username: "puf" });
});
}
console.log("Wilma's data: ", snapshot.val());
}, false /* don't apply the change locally */);
Pretty involved and the astute reader will notice that I cheat in the handling of the messages. First cheat is that I never call off for the listener, but I also don't use a transaction.
If we want to securely do this type of operation from the client, we'd need:
security rules that ensure the names in both places match. But the rules need to allow enough flexibility for them to temporarily be different while we're changing the name. So this turns into a pretty painful two-phase commit scheme.
change all username fields for messages by so:209103 to null (some magic value)
change the name of user so:209103 to 'puf'
change the username in every message by so:209103 that is null to puf.
that query requires an and of two conditions, which Firebase queries don't support. So we'll end up with an extra property uid_plus_name (with value so:209103_puf) that we can query on.
client-side code that handles all these transitions transactionally.
This type of approach makes my head hurt. And usually that means that I'm doing something wrong. But even if it's the right approach, with a head that hurts I'm way more likely to make coding mistakes. So I prefer to look for a simpler solution.
Eventual consistency
Update (20150925): Firebase released a feature to allow atomic writes to multiple paths. This works similar to approach below, but with a single command. See the updated section above to read how this works.
The second approach depends on splitting the user action ("I want to change my name to 'puf'") from the implications of that action ("We need to update the name in profile so:209103 and in every message that has user = so:209103).
I'd handle the rename in a script that we run on a server. The main method would be something like this:
function renameUser(ref, uid, name) {
ref.child('users').child(uid).update({ name: name });
var query = ref.child('messages').orderByChild('user').equalTo(uid);
query.once('value', function(snapshot) {
snapshot.forEach(function(messageSnapshot) {
messageSnapshot.update({ username: name });
})
});
}
Once again I take a few shortcuts here, such as using once('value' (which is in general a bad idea for optimal performance with Firebase). But overall the approach is simpler, at the cost of not having all data completely updated at the same time. But eventually the messages will all be updated to match the new value.
Not caring
The third approach is the simplest of all: in many cases you don't really have to update the duplicated data at all. In the example we've used here, you could say that each message recorded the name as I used it at that time. I didn't change my name until just now, so it makes sense that older messages show the name I used at that time. This applies in many cases where the secondary data is transactional in nature. It doesn't apply everywhere of course, but where it applies "not caring" is the simplest approach of all.
Summary
While the above are just broad descriptions of how you could solve this problem and they are definitely not complete, I find that each time I need to fan out duplicate data it comes back to one of these basic approaches.
To add to Franks great reply, I implemented the eventual consistency approach with a set of Firebase Cloud Functions. The functions get triggered whenever a primary value (eg. users name) gets changed, and then propagate the changes to the denormalized fields.
It is not as fast as a transaction, but for many cases it does not need to be.
I'm new to Android studio. I was able to create login and auth for my app using firebase.
I'm now trying to create a submission form for the user to create and log all the fields in firebase. I would also need the ability as admin to approve the submission request and notify user by push notification or email. The email is one of the fields in the submission form.
Looking for some guidance.
Thanks
Use Firebase Realtime Database + Firebase Functions.
You can see examples here.
You'll need to use FirebaseDatabase for that, go through this to set things up-
https://firebase.google.com/docs/database/android/start/
So first of all, add a EditText fields (simplest way) for each entry you want in your xml such as emails, password, etc.
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Email"/>
</EditText>
Next, in your java file, convert these entries to strings.
Create a DatabaseReference (Make sure you have included firebase database library in you app level build.gradle file) and point to the location where you want to store the form entries.
Create a HashMap in which you store id and entry of each field. Directly upload this map instead of uploading each and every field separately.
Don't forget to check that the entries aren't empty.
Somewhat like this-
//global variables in class
private EditText email;
private DatabaseReference mdatabase;
//inside onCreate() method
mdatabase=FirebaseDatabase.getInstance().getReference().child("Wherever you want to store this data");
email = (EditText) findViewById(R.id.emailf);
String mymail = email.getText().toString();
if(!TextUtils.isEmpty(myname)&&!TextUtils.isEmpty(mymail)&&!TextUtils.isEmpty(mypass))
{
//create a map
HashMap< String,String> hm =
new HashMap< String,String>();
hm.put("email", mymail);
hm.put("password", mypass);
hm.put("name", myname);
mdatabase.setValue(hm).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful())
{
//do whatever you want, your details have been uploaded to your database- mdatabase
}
}
});
}
Push Notifications can be implemented using Firebase Cloud Messaging service and node.js
You'll also need to learn about Firebase Functions, very necessary.
Begin here-
https://firebase.google.com/docs/cloud-messaging/
and here-
https://www.youtube.com/watch?v=BsCBCudx58g
and then here-
https://www.youtube.com/watch?v=2MYgZQjX7N0&list=PLk7v1Z2rk4hjxP_CHAhjXQN3zrcEhluF_
I hope this answered your question!
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)
})
}
How can I update a user data like username and password in syncano using android programming as a syntax ?
This is taken from tests of Syncano android library.
Going from the start of user creation:
User newUser = new User("username", "password");
Response<User> responseCreateUser = syncano.registerUser(newUser).send();
user = responseCreateUser.getData();
Now you can update his password:
user.setPassword("new password");
Response<User> responseUpdateUser = syncano.updateUser(user).send();
Updating username works in the same way. You can find a lot of examples and use cases in the tests, located under https://github.com/Syncano/syncano-android/tree/master/library/src/test/java/com/syncano/library/tests