<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Acl on Janik von Rotz</title>
    <link>https://janikvonrotz.ch/tags/acl/</link>
    <description>Recent content in Acl on Janik von Rotz</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Fri, 12 May 2017 09:46:48 +0000</lastBuildDate>
    <atom:link href="https://janikvonrotz.ch/tags/acl/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>The most simple access control for your Meteor React app</title>
      <link>https://janikvonrotz.ch/2017/05/12/the-most-simple-access-control-for-your-meteor-react-app/</link>
      <pubDate>Fri, 12 May 2017 09:46:48 +0000</pubDate>
      <guid>https://janikvonrotz.ch/2017/05/12/the-most-simple-access-control-for-your-meteor-react-app/</guid>
      <description>&lt;p&gt;For my last Meteor React app I&amp;rsquo;ve designed the most simple role based access control. The basic idea is that users can have multiple roles and every action possible is only allowed by a specified set of roles. For my Meteor React app the following scenarios were considered:&lt;/p&gt;&#xA;&lt;p&gt;Only users with specific roles are allowed to&amp;hellip;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;call a Meteor method.&lt;/li&gt;&#xA;&lt;li&gt;subscribe to a publication.&lt;/li&gt;&#xA;&lt;li&gt;display React components in the UI.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;To solve this problem a role based access control (RBAC) has been implemented:&lt;/p&gt;&#xA;&lt;p&gt;user -&amp;gt; roles -&amp;gt; action -&amp;gt; permission for publication / method / component&lt;/p&gt;&#xA;&lt;p&gt;Now you might miss the url access control as a scenario, why did I ignore it? Well, the answer is simple, a user won&amp;rsquo;t access what he can&amp;rsquo;t see. As long as we hide buttons and links which might lead to a restricted view and control access on the data layer properly, there&amp;rsquo;s no need to have access control for urls.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;/imports/helpers/config.js&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The config file merges the access control list (acl) and Meteor settings. For every possible action a set of roles is defined.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import { Meteor } from &amp;#39;meteor/meteor&amp;#39;&#xA;import { objectAssign } from &amp;#39;./index&amp;#39;&#xA;&#xA;let acl = {&#xA;  // routers permissions&#xA;  &amp;#39;routers.read&amp;#39;: [ &amp;#39;admin&amp;#39;, &amp;#39;spec&amp;#39;, &amp;#39;tech&amp;#39;, &amp;#39;user&amp;#39; ],&#xA;  &amp;#39;routers.insert&amp;#39;: [ &amp;#39;admin&amp;#39;, &amp;#39;spec&amp;#39;, &amp;#39;tech&amp;#39; ],&#xA;  &amp;#39;routers.update&amp;#39;: [ &amp;#39;admin&amp;#39;, &amp;#39;spec&amp;#39;, &amp;#39;tech&amp;#39; ],&#xA;  &amp;#39;routers.remove&amp;#39;: [ &amp;#39;admin&amp;#39;, &amp;#39;spec&amp;#39; ],&#xA;  &amp;#39;routers.restore&amp;#39;: [ &amp;#39;admin&amp;#39; , &amp;#39;spec&amp;#39; ],&#xA;  &amp;#39;routers.export&amp;#39;: [ &amp;#39;admin&amp;#39;, &amp;#39;spec&amp;#39; ],&#xA;&#xA;  // notification permissions&#xA;  &amp;#39;notifications.read&amp;#39;: [ &amp;#39;admin&amp;#39;, &amp;#39;spec&amp;#39;, &amp;#39;tech&amp;#39; ],&#xA;  &amp;#39;notifications.receive&amp;#39;: [ &amp;#39;admin&amp;#39;, &amp;#39;spec&amp;#39;, &amp;#39;tech&amp;#39; ],&#xA;  &amp;#39;notifications.insert&amp;#39;: [ &amp;#39;admin&amp;#39;, &amp;#39;spec&amp;#39;, &amp;#39;tech&amp;#39; ],&#xA;  &amp;#39;notifications.remove&amp;#39;: [ &amp;#39;admin&amp;#39;, &amp;#39;spec&amp;#39;, &amp;#39;tech&amp;#39; ],&#xA;  &amp;#39;notifications.export&amp;#39;: [ &amp;#39;admin&amp;#39;, &amp;#39;spec&amp;#39; ],&#xA;}&#xA;&#xA;export default objectAssign(Meteor.settings.private, Meteor.settings.public, { acl: acl })&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Of course you could create a collection to store the acl and make the permission model dynamic.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;/imports/helpers/isAllowed.js&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;This is the only function required to check the users permission.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import { config } from &amp;#39;./index&amp;#39;&#xA;&#xA;export default (action, roles) =&amp;gt; {&#xA;  let allowed = false&#xA;  let allowedRoles = config.acl[action]&#xA;  roles = roles != null ? roles : []&#xA;  roles.map((role) =&amp;gt; {&#xA;     allowed = allowedRoles.indexOf(role) != -1&#xA;  })&#xA;  return allowed&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Simple isn&amp;rsquo;t it?&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;/server/methods/routers.js&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;This example shows how the permission is checked in a Meteor method.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;...&#xA;import { isAllowed } from &amp;#39;/imports/helpers&amp;#39;&#xA;&#xA;export default () =&amp;gt; {&#xA;  Meteor.methods({&#xA;    &amp;#39;routers.insert&amp;#39;(object) {&#xA;      check(object, Object)&#xA;&#xA;      // check permissions&#xA;      let roles = Meteor.userId() ? Meteor.user().roles : null&#xA;      if (!isAllowed(&amp;#39;routers.insert&amp;#39;, roles)) {&#xA;        throw new Meteor.Error(i18n.error.insufficent_rights, i18n.message.insufficent_rights_for_method)&#xA;      }&#xA;&#xA;      // insert object&#xA;      ...&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Make sure the permission check is the first thing done when the method is executed.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;/server/publication/router.js&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Now we restrict access on the data access layer.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;...&#xA;import { isAllowed } from &amp;#39;/imports/helpers&amp;#39;&#xA;&#xA;export default () =&amp;gt; {&#xA;&#xA;  Meteor.publish(&amp;#39;routers.list&amp;#39;, function(selector = {}) {&#xA;&#xA;    // check permissions&#xA;    let user = Meteor.users.findOne(this.userId)&#xA;    let roles = user ? user.roles : null&#xA;    if (isAllowed(&amp;#39;routers.read&amp;#39;, roles)) {&#xA;      return Routers.find(selector)&#xA;    } else {&#xA;      this.stop()&#xA;      return&#xA;    }&#xA;  })&#xA;  ...&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The user roles are accessible using &lt;code&gt;this.user&lt;/code&gt; in a publication.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;client/routers/Router.js&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Finally you can restrict the visibility of React components quite easily.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;...&#xA;import { isAllowed } from &amp;#39;/imports/helpers&amp;#39;&#xA;&#xA;class Router extends React.Component {&#xA;  ...&#xA;  render() {&#xA;    let { loading, user, i18n } = this.props&#xA;    return loading ? &amp;lt;CircularProgress /&amp;gt; : &amp;lt;Card&amp;gt;&#xA;          ...&#xA;&#xA;          { isAllowed(&amp;#39;routers.update&amp;#39;, user ? user.roles : null) ?&#xA;          &amp;lt;RaisedButton&#xA;          type=&amp;#34;submit&amp;#34;&#xA;          label={ i18n.button.update }&#xA;          primary={ true } /&amp;gt;&#xA;          : null }&#xA;&#xA;          { isAllowed(&amp;#39;routers.remove&amp;#39;, user ? user.roles : null) ?&#xA;          &amp;lt;RaisedButton&#xA;          onTouchTap={ this.toggleDialog.bind(this, &amp;#39;openRemoveDialog&amp;#39;) }&#xA;          label={ i18n.button.remove }&#xA;          secondary={ true } /&amp;gt;&#xA;          : null }&#xA;&#xA;          ...&#xA;    &amp;lt;/Card&amp;gt;&#xA;  }&#xA;}&#xA;&#xA;export default Router&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can see the &lt;code&gt;isAllowed&lt;/code&gt; function is used for all scenarios.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://janikvonrotz.ch/wp-content/uploads/2017/05/Meteor-React-component-access-control.gif&#34;&gt;&lt;img src=&#34;https://janikvonrotz.ch/wp-content/uploads/2017/05/Meteor-React-component-access-control.gif&#34; alt=&#34;Untitled&#34;&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Do you like this solution? Leave a comment and tell my more.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
