I'm populating an app using MeteorJS. Now there's one security issue that I need to fix is that in my app, an anonymous user can freely use this:
Meteor.users.find().fetch()
...in the console to get all the current users' information. And I also have the packages insecure & autopublish removed.
When I publish this users collection from the server, I did this:
Meteor.publish 'users basic info', ->
Meteor.users.find {}, fields:
"emails" :1
"profile" :1
But when any clients retrieve the data using Meteor.users.find().fetch(), they got more fields than what I restricted, for example position, roles etc...
Hope that you guys can help me with this. Thanks so much in advanced !
Your publish function is returning all user documents to the client as you have an empty object as the query, which is why the client can see all user data. You need to do the following (apologies, but I don't use CS):
Meteor.publish('users basic info', function() {
return Meteor.users.find({
_id: this.userId
}, {
fields: {
profile: 1,
emails: 1
});
});
Note that this.userId is a constant as opposed to a reactive variable, but the whole publish function reruns when the logged in user changes (including from null), so this should work fine.
Are the position, roles, etc. fields not subfields of profile? If they're not and you have no other publish function for user data (and autopublish is removed), then it's really not clear why you can see those fields on the client.
Related
Using this :
firebase.auth().onAuthStateChanged(function(user){
if (user){
Firebase is returning the user, which includes a userID.
When a webpage get this object it takes the user ID, and need to access again the DB records to check some very basic stuff ( if it's a paid user, or another simple key) in order to choose the UI.
Is there away to return in this callback the user including some predefined basic info about him?
If you want to add some extra info to a user account you cannot use the User object, which has a fixed list of properties.
There are two classical approaches:
1. Create a specific User record
You create a Firestore document (or a Realtime Database node) for each user. Usually you use the User's uid as the id of the Firestore Document/RTDB node.
This means that you will need to fetch the database to get this extra user info, e.g.
firebase.auth().onAuthStateChanged(function(user){
if (user) {
firebase.firestore().collection('users').doc(user.uid).get()
.then(...)
}
//...
}
2. Use Custom Claims
If you need to store only a limited number of extra information in order to provide access control, you could use Custom Claims. This is particularly adapted for storing user roles, e.g. paid user.
Custom user claims are accessible via user's authentication tokens, therefore you can use them to modify the client UI based on the user's role or access level. See more details here in the doc.
To get the Claims in the front end, you can do as follows:
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
user.getIdTokenResult()
.then((idTokenResult) => {
// Confirm the user is a paid user.
if (!!idTokenResult.claims.paidUser) {
// Show admin UI.
showPaidUserUI();
} else {
// Show regular user UI.
showRegularUI();
}
})
.catch((error) => {
console.log(error);
});
}
//...
}
It is important to note the following section in the doc:
Custom claims are only used to provide access control. They are not
designed to store additional data (such as profile and other custom
data). While this may seem like a convenient mechanism to do so, it is
strongly discouraged as these claims are stored in the ID token and
could cause performance issues because all authenticated requests
always contain a Firebase ID token corresponding to the signed in
user.
Use custom claims to store data for controlling user access only. All other data should be stored separately via the real-time database
or other server side storage.
Custom claims are limited in size. Passing a custom claims payload greater than 1000 bytes will throw an error.
I am having a problem with Roles in my application when I reload the page. If I use the buttons and so on and navigate like you should on a website the problem is not there.
But what is the problem? In my meteor template, I should show a different view for admins and normal users, so in my template onRendered function I check the role and react on that. Here follows the code:
/* Predefined changes to current html are done in this function!*/
Template.Planning.onRendered(function () {
var userid = Meteor.userId();
if (Roles.userIsInRole(userid,'admin', Roles.GLOBAL_GROUP)){
//do something
}
});
For a specific reason the code inside the if-block is not executed when I am logged in as admin but reload the page. It does work when I visit it using the navigationbar, so I guess the Roles are not yet loaded when the onRendered-function is called. How can I fix this issue?
The package alanning:roles works in a way, that it "autopublishes" the roles definitions to the client, so that you are able to have them available "immediately".
Immediately means in this case, when their subscription is ready. Until then the function Roles.userIsInRole will not return anything truthy.
In order to check of they are available use the reactive Roles.subscription.ready() method.
If you have trouble with reactivity in onRendered you may check for the roles in autorun of onCreated.
For example:
Template.Planning.onCreated(function () {
const instance = this
instance.state = new ReactiveDict()
instance.autorun(() => {
if (Roles.subscription.ready()) {
var userid = Meteor.userId()
if (Roles.userIsInRole(userid,'admin', Roles.GLOBAL_GROUP)){
instance.state.set('isAdmin', true) // reactive data source
}
}
})
})
Using it in a router
You can even use this method on the router level in order to "wait" for all the roles to be loaded. This makes sense especially when your client routing logic makes heavy use of roles to manage access.
Note on Security
Don't forget, that this is just UI Candy. Roles (as well as routes) on the client can be bypassed. Re-check every method call and subscription that are sensitive twice in side the methods / publications using Roles.userIsInRole.
When a person signs up (accounts entry) it takes them to a page called "dashboard". I want it so that after they sign-up if it is the first time (for that account) that they are seeing the page it will show some sort of welcome message. Is this possible?
Thanks.
Update:
I tried this:
Accounts.onCreateUser(function(options, user) {
console.log('New account created!');
});
But it gave:
Exception while invoking method 'entryCreateUser' Error: insert requires an argument
I am using Accounts Entry. Is there any fix for this?
Without knowing more about your app it's difficult to advise on the best way for you to do this. But here are three possible approaches.
If you are creating your own signup/login events, just route to a 'welcome' route/template on signup event, and 'dashboard route/template on login.
If you are wanting to use default accounts-ui, you can use the Accounts.onCreateUser hook server side to add {'isNewUser' : true} to the user account document. Then check for this property client side to decide what template to display.
Or you can try wrapping Accounts.createUser to include the extra logic you require client side to go to your welcome route/template rather than the dashboard.
The simplest way is to set a session variable then use a helper in your template to key off of that:
In your new account code:
Session.set('isNewUser',true);
Router.go('dashboard')
HTML:
<template name='dashboard'>
{{#if newUser}}Welcome!!{{/if}
... rest of your template ...
</template>
js:
Template.dashboard.helpers({
newUser: function(){
return Session.get('isNewUser');
}
});
You'll also need code to later either delete that Session variable or set it to false.
I have an app I am working on that has one or two "main" accounts linked to other accounts. I am using accounts-password, accounts-google, and alanning:roles currently, but I may add other OAuth providers later.
The idea is that there will be one or two users that are "admins" for a larger group, and as such I'd like to be able to allow them to add users. I don't mind if I have to go through the OAuth authorization page on first login, but I would like to be able to add the users to the Meteor.users collection and allow the admins to set things up prior to their logging in, but I am not sure how to do it at all. Can I simply add a user to the Meteor.users collection with only the email attribute of the google sub-document populated? I would guess that the email is NOT what Meteor uses to connect a stored Meteor.users document to the corresponding Google OAuth account as there is also an id attribute that seems like it might be more useful for that purpose.
So based on what you say on the comment i elaborate this DEMO, since you want to create users and add that users to some kind of "role" or "group" im using here the meteor-roles package.
allow people to create a group of users
This could be done on some differentes ways, for the demo purpose im creating 1x1 user and leater assign them a Role.
to avoid the autologin behavior you should use a Meteor.method.
//server
Meteor.methods({
createSimpleUser:function(email){
return Accounts.createUser({
email:email,
password:"test123" //you can force the user in the first login to change the password.
})
}
})
//Client
Meteor.call('createUser',"test#gmail.com")
Now that you have the user created, you should assign them a role.
So on some event handler you want,do something like this.
//this should be incide an {{#each}} or {{#with}} in order to this._id works, if not use Sessions
Meteor.users.update({_id:this._id},{$set:{group:newGroup}})
This is just the global idea, you should protect in the allow rules, who can edit the users group, you can also use template helpers like
{{if userIsInRole 'nameGroup'}}
<!-- Show content only available to users in this group -->
{{else}}
<!-- Some warning access denied template -->
{{/if}}
This process will add some stuff to the profile of the user so the app
can associate all the users together
Filter by groups.
Template.example.helpers({
groupX:function(){
return Meteor.users.find({group:"X"})
}
})
when they first login without erasing the stuff I setup for them prior
to them signing in.
Here again there could be many reasons, for example you can add some field inside the current user created.
Meteor.methods({
createSimpleUser:function(email){
return Accounts.createUser({
email:email,
password:"test123",
firstLogin:false //by default the users login is false.
})
}
})
and in the iron route, create a function(why on the router? see Overworked helpers on the David Weldon webpage).
requireEdit = function(){
var query = Meteor.users.findOne({_id:this.params._id})
if(query.firstLogin == false){
this.render('profileEditin') //do some change password here and other stuff
}else{
this.next()'
}
}
And call it on the onBeforeAction() method
This is just the idea and demo is a ugly-fast example of how it should work, good luck
I doing the following. Adding the current users id as userId in a collection as the author. I would like to be able to access other information in that users (not necessarily the logged in users but could be) so that I could display a profile page. The only work around I have found is to just duplicate the fields from the profile and place them in the collection.
This seems to be less than ideal. Is there a way to access this directly? I am using autopublish so I don't think there should be any permission issues. I am also using iron-router so ideally I would have a route set up like:
Router.map(function() {
...
this.route('profile', {path: '/profile', data: function() { this.params._id}});
});
If you've got autopublish on, then Meteor.users.findOne({_id: 'USERID'}) will give you that user's profile. Obviously, if you turn off autopublish you'll have to work out what information to publish.