I'm trying to create an access control system at the document level in Meteor, and I seem to be missing how to prevent users from fetching documents.
I've read the documentation around collection.allow and collection.deny. Via these objects we can control "who" can update, remove and insert. The problem is that Meteor doesn't seem to supply similar functionality for fetch operations. What is proper way to deny unauthorized users from reading documents?
Extra requirement: this needs to happen server side so we don't leak documents to unauthorized users over the network.
There is no way to deny reads to collection data once it has arrived on the client. In theory, there's no way to actually enforce anything on the client because the user could modify the code. You can, however, enforce which documents get published.
A publish function can handle authorization rules of arbirtary complexity. Here's a simple example where we want to publish documents from the Messages collection only to users who are members of the given group:
Meteor.publish('messagesForGroup', function(groupId) {
check(groupId, String);
var group = Groups.findOne(groupId);
// make sure we have a valid group
if (!group)
throw new Meteor.Error(404, 'Group not found');
// make sure the user is a member
if (!_.contains(group.members, this.userId))
throw new Meteor.Error(403, 'You are not a member of the group');
return Messages.find({groupId: groupId});
});
1) Remove autopublish package from your app.
2) Create your own publish and deny access to unauthorized users
Meteor.publish("userData", function () {
if (this.userId) { // Check user authorized
return MyCollection.find(); // Share data
} else {
this.ready(); // Share nothing
}
});
Related
I'm working on a medium-sized website and I figured that I need to write more maintainable code with better project structure.
I found this article and a few others describing basically the same idea about 3-layer architecture
I find it great as I wasn't using the service layer before and It helped to DRY the code
but the article doesn't include anything about validation and how it should be handled. especially when validation is done against database (like checking resource existence)
Should I do the validation in the service layer or write validation middleware (which will need to access database and I think this is against the pattern described)
for example, I ended up with two API endpoints where website users update and remove their already added DeliveryAdddress. As you can see below validation in the service layer led to routes having duplicated code to handling HTTP response.
my routes files
router.put('/delivery-addresse/:id', DeliveryAddressesController.update);
router.delete('/delivery-addresses/:id', DeliveryAddressesController.remove);
DeliveryAddressesController
async update(req, res){
try {
....
await AddressesService.updateDeliveryAddress(userId, address);
....
} catch(error){
if (error instanceof ValidationError){
if (error.name === 'NO_SUCH_ADDRESS'){
return res.status(404).json(error);
}
}
....
}
},
async remove(req, res){
try {
...
await AddressesService.removeDeliveryAddress(userId, addressId);
....
} catch(error){
if (error instanceof ValidationError){
if (error.name === 'NO_SUCH_ADDRESS'){
return res.status(404).json(error);
}
}
....
}
},
Options
I could think of these options but not sure if they're good and which one.
Validation middleware before the controller which (the middleware) itself will call the
validation method in the service.
I think it's a good option but, maybe I
will end up with multiple database calls to fetch the resource if I don't store the result
in the req object
Using a function in the catch block to check for any ValidationErrors and respond but it's not a great readable way.
The first question you need to ask is what credentials I should write for ... The middleware layer is between the two layers of operation and application, and it is very important what kind of validation you want to do ... For example, if you want two communication services on a network, Establish a key for it and you need a validator to check every key in each service. In this case, it is better to send this key in the header and validate it in middleware, but sometimes you want to evaluate information from other services or even from the database, which requires several validation steps. It is better to implement them all in your services layer! I suggest option 2 for this 😉
I am building an app with Firebase that requires private messaging between users.
What I need is to build single chats for 1-1 chats, storing messages in Firestore.
My idea: I guess the best way is to build a single collection for each chat, with the right security rules. Let's say an user with tokenID 1234 wants to talk with user with tokenID 1111, a new collection called 1234_1111 will be created (if not existing), and the security will allow only these two users to read and write.
My question: Is it the right way? And how to do that in Javascript? I'm not sure how to define security rules directly in the JS code, neither how to create a collection with the two users ID.
Security Rules are not defined in your JavaScript code, they are defined separately. What you suggest would be a reasonable approach to take although I'd use a subcollection for it, and a simplified version of your security rules might look something like:
service cloud.firestore {
match /databases/{database}/documents {
match /dms/{idPair}/messages/{msgId} {
allow read, write: if
idPair.split('_')[0] == request.auth.uid ||
idPair.split('_')[1] == request.auth.uid;
}
}
}
Then in your JS code you could do something along the lines of:
// generate idPair
function dmCollection(uid) {
const idPair = [firebase.auth().currentUser.uid, toUid].join('_').sort();
return firebase.firestore().collection('dms').doc(idPair).collection('messages');
}
// send a DM
function sendDM(toUid, messageText) {
return dmCollection(toUid).add({
from: firebase.auth().currentUser.uid,
text: messageText,
sent: firebase.firestore.FieldValue.serverTimestamp(),
});
}
// retrieve DMs
function messagesWith(uid) {
return dmCollection(uid).orderBy('sent', 'desc').get();
}
Note that the idPair is constructed by joining a sorted pair of UIDs so that it will be stable no matter which user sends.
New to Meteor, I'm using the alanning:roles package to handle roles.
I've managed to be able to only publish a collection when a user is logged in, when the page is first refreshed.
Meteor.publish('col', function(){
if (Roles.userIsInRole(this.userId, 'admin')) {
console.log('authed');
return Sessions.find({});
} else {
console.log('not auth');
// user unauthorized
this.stop();
return;
}
});
Logging out kills access to the collection (I'm using mongol to see). Logging back in after logging out, or logging in from a logged out state when the page is first loaded, will not give me access.
The webapp I'm trying to build is something like an ticketing system. I'm trying to be secure, so no unnecessary publishing if the user is not authorized.
What I'm trying to do is, get ticket information submitted from users from a collection, and display it on the client screen (as long as the client is authorized first). Maybe a better way to handle this is to force a refresh (how do I do that?) after a user change so unauthorized users are "kicked" out? And render all relevant data from the private collection right after the user is authorized?
I actually managed to get what I want for now with helpers...
In my ./client/Subs.js file:
Meteor.subscribe('col');
Template.NewTicket.helpers({ // match the template name
// move this into method later, not secure because on client??
isAdmin() {
console.log('is admin.');
console.log(Meteor.userId());
Meteor.subscribe('col');
return Roles.userIsInRole(Meteor.userId(), 'admin');
},
});
and then somewhere in my template file ./client/partials/NewTicket.html:
<template name="NewTicket">
{{#if isAdmin}}
{{/if}}
</template>
to trigger the check? I'm 99% sure theres a better way.
Is it possible to check users role on Sign in, and than if user is in role "admin" to display one page, and if is it in role "basic-user" to display another page ( go to another route).
Lets have a look at the Routes section of the documentation for the useraccounts:iron-routing package.
this should solve your problem
AccountsTemplates.configureRoute('signIn', {
redirect: function(){
var user = Meteor.user();
if (user && Roles.userIsInRole(user, ['admin'])) {
Router.go('admin');
}
else {
Router.go('home');
}
}
});
Be careful to check you can access the user roles field from the client side: lets check the allanning:roles official documentation
To define a default role for a user I use this:
// server
Accounts.onLogin(function(user) {
var user = user.user;
var defaultRole = ['student'];
if (!user.roles){
Roles.addUsersToRoles(user, defaultRole)
};
})
I'm using meteor-useraccounts and alanning meteor-roles packages and that work fine for me.
If I'm not outdated (and a look at http://docs.meteor.com/#/full/meteor_users suggests I'm not) there is no built in way for user roles. There should be some extensions for that task with which you could go and depending on what you choose you would have to check their documentation.
However it's not very hard to implement a own simple roles logic in Meteor:
First in your Accounts.onCreateUser function give your users object a new attribute role and assign them to a default role. If you don't have a Accounts.onCreateUser yet create one server side. It could look like something like this:
Accounts.onCreateUser(function(options, user) {
// Add an user roles array
user.roles = ["default-user"];
if (options.profile)
user.profile = options.profile;
return user;
}
Next you would need to implement some logic to add "admin" or whatever you like for trusted users to their roles array. That's up to you and for the beginning if you don't have dozens of admins you could also choose to do that manually in your MongoDB.
Now make sure you publish the new attribute of your user object to the currently logged in user. To do so make use of Meteor.publish with null as first parameter to address the current user like so:
Meteor.publish(null, function () {
return Meteor.users.find({_id: this.userId}, {fields: {
'roles': 1,
'profile': 1, // You probably want to publish the profile of a user to himself
// And here would be any other custom stuff you need
}});
});
And with that you are already in a state where you can do individual styling or routing client side. For example you could do something like this:
if (Meteor.user().roles.indexOf("admin") > -1) {
// Route for admins!
}
You could also parse through your array and add the user roles as class to your body element to for example only show certain elements to admins. This could be done this way:
Meteor.user().roles.forEach(function(role){
$('body').addClass(role);
});
Note that this will only be "cosmetic" but you can also implement real security with that as long as you do it server side. So if you want a Meteoer subscription or Meteor method only to be available for admins add something like this to it:
var requestingUser = Meteor.users.findOne({ '_id': this.userId});
if (!_.contains(requestingUser.roles, "admin")) {
// Terminate the pbulish function or Meteor method here when there is no "admin" role
return;
}
As said, this only works sever side and should be at the start of Meteor.publish functions or at the start of functions within Meteor.methods blocks.
I am not too familiar with LDAP, however I am working on authentication in a Node.js app, and the user credentials for the web app is going to be gotten from the organization's Windows domain.
I have LDAP lookups working (using the Passport.js Node module), however to make it work, I have to put the user's full-fledged DN into Node. For example, let's say:
My FQDN is mydomain.private.net.
My users are stored in an organizational unit, let's say staff.
Now, if I want to lookup user joe, I have to put this string into Node:
var username = 'CN=joe,OU=staff,DC=mydomain,DC=private,DC=net';
do i really have to keep track of all that?
What if my users are in two different organizational units? The client-side browser doesn't know that! It just knows:
username = 'joe';
password = 'xxxxx';
What if you try to log on as administrator? Administrators are always in a totally different OU by default.
Is there a way to reference an LDAP object by just the name and nothing else?
This is a general LDAP problem. You need to get a unique identifier from the user, and then look for it.
Typically this is what the uid attribute is used for. Active Directory may or may not have that populated, and generally relies on sAMAccountName which must be unique within the domain.
So you need a two step process.
1) Query for uid=joe or samAccountName=joe
2) Use the results to test a bind or password compare.
You would then use the DC=mydomain,DC=private,DC=net value as the root to search from.
(answer to my own question)
geoffc's answer was correct, and this is the working solution adapted to my Node.js app using the activedirectory npm module:
// First search for the user itself in the domain.
// If successfully found, the findUser function
// will return the full DN string, which is
// subsequently used to properly query and authenticate
// the user.
var AD = self.ADs[domain];
AD.findUser(username, function(err, user) {
if (err) {
cb(false, 'AD error on findUser', err);
return;
}
if (!user) {
cb(false, 'User does not exist', void 0);
} else {
username = user.dn;
AD.authenticate(username, password, function(err, authenticated) {
if (authenticated == false) {
cb(false, err, void 0);
return;
} else {
cb(true, 'Authenticated', void 0);
}
});
}
});