sails
Version:
API-driven framework for building realtime apps, using MVC conventions (based on Express and Socket.io)
103 lines (72 loc) • 3.57 kB
Markdown
# Access control and permissions
Policies in Sails are designed for controlling binary ("yes or no") access to particular actions. They work great for checking whether a user is logged in or for other simple "yes or no" checks, like whether the logged in user is a "super admin".
To see an example of access control in action—as well as login, authentication, and password recovery—generate the starter web app:
```bash
sails new foo
# Then choose "Web App"
```
### Dynamic permissions
For more complex permission schemes, like those in which a requesting user agent's access rights depend on both _who they are_ and _what they're trying to do_, you'll want to involve the database. While you can use policies to accomplish this, it's usually more straightforward and maintainable to use a [helper](https://sailsjs.com/documentation/concepts/helpers).
For example, you might create `api/helpers/check-permissions.js`:
```javascript
module.exports = {
friendlyName: 'Check permissions',
description: 'Look up a user\'s "rights" within a particular organization.',
inputs: {
userId: { type: 'number', required: true },
orgId: { type: 'number', required: true }
},
exits: {
success: {
outputFriendlyName: 'Rights',
outputDescription: `A user's "rights" within an org.`,
outputType: ['string']
},
orgNotFound: {
description: 'No such organization exists.'
}
},
fn: async function(inputs, exits) {
var org = await Organization.findOne(inputs.orgId)
.populate('adminUsers', { id: inputs.userId })
.populate('regularUsers', { id: inputs.userId });
if (!org) { throw 'orgNotFound'; }
var rights = [];
if (org.regularUsers.length !== 0) {
rights = ['basicAccess', 'inviteRegularUsers'];
} else if (org.adminUsers.length !== 0) {
rights = ['basicAccess', 'inviteRegularUsers', 'removeRegularUsers', 'inviteOrgAdmins'];
} else if (org.owner === inputs.userId) {
rights = ['basicAccess', 'inviteRegularUsers', 'removeRegularUsers', 'inviteOrgAdmins', 'removeOrDemoteOrgAdmins'];
}
// ^^This could be as simple or as granular as you need, e.g.
// ['basicAccess', 'inviteRegularUsers', 'inviteOrgAdmins', 'removeRegularUsers', 'removeOrDemoteOrgAdmins']
return exits.success(rights);
}
};
```
Your action—`api/controllers/demote-org-admin.js`, for example—might look like this:
```javascript
//…
var rights = await checkPermissions(this.req.session.userId, inputs.orgId)
.intercept('orgNotFound', 'notFound');
if (!_.contains(rights, 'removeOrDemoteOrgAdmins')) {
throw 'forbidden';
}
await Organization.removeFromCollection(inputs.orgId, 'adminUsers', inputs.targetUserId);
await Organization.addToCollection(inputs.orgId, 'regularUsers', inputs.targetUserId);
return exits.success();
```
> ### Note
> Remember that, while we used `checkPermissions(…,…)` here, we could have
> also used `.with()` and switched to named parameters:
>
> ```js
> await checkPermissions.with({
> userId: this.req.session.userId,
> orgId: inputs.orgId
> });
> ```
>
> You may choose to use different ways of calling a helper in order to enhance code readability in different situations. When in doubt, a good best practice is to optimize first for explicitness, then for readability, and last for conciseness. Still, these priorities may shift as you implement a helper more frequently and become more familiar with its usage.
<docmeta name="displayName" value="Access Control and Permissions">