In Meteor.js, how do one generally give different users (say depending on a field role in the users collection, which can have values like admin, users, testers) different versions of the site (both clientside and serverside)?
First, I am using Iron Router for my routing. At some point I may drop it and write my own, at least for this project, as I am not using half of the features, but for now this is what I have.
For roles I am using the alanning:roles package. Again, I could probably write my own, but it does what I need it too for now, so I am happy with it.
Next I have a custom package I wrote. In it I set up a template for signin and signout, with routes etc. I also provide a utility class that provides a function called authenticationRequired. This function will check if the current user is logged in, and if roles are passed in that they have those roles. The code looks like this:
AccountUtils.authenticationRequired = function(router, roles) {
if (!router) {
throw new Meteor.Error(500, 'Router is a required parameter');
}
if (!Meteor.loggingIn()) {
var currUser = Meteor.user();
if (currUser) {
if (roles) {
if (!Roles.userIsInRole(currUser, roles)) {
console.log('User was not in the required roles: %j, %j', currUser.roles, roles);
//throw new Meteor.Error(403, 'Access Denied');
Router.go('notAuthorized');
}
}
} else {
console.log('User not found');
Router.go(AccountUtils.settings.signinRoute);
}
}
}
Now, in my Router router I do something like:
this.route('admin', {
path: '/admin',
onBeforeAction: function() { AccountUtils.authenticationRequired(this, ['ADMIN']); }
});
There are a few moving parts around all of this, but that's the jest of it. In the onBeforeAction of the route, check if the user has the required role. If not, send them to the notAuthorize page. If they are, let them through. I am still missing a few pieces that I haven't worked out yet in this code, but it works for the most part.
Hope that gives you a jumping-off point.
Related
Is there any way I can trigger a policy on a specific request method (e.g. DELETE) rather than on specific routes?
I'd imagine something like this:
module.exports.policies = {
'DELETE *': 'isAdmin'
}
My goal here is to expose the blueprint api to admins only, so that I can keep it in production, as it's a very useful tool for allowing third party scripts to add extra functionality.
I'm on Sails 1.0 right now.
One way to do that might be to add the check for the request method to the actual admin policy, however that doesn't quite seem like the best solution to me.
You can override the blueprint for all models for a particular method. You can do this for DELETE by creating a file destroy.js in /api/blueprints/ and then adding your code for what you want to do when a DELETE comes through:
module.exports = function(req,res, next) {
if(ACLService.hasPermission(req.user.acl, 'admin')) {
//Ok to allow delete here
} else {
return res.unauthorized();
}
};
This is how I've done it in the past, but looking at the docs for the just released SailsJS 1.0:
https://sailsjs.com/documentation/reference/blueprint-api
You may need to add this hook for overriding blueprints in 1.0
https://www.npmjs.com/package/sails-hook-custom-blueprints
Here is one method that you can use, I am not claiming that it is the right way, but you can consider it:
You can write your own hook. How to do this: https://sailsjs.com/documentation/concepts/extending-sails/hooks/project-hooks
Basically here is the solution with a hook:
1 Create a hooks folder under your api folder.
2 In the hooks folder create another folder - the name will be the name of your hook (say my-hook).
3 In api/hooks/my-hook create a file index.js and in it put the following code:
module.exports = function myHook(sails) {
return {
routes: {
before: {
'/*': function (req, res, next) {
if (req.method.toUpperCase() === 'DELETE') {
return sails.hooks.policies.middleware.isadmin(req, res, next); // note - your policy function name must be called here with all lowercase, otherwise it will not work.
}
return next();
}
}
}
};
};
Then in your isAdmin.js policy you can check if your user is an admin and if not:
return res.forbidden();
if it is admin:
return next();
What is wrong with my code. Meteor.users.findOne() or Meteor.user.find() only works in Chrome. Does not work in Firefox nor Safari -- Im on a Mac. My error is:
TypeError: Meteor.users.findOne(...) is undefined
I want to have a user profile so upon registration, the profile field is created using Reactjs:
Registration component (Client):
Accounts.createUser({
...
profile: [] //later you'll see why for this.
});
Server:
// We us this to add more fields to a user registration:
Accounts.onCreateUser(function(options, user) {
user['regtype'] = options.regtype,
user['profile'] = options.profile
return user
});
Meteor.publish(null, function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'regtype': 1, 'profile': 1}});
} else {
this.ready();
}
});
The error comes from my Profile.jsx:
...
mixins: [ReactMeteorData],
getMeteorData() {
return {
currentUser: Meteor.user(),
};
},
getInitialState(){
profile = Meteor.users.findOne().profile; // the error is here
return{name: profile.name}
}
...
Strangely if I console.log(Meteor.user.find()) it shows as undefined. But works great in Chrome only. I have not tried MS Edge.
According to meteor docs
The basic Accounts system is in the accounts-base package, but
applications typically include this automatically by adding one of the
login provider packages: accounts-password, accounts-facebook,
accounts-github, accounts-google, accounts-meetup, accounts-twitter,
or accounts-weibo.
So If you have the accounts-base package you'll get the Meteor.user() and Meteor.users() functions. Check your .meteor/packages file for account-base package. Add if it is not listed in there.
Here is my answer to this. I hope others find this very useful. Basically I wish to bind the input fields with user's data (profile fields). Upon user registration, I add a profile field and set it to an empty array: profile: []
I need not to use getInitialState() but instead use getMeteorData(). With same code, I need to move the component's render in a function and render that:
...
renderedComponent(){
let instance = this;
render(<div><input value={instance.data.currentUser.profile.name} /></div>)
},
// we now wait until currentUser is loaded before calling the render function
render(){
return(<div>{this.data.currentUser? this.renderedComponent() : <p>Loading...</p>}</div>)
}
...
To sum up, nothing is wrong with firefox or any part of my code. When working with Meteor and React, this is the best approach.
Basically the objective is render the account page if user is logged in, otherwise redirect to a login page.
I have the following routes:
App.Router.map(function() {
this.resource('account', { path: '/'});
this.route('login', { path: '/login' });
})
My current code tries to access a loggedIn attribute in the account controller in the route:
App.AccountRoute = Ember.Route.extend({
renderTemplate: function(controller) {
var loggedIn = controller.get('loggedIn'); // ERROR: controller undefined
if (!loggedIn) {
this.transitionTo('login');
}
}
});
Should I implement this logic in the router? Why is the controller undefined in my route? Thanks!
Here are a couple ideas that might help you:
Your controller does not always exist. It is created by Ember when it needs it the first time. You can use the Chrome extension for Ember debugging to see which controllers are already created. In your case it should be available though since you are in the renderTemplate hook. In general, redirects should be done either in the beforeModel hook or the redirect hook:
redirect: function () {
if (!this.controller.get('loggedIn')) {
this.transitionTo('login');
}
}
Consider moving the authentication logic into an Ember service (example). A service in Ember is simply a class that extends Ember.Object. You will have the ability to inject that service into all your controllers and routes so it will be always available.
Even better: consider using the excellent ember-simple-auth that handles both authentication and authorization. It will create a session service available everywhere in your app, so you will be able to do things such as:
// Ensures the user is authenticated
if (!this.get('session.isAuthenticated')) {
this.transitionTo('login');
}
Or even better (since you don't want to copy paste that stuff everywhere):
// This route is now authenticated!
App.AccountRoute = Ember.Route.extend(AuthenticatedRouteMixin, {
...
}
And many other cool things!
Also, I see that you are not using Ember CLI yet. I'd recommend it once you feel more comfortable with Ember. Ember CLI is the future of Ember, it comes with a slightly different syntax but lot of great things.
i am following Discover meteor book and in one of the chapter author taught me to use Meteor.logginIn() method.
The goal is Simple, there is a submit page for new post and if there are no users logged in, it should display access denied template, else it should display post submit template. But if the user is loggin in or in wait state it should display loading template. I followed the tutorial and did what was told, the code looks exactly the same as the book. But, when user is logged in it rightfully display the submit page and when user is logged out it should display the access denied page but instead shows the loading template, while showing loading template if i log in, then it also display the submit page. The only trouble is while it should display the access denied, it is showing loading template.
Here is the routing code
Router.route('/submit', {name: 'postSubmit'});
var requireLogin = function(){
if(!Meteor.user()){
if(Meteor.logginIn()){
this.render(this.loadingTemplate);
}else{
this.render('accessDenied');
}
}else {
this.next();
}
}
Router.onBeforeAction('dataNotFound', {only:'postPage'});
Router.onBeforeAction(requireLogin, {only:'postSubmit'});
Eh, that code could use a little help. Like for this instance, we are going to separate concerns and call them in different hooks
First, keep in mind that every time you call onBeforeAction it adds that function to an array of and called sequentially. Although it's always a good practice not to actually depend on the sequential order, but it's great to keep in mind.
var userLoggingIn = function() {
if(Meteor.loggingIn()) {
this.render(this.loadingTemplate);
} else {
this.next(); // Done logging in? You may pass.
}
}
var requireLogin = function() {
if(!Meteor.user()) {
this.render('accessDenied');
} else {
this.next(); // User is logged in. I'll allow it.
}
}
// finally, use it.
Router.onBeforeAction([userLoggingIn, requireLogin], {only: 'postSubmit'});
Router.onBeforeAction('loading'); // built-in hook, just like dataNotFound. use in Routes with subscriptions.
Router.onBeforeAction('dataNotFound', {only:'postPage'});
Isn't that way better, and manageable? I'm not sure which order it's going to execute as I'm travelling and just typing this out at the airport.. but you can try swapping the hooks order around if it's not behaving accordingly.
I've run into an annoying issue when loading data asynchronously in an ember route's model callback. The issue seems to be that if the model method of my route returns a promise which is rejected then the route will never attempt to re-evaluate that route model. It just automatically returns the same rejected promise the next time it tries to go to that route without even trying to re-fetch the data!
I understand from this answer that an ember route will only call it's model method when trying to convert the url into a model. I'm guessing that in the case of routes with dynamic segments it may be called if it has never encountered that particular dynamic segment before.
Here is what I've got in my router setup.
window.App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true
});
App.Router.map(function() {
this.route('login');
this.resource('users', { path: '/users' }, function() {
this.resource('user', { path: '/:user_id' });
this.route('create', { path: '/create' });
});
});
And this is my route.
App.UserRoute = Ember.Route.extend({
model: function(params) {
// This returns a promise
return App.User.fetch(params.user_id);
}
});
I have some special handling for errors in my application route so that routes which fail due to authentication exceptions redirect the user to the login screen.
App.ApplicationRoute = Ember.Route.extend({
actions: {
sessionExpired: function() {
this.controllerFor('login').set("tokenExpired", true);
this.transitionTo('login');
},
error: function(err) {
if (err.type === "TokenException") {
this.send('sessionExpired');
}
}
}
});
The Problem
I navigate to the /users route
For some reason my token expires (inactivity, whatever...)
I navigate to the /users/1 route
The route's model method returns a promise which rejects and I am kicked out to the login screen
I log back in and try to navigate back to the /users/1 route
The route automatically just returns the same failed promise it did last time and I'm kicked out to the login screen. :(
I'm thinking that what I want is some way to clear all the evaluated route models after a user logs in. If this was a multi-user system and one user logs out and another user logs in on the same computer without refreshing the page then that new user shouldn't have routes automatically resolved from the previous user's session.
This seems to me like it would be a common problem yet I can't find any sort of app-wide invalidate cache method. How should I solve this?
I'm not sure where ember data stands on the cache clearing feature, but here is one way to do it
clearCache: function (type) {
var map = App.store.typeMapFor(type);
map.idToCid = {};
map.clientIds = [];
map.recordArrays = [];
map.findAllCache = null;
}
And here is an example as to how the ember firebase library handles a fail find using cache clearing.
delete store.typeMapFor(store.modelFor('user')).idToRecord[username];
Full example here:
https://github.com/firebase/emberFire/blob/master/examples/blog/js/app.js
For anyone else who finds this - I never found a way to reset the ember application and cause it to forget all resolved routes. I did find a few other work-arounds.
In the end, I opted to just window.reload() any time that a user logged out of the system or had their authentication token expire.
Authenticated URLs
Another reasonable approach would be to put a random unique id in the hash state. Essentially just do this.
Instead of a route like:
#/contacts/1
prefix every authenticated route with some kind of unique id
#/PyUE4E+JEdOaDAMF6CwzAQ/contacts/1
App.reset
I tried tried a number of things. One of the more promising things I tried was redirecting to the login screen and using the Application's reset method on my global App object. http://emberjs.com/api/classes/Ember.Application.html#method_reset
That didn't work though, it seems that even a reset Application remember's the models of any routes that it has resolved - weird.