-
Notifications
You must be signed in to change notification settings - Fork 9
/
index.js
139 lines (117 loc) · 4.44 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
const _ = require('lodash');
const {
getAuthorizedFields,
hasPermission,
authIsDisabled,
sanitizeDocumentList,
getUpdatePaths,
} = require('./lib/helpers');
const PermissionDeniedError = require('./lib/PermissionDeniedError');
module.exports = (schema) => {
function save(doc, options, next) {
if (doc.isNew && !hasPermission(schema, options, 'create', doc)) {
return next(new PermissionDeniedError('create'));
}
const authorizedFields = getAuthorizedFields(schema, options, 'write', doc);
const modifiedPaths = doc.modifiedPaths();
const discrepancies = _.difference(modifiedPaths, authorizedFields);
if (discrepancies.length > 0) {
return next(new PermissionDeniedError('write', discrepancies));
}
return next();
}
function removeQuery(query, next) {
if (!hasPermission(schema, query.options, 'remove')) {
return next(new PermissionDeniedError('remove'));
}
return next();
}
function removeDoc(doc, options, next) {
if (!hasPermission(schema, options, 'remove', doc)) {
return next(new PermissionDeniedError('remove'));
}
return next();
}
function find(query, docs, next) {
const sanitizedResult = sanitizeDocumentList(schema, query.options, docs);
return next(null, sanitizedResult);
}
function update(query, next) {
// If this is an upsert, you'll need the create permission
// TODO add some tests for the upset case
if (
query.options
&& query.options.upsert
&& !hasPermission(schema, query.options, 'create')
) {
return next(new PermissionDeniedError('create'));
}
const authorizedFields = getAuthorizedFields(schema, query.options, 'write');
// check to see if the group is trying to update a field it does not have permission to
const modifiedPaths = getUpdatePaths(query._update);
const discrepancies = _.difference(modifiedPaths, authorizedFields);
if (discrepancies.length > 0) {
return next(new PermissionDeniedError('write', discrepancies));
}
// TODO handle the overwrite option
// TODO handle Model.updateMany
// Detect which fields can be returned if 'new: true' is set
const authorizedReturnFields = getAuthorizedFields(schema, query.options, 'read');
// create a sanitizedReturnFields object that will be used to return only the fields that a
// group has access to read
const sanitizedReturnFields = {};
authorizedReturnFields.forEach((field) => {
if (!query._fields || query._fields[field]) {
sanitizedReturnFields[field] = 1;
}
});
query._fields = sanitizedReturnFields;
return next();
}
// Find paths with permissioned schemas and store those so deep checks can be done
// on the right paths at call time.
schema.pathsWithPermissionedSchemas = {};
schema.eachPath((path, schemaType) => {
const subSchema = schemaType.schema;
if (subSchema && subSchema.permissions) {
schema.pathsWithPermissionedSchemas[path] = subSchema;
}
});
schema.pre('findOneAndRemove', function preFindOneAndRemove(next) {
if (authIsDisabled(this.options)) { return next(); }
return removeQuery(this, next);
});
// TODO, WTF, how to prevent someone from Model.find().remove().exec(); That doesn't
// fire any remove hooks. Does it fire a find hook?
schema.pre('remove', function preRemove(next, options) {
if (authIsDisabled(options)) { return next(); }
return removeDoc(this, options, next);
});
schema.pre('save', function preSave(next, options) {
if (authIsDisabled(options)) { return next(); }
return save(this, options, next);
});
schema.post('find', function postFind(doc, next) {
if (authIsDisabled(this.options)) { return next(); }
return find(this, doc, next);
});
schema.post('findOne', function postFindOne(doc, next) {
if (authIsDisabled(this.options)) { return next(); }
return find(this, doc, next);
});
schema.pre('update', function preUpdate(next) {
if (authIsDisabled(this.options)) { return next(); }
return update(this, next);
});
schema.pre('findOneAndUpdate', function preFindOneAndUpdate(next) {
if (authIsDisabled(this.options)) { return next(); }
return update(this, next);
});
schema.query.setAuthLevel = function setAuthLevel(authLevel) {
this.options.authLevel = authLevel;
return this;
};
schema.statics.canCreate = function canCreate(options) {
return hasPermission(this.schema, options, 'create');
};
};