├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── book.json
├── dist
├── getRoles.js
├── index.js
├── isAllowed.js
├── isAuthorized.js
├── lens.js
├── screen.js
└── screenDeep.js
├── docs
├── Authorized.md
├── Endpoint.md
├── Introduction.md
├── Roles.md
├── Rules.md
├── Screen.md
└── screenDeep.md
├── package.json
├── src
├── getRoles.js
├── index.js
├── isAllowed.js
├── isAuthorized.js
├── lens.js
├── screen.js
└── screenDeep.js
└── test
├── fixtures
└── createModel.js
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | build
3 | lib-cov
4 | *.seed
5 | *.log
6 | *.csv
7 | *.dat
8 | *.out
9 | *.pid
10 | *.gz
11 | _book
12 |
13 | pids
14 | logs
15 | results
16 |
17 | npm-debug.log
18 | node_modules
19 | *.sublime*
20 | *.node
21 | coverage
22 | *.orig
23 | .idea
24 | sandbox
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5"
4 | env:
5 | - CXX=g++-4.8
6 | addons:
7 | apt:
8 | sources:
9 | - ubuntu-toolchain-r-test
10 | packages:
11 | - g++-4.8
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Contra
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Role-based security, authorization, and filtering utilities for Thinky/RethinkDB
4 |
5 |
6 | ## Install
7 |
8 | One command and you're ready to secure your data:
9 |
10 | ```
11 | npm install palisade --save
12 | ```
13 |
14 | **Now**, check out the [documentation](http://shasta.tools/palisade/docs/Rules.html) to get started!
15 |
16 | ## Example
17 |
18 | The documentation has more detailed examples, but here's a quick peek:
19 |
20 | ### ES6
21 |
22 | ```js
23 | // your thinky connection instance
24 | import db from 'connections/thinky'
25 | import palisade, { screenDeep } from 'palisade'
26 |
27 | const User = db.createModel('User', {
28 | id: String,
29 | name: String,
30 | birthday: Date,
31 | times: {
32 | created: Date
33 | }
34 | })
35 |
36 | // Anyone can list and read users and their public fields (id and name)
37 | // Users can update themselves, but only their own birthday
38 | // Admins can create, update, replace, or delete any user
39 | palisade(User, {
40 | document: {
41 | read: ['public'],
42 | create: ['admin'],
43 | update: ['admin', 'self'],
44 | replace: ['admin'],
45 | delete: ['admin']
46 | },
47 | read: {
48 | id: ['public'],
49 | name: ['public'],
50 | birthday: ['admin', 'self'],
51 | times: {
52 | created: ['admin']
53 | }
54 | },
55 | write: {
56 | id: ['admin'],
57 | name: ['admin'],
58 | birthday: ['admin', 'self'],
59 | times: {
60 | created: ['admin']
61 | }
62 | }
63 | })
64 | ```
65 |
66 | ### ES5
67 |
68 | ```js
69 | // your thinky connection instance
70 | var db = require('connections/thinky');
71 | var palisade = require('palisade');
72 | var screenDeep = palisade.screenDeep;
73 |
74 | var User = db.createModel('User', {
75 | id: String,
76 | name: String,
77 | birthday: Date,
78 | times: {
79 | created: Date
80 | }
81 | });
82 |
83 | // Anyone can list and read users and their public fields (id and name)
84 | // Users can update themselves, but only their own birthday
85 | // Admins can create, update, replace, or delete any user
86 | palisade(User, {
87 | document: {
88 | read: ['public'],
89 | create: ['admin'],
90 | update: ['admin', 'self'],
91 | replace: ['admin'],
92 | delete: ['admin']
93 | },
94 | read: {
95 | id: ['public'],
96 | name: ['public'],
97 | birthday: ['admin', 'self'],
98 | times: {
99 | created: ['admin']
100 | }
101 | },
102 | write: {
103 | id: ['admin'],
104 | name: ['admin'],
105 | birthday: ['admin', 'self'],
106 | times: {
107 | created: ['admin']
108 | }
109 | }
110 | });
111 | ```
112 |
--------------------------------------------------------------------------------
/book.json:
--------------------------------------------------------------------------------
1 | {
2 | "gitbook": "2.4.3",
3 | "structure": {
4 | "summary": "docs/Introduction.md"
5 | },
6 | "plugins": [
7 | "edit-link",
8 | "prism",
9 | "-highlight",
10 | "github",
11 | "anchors",
12 | "collapsible-menu"
13 | ],
14 | "pluginsConfig": {
15 | "edit-link": {
16 | "base": "https://github.com/shastajs/palisade/tree/master",
17 | "label": "Edit This Page"
18 | },
19 | "github": {
20 | "url": "https://github.com/shastajs/palisade/"
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/dist/getRoles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | exports.default = function (user, data) {
8 | var roles = ['public'];
9 | if (user) roles.push('loggedIn');
10 | if (!user) return roles; // nothing left to do
11 |
12 | if (user.role) roles.push(user.role);
13 | if (user.roles) roles = roles.concat(user.roles);
14 | if (data && user.id === data.id) roles.push('self');
15 | return roles;
16 | };
17 |
18 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.screenDeep = undefined;
7 |
8 | var _screen = require('./screen');
9 |
10 | var _screen2 = _interopRequireDefault(_screen);
11 |
12 | var _isAuthorized = require('./isAuthorized');
13 |
14 | var _isAuthorized2 = _interopRequireDefault(_isAuthorized);
15 |
16 | var _screenDeep2 = require('./screenDeep');
17 |
18 | var _screenDeep3 = _interopRequireDefault(_screenDeep2);
19 |
20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21 |
22 | exports.screenDeep = _screenDeep3.default;
23 |
24 | exports.default = function (Model, rules) {
25 | Model.security = rules;
26 | Model.defineStatic('authorized', _isAuthorized2.default.bind(null, Model.security));
27 | Model.define('authorized', function (type, user) {
28 | return Model.authorized(type, user, this);
29 | });
30 | Model.defineStatic('screen', _screen2.default.bind(null, Model.security));
31 | Model.define('screen', function (type, user) {
32 | return Model.screen(type, user, this);
33 | });
34 | return Model;
35 | };
--------------------------------------------------------------------------------
/dist/isAllowed.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _lodash = require('lodash.some');
8 |
9 | var _lodash2 = _interopRequireDefault(_lodash);
10 |
11 | var _getRoles = require('./getRoles');
12 |
13 | var _getRoles2 = _interopRequireDefault(_getRoles);
14 |
15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16 |
17 | exports.default = function (rules, user, data) {
18 | if (!Array.isArray(rules) && typeof rules !== 'function') return false;
19 | var roles = (0, _getRoles2.default)(user, data);
20 | var fnOpt = {
21 | user: user,
22 | data: data,
23 | roles: roles
24 | };
25 |
26 | // auth is a fn
27 | if (typeof rules === 'function') {
28 | return rules(fnOpt);
29 | }
30 |
31 | // auth is an array of strings or fns
32 | return (0, _lodash2.default)(rules, function (v) {
33 | if (typeof v === 'function') return v(fnOpt);
34 | if (typeof v === 'string') return roles.indexOf(v) !== -1;
35 | return false;
36 | });
37 | };
38 |
39 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/dist/isAuthorized.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _isAllowed = require('./isAllowed');
8 |
9 | var _isAllowed2 = _interopRequireDefault(_isAllowed);
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | exports.default = function (rules, type, user, data) {
14 | if (!type) throw new Error('Missing operation type');
15 | if (!rules.document) return false;
16 | return (0, _isAllowed2.default)(rules.document[type], user, data);
17 | };
18 |
19 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/dist/lens.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _typeof2 = require('babel-runtime/helpers/typeof');
4 |
5 | var _typeof3 = _interopRequireDefault(_typeof2);
6 |
7 | var _lodash = require('lodash.reduce');
8 |
9 | var _lodash2 = _interopRequireDefault(_lodash);
10 |
11 | var _lodash3 = require('lodash.isdate');
12 |
13 | var _lodash4 = _interopRequireDefault(_lodash3);
14 |
15 | var _isAllowed = require('./isAllowed');
16 |
17 | var _isAllowed2 = _interopRequireDefault(_isAllowed);
18 |
19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
20 |
21 | var filterWithLens = function filterWithLens(schema, user, data) {
22 | if ((typeof data === 'undefined' ? 'undefined' : (0, _typeof3.default)(data)) !== 'object') return;
23 | if ((0, _lodash4.default)(data)) return data.toISOString();
24 | return (0, _lodash2.default)(data, function (o, v, k) {
25 | if (!data.hasOwnProperty(k)) return;
26 | var rules = schema[k];
27 | var needsNesting = (typeof rules === 'undefined' ? 'undefined' : (0, _typeof3.default)(rules)) === 'object' && !Array.isArray(rules);
28 |
29 | if (needsNesting) {
30 | if ((typeof v === 'undefined' ? 'undefined' : (0, _typeof3.default)(v)) === 'object') {
31 | o[k] = filterWithLens(rules, user, v);
32 | }
33 | } else if ((0, _isAllowed2.default)(rules, user, data)) {
34 | o[k] = v;
35 | }
36 |
37 | return o;
38 | }, {});
39 | };
40 |
41 | module.exports = filterWithLens;
--------------------------------------------------------------------------------
/dist/screen.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _typeof2 = require('babel-runtime/helpers/typeof');
8 |
9 | var _typeof3 = _interopRequireDefault(_typeof2);
10 |
11 | var _lodash = require('lodash.map');
12 |
13 | var _lodash2 = _interopRequireDefault(_lodash);
14 |
15 | var _lens = require('./lens');
16 |
17 | var _lens2 = _interopRequireDefault(_lens);
18 |
19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
20 |
21 | var screen = function screen(rules, type, user, data) {
22 | if (Array.isArray(data)) {
23 | if (!rules[type]) return [];
24 | return (0, _lodash2.default)(data, screen.bind(null, rules, type, user));
25 | }
26 | if (data && (typeof data === 'undefined' ? 'undefined' : (0, _typeof3.default)(data)) === 'object') {
27 | if (!rules[type]) return {};
28 | return (0, _lens2.default)(rules[type], user, data);
29 | }
30 | };
31 |
32 | exports.default = screen;
33 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/dist/screenDeep.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _typeof2 = require('babel-runtime/helpers/typeof');
8 |
9 | var _typeof3 = _interopRequireDefault(_typeof2);
10 |
11 | var _lodash = require('lodash.reduce');
12 |
13 | var _lodash2 = _interopRequireDefault(_lodash);
14 |
15 | var _lodash3 = require('lodash.isdate');
16 |
17 | var _lodash4 = _interopRequireDefault(_lodash3);
18 |
19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
20 |
21 | var screenDeep = function screenDeep(user, data, returnEmpty) {
22 | if ((0, _lodash4.default)(data)) return data.toISOString();
23 |
24 | // check if the user can even see the doc
25 | if ((typeof data === 'undefined' ? 'undefined' : (0, _typeof3.default)(data)) === 'object' && !Array.isArray(data)) {
26 | if (data.authorized && !data.authorized('read', user)) {
27 | if (returnEmpty) return;
28 | return Array.isArray(data) ? [] : {};
29 | }
30 | // single instance w/ lens
31 | if (data.screen) {
32 | return data.screen('read', user);
33 | }
34 |
35 | // object with values as data
36 | return (0, _lodash2.default)(data, function (p, v, k) {
37 | if (!data.hasOwnProperty(k)) return;
38 | var nv = screenDeep(user, v, true);
39 | if (typeof nv !== 'undefined') p[k] = nv;
40 | return p;
41 | }, {});
42 | }
43 |
44 | // array of data
45 | if (Array.isArray(data)) {
46 | return (0, _lodash2.default)(data, function (p, v) {
47 | var nv = screenDeep(user, v, true);
48 | if (typeof nv !== 'undefined') p.push(nv);
49 | return p;
50 | }, []);
51 | }
52 |
53 | return data;
54 | };
55 |
56 | exports.default = screenDeep;
57 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/docs/Authorized.md:
--------------------------------------------------------------------------------
1 | # Model.authorized(type, user, data)
2 |
3 | Palisade adds a `.authorized()` function to both the Model class as well as all instances of the model (via define and defineStatic). This function checks the document rules in your security schema and returns `true` if everything is cool.
4 |
5 | When using the instance method, the data argument is set to the instance.
6 |
7 | ## API
8 |
9 | - `type` argument is the type of operation being requested on the document
10 | - Can be any type you [specified in your document ruleset](docs/Rules.md)
11 | - `user` is the requesting user you want to [pull roles from](docs/Roles.md)
12 | - Optional
13 | - `data` is the document the operating is being requested for
14 | - Optional
15 |
16 | ### Considerations
17 |
18 | - If no rules are specified for a document operation type, defaults to denying access
19 | - If an object with an `id` attribute isn't given for the data argument, the `self` role can't be provided
20 | - *Hot tip:* If you don't have access to the whole document but you know the id, just `{id: 'the id'}` will work fine
21 |
22 | ## Read Example
23 |
24 | In this example, we have two users: one admin, and one moderator. The schema specifies that only admins can read user documents, so only the admin user gets a `true` return from calling `authorized()`.
25 |
26 | ```js
27 | // create and wire in the schema
28 | var schema = {
29 | document: {
30 | read: ['admin']
31 | }
32 | }
33 | palisade(User, schema)
34 |
35 | // create some dummy data
36 | var me = {
37 | role: 'admin',
38 | name: 'Contra'
39 | }
40 |
41 | var them = {
42 | role: 'moderator',
43 | name: 'Todd'
44 | }
45 |
46 | // ask for a read on them from me
47 | var canRead = User.authorize('read', me, them)
48 | console.log(canRead) // true
49 |
50 | // ask for a read on me from them
51 | var canRead2 = User.authorize('read', them, me)
52 | console.log(canRead2) // false
53 | ```
54 |
--------------------------------------------------------------------------------
/docs/Endpoint.md:
--------------------------------------------------------------------------------
1 | # API Endpoint
2 |
3 | To tie it all together, here's how you would use Palisade to create some secure API endpoints:
4 |
5 | ## Reading a User by Id
6 |
7 | ```js
8 | import palisade, {screenDeep} from 'palisade'
9 | import User from 'models/User'
10 |
11 | export default (req, res, next) => {
12 | // first check if the request is allowed to read
13 | // anything from the users collection
14 | if (!User.authorized('read', req.user)) {
15 | return res.status({status: 403})
16 | }
17 |
18 | // run the query
19 | User.get(req.params.id).run(function(err, data){
20 | if (err) return next(err)
21 |
22 | // send back our secured data!
23 | res.json(screenDeep(req.user, filtered))
24 | })
25 | }
26 | ```
27 |
28 | ## Updating a User by Id
29 |
30 | ```js
31 | import palisade, {screenDeep} from 'palisade'
32 | import User from 'models/User'
33 |
34 | export default (req, res, next) => {
35 | // checks if the request is allowed to update the
36 | // document at all. provides the id to enable the `self` role to exist
37 | if (!User.authorized('update', req.user, {id: req.params.id})) {
38 | return res.status({status: 403})
39 | }
40 |
41 | // screen the user-provided data
42 | var change = User.screen('write', req.user, req.body)
43 |
44 | // run the query to update
45 | User.get(req.params.id)
46 | .update(change, {returnChanges: true})
47 | .execute((err, res) => {
48 | if (err) return next(err)
49 |
50 | // send back our new secured data!
51 | res.json(screenDeep(req.user, filtered))
52 | })
53 | }
54 | ```
55 |
--------------------------------------------------------------------------------
/docs/Introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | - [ACL Rules](docs/Rules.md)
4 | - [User Roles](docs/Roles.md)
5 | - [Model.authorized()](docs/Authorized.md)
6 | - [Model.screen()](docs/Screen.md)
7 | - [screenDeep()](docs/ScreenDeep.md)
8 | - [API Endpoint](docs/Endpoint.md)
9 |
--------------------------------------------------------------------------------
/docs/Roles.md:
--------------------------------------------------------------------------------
1 | # User Roles
2 |
3 | [`Model.authorized()`](docs/Authorized.md), [`Model.screen()`](docs/Screen.md), and [`.screenDeep()`](docs/ScreenDeep.md) take an optional user object as an argument which can provide role information.
4 |
5 | ## Assumed Roles
6 |
7 | - `public` is always assumed
8 | - `loggedIn` is assumed if any user object was provided
9 | - `self` is assumed if the user provided has the same id as the document being accessed
10 |
11 | ## Other Roles
12 |
13 | - If a user has a `role` attribute, it will be included
14 | - This can either be a string, or a function that returns a boolean
15 | - Any functions given will receive an object as the sole argument, which contains `user`, `data`, and `roles` keys
16 | - If a user has a `roles` attribute, it will be included
17 | - Expects either an array of strings, or functions that returns a boolean
18 | - (or any combination of the two!)
19 | - Any functions given will receive an object as the sole argument, which contains `user`, `data`, and `roles` keys
20 |
21 | ## Naming Roles
22 |
23 | Your can name your roles however you like, but I typically use:
24 |
25 | - `public` (provided by palisade)
26 | - `self` (provided by palisade)
27 | - `loggedIn` (provided by palisade)
28 | - `admin` (people responsible for moderating or performing maintenance)
29 | - `root` (you, the engineer)
30 |
--------------------------------------------------------------------------------
/docs/Rules.md:
--------------------------------------------------------------------------------
1 | # ACL Rules
2 |
3 | Palisade has three categories of security rules:
4 |
5 | ### `document`
6 |
7 | - Each key is an operation type (for example: list, read, create, delete, update) that can be performed at a document level
8 | - You may specify any number of operation types named however you like
9 | - Personally, I always use these ones as they translate well to REST endpoints:
10 | - `list` - Can they query for more than one document
11 | - `read` - Can they read a single document
12 | - `create` - Can they create a new document
13 | - `update` - Can they update fields on an existing document
14 | - `replace` - Can they completely replace an existing document
15 | - `delete` - Can they delete an existing document
16 |
17 | ### `read`
18 |
19 | - Each key is a field name that corresponds to your schema
20 | - The value is an array of roles that can update the field
21 | - The value can also be an object with more fields inside of it for nested data
22 |
23 | ### `write`
24 |
25 | - Each key is a field name that corresponds to your schema
26 | - The value is an array of roles that can update the field
27 | - The value can also be an object with more fields inside of it for nested data
28 |
29 | ## Example Schema
30 |
31 | In this example ruleset:
32 |
33 | - Anyone can list users
34 | - Anyone can read a single user
35 | - Anyone read a user's public fields `id` and `name`
36 | - Users can update themselves, but only their own birthday
37 | - Admins can create, update, replace, or delete any user
38 |
39 | ```js
40 | var userSchema = {
41 | document: {
42 | read: ['public'],
43 | create: ['admin'],
44 | update: ['admin', 'self'],
45 | replace: ['admin'],
46 | delete: ['admin']
47 | },
48 | read: {
49 | id: ['public'],
50 | name: ['public'],
51 | birthday: ['admin', 'self'],
52 | times: {
53 | created: ['admin']
54 | }
55 | },
56 | write: {
57 | id: ['admin'],
58 | name: ['admin'],
59 | birthday: ['admin', 'self'],
60 | times: {
61 | created: ['admin']
62 | }
63 | }
64 | };
65 | ```
66 |
67 | If you don't define a category or a rule for a field or type, the default is to deny access.
68 |
69 | ## Plugging it in
70 |
71 | To add the security functionality to a [Thinky](http://thinky.io) model, pass the palisade function the model and your security schema.
72 |
73 | This does a few things:
74 | - Adds a [.screen()](Screen.md) function to the Model class and all Model instances
75 | - Adds a [.authorized()](Authorized.md) function to the Model class and all Model instances
76 | - Adds your security ruleset as a `.security` property on the Model class
77 | - This makes it easy for models to extend eachother's security rules
78 |
79 | ```js
80 | import db from 'connections/thinky'
81 | import palisade from 'palisade'
82 |
83 | const User = db.createModel('User', {
84 | id: String,
85 | name: String,
86 | birthday: Date,
87 | times: {
88 | created: Date
89 | }
90 | })
91 | palisade(User, userSchema)
92 | ```
93 |
--------------------------------------------------------------------------------
/docs/Screen.md:
--------------------------------------------------------------------------------
1 | # Model.screen(type, user, data)
2 |
3 | Palisade adds a `.screen()` function to both the Model class as well as all instances of the model (via define and defineStatic). This function recursively filters out fields based on your security schema and the requesting user.
4 |
5 | When using the instance method, the data argument is set to the instance.
6 |
7 | ## API
8 |
9 | - `type` argument is the type of access
10 | - Can be either `read` or `write`
11 | - `user` is the requesting user you want to [pull roles from](docs/Roles.md)
12 | - Optional
13 | - `data` is the data to be sanitized
14 | - Can be an object, array of objects, or
15 |
16 | ### Considerations
17 |
18 | - If no rules are specified for a field, defaults to denying access
19 | - If data is not an object or an array, it is simply returned
20 |
21 | ## Read Example
22 |
23 | In this example, we have two users: one admin, and one moderator. The schema specifies that only admins can read the name field, so only the admin user is able to see it.
24 |
25 | ```js
26 | // create and wire in the schema
27 | var schema = {
28 | document: {
29 | read: ['public']
30 | },
31 | read: {
32 | name: ['admin']
33 | }
34 | }
35 | palisade(User, schema)
36 |
37 | // create some dummy data
38 | var me = {
39 | role: 'admin',
40 | name: 'Contra'
41 | }
42 |
43 | var them = {
44 | role: 'moderator',
45 | name: 'Todd'
46 | }
47 |
48 | // ask for a read on them from me
49 | var filtered = User.screen('read', me, them)
50 | console.log(filtered) // {name: 'Todd'}
51 |
52 | // ask for a read on me from them
53 | var filtered2 = User.screen('read', them, me)
54 | console.log(filtered2) // {}
55 | ```
56 |
57 | ## Write Example
58 |
59 | In the same way that you can sanitize reads, you can also sanitize writes. Before inserting anything into the database or updating any documents, run all user-provided data through `.screen()` to ensure that any updates they don't have permission for are removed.
60 |
61 | In this example, we have two users: one admin, and one moderator. The schema specifies that only admins can write the name field, so the moderator provided data has that field removed.
62 |
63 | ```js
64 | // create and wire in the schema
65 | var schema = {
66 | document: {
67 | read: ['public']
68 | },
69 | write: {
70 | name: ['admin']
71 | }
72 | }
73 | palisade(User, schema)
74 |
75 | // create some dummy data
76 | var me = {
77 | role: 'admin',
78 | name: 'Contra'
79 | }
80 |
81 | var them = {
82 | role: 'moderator',
83 | name: 'Todd'
84 | }
85 |
86 | // ask for a write on them from me
87 | var filtered = User.screen('write', me, them)
88 | console.log(filtered) // {name: 'Todd'}
89 |
90 | // ask for a write on me from them
91 | var filtered2 = User.screen('write', them, me)
92 | console.log(filtered2) // {}
93 | ```
94 |
--------------------------------------------------------------------------------
/docs/screenDeep.md:
--------------------------------------------------------------------------------
1 | # screenDeep(user, data)
2 |
3 | Palisade exports a `.screenDeep()` function that lets you ensure complex objects or arrays have all nested data within it secured. It recursively walks through data and runs `.screen()` + `.authorized()` on any objects that expose it. This is extremely useful for exposing sets of data that contain multiple types of models, or are nested in any way.
4 |
5 | ## API
6 |
7 | - `user` is the requesting user you want to [pull roles from](docs/Roles.md)
8 | - Optional
9 | - `data` is the data to be sanitized
10 | - Can be an object, array of objects, or
11 |
12 | ## Considerations
13 |
14 | - Objects and arrays will be recursively processed
15 | - Objects that expose a [`.screen()`](docs/Screen.md) function will be replaced with the output of that call with a type of `read`
16 | - Objects that expose a [`.authorized()`](docs/Authorized.md) function will be removed if the output of that call with a type of `read` is false
17 | - Objects removed from either `screen()` or `authorized()` will be stripped
18 | - If you have 20 items where the user can't see 10, it will return an array of 10 items
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "palisade",
3 | "version": "1.1.6",
4 | "description": "Role-based security, authorization, and filtering utilities for thinky and RethinkDB",
5 | "main": "dist/index.js",
6 | "keywords": [
7 | "security",
8 | "authorization",
9 | "filter",
10 | "filtering",
11 | "thinky",
12 | "rethink",
13 | "rethinkdb",
14 | "api",
15 | "user",
16 | "role",
17 | "acl"
18 | ],
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/shastajs/palisade.git"
22 | },
23 | "author": "Contra (http://contra.io)",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/shastajs/palisade/issues"
27 | },
28 | "homepage": "https://github.com/shastajs/palisade#readme",
29 | "files": [
30 | "dist"
31 | ],
32 | "scripts": {
33 | "preversion": "npm run clean && npm run build && npm docs",
34 | "postversion": "npm run changelog",
35 | "build": "babel src --out-dir dist",
36 | "clean": "rimraf dist",
37 | "lint": "eslint src test",
38 | "changelog": "github-changes -o contra -r palisade -b master -f ./CHANGELOG.md --order-semver --use-commit-body",
39 | "test": "npm run-script lint && mocha --compilers js:babel-register --recursive --reporter spec",
40 | "docs": "npm run docs:pre && npm run docs:build && npm run docs:publish",
41 | "docs:pre": "gitbook install && rimraf _book",
42 | "docs:build": "gitbook build -g shastajs/palisade",
43 | "docs:publish": "cd _book && git init && git commit --allow-empty -m 'update book' && git checkout -b gh-pages && touch .nojekyll && git add . && git commit -am 'update book' && git push git@github.com:shastajs/palisade gh-pages --force"
44 | },
45 | "devDependencies": {
46 | "babel": "^6.3.26",
47 | "babel-cli": "^6.4.0",
48 | "babel-core": "^6.4.0",
49 | "babel-eslint": "^7.0.0",
50 | "babel-loader": "^6.2.1",
51 | "babel-plugin-add-module-exports": "^0.2.1",
52 | "babel-plugin-transform-runtime": "^6.4.3",
53 | "babel-preset-es2015": "^6.3.13",
54 | "babel-preset-es2015-loose": "^8.0.0",
55 | "babel-preset-stage-0": "^6.3.13",
56 | "babel-register": "^6.4.3",
57 | "babelify": "^7.2.0",
58 | "eslint": "^3.8.1",
59 | "eslint-config-rackt": "^1.1.1",
60 | "gitbook-cli": "^2.3.0",
61 | "github-changes": "^1.0.1",
62 | "mocha": "^3.0.0",
63 | "rimraf": "^2.5.0",
64 | "should": "^11.0.0",
65 | "thinky": "^2.3.7"
66 | },
67 | "babel": {
68 | "presets": [
69 | "es2015",
70 | "stage-0"
71 | ],
72 | "plugins": [
73 | "transform-runtime",
74 | "add-module-exports"
75 | ]
76 | },
77 | "eslintConfig": {
78 | "parser": "babel-eslint",
79 | "extends": "rackt",
80 | "env": {
81 | "node": true,
82 | "es6": true,
83 | "mocha": true
84 | },
85 | "ecmaFeatures": {
86 | "modules": true
87 | },
88 | "globals": {
89 | "it": true,
90 | "describe": true
91 | }
92 | },
93 | "dependencies": {
94 | "lodash.filter": "^4.0.1",
95 | "lodash.foreach": "^4.0.0",
96 | "lodash.includes": "^4.0.1",
97 | "lodash.intersection": "^4.0.1",
98 | "lodash.isdate": "^4.0.0",
99 | "lodash.isempty": "^4.0.0",
100 | "lodash.map": "^4.1.0",
101 | "lodash.mapvalues": "^4.0.1",
102 | "lodash.partial": "^4.0.1",
103 | "lodash.reduce": "^4.1.0",
104 | "lodash.some": "^4.1.0"
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/getRoles.js:
--------------------------------------------------------------------------------
1 | export default (user, data) => {
2 | let roles = [ 'public' ]
3 | if (user) roles.push('loggedIn')
4 | if (!user) return roles // nothing left to do
5 |
6 | if (user.role) roles.push(user.role)
7 | if (user.roles) roles = roles.concat(user.roles)
8 | if (data && user.id === data.id) roles.push('self')
9 | return roles
10 | }
11 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import screen from './screen'
2 | import isAuthorized from './isAuthorized'
3 |
4 | export screenDeep from './screenDeep'
5 |
6 | export default (Model, rules) => {
7 | Model.security = rules
8 | Model.defineStatic('authorized', isAuthorized.bind(null, Model.security))
9 | Model.define('authorized', function (type, user) {
10 | return Model.authorized(type, user, this)
11 | })
12 | Model.defineStatic('screen', screen.bind(null, Model.security))
13 | Model.define('screen', function (type, user) {
14 | return Model.screen(type, user, this)
15 | })
16 | return Model
17 | }
18 |
--------------------------------------------------------------------------------
/src/isAllowed.js:
--------------------------------------------------------------------------------
1 | import some from 'lodash.some'
2 | import getRoles from './getRoles'
3 |
4 | export default (rules, user, data) => {
5 | if (!Array.isArray(rules) && typeof rules !== 'function') return false
6 | const roles = getRoles(user, data)
7 | const fnOpt = {
8 | user: user,
9 | data: data,
10 | roles: roles
11 | }
12 |
13 | // auth is a fn
14 | if (typeof rules === 'function') {
15 | return rules(fnOpt)
16 | }
17 |
18 | // auth is an array of strings or fns
19 | return some(rules, (v) => {
20 | if (typeof v === 'function') return v(fnOpt)
21 | if (typeof v === 'string') return roles.indexOf(v) !== -1
22 | return false
23 | })
24 | }
25 |
--------------------------------------------------------------------------------
/src/isAuthorized.js:
--------------------------------------------------------------------------------
1 | import isAllowed from './isAllowed'
2 |
3 | export default (rules, type, user, data) => {
4 | if (!type) throw new Error('Missing operation type')
5 | if (!rules.document) return false
6 | return isAllowed(rules.document[type], user, data)
7 | }
8 |
--------------------------------------------------------------------------------
/src/lens.js:
--------------------------------------------------------------------------------
1 | import reduce from 'lodash.reduce'
2 | import isDate from 'lodash.isdate'
3 | import isAllowed from './isAllowed'
4 |
5 | const filterWithLens = (schema, user, data) => {
6 | if (typeof data !== 'object') return
7 | if (isDate(data)) return data.toISOString()
8 | return reduce(data, (o, v, k) => {
9 | if (!data.hasOwnProperty(k)) return
10 | const rules = schema[k]
11 | const needsNesting = typeof rules === 'object' && !Array.isArray(rules)
12 |
13 | if (needsNesting) {
14 | if (typeof v === 'object') {
15 | o[k] = filterWithLens(rules, user, v)
16 | }
17 | } else if (isAllowed(rules, user, data)) {
18 | o[k] = v
19 | }
20 |
21 | return o
22 | }, {})
23 | }
24 |
25 | module.exports = filterWithLens
26 |
--------------------------------------------------------------------------------
/src/screen.js:
--------------------------------------------------------------------------------
1 | import map from 'lodash.map'
2 | import lens from './lens'
3 |
4 | const screen = (rules, type, user, data) => {
5 | if (Array.isArray(data)) {
6 | if (!rules[type]) return []
7 | return map(data, screen.bind(null, rules, type, user))
8 | }
9 | if (data && typeof data === 'object') {
10 | if (!rules[type]) return {}
11 | return lens(rules[type], user, data)
12 | }
13 | }
14 |
15 | export default screen
16 |
--------------------------------------------------------------------------------
/src/screenDeep.js:
--------------------------------------------------------------------------------
1 | import reduce from 'lodash.reduce'
2 | import isDate from 'lodash.isdate'
3 |
4 | const screenDeep = (user, data, returnEmpty) => {
5 | if (isDate(data)) return data.toISOString()
6 |
7 | // check if the user can even see the doc
8 | if (typeof data === 'object' && !Array.isArray(data)) {
9 | if (data.authorized &&
10 | !data.authorized('read', user)) {
11 | if (returnEmpty) return
12 | return Array.isArray(data) ? [] : {}
13 | }
14 | // single instance w/ lens
15 | if (data.screen) {
16 | return data.screen('read', user)
17 | }
18 |
19 | // object with values as data
20 | return reduce(data, (p, v, k) => {
21 | if (!data.hasOwnProperty(k)) return
22 | let nv = screenDeep(user, v, true)
23 | if (typeof nv !== 'undefined') p[k] = nv
24 | return p
25 | }, {})
26 | }
27 |
28 | // array of data
29 | if (Array.isArray(data)) {
30 | return reduce(data, (p, v) => {
31 | let nv = screenDeep(user, v, true)
32 | if (typeof nv !== 'undefined') p.push(nv)
33 | return p
34 | }, [])
35 | }
36 |
37 | return data
38 | }
39 |
40 | export default screenDeep
41 |
--------------------------------------------------------------------------------
/test/fixtures/createModel.js:
--------------------------------------------------------------------------------
1 | import thinky from 'thinky'
2 |
3 | const db = thinky({
4 | silent: true
5 | })
6 | db.r.getPoolMaster()._flushErrors = () => {}
7 |
8 | export default (name, schema) =>
9 | db.createModel(name, schema)
10 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | /*global it: true, describe: true */
2 | /*eslint no-console: 0*/
3 | import should from 'should'
4 | import palisade, { screenDeep } from '../src'
5 | import createModel from './fixtures/createModel'
6 |
7 | const createUser = () =>
8 | createModel(`User-${Math.random()}`, {
9 | name: String,
10 | bday: Date
11 | })
12 |
13 | describe('palisade', () => {
14 | it('should export a plugin function', () => {
15 | should.exist(palisade)
16 | palisade.should.be.a.function
17 | })
18 | it('should export a screenDeep', () => {
19 | should.exist(screenDeep)
20 | screenDeep.should.be.a.function
21 | })
22 |
23 | it('should return a Model class', () => {
24 | let rules = {}
25 | let User = createUser()
26 | palisade(User, rules).should.equal(User)
27 | })
28 |
29 | it('should add a security field to a Model class', () => {
30 | let rules = {}
31 | let User = createUser()
32 | palisade(User, rules)
33 | should.exist(User.security)
34 | User.security.should.equal(rules)
35 | })
36 | })
37 |
38 | describe('Model.authorized', () => {
39 | it('should exist on class', () => {
40 | let rules = {}
41 | let User = createUser()
42 | palisade(User, rules)
43 | should.exist(User.authorized)
44 | })
45 | it('should exist on instance', () => {
46 | let rules = {}
47 | let User = createUser()
48 | palisade(User, rules)
49 | should.exist(new User({}).authorized)
50 | })
51 | it('should return false if no document rules specified', () => {
52 | let rules = {}
53 | let User = createUser()
54 | palisade(User, rules)
55 | User.authorized('read').should.equal(false)
56 | })
57 | it('should return false if no document rules for type specified', () => {
58 | let rules = {
59 | document: {}
60 | }
61 | let User = createUser()
62 | palisade(User, rules)
63 | User.authorized('read').should.equal(false)
64 | })
65 | it('should return false if document rules specified have no roles', () => {
66 | let rules = {
67 | document: {
68 | read: []
69 | }
70 | }
71 | let User = createUser()
72 | palisade(User, rules)
73 | User.authorized('read').should.equal(false)
74 | })
75 | it('should return true if document rules are public and no user given', () => {
76 | let rules = {
77 | document: {
78 | read: [ 'public' ]
79 | }
80 | }
81 | let User = createUser()
82 | palisade(User, rules)
83 | User.authorized('read').should.equal(true)
84 | })
85 | it('should return false if user role attr doesnt match', () => {
86 | let rules = {
87 | document: {
88 | read: [ 'admin' ]
89 | }
90 | }
91 | let User = createUser()
92 | palisade(User, rules)
93 | User.authorized('read', {
94 | role: 'pleb'
95 | }).should.equal(false)
96 | })
97 | it('should return false if user roles attr doesnt match', () => {
98 | let rules = {
99 | document: {
100 | read: [ 'admin' ]
101 | }
102 | }
103 | let User = createUser()
104 | palisade(User, rules)
105 | User.authorized('read', {
106 | roles: [ 'pleb' ]
107 | }).should.equal(false)
108 | })
109 | it('should return true if user role attr matches', () => {
110 | let rules = {
111 | document: {
112 | read: [ 'admin', 'pleb' ]
113 | }
114 | }
115 | let User = createUser()
116 | palisade(User, rules)
117 | User.authorized('read', {
118 | role: 'admin'
119 | }).should.equal(true)
120 | })
121 | it('should return true if user roles attr matches', () => {
122 | let rules = {
123 | document: {
124 | read: [ 'admin', 'super' ]
125 | }
126 | }
127 | let User = createUser()
128 | palisade(User, rules)
129 | User.authorized('read', {
130 | roles: [ 'pleb', 'admin' ]
131 | }).should.equal(true)
132 | })
133 | it('should return true if user roles attr fn matches', () => {
134 | const isAdmin = ({ user, data }) => {
135 | should.exist(user)
136 | should.exist(user.roles)
137 | should.exist(data)
138 | data.id.should.equal(123)
139 | return true
140 | }
141 | let rules = {
142 | document: {
143 | read: [ isAdmin, 'super' ]
144 | }
145 | }
146 | let User = createUser()
147 | palisade(User, rules)
148 | User.authorized('read', {
149 | roles: [ 'pleb' ]
150 | }, { id: 123 }).should.equal(true)
151 | })
152 | it('should return true if user roles attr fn doesnt match', () => {
153 | const isAdmin = ({ user, data }) => {
154 | should.exist(user)
155 | should.exist(user.roles)
156 | should.exist(data)
157 | data.id.should.equal(123)
158 | return false
159 | }
160 | let rules = {
161 | document: {
162 | read: [ isAdmin, 'super' ]
163 | }
164 | }
165 | let User = createUser()
166 | palisade(User, rules)
167 | User.authorized('read', {
168 | roles: [ 'pleb' ]
169 | }, { id: 123 }).should.equal(false)
170 | })
171 | it('should return true if self role matches', () => {
172 | let rules = {
173 | document: {
174 | read: [ 'self' ]
175 | }
176 | }
177 | let User = createUser()
178 | palisade(User, rules)
179 | let u1 = new User({ id: '123' })
180 | User.authorized('read', u1, u1).should.equal(true)
181 | u1.authorized('read', u1).should.equal(true)
182 | })
183 | })
184 |
185 | describe('Model.screen', () => {
186 | it('should exist on class', () => {
187 | let rules = {}
188 | let User = createUser()
189 | palisade(User, rules)
190 | should.exist(User.screen)
191 | })
192 | it('should exist on instance', () => {
193 | let rules = {}
194 | let User = createUser()
195 | palisade(User, rules)
196 | should.exist(new User({}).screen)
197 | })
198 | it('should return empty if no read rules specified', () => {
199 | let rules = {}
200 | let User = createUser()
201 | palisade(User, rules)
202 | User.screen('read', null, { name: 'test' }).should.eql({})
203 | })
204 | it('should return empty if no document rules for type specified', () => {
205 | let rules = {
206 | read: {}
207 | }
208 | let User = createUser()
209 | palisade(User, rules)
210 | User.screen('read', null, { name: 'test' }).should.eql({})
211 | })
212 | it('should return empty if document rules specified have no roles', () => {
213 | let rules = {
214 | read: {
215 | name: []
216 | }
217 | }
218 | let User = createUser()
219 | palisade(User, rules)
220 | User.screen('read', null, { name: 'test' }).should.eql({})
221 | })
222 | it('should return data if read rules are public and no user given', () => {
223 | let rules = {
224 | read: {
225 | name: [ 'public' ]
226 | }
227 | }
228 | let User = createUser()
229 | let o = { name: 'test' }
230 | palisade(User, rules)
231 | User.screen('read', null, o).should.eql(o)
232 | })
233 | it('should return empty if user role attr doesnt match', () => {
234 | let rules = {
235 | read: {
236 | name: [ 'admin' ]
237 | }
238 | }
239 | let User = createUser()
240 | let u1 = { role: 'pleb' }
241 | let o = { name: 'test' }
242 | palisade(User, rules)
243 | User.screen('read', u1, o).should.eql({})
244 | })
245 | it('should return empty if user roles attr doesnt match', () => {
246 | let rules = {
247 | read: {
248 | name: [ 'admin' ]
249 | }
250 | }
251 | let User = createUser()
252 | let u1 = { roles: [ 'pleb' ] }
253 | let o = { name: 'test' }
254 | palisade(User, rules)
255 | User.screen('read', u1, o).should.eql({})
256 | })
257 | it('should return data if user role attr matches', () => {
258 | let rules = {
259 | read: {
260 | name: [ 'admin', 'pleb' ]
261 | }
262 | }
263 | let User = createUser()
264 | let u1 = { role: 'admin' }
265 | let o = { name: 'test' }
266 | palisade(User, rules)
267 | User.screen('read', u1, o).should.eql(o)
268 | })
269 | it('should return array data if user role attr matches', () => {
270 | let rules = {
271 | read: {
272 | name: [ 'admin', 'pleb' ]
273 | }
274 | }
275 | let User = createUser()
276 | let u1 = { role: 'admin' }
277 | let o = { name: 'test' }
278 | let data = [ o, o ]
279 | palisade(User, rules)
280 | User.screen('read', u1, data).should.eql(data)
281 | })
282 | it('should return data if user roles attr matches', () => {
283 | let rules = {
284 | read: {
285 | name: [ 'admin', 'super' ]
286 | }
287 | }
288 | let User = createUser()
289 | let u1 = { roles: [ 'admin', 'pleb' ] }
290 | let o = { name: 'test' }
291 | palisade(User, rules)
292 | User.screen('read', u1, o).should.eql(o)
293 | })
294 | it.skip('should return data if self role matches', () => {
295 | let rules = {
296 | read: {
297 | id: [ 'self' ],
298 | name: [ 'self' ]
299 | }
300 | }
301 | let User = createUser()
302 | palisade(User, rules)
303 |
304 | let o = { id: '123', name: 'test' }
305 | let u1 = new User(o)
306 | User.screen('read', u1, u1).should.eql(o)
307 | u1.screen('read', u1).should.eql(o)
308 | })
309 | it('should return nested data if user roles attr matches', () => {
310 | let rules = {
311 | read: {
312 | attributes: {
313 | name: [ 'admin', 'super' ]
314 | }
315 | }
316 | }
317 | let User = createUser()
318 | let u1 = { roles: [ 'admin', 'pleb' ] }
319 | let o = { attributes: { name: 'test' } }
320 | palisade(User, rules)
321 | User.screen('read', u1, o).should.eql(o)
322 | })
323 | it('should return empty nested data if user roles attr doesnt match', () => {
324 | let rules = {
325 | read: {
326 | attributes: {
327 | name: [ 'root', 'super' ]
328 | }
329 | }
330 | }
331 | let User = createUser()
332 | let u1 = { roles: [ 'admin', 'pleb' ] }
333 | let o = { attributes: { name: 'test' } }
334 | palisade(User, rules)
335 | User.screen('read', u1, o).should.eql({ attributes: {} })
336 | })
337 | it('should return nested data if user roles attr fn matches', () => {
338 | const yo = () => true
339 | let rules = {
340 | read: {
341 | attributes: {
342 | name: [ yo, 'root', 'super' ]
343 | }
344 | }
345 | }
346 | let User = createUser()
347 | let u1 = { roles: [ 'admin', 'pleb' ] }
348 | let o = { attributes: { name: 'test' } }
349 | palisade(User, rules)
350 | User.screen('read', u1, o).should.eql({ attributes: { name: 'test' } })
351 | })
352 | it('should return empty nested data if user roles attr fn doesnt match', () => {
353 | const yo = () => false
354 | let rules = {
355 | read: {
356 | attributes: {
357 | name: [ yo ]
358 | }
359 | }
360 | }
361 | let User = createUser()
362 | let u1 = { roles: [ 'admin', 'pleb' ] }
363 | let o = { attributes: { name: 'test' } }
364 | palisade(User, rules)
365 | User.screen('read', u1, o).should.eql({ attributes: {} })
366 | })
367 | })
368 |
369 | describe('screenDeep', () => {
370 | it('should return an empty array when no read specified', () => {
371 | let User = createUser()
372 | palisade(User, {})
373 | let u1 = new User({
374 | id: '123',
375 | name: 'test1'
376 | })
377 | let u2 = new User({
378 | id: '456',
379 | name: 'test2'
380 | })
381 | let data = [ u1, u2 ]
382 | screenDeep(null, data).should.eql([])
383 | })
384 | it('should return an empty nested array when no read specified', () => {
385 | let User = createUser()
386 | palisade(User, {})
387 | let u1 = new User({
388 | id: '123',
389 | name: 'test1'
390 | })
391 | let u2 = new User({
392 | id: '456',
393 | name: 'test2'
394 | })
395 | let data = [ [ u1, u2 ] ]
396 | screenDeep(null, data).should.eql([ [ ] ])
397 | })
398 | it('should return an empty object when no read specified', () => {
399 | let User = createUser()
400 | palisade(User, {})
401 | let u1 = new User({
402 | id: '123',
403 | name: 'test1'
404 | })
405 | let u2 = new User({
406 | id: '456',
407 | name: 'test2'
408 | })
409 | let data = { a: u1, b: u2 }
410 | screenDeep(null, data).should.eql({})
411 | })
412 | it('should return an empty nested object when no read specified', () => {
413 | let User = createUser()
414 | palisade(User, {})
415 | let u1 = new User({
416 | id: '123',
417 | name: 'test1'
418 | })
419 | let u2 = new User({
420 | id: '456',
421 | name: 'test2'
422 | })
423 | let data = { c: { a: u1, b: u2 } }
424 | screenDeep(null, data).should.eql({ c: {} })
425 | })
426 | it('should return data when read public specified', () => {
427 | let User = createUser()
428 | palisade(User, {
429 | document: {
430 | read: [ 'public' ]
431 | },
432 | read: {
433 | id: [ 'public' ],
434 | name: [ 'self' ]
435 | }
436 | })
437 | let u1 = new User({
438 | id: '123',
439 | name: 'test1'
440 | })
441 | let u2 = new User({
442 | id: '456',
443 | name: 'test2'
444 | })
445 | screenDeep(u1, u2).should.eql({ id: '456' })
446 | })
447 | it('should return generic data', () => {
448 | let User = createUser()
449 | palisade(User, {
450 | document: {
451 | read: [ 'public' ]
452 | },
453 | read: {
454 | id: [ 'public' ],
455 | time: [ 'public' ],
456 | name: [ 'self' ]
457 | }
458 | })
459 | let u1 = new User({
460 | id: '123',
461 | name: 'test1'
462 | })
463 | let u2 = {
464 | id: '456',
465 | bday: new Date('2016-02-14T23:31:46.733Z'),
466 | name: 'test2'
467 | }
468 | screenDeep(u1, u2).should.eql({
469 | id: '456',
470 | name: 'test2',
471 | bday: '2016-02-14T23:31:46.733Z'
472 | })
473 | })
474 | })
475 |
--------------------------------------------------------------------------------