Meteor 1.3 Routing Redirect best practices?

Hi,

I’m working on simple Meteor 1.3 Mantra project.
Currently I’m implementing the routing logic and have some questions on best practices.

I want to redirect a user when accessing a route and having insufficient access rights.

This is my approach:

A function that can be called from every route and checks the privileges:

const can_view = function(routename, redirect){
  // check permissions here
  redirect('/login');
}

A common route for a module:

import React from 'react';
import {mount} from 'react-mounter';
import {can_view} from '/lib/access-control';
import App from '../core/components/App.jsx';
import {MainPage} from '../core/containers/index.js';

export default function (injectDeps, {FlowRouter}) {
  const AppLayout = injectDeps(App);

  FlowRouter.route('/posts', {
    name: 'post.list',
    triggersEnter: [function(context, redirect) {
      can_view('post.list', redirect);
    }],
    action() {
      mount(AppLayout, {
        content: () => (<MainPage />)
      });
    }
  });

  FlowRouter.route('/posts/:postId/edit', {
    name: 'post.edit',
    triggersEnter: [function(context, redirect) {
      can_view('post.edit', redirect);
    }],
    action({postId}) {
      mount(AppLayout, {
        content: () => (<MainPage postId={postId}/>)
      });
    }
  });
}

Do you think this a good approach? Are there any best practices?

I think it’s wird that no nether the Meteor guide nor the Mantra specs have some explanation or examples on this topic. Or am I totally wrong?

regards and thanks

Janik

1 Like

Arunoda advocates for redirects inside of the templates (or container components), although many in the community do not agree.

As for mantra, all business logic should be handled by actions.

Therefore, you’d probably have to pass a handleCannotView prop to the container, then inside componentWillMount perform the check (you’ll probably need to pass the user object as a prop as well).

You can then call the action handleCannotView in which your redirect function called.

ok thanks, makes sense. But why not make the action call from the routes.jsx (I have one for every module).

However I tried your approach, but it does not work. I think you cannot redirect to another route while mounting the component.

I tried:

componentWillMount(){
    FlowRouter.go('/login');
  }

and it only works when I used the redirect inside componentDidMount.

So heres my new idea, it might be a bit sketchy, but let me explain :slight_smile:

routes.jsx

FlowRouter.route('/posts', {
    name: 'post.list',
    triggersEnter: [function(context, redirect) {
      actions.posts.read('post.list', redirect);
    }],
    action() {
      mount(AppLayout, {
        content: () => (<MainPage />)
      });
    }
  });

Import the action in the route and call the read function (part of CRUD).

It just feels weird to do a redirect in component, because in some case you don’t want show the component and of course not let it load data from a container.

client/modules/post/actions/posts.js

read({Meteor, FlowRouter}, routename, redirect) {
    if(!can_access(routename)){
      redirect('/posts');
    }
  }

Here is the logic and redirect. The can_access function comes from my access_control library.
Why did name the function read? Because usually there are CRUD functions in my actions file. However data is loaded within the container so the read function is available for other logic like this.

/lib/access_control.js

const groups = [
  {
    "name": "post.insert",
    "roles": [
      "Admin",
      "Author"
    ]
  },
  {
    "name": "post.update",
    "roles": [
      "Admin",
      "Author"
    ]
  },
  {
    "name": "post.remove",
    "roles": [
      "Admin",
      "Author"
    ]
  },
  {
    "name": "post.read",
    "roles": [
      "Admin",
      "Author",
      "Manager",
      "Public"
    ]
  },
  {
    "name": "node.delete",
    "roles": [
      "Admin",
      "Manager"
    ]
  },
  {
    "name": "node.insert",
    "roles": [
      "Admin",
      "Manager"
    ]
  }
];

export function can_access(routename){
  // check role membership
  return true;
};

export function is_allowed(action){
  // check role membership
  throw new Meteor.Error("permission-denied", "Insufficient rights for this action.");
};

Here I’ll bundle everything that is related in someway with permissions and access rights. The functions in this library are either used by the client or the server. F.g. the is_allowed function is used in server methods before validating the parameters. I will use alanning roles to check role membership later on.

When assigning and checking for permissions with user, groups and rules I always use this schema:

Object <-> Access Group <-> Role <-> User

What do you think? Total bullshit or a good start?

I think one advantage for using high level container to store some global state(e.g: auth ,etc) is you don’t need to trigger it for every child components. but if you are doing it for just one or few component, I think what you do is totally make sense