I am newbie dev here.
I am working on a personal project (React Native + firebase) where the user login flow is something like this:
Each app user already has an id card with a unique user id provided to them in the form of a QR code.
User scans the QR code in the login page for the app.
If the unique id matches with the user id already present in the database - the user is authorized to access the app.
Note there is no sign up in my app.
I have trouble understanding how the implementation flow would look like in firebase. How do I authorize the user in firebase? Is it possible to use the firebase-auth for this?
Currently, I am just manually looking if the unique id exists in the database. I am allowing the user to navigate from the login page to other pages if the id matches with the id present in the database. Any user can make any changes to the database at present.
Here is the code I am using at the present:
_login = (status)=> {
if(status === "success") {
this.setState ({
logInStatus: "Login successful!"
})
this.props.navigation.navigate("Question")
}
else {
this.setState ({
logInStatus: "Login failed!"
})
this.props.navigation.navigate("Login")
}
}
//check if user exists in the database
userExists = async (data) => {
this.setState ({
logInStatus: "Logging you in!"
})
//connect to firebase
let userRef = firebase.database().ref('/users/')
let status
try {
let value = await userRef.child(data).once('value', function(snapshot) {
if (snapshot.exists()) {
status = "success"
}
})
}
catch(error) {
this.setState ({
logInStatus: "Login failed. Try again!"
})
console.log("Reached error")
}
this._login(status)
}
//when user has scanned once
_handleBarCodeRead = async ({data})=> {
this.userExists(data)
//stop user from scanning again
this.setState ({
barCodeRead: true,
fullScreen: false,
})
}
Clearly this is not a secure way to do the login...what is a better alternative?
This is the database structure which I am using as of now:
{
"schools": {
"school_001":{
"name": <name>,
"id": <school_id>,
.....other properties unique to the school
},
"school_002": {},
"school_003": {},
.....
"school_N": {}
},
"groups":{
"group_001":{
"group_id": <group ID>,
"school_id": <school ID>,
"members":[array list of group memebers/users]
....other group properties unique to the group
},
"group_002":{},
.....
"group_N":{}
},
"users":{
"user_001":{
"user_id":<user ID>,
"group_id":<group ID>,
"school_id": <school ID>
....other user properties unique to the user
},
"user_002":{},
....
"user_N":{}
},
"activity_feed":{
"school_001":{
"group_001":{
"activity_001":{
"actor": <name of the user>,
"action": <user's action>,
....other properties related to the activity
},
"activity_002":{},
......
},
"group_002":{},
....
},
"school_002":{},
......
}
}
I want users to have write access to only their own user properties, particular group properties and group activity feed.
You should put this in your firebase security rule
{
"rules": {
".read": "auth !== null",
".write": "auth !== null"
}
}
Related
I'm working on a chat module using fiebase , I have a structure as below .
My main node is mychatexperiment
inside it I have another node ChatListing and some other chat keys.
Today I set up setValues in my structure and when I passed my url without any node it deletes all of my data inside the parent node.
What i want is to set up the rules
One can create the node in any case
One can update the node in any case
One can not delete the node in any case
One Can only update or set the data inside ChatListing
I was trying using this but it does not work . any idea how to implement these things .
{
"rules": {
".write": "!data.exists() || !newData.exists()",
"read" : true
}
}
Note : I'm not using any authentication so need to implement rules without any authentication
Revise Requirements :
I have a structure where I have a single parent node and inside it I have multiple chat keys that is generated through firebase , Inside chat keys I have a node of individual message inside that chat .
The second thing which is most important is that I have a node called chatlisting in which I am storing my userids in a unique format like If My userid is 5 so inside chatlisting I am storing 5 and inside that 5 I have all the chat keys nodes which are related to me .
{
"ChatListing": {
"1126_2": { //userUnique key
"MjmhpHb6qR7VSkYzbjI": { // 1126 userid has this chat key and inside this chat last msg was welcome and its unread count is 0
"UnReadCount": 0,
"lastmessage": "Welcome",
"lastmessagetime": 1631870264251
}
},
"4184_1": {
"MjmhpHb6qR7VSkYzbjI": { // 4184 userid has this chat key as well and inside this chat last msg was welcome and its unread count is 1
"UnReadCount": 1,
"lastmessage": "Welcome",
"lastmessagetime": 1.6318646965369204E12
}
}
},
"MjmhpHb6qR7VSkYzbjI": { // chat key
"-MjmhpQbBaL7EbHPHayA": { // mesg key
"data": "Thankyou",
"time": 1.6318646965369204E12,
"type": 0,
"user": 4184 // the msg is sent by this user
},
"-Mjn21A4067dT4emYe05": { // another msg in the same chat
"data": "Welcome",
"time": 1631870264251,
"type": 0,
"user": 1126 // the msg is sent by this user
}
}
}
What I want is to setup the rules in which no one can run update , set or delete inside parent node (except ChatList node) . Any one can create chat keys and read chat keys inside parent node , nothing else they can do .
but inside chatlist they can perform create read , set and update(not delete) as I need to update the last message in this node against user chat .
So reusing the points as covered by my other answer, you would apply those rules using:
{
"rules": {
"ChatListing": {
"$userid": { // the user's unique ID
// anyone who knows this user ID can read their messages
".read": true,
"$chatid": { // a chatroom the user is in
// data stored here MUST have this shape (with nothing else)
// {
// UnReadCount: number,
// lastmessage: string,
// lastmessagetime: number
// }
// Data may be created or updated, but not deleted
".validate": "newData.child('UnReadCount').isNumber() && newData.child('lastmessage').isString() && newData.child('lastmessagetime').isNumber()",
"UnReadCount": { ".write": "newData.exists()" },
"lastmessage": { ".write": "newData.exists()" },
"lastmessagetime": { ".write": "newData.exists()" }
}
}
},
// when using $ keys at the same level as defined keys,
// this rule will catch everything that doesn't match
// the above rules
"$chatId": { // a chatroom where messages can be sent
// anyone who knows this chat ID can read its messages
".read": true,
"$msgId": { // a message in this chatroom
// Data stored here MUST have this shape (with nothing else)
// {
// data: string,
// time: number
// type: number,
// user: string, // see note
// }
// Data may be created, but not edited or deleted
// change like above if needed
".validate": "newData.child('data').isString() && newData.child('time').isNumber() && newData.child('type').isNumber() && newData.child('user').isString()",
"data": { ".write": "!data.exists()" },
"time": { ".write": "!data.exists()" },
"type": { ".write": "!data.exists()" },
"user": { ".write": "!data.exists()" }
}
}
}
}
Notes:
Don't use numeric user IDs as they are easily guessable, generate something random. You could even use const userId = push(getReference(getDatabase())).key. Consider securing the data with anonymous authentication.
Unlike your requirements, I have made the messages in the chat immutable. Once sent, no one can edit them. This prevents someone other than the sender from coming in and changing the message. With authentication, edits could be allowed because it's more secure.
Take note how unlike my /cars example, I haven't put ".read": true at the root of the database or at /ChatListing. This prevents someone coming along and running either of the below pieces of code to pull all of your stored data or pull all stored user IDs at once which will then allow them to find messages not meant for them. It does not prevent brute-forcing the data though.
const rootRef = ref(getDatabase());
rootRef
.then((snapshot) => {
console.log(snapshot.val()) // all data in database!
});
const chatListingRef = ref(getDatabase(), "ChatListing");
chatListingRef
.then((snapshot) => {
const usersArray = [];
const chatIdSet = new Set();
snapshot.forEach(userData => {
usersArray.push(userData.key)
userData.forEach(lastChatData => chatIdSet.add(lastChatData.key));
});
// logs all user IDs in the database!
console.log("User IDs:", usersArray)
// logs all chatroom IDs in the database!
console.log("Chatroom IDs:", [...chatIdSet])
});
I'm trying to add security rules to a new Firestore project I'm working on. I have a collection named users that has all my user data in it in this format in my Firestore database:
var users = {
"userId": {
friend_requests: [],
friends: [
/users/friendId1,
/users/friendId2
],
name: "User One",
username: "user1"
},
"friendId1": {
friend_requests: [],
friends: [
/users/userId
],
name: "User Two",
username: "user2"
},
"friendId2": {
friend_requests: [],
friends: [
/users/userId
],
name: "User Three",
username: "user3"
},
"lonelyUser": {
friend_requests: [],
friends: [],
name: "Lonely User",
username: "lonely_user"
}
}
My Firestore rules are this, verbatim:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /users/{userId} {
allow read: if isOwner(userId) || isFriendOf(userId);
}
}
function isOwner(userId) {
return userId == currentUser().uid;
}
function isFriendOf(userId) {
return getUserPath(userId) in getUserData().friends;
}
function currentUser() {
return request.auth;
}
function getUserData() {
return get(getUserPath(currentUser().uid)).data;
}
function getUserPath(userId) {
return /databases/$(database)/documents/users/$(userId);
}
}
The keys in the map I outlined above are Firebase user ids in my actual db, so when logged in as user "userId" I'd expect to be able to read the user document for both "friendId1" and "friendId2" users.
The problem I'm having is that isFriendOf is returning false. I've tried a few variants of wrapping the comparison data in a get call and passing in other values like id and data off the resource it returns.
I've also tried wrapping the getUserPath call in isFriendOf in a get and then using the __name__ property of the document as the comparison value as well. No luck there either.
Any help would be greatly appreciated.
Edit: Including a screenshot of the actual documents for clarification.
Screenshot of Firebase Documents
Edit 2: I've made a clean firebase project with only the information in these new screenshots in it. I'm including screenshots of Firebase Authentication page, both user entries in the database, as well as a failed simulator run as one user trying to get the document of the other user.
Authentication Configuration
user1
user2
Simulated Request
I have found various other posts that all seem to make me believe that my implementation should be working but something is still missing.
Here is my Firebase Realtime database rules:
"usernames": {
".read" : true,
".write" : "auth !== null",
"$username":{
".validate": "!root.child('usernames').hasChild($username)"
}
}
Inside my usernames node i have for example:
"usernames"
|---"johnDoe123" : "XrG34odsfla82343174094389"
|--- etc.....
I have a cloud function that I am calling by passing a token and when the cloud function runs it attempts to update the "usernames" node by adding the new requested username assuming the username is not yet taken.
Here is the portion of the function that I am using that writes to the Firebase usernames node:
let newUser = {}
newUser[req.query.username] = decoded.uid
admin.database()
.ref('usernames')
.update(newUser)
.then(() => {
console.log(`New username <${req.query.username}> added`)
res.status(200).send(decoded)
}).catch(e=>{
console.log('Error', e)
})
The cloud function is updating the usernames node but my problem is that I need to have it reject the update with the validation if another child node has the same username. As of now my rules continue to allow the update to occur despite having another child node with the same exact username which is associated to a different uid.
Any ideas what I'm doing wrong?
Not sure if this is the best way, but it works for my purpose which is to check the first time a user is getting setup within my app to determine if the username being requested is already taken. If so it sends back JSON response that it was already taken. If it was not taken then it creates the record and then sends back JSON response of success..
module.exports = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const tokenId = req.get('Authorization').split('Bearer ')[1];
return admin.auth().verifyIdToken(tokenId)
.then((decoded) => {
admin.database()
.ref('usernames')
.child(req.query.username)
.once('value', function(snapshot) {
if(snapshot.val() === null){
let newUser = {}
newUser[req.query.username] = decoded.uid
admin.database()
.ref('usernames')
.update(newUser)
.then(() => {
console.log(`New username <${req.query.username}> added`)
res.status(200).send({
success: true,
message: "Username created"
})
}).catch(err=>{
res.status(500).send(err)
})
}
else{
res.status(200).send({
success: false,
message: "Username already taken"
})
}
});
})
.catch((err) => {
res.status(401).send(err)
});
})
});
If you use the Firebase Authentication to register all users, then this system will tell the user automatically that the email address they have used to register with is already an account.
You could also make the username part of the tree path /users/AUserName so it would return null when queried
https://github.com/kristinyim/ClassroomChat
I want to add an upvoting feature to the messages on this chatroom similar to what you have on GroupMe, but I'm new to React and built this off of a tutorial so don't know where to even begin. I'm good with webdev but am just getting started with the basics of React.js and Firebase. Thanks!
NB: There are many ways to achieve this, so the following is just a suggestion.
First you must think of how you want to store your data in the database. If you have users, messages and message-likes, you could structure it like this:
"root": {
"users": {
"$userId": {
...
"messages": {
"$messageId1": true,
"$messageId2": true,
...
}
}
},
"messages": {
"$messageId": {
"author": $userId,
"timestamp": ServerValue.TIMESTAMP
}
},
"likesToMessages": {
"$messageId": {
"$likeId": {
liker: $userId,
"message": $messageId,
"timestamp": ServerValue.TIMESTAMP
}
}
}
}
Whenever a user clicks "like" on a message, you want to write to
var messageId = ?; // The id of the message that was liked
var like = {
liker: currentUserId, // id of logged in user
message: messageId,
timestamp: firebase.database.ServerValue.TIMESTAMP
};
firebase.database.ref().child('likesToMessages').child(messageId).push(like);
Then you get a new like in the database, matching the proposed structure.
Then, when you want to read and show the count of likes for a message, you can do like this:
const Message = React.createClass({
propTypes: {
message: React.PropTypes.object,
messageId: React.PropTypes.string // you need to add this prop
}
componentWillMount() {
firebase.database.ref().child('likesToMessages').child(this.props.messageId).on('value', this.onLikesUpdated)
}
onLikesUpdated(dataSnapshot) {
var likes = snap.val();
this.setState({
likes
});
}
render() {
const {name, message} = this.props.message;
const emojifiedString = emoji.emojify(message);
return (
<p>
{name}: {emojifiedString} [{this.state.likes.length}♥]
</p>
);
}
});
Also, in your database security rules, you'd want to index by timestamp for message and like so you can quickly query the newest messages.
Also, feel free to check out a similar app I made, code in GitHub and demo on wooperate.firebaseapp.com.
Firebase Simple login provides an email/password option, how do I use it? Starting from from creating a user, storing data for that user, to logging them in and out.
There are three distinct steps to be performed (let's assume you have jQuery):
1. Set up your callback
var ref = new Firebase("https://demo.firebaseio-demo.com");
var authClient = new FirebaseAuthClient(ref, function(error, user) {
if (error) {
alert(error);
return;
}
if (user) {
// User is already logged in.
doLogin(user);
} else {
// User is logged out.
showLoginBox();
}
});
2. User registration
function showLoginBox() {
...
// Do whatever DOM operations you need to show the login/registration box.
$("#registerButton").on("click", function() {
var email = $("#email").val();
var password = $("#password").val();
authClient.createUser(email, password, function(error, user) {
if (!error) {
doLogin(user);
} else {
alert(error);
}
});
});
}
3. User login
function showLoginBox() {
...
// Do whatever DOM operations you need to show the login/registration box.
$("#loginButton").on("click", function() {
authClient.login("password", {
email: $("#email").val(),
password: $("#password").val(),
rememberMe: $("#rememberCheckbox").val()
});
});
}
When the login completes successfully, the call you registered in step 1 will be called with the correct user object, at which point we call doLogin(user) which is a method you will have to implement.
The structure of the user data is very simple. It is an object containing the following properties:
email: Email address of the user
id: Unique numeric (auto-incrementing) ID for the user
FirebaseAuthClient will automatically authenticate your firebsae for you, not further action is required. You can now use something like the following in your security rules:
{
"rules": {
"users": {
"$userid": {
".read": "auth.uid == $userid",
".write": "auth.uid == $userid"
}
}
}
}
This means, if my User ID is 42, only I can write or read at example.firebaseio-demo.com/users/42 - when I am logged in - and no-one else.
Note that Simple Login does not store any additional information about the user other than their ID and email. If you want to store additional data about the user, you must do so yourself (probably in the success callback for createUser). You can store this data as you normally would store any data in Firebase - just be careful about who can read or write to this data!
Just incase someone is reached to this thread and looking for some example application using the firebase authentication. Here are two examples
var rootRef = new Firebase('https://docs-sandbox.firebaseio.com/web/uauth');
......
.....
....
http://jsfiddle.net/firebase/a221m6pb/embedded/result,js/
http://www.42id.com/articles/firebase-authentication-and-angular-js/