Inviting users to share a Firebase object - firebase

I have an app with checklists where I want to be able to share checklists with other users. Those users might not be registered yet in my app. Currently my database is structured as follows:
{
"checklists" : {
"-KQKfnuGEhhoSIXyFj71" : {
"admins" : [
{ "C9fdXO6jWTZIxzgbhy0K10TEBqx1" : true },
{"ZQKHTbTlVMQwnZzFkQUcJF84SaA3" : true }
],
"users" : {
"-KQKfnuGEhhoSIXyFj74" : {
"email" : "admin1#example.com",
"uid" : "C9fdXO6jWTZIxzgbhy0K10TEBqx1",
"active" : true,
"name" : "Moses"
},
"-KQKfnuGEhhoSIXyFj75" : {
"email" : "admin2#example.com",
"uid" : "ZQKHTbTlVMQwnZzFkQUcJF84SaA3",
"active" : true,
"name" : "John"
},
"-KQKfnuGEhhoSIXyFj76" : {
"email" : "user1#example.com",
"uid" : "1ZxxYFGzoCPIc7Am07GVRxSN7xT2",
"active" : true,
"name" : "Kate"
},
"-KQKfnuGEhhoSIXyFj77" : {
"email" : "user2#example.com",
"active" : false
}
},
"data" : {
"task1" : "hello",
"task2" : "good bye"
}
}
}
}
My bolt file is as follows:
// admin can do both read/ write to the while checklist
path /checklists/{checklistId} {
read() { isChecklistAdmin(checklistId) }
write() { isChecklistAdmin(checklistId) }
}
// users of checklist can do read/write on data only
path /checklists/{checklistId}/data {
read() { isChecklistUser(checklistId) }
write() { isChecklistUser(checklistId) }
}
// users of checklist can add/ remove themselves from the list by updating active flag
path /checklists/{checklistId}/users/{checklistUserId} {
// all checklist users can read other users
read() { isChecklistUser(checklistId) }
// only admin (implicit) or the user can accept/reject (active) invitation or update their details
write() { isChecklistUser(checklistId) && ref.parent().email == auth.email }
}
// is authenticated user in the list of checklist admins?
isChecklistAdmin(checklistId) { isSignedIn() && root.checklists[checklistId].admins[auth.uid] != null }
// is authenticated user in the list of checklist users (test email ==)
isChecklistUser(checklistId) { isSignedIn() && root.checklists[checklistId].users['*'].email == auth.email }
isSignedIn() { auth != null }
The flow I had in mind was:
Admin adds a node under users which has the email of the invited user
Admin sends an email invite (I would have liked to use Firebase invites but I don't see how to integrate it in this scenario - would love suggestions).
User registers/ logins and updates active flag to true
My question: if you look at the isChecklistUser function, you will find I am using a "wildcard" ("*") since I do not know what that value is. Admin cannot set it at invitation since the user might not be registered yet.

Related

How to access Meteor.users() fields from the client?

I want to access users emails in from the client,
here is what I have done, in the server side:
Meteor.publish('userList', function (){
return Meteor.users.find({}, {fields:{emails: 1, profile: 1}});
});
In the client side:
Template.usersManagement.onCreated(function () {
var self = this;
self.autorun(function() {
self.subscribe('userList');
});
});
And the template helper to retrieve the users:
Template.usersManagement.helpers({
allUsers: function() {
console.log(Meteor.users.find({}).fetch())
return Meteor.users.find({}).fetch()
}
})
In the usersManagement template:
{{#each allUsers}}
<h1>{{this.emails.address}}</h1>
<h1>{{this.profile.name}}</h1>
{{/each}}
The users names are displayed but the email isn't and no errors showing in the console.
Here is the look on how the users stored in the database:
{
"_id" : "m7admvZc32Jr3SeiE",
"createdAt" : ISODate("2017-12-27T21:24:48.927Z"),
"services" : {
"password" : {
"bcrypt" : "$2a$10$wv6KsRp6s91A.0mHH89Q0eT3jrZmJjKJhw8SIH9c8c8OpwMrXyGMC"
}
},
"emails" : [
{
"address" : "222#222.com",
"verified" : false
}
],
"profile" : {
"name" : "222",
"createdAt" : ISODate("2017-12-27T21:24:48.707Z"),
"updatedAt" : ISODate("2017-12-27T21:24:48.707Z"),
"group" : "admin"
},
"status" : {
"online" : false
}
}
My question is, how can I retrieve the emails of the users?
EDIT
Here is how the emails are retrieved (from the console):
There is another field (0) below the emails. I tried this.emails.0.address it didn't work (desperate attempt)
Your desperate attempt almost nailed it, try this:
this.emails.[0].address
An item in the Meteor.users collection store emails as an array, for you to be able to store more than one email-address per user. So since it is so, the right way would be:
<h1>{{this.emails[0].address}}</h1>

How to implement user based security in the firebase database?

This is the database for example:
"Messages" : {
"Message1" : {
"Uid" : "sampleid1"
"Text" : "hi"
},
"Message2" : {
"Uid" : "sampleid2"
"Text" : " hello"
}
}
I want only those users to read the messages whose uid is equal to the Uid field of Message#.
The structure of database given in firebase documentation(i.e. using user id based messages in the database where the node of each message represents the uid of the user who sent the message) doesn't achieve the goal of my project as I need to know the uid of the user who sent the message each time any user sends a message.
Therefore, please suggest the rules that would help me achieve my task as mentioned in this question
Also, when I applied certain rules on the above structure of database, I couldn't read any data because 'firebase rules are not filters'.
Please ignore the syntax and format of json written in above example as it is just for reference
Please help!
Structure your data so:
"messages" : {
"<receiver_uid>" : {
"msg_1" : {
"text" : "Hello world...",
"uid" : "<sender_uid>"
}
// more msgs for this receiver ...
}
}
the rules should be something like
{
"rules" : {
"messages" : {
"$receiver" : {
".read" : "auth.uid == $receiver", // only receiver shall read
".write" : "auth != false" // any authenticated user can write
}
}
}
}

Firebase Wildcard Paths For Security Rules Not Working? [duplicate]

This question already has answers here:
Restricting child/field access with security rules
(3 answers)
Closed 6 years ago.
I'm using the Objective-C API for Firebase to fetch data and am able to do so when my security rules (set via the Firebase online dashboard) don't utilize any wildcard paths, e.g.:
{
"rules": {
"user" : {
".read" : true,
".write" : true
},
"users" : {
".read" : true,
".write" : false
}
}
}
But when I try enact what should be identical security rules using wildcard paths and fetch objects, the completion handler never executes, e.g.:
{
"rules": {
"user" : {
".read" : true,
".write" : true
},
"users" : {
"$userId" : {
".read" : true,
".write" : false
}
}
}
}
I used the Firebase documentation at the following URL and can't figure out what I'm doing wrong: https://www.firebase.com/docs/security/quickstart.html
I don't think the problem is Objective-C specific, but just to be thorough I'm using the method -[FQuery observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) { }] to fetch my data.
Update: Here's the output of po query for the particularly FQuery I'm using to debug:
(/users {
ep = 0;
i = hidden;
sp = 0;
})
Update 2: Here's my data structure, in case that is relevant:
{
"user" : {
"HhMeloQDY4" : {
"info" : {
"name" : "Anita Borg"
}
},
"QxnjCNOj3H" : {
"info" : {
"name" : "Charles Babbage"
}
},
"zeNalC4ktf" : {
"info" : {
"name" : "Beyoncé"
}
}
},
"users" : {
"HhMeloQDY4" : {
"hidden" : false
},
"QxnjCNOj3H" : {
"hidden" : false
},
"zeNalC4ktf" : {
"hidden" : true
}
}
}
Update 3: Here's my Objective-C code for how I create my FQuery object:
Firebase *firebase = [[Firebase alloc] initWithUrl:#"https://<my-app-name>.firebaseio.com"];
[[firebase childByAppendingPath:#".info/connected"] observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
BOOL isConnected = [snapshot.value boolValue];
// broadcast whether app is connected to Firebase
}];
Firebase *directory = [firebase childByAppendingPath:#"users"];
FQuery *query = [directory queryOrderedByChild:#"hidden"];
query = [query queryEqualToValue:value];
[query observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
// data successfully retrieved from Firebase
}];
You have added read access at the path /users/specific_user_id/ but you're attempting to read at the path /users/, which has no read access allowed.
You'll need to provide access to the path you are attempting to read, not just a subset of its children. See security rules are not filters.
Edit: Just adding some ObjC code to clarify
With the query presented
Firebase *directory = [self.myRootRef childByAppendingPath:#"users"];
you are querying the nodes directly inside the users node. However, if you review the structure, what's inside the users node is not queryable as there are no rules directly under /users, where I have commented.
"users" : {
//OH NOES! There are no rules here!
"$userId" : {
".read" : true,
".write" : false
}
Your rules are inside the $userId, which represents and applies to that parent only
"$userId" : {
//these rules *only* apply inside each userId.
".read" : true,
".write" : false
}
So with your structure, this query would work and it would query the content inside users/HhMeloQDY4 only.
Firebase *directory = [self.myRootRef childByAppendingPath:#"users/HhMeloQDY4"];
So the end result is that you need to assign the rules directly under the /users node that will allow you to query for content within it's child nodes.
"users" : {
".read" : true,
".write" : false
"$userId" : {
}
This would allow you to read each node under users node ($userId and it's children) but not write to them.

How to set a "primary key" in Firebase?

This is the schema I plan to have for my message board app where users can create their own message boards:
{
"boards" : {
"jane-board" : {
// meta information like who created this board
},
"john-board" : {
// meta information like who created this board
},
...
},
"jane-board" : {
// data
},
"john-board" : {
// data
}
}
What would be the rule to ensure that "boards" cannot contain two "jane-board"?
I tried writing a rule but it fails:
{
"rules" : {
".read" : true,
"boards" : {
".write" : true,
"$board_name" : {
".validate" : "!newData.parent().hasChild($board_name)"
}
}
}
}
Since you are using the board's name as the key to store it under, there is already a guarantee that each board name can exist at most once.
It is not entire clear what you're trying to accomplish. But if you are trying to prevent a board's data from being overwritten, you can accomplish that with:
{
"rules" : {
".read" : true,
"boards" : {
"$board_name" : {
".write" : "!data.parent().hasChild($board_name)"
}
}
}
}
Changes for your rules:
I removed the ".write": true from boards. With that rule in place everyone can read all boards, since you cannot take permissions away on a lower level.
I changed the rule to a ".write" rule, since it feels more like preventing a write than validating structure
I check whether the new board already exists in the current data. You were checking in newData, but that doesn't make any sense: the new board will always exist in the new data.

Meteor Accounts via external services dont set user.username

I'm a complete newbie, and I've been fiddling with the Meteor 1.0 sample todo list app to connect google oauth to it.
When I do so the page no longer renders properly because {{username}} is not set at all.
https://docs.meteor.com/#/full/meteor_users says "username: a unique String identifying the user." but the oauth stuff doesn't create one for you.
Connect service to existing meteor account talks about linking an already existing account to another service, but in this case I just want to use the external service.
https://stackoverflow.com/questions/25182903/meteor-facebook-registration uses onCreateUser() to manually set
user.username = user.services.facebook.name
but this isn't portable across services nor to guarantee uniqueness.
https://github.com/aldeed/meteor-collection2 defines the User schema so that username is mandatory.
When I dump the users table (some fields removed) the google account doesn't have a username, and there is no field that can really take on that value automatically as there could be a clash. Email could be used but I'd rather the username wasn't the email address. Do I just force the user to specify a desired username?
meteor:PRIMARY> db.users.find()
{
"_id" : "YNWt2cATMsKFG7od6",
"createdAt" : ISODate("2014-11-05T11:08:00.406Z"),
"services" : {
"password" : {
},
},
"username" : “a_user”
}
{
"_id" : "CyQsJqcez3kWTRHyQ",
"createdAt" : ISODate("2014-11-05T12:09:40.139Z"),
"profile" : {
"name" : “Alice User”
},
"services" : {
"google" : {
"email" : “a_user#example.com",
"family_name" : “User”,
"gender" : “female",
"given_name" : "Alice”,
"id" : "1115",
"name" : “Alice User,
}
}
}
What is the correct way of handling this?
This is how I did it myself with facebook and google
Accounts.onCreateUser(function (options, user) {
if (options && options.profile) {
user.profile = options.profile;
}
if (user.services) {
var service = _.pairs(user.services)[0];
var serviceName = service[0];
var serviceData = service[1];
console.log("serviceName", serviceName)
if (serviceName == "facebook") {
user.emails = [
{"address": serviceData.email, "verified": true}
];
user.profile = {"first_name": serviceData.first_name, "last_name": serviceData.last_name, "avatar": getFbPicture(serviceData.id)};
}
else if (serviceName == "google") {
user.emails = [
{"address": serviceData.email, "verified": true}
];
user.profile = {"first_name": serviceData.given_name, "last_name": serviceData.family_name, "avatar": getGooglePicture(serviceData.id)};
}
}
console.log("user created :", user)
return user;
});
I do not use username but I use email so that I'm sure that it will be unique. After that
I could allow the user to set his username or display name like Stackoverflow or other services do.
However you could use the email as username and again let the user change it later.
In my application, I am using this to handle the same problem.
username = user.services.facebook.name
user.username=generateUsername(username)
generateUsername = function(username) {
var count;
username = username.toLowerCase().trim().replace(" ", "");
count = Meteor.users.find({"profile.un": username}).count();
if (count === 0) {
return username;
}
else {
return username + (count + 1)
}
This is will create a unique username. After successful signup you can allow the users to change the username and check your db for its existence.
In my application I use
if(user.services.facebook)
this.user = user.services.facebook.name
if(user.services.google)
this.user = user.services.google.name

Resources