@casl/mongoose 7.1.2 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -32
- package/dist/es6c/index.js +1 -1
- package/dist/es6c/index.js.map +1 -1
- package/dist/es6m/index.mjs +1 -1
- package/dist/es6m/index.mjs.map +1 -1
- package/dist/types/accessible_fields.d.ts +2 -2
- package/dist/types/accessible_records.d.ts +4 -2
- package/dist/types/index.d.ts +2 -1
- package/dist/types/mongo.d.ts +14 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -16,12 +16,120 @@ yarn add @casl/mongoose @casl/ability
|
|
|
16
16
|
pnpm add @casl/mongoose @casl/ability
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Usage
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
`@casl/mongoose` can be integrated not only with [mongoose] but also with any [MongoDB] JS driver thanks to new `accessibleBy` helper function.
|
|
22
|
+
|
|
23
|
+
### `accessibleBy` helper
|
|
24
|
+
|
|
25
|
+
This neat helper function allows to convert ability rules to MongoDB query and fetch only accessible records from the database. It can be used with mongoose or [MongoDB adapter][mongo-adapter]:
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
#### MongoDB adapter
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
const { accessibleBy } = require('@casl/mongoose');
|
|
32
|
+
const { MongoClient } = require('mongodb');
|
|
33
|
+
const ability = require('./ability');
|
|
34
|
+
|
|
35
|
+
async function main() {
|
|
36
|
+
const db = await MongoClient.connect('mongodb://localhost:27017/blog');
|
|
37
|
+
let posts;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
posts = await db.collection('posts').find(accessibleBy(ability, 'update').Post);
|
|
41
|
+
} finally {
|
|
42
|
+
db.close();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log(posts);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This can also be combined with other conditions with help of `$and` operator:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
posts = await db.collection('posts').find({
|
|
53
|
+
$and: [
|
|
54
|
+
accessibleBy(ability, 'update').Post,
|
|
55
|
+
{ public: true }
|
|
56
|
+
]
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Important!**: never use spread operator (i.e., `...`) to combine conditions provided by `accessibleBy` with something else because you may accidentally overwrite properties that restrict access to particular records:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// returns { authorId: 1 }
|
|
64
|
+
const permissionRestrictedConditions = accessibleBy(ability, 'update').Post;
|
|
65
|
+
|
|
66
|
+
const query = {
|
|
67
|
+
...permissionRestrictedConditions,
|
|
68
|
+
authorId: 2
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
In the case above, we overwrote `authorId` property and basically allowed non-authorized access to posts of author with `id = 2`
|
|
73
|
+
|
|
74
|
+
If there are no permissions defined for particular action/subjectType, `accessibleBy` will return `{ $expr: false }` and when it's sent to MongoDB, it will return an empty result set.
|
|
75
|
+
|
|
76
|
+
#### Mongoose
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
const Post = require('./Post') // mongoose model
|
|
80
|
+
const ability = require('./ability') // defines Ability instance
|
|
81
|
+
|
|
82
|
+
async function main() {
|
|
83
|
+
const accessiblePosts = await Post.find(accessibleBy(ability).Post);
|
|
84
|
+
console.log(accessiblePosts);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`accessibleBy` returns a `Proxy` instance and then we access particular subject type by reading its property. Property name is then passed to `Ability` methods as `subjectType`. With Typescript we can restrict this properties only to know record types:
|
|
89
|
+
|
|
90
|
+
#### `accessibleBy` in TypeScript
|
|
91
|
+
|
|
92
|
+
If we want to get hints in IDE regarding what record types (i.e., entity or model names) can be accessed in return value of `accessibleBy` we can easily do this by using module augmentation:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { accessibleBy } from '@casl/mongoose';
|
|
96
|
+
import { ability } from './ability'; // defines Ability instance
|
|
97
|
+
|
|
98
|
+
declare module '@casl/mongoose' {
|
|
99
|
+
interface RecordTypes {
|
|
100
|
+
Post: true
|
|
101
|
+
User: true
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
accessibleBy(ability).User // allows only User and Post properties
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This can be done either centrally, in the single place or it can be defined in every model/entity definition file. For example, we can augment `@casl/mongoose` in every mongoose model definition file:
|
|
109
|
+
|
|
110
|
+
```js @{data-filename="Post.ts"}
|
|
111
|
+
import mongoose from 'mongoose';
|
|
112
|
+
|
|
113
|
+
const PostSchema = new mongoose.Schema({
|
|
114
|
+
title: String,
|
|
115
|
+
author: String
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
declare module '@casl/mongoose' {
|
|
119
|
+
interface RecordTypes {
|
|
120
|
+
Post: true
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const Post = mongoose.model('Post', PostSchema)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Historically, `@casl/mongoose` was intended for super easy integration with [mongoose] but now we re-orient it to be more MongoDB specific package because mongoose keeps bringing complexity and issues with ts types.
|
|
22
128
|
|
|
23
129
|
### Accessible Records plugin
|
|
24
130
|
|
|
131
|
+
This plugin is deprecated, the recommended way is to use [`accessibleBy` helper function](#accessibleBy-helper)
|
|
132
|
+
|
|
25
133
|
`accessibleRecordsPlugin` is a plugin which adds `accessibleBy` method to query and static methods of mongoose models. We can add this plugin globally:
|
|
26
134
|
|
|
27
135
|
```js
|
|
@@ -201,36 +309,7 @@ post.accessibleFieldsBy(ability); // ['title']
|
|
|
201
309
|
|
|
202
310
|
As you can see, a static method returns all fields that can be read for all posts. At the same time, an instance method returns fields that can be read from this particular `post` instance. That's why there is no much sense (except you want to reduce traffic between app and database) to pass the result of static method into `mongoose.Query`'s `select` method because eventually you will need to call `accessibleFieldsBy` on every instance.
|
|
203
311
|
|
|
204
|
-
##
|
|
205
|
-
|
|
206
|
-
In case you don't use mongoose, this package provides `toMongoQuery` function which can convert CASL rules into [MongoDB] query. Lets see an example of how to fetch accessible records using raw [MongoDB adapter][mongo-adapter]
|
|
207
|
-
|
|
208
|
-
```js
|
|
209
|
-
const { toMongoQuery } = require('@casl/mongoose');
|
|
210
|
-
const { MongoClient } = require('mongodb');
|
|
211
|
-
const ability = require('./ability');
|
|
212
|
-
|
|
213
|
-
async function main() {
|
|
214
|
-
const db = await MongoClient.connect('mongodb://localhost:27017/blog');
|
|
215
|
-
const query = toMongoQuery(ability, 'Post', 'update');
|
|
216
|
-
let posts;
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
if (query === null) {
|
|
220
|
-
// returns null if ability does not allow to update posts
|
|
221
|
-
posts = [];
|
|
222
|
-
} else {
|
|
223
|
-
posts = await db.collection('posts').find(query);
|
|
224
|
-
}
|
|
225
|
-
} finally {
|
|
226
|
-
db.close();
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
console.log(posts);
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
## TypeScript support
|
|
312
|
+
## TypeScript support in mongoose
|
|
234
313
|
|
|
235
314
|
The package is written in TypeScript, this makes it easier to work with plugins and `toMongoQuery` helper because IDE provides useful hints. Let's see it in action!
|
|
236
315
|
|
package/dist/es6c/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";var t=require("@casl/ability");var n=require("@casl/ability/extra");function r(t){const n=t.conditions;return t.inverted?{$nor:[n]}:n}function e(t,e,o="read"){return n.rulesToQuery(t,o,e,r)}function o(t,n="read"){return new Proxy({t:t,o:n},c)}const c={get(t,e){const o=n.rulesToQuery(t.t,t.o,e,r);return o===null?{$expr:false}:o}};function i(n,r,e,o){o.where({$expr:false});const c=o;if(typeof c.pre==="function")c.pre((o=>{const c=t.ForbiddenError.from(n).unlessCan(r,e);o(c)}));return o}function s(t,n,r){const o=n.detectSubjectType({constructor:t.model});if(!o)throw new TypeError(`Cannot detect subject type of "${t.model.modelName}" to return accessible records`);const c=e(n,o,r);if(c===null)return i(n,r||"read",o,t.where());return t.and([c])}function u(t,n){return s(this.where(),t,n)}function f(t,n){return s(this,t,n)}function a(t){t.query.accessibleBy=f;t.statics.accessibleBy=u}const l=t=>Object.keys(t.paths);function d(n,r){const e=r.getFields(n);if(!r||!("except"in r))return e;const o=t.wrapArray(r.except);return e.filter((t=>o.indexOf(t)===-1))}function p(){let n;return(r,e)=>{if(!n){const o=e&&"only"in e?t.wrapArray(e.only):d(r,e);n=t=>t.fields||o}return n}}function x(t,r){const e=Object.assign({getFields:l},r);const o=p();function c(r,c){return n.permittedFieldsOf(r,c||"read",this,{fieldsFrom:o(t,e)})}function i(r,c){const i={constructor:this};return n.permittedFieldsOf(r,c||"read",i,{fieldsFrom:o(t,e)})}t.statics.accessibleFieldsBy=i;t.method("accessibleFieldsBy",c)}exports.accessibleBy=o;exports.accessibleFieldsPlugin=x;exports.accessibleRecordsPlugin=a;exports.getSchemaPaths=l;exports.toMongoQuery=e;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/es6c/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/mongo.ts","../../src/accessible_records.ts","../../src/accessible_fields.ts"],"sourcesContent":["import { AnyMongoAbility } from '@casl/ability';\nimport { AbilityQuery, rulesToQuery } from '@casl/ability/extra';\n\nfunction convertToMongoQuery(rule: AnyMongoAbility['rules'][number]) {\n const conditions = rule.conditions!;\n return rule.inverted ? { $nor: [conditions] } : conditions;\n}\n\nexport function toMongoQuery<T extends AnyMongoAbility>(\n ability: T,\n subjectType: Parameters<T['rulesFor']>[1],\n action: Parameters<T['rulesFor']>[0] = 'read'\n): AbilityQuery | null {\n return rulesToQuery(ability, action, subjectType, convertToMongoQuery);\n}\n","import { Normalize, AnyMongoAbility, Generics, ForbiddenError } from '@casl/ability';\nimport { Schema, QueryWithHelpers, Model, Document, HydratedDocument, Query } from 'mongoose';\nimport { toMongoQuery } from './mongo';\n\nfunction failedQuery(\n ability: AnyMongoAbility,\n action: string,\n modelName: string,\n query: QueryWithHelpers<Document, Document>\n) {\n query.where({ __forbiddenByCasl__: 1 }); // eslint-disable-line\n const anyQuery: any = query;\n\n if (typeof anyQuery.pre === 'function') {\n anyQuery.pre((cb: (error?: Error) => void) => {\n const error = ForbiddenError.from(ability).unlessCan(action, modelName);\n cb(error);\n });\n }\n\n return query;\n}\n\nfunction accessibleBy<T extends AnyMongoAbility>(\n baseQuery: Query<any, any>,\n ability: T,\n action?: Normalize<Generics<T>['abilities']>[0]\n): QueryWithHelpers<Document, Document> {\n const subjectType = ability.detectSubjectType({\n constructor: baseQuery.model\n });\n\n if (!subjectType) {\n throw new TypeError(`Cannot detect subject type of \"${baseQuery.model.modelName}\" to return accessible records`);\n }\n\n const query = toMongoQuery(ability, subjectType, action);\n\n if (query === null) {\n return failedQuery(ability, action || 'read', subjectType, baseQuery.where());\n }\n\n return baseQuery.and([query]);\n}\n\ntype GetAccessibleRecords<T, TQueryHelpers, TMethods, TVirtuals> = <U extends AnyMongoAbility>(\n ability: U,\n action?: Normalize<Generics<U>['abilities']>[0]\n) => QueryWithHelpers<\nArray<T>,\nT,\nAccessibleRecordQueryHelpers<T, TQueryHelpers, TMethods, TVirtuals>\n>;\n\nexport type AccessibleRecordQueryHelpers<T, TQueryHelpers = {}, TMethods = {}, TVirtuals = {}> = {\n accessibleBy: GetAccessibleRecords<\n HydratedDocument<T, TMethods, TVirtuals>,\n TQueryHelpers,\n TMethods,\n TVirtuals\n >\n};\nexport interface AccessibleRecordModel<\n T,\n TQueryHelpers = {},\n TMethods = {},\n TVirtuals = {}\n> extends Model<T,\n TQueryHelpers & AccessibleRecordQueryHelpers<T, TQueryHelpers, TMethods, TVirtuals>,\n TMethods,\n TVirtuals> {\n accessibleBy: GetAccessibleRecords<\n HydratedDocument<T, TMethods, TVirtuals>,\n TQueryHelpers,\n TMethods,\n TVirtuals\n >\n}\n\nfunction modelAccessibleBy(this: Model<unknown>, ability: AnyMongoAbility, action?: string) {\n return accessibleBy(this.where(), ability, action);\n}\n\nfunction queryAccessibleBy(\n this: Query<unknown, unknown>,\n ability: AnyMongoAbility,\n action?: string\n) {\n return accessibleBy(this, ability, action);\n}\n\nexport function accessibleRecordsPlugin(schema: Schema<any>): void {\n (schema.query as Record<string, unknown>).accessibleBy = queryAccessibleBy;\n schema.statics.accessibleBy = modelAccessibleBy;\n}\n","import { wrapArray, Normalize, AnyMongoAbility, Generics } from '@casl/ability';\nimport { permittedFieldsOf, PermittedFieldsOptions } from '@casl/ability/extra';\nimport type { Schema, Model, Document } from 'mongoose';\n\nexport type AccessibleFieldsOptions =\n {\n getFields(schema: Schema<Document>): string[]\n } &\n ({ only: string | string[] } | { except: string | string[] });\n\nexport const getSchemaPaths: AccessibleFieldsOptions['getFields'] = schema => Object.keys((schema as { paths: object }).paths);\n\nfunction fieldsOf(schema: Schema<Document>, options: Partial<AccessibleFieldsOptions>) {\n const fields = options.getFields!(schema);\n\n if (!options || !('except' in options)) {\n return fields;\n }\n\n const excludedFields = wrapArray(options.except);\n return fields.filter(field => excludedFields.indexOf(field) === -1);\n}\n\ntype GetAccessibleFields<T> = <U extends AnyMongoAbility>(\n this: Model<T> | T,\n ability: U,\n action?: Normalize<Generics<U>['abilities']>[0]\n) => string[];\n\nexport interface AccessibleFieldsModel<\n T,\n TQueryHelpers = {},\n TMethods = {},\n TVirtuals = {}\n> extends Model<T, TQueryHelpers, TMethods & AccessibleFieldDocumentMethods<T>, TVirtuals> {\n accessibleFieldsBy: GetAccessibleFields<T>\n}\n\nexport interface AccessibleFieldDocumentMethods<T = Document> {\n accessibleFieldsBy: GetAccessibleFields<T>\n}\n\n/**\n * @deprecated Mongoose recommends against `extends Document`, prefer to use `AccessibleFieldsModel` instead.\n * See here: https://mongoosejs.com/docs/typescript.html#using-extends-document\n */\nexport interface AccessibleFieldsDocument extends Document, AccessibleFieldDocumentMethods {}\n\nfunction modelFieldsGetter() {\n let fieldsFrom: PermittedFieldsOptions<AnyMongoAbility>['fieldsFrom'];\n return (schema: Schema<any>, options: Partial<AccessibleFieldsOptions>) => {\n if (!fieldsFrom) {\n const ALL_FIELDS = options && 'only' in options\n ? wrapArray(options.only as string[])\n : fieldsOf(schema, options);\n fieldsFrom = rule => rule.fields || ALL_FIELDS;\n }\n\n return fieldsFrom;\n };\n}\n\nexport function accessibleFieldsPlugin(\n schema: Schema<any>,\n rawOptions?: Partial<AccessibleFieldsOptions>\n): void {\n const options = { getFields: getSchemaPaths, ...rawOptions };\n const fieldsFrom = modelFieldsGetter();\n\n function istanceAccessibleFields(this: Document, ability: AnyMongoAbility, action?: string) {\n return permittedFieldsOf(ability, action || 'read', this, {\n fieldsFrom: fieldsFrom(schema, options)\n });\n }\n\n function modelAccessibleFields(this: Model<unknown>, ability: AnyMongoAbility, action?: string) {\n const document = { constructor: this };\n return permittedFieldsOf(ability, action || 'read', document, {\n fieldsFrom: fieldsFrom(schema, options)\n });\n }\n\n schema.statics.accessibleFieldsBy = modelAccessibleFields;\n schema.method('accessibleFieldsBy', istanceAccessibleFields);\n}\n"],"names":["convertToMongoQuery","rule","conditions","inverted","$nor","toMongoQuery","ability","subjectType","action","rulesToQuery","failedQuery","modelName","query","where","__forbiddenByCasl__","anyQuery","pre","cb","error","ForbiddenError","from","unlessCan","accessibleBy","baseQuery","detectSubjectType","constructor","model","TypeError","and","modelAccessibleBy","this","queryAccessibleBy","accessibleRecordsPlugin","schema","statics","getSchemaPaths","Object","keys","paths","fieldsOf","options","fields","getFields","excludedFields","wrapArray","except","filter","field","indexOf","modelFieldsGetter","fieldsFrom","ALL_FIELDS","only","accessibleFieldsPlugin","rawOptions","istanceAccessibleFields","permittedFieldsOf","modelAccessibleFields","document","accessibleFieldsBy","method"],"mappings":"0IAGA,SAASA,EAAoBC,SACrBC,EAAaD,EAAKC,kBACjBD,EAAKE,SAAW,CAAEC,KAAM,CAACF,IAAgBA,EAG3C,SAASG,EACdC,EACAC,EACAC,EAAuC,eAEhCC,eAAaH,EAASE,EAAQD,EAAaP,GCTpD,SAASU,EACPJ,EACAE,EACAG,EACAC,GAEAA,EAAMC,MAAM,CAAEC,oBAAqB,UAC7BC,EAAgBH,KAEM,oBAAjBG,EAASC,IAClBD,EAASC,KAAKC,UACNC,EAAQC,iBAAeC,KAAKd,GAASe,UAAUb,EAAQG,GAC7DM,EAAGC,aAIAN,EAGT,SAASU,EACPC,EACAjB,EACAE,SAEMD,EAAcD,EAAQkB,kBAAkB,CAC5CC,YAAaF,EAAUG,YAGpBnB,QACG,IAAIoB,UAAW,kCAAiCJ,EAAUG,MAAMf,iDAGlEC,EAAQP,EAAaC,EAASC,EAAaC,MAEnC,OAAVI,SACKF,EAAYJ,EAASE,GAAU,OAAQD,EAAagB,EAAUV,gBAGhEU,EAAUK,IAAI,CAAChB,IAqCxB,SAASiB,EAAwCvB,EAA0BE,UAClEc,EAAaQ,KAAKjB,QAASP,EAASE,GAG7C,SAASuB,EAEPzB,EACAE,UAEOc,EAAaQ,KAAMxB,EAASE,GAG9B,SAASwB,EAAwBC,GACrCA,EAAOrB,MAAkCU,aAAeS,EACzDE,EAAOC,QAAQZ,aAAeO,QCnFnBM,EAAuDF,GAAUG,OAAOC,KAAMJ,EAA6BK,OAExH,SAASC,EAASN,EAA0BO,SACpCC,EAASD,EAAQE,UAAWT,OAE7BO,KAAa,WAAYA,UACrBC,QAGHE,EAAiBC,YAAUJ,EAAQK,eAClCJ,EAAOK,QAAOC,IAA4C,IAAnCJ,EAAeK,QAAQD,KA4BvD,SAASE,QACHC,QACG,CAACjB,EAAqBO,SACtBU,EAAY,OACTC,EAAaX,GAAW,SAAUA,EACpCI,YAAUJ,EAAQY,MAClBb,EAASN,EAAQO,GACrBU,EAAajD,GAAQA,EAAKwC,QAAUU,SAG/BD,GAIJ,SAASG,EACdpB,EACAqB,SAEMd,iBAAYE,UAAWP,GAAmBmB,SAC1CJ,EAAaD,aAEVM,EAAwCjD,EAA0BE,UAClEgD,oBAAkBlD,EAASE,GAAU,OAAQsB,KAAM,CACxDoB,WAAYA,EAAWjB,EAAQO,cAI1BiB,EAA4CnD,EAA0BE,SACvEkD,EAAW,CAAEjC,YAAaK,aACzB0B,oBAAkBlD,EAASE,GAAU,OAAQkD,EAAU,CAC5DR,WAAYA,EAAWjB,EAAQO,KAInCP,EAAOC,QAAQyB,mBAAqBF,EACpCxB,EAAO2B,OAAO,qBAAsBL"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/mongo.ts","../../src/accessible_records.ts","../../src/accessible_fields.ts"],"sourcesContent":["import { AnyMongoAbility } from '@casl/ability';\nimport { AbilityQuery, rulesToQuery } from '@casl/ability/extra';\n\nfunction convertToMongoQuery(rule: AnyMongoAbility['rules'][number]) {\n const conditions = rule.conditions!;\n return rule.inverted ? { $nor: [conditions] } : conditions;\n}\n\n/**\n * @deprecated use accessibleBy instead\n *\n * Converts ability action + subjectType to MongoDB query\n */\nexport function toMongoQuery<T extends AnyMongoAbility>(\n ability: T,\n subjectType: Parameters<T['rulesFor']>[1],\n action: Parameters<T['rulesFor']>[0] = 'read'\n): AbilityQuery | null {\n return rulesToQuery(ability, action, subjectType, convertToMongoQuery);\n}\n\nexport interface RecordTypes {\n}\ntype StringOrKeysOf<T> = keyof T extends never ? string : keyof T;\n\n/**\n * Returns Mongo query per record type (i.e., entity type) based on provided Ability and action.\n * In case action is not allowed, it returns `{ $expr: false }`\n */\nexport function accessibleBy<T extends AnyMongoAbility>(\n ability: T,\n action: Parameters<T['rulesFor']>[0] = 'read'\n): Record<StringOrKeysOf<RecordTypes>, AbilityQuery> {\n return new Proxy({\n _ability: ability,\n _action: action\n }, accessibleByProxyHandlers) as unknown as Record<StringOrKeysOf<RecordTypes>, AbilityQuery>;\n}\n\nconst accessibleByProxyHandlers: ProxyHandler<{ _ability: AnyMongoAbility, _action: string }> = {\n get(target, subjectType) {\n const query = rulesToQuery(target._ability, target._action, subjectType, convertToMongoQuery);\n return query === null ? { $expr: false } : query;\n }\n};\n","import { Normalize, AnyMongoAbility, Generics, ForbiddenError } from '@casl/ability';\nimport { Schema, QueryWithHelpers, Model, Document, HydratedDocument, Query } from 'mongoose';\nimport { toMongoQuery } from './mongo';\n\nfunction failedQuery(\n ability: AnyMongoAbility,\n action: string,\n modelName: string,\n query: QueryWithHelpers<Document, Document>\n) {\n query.where({ $expr: false }); // rule that returns empty result set\n const anyQuery: any = query;\n\n if (typeof anyQuery.pre === 'function') {\n anyQuery.pre((cb: (error?: Error) => void) => {\n const error = ForbiddenError.from(ability).unlessCan(action, modelName);\n cb(error);\n });\n }\n\n return query;\n}\n\nfunction accessibleBy<T extends AnyMongoAbility>(\n baseQuery: Query<any, any>,\n ability: T,\n action?: Normalize<Generics<T>['abilities']>[0]\n): QueryWithHelpers<Document, Document> {\n const subjectType = ability.detectSubjectType({\n constructor: baseQuery.model\n });\n\n if (!subjectType) {\n throw new TypeError(`Cannot detect subject type of \"${baseQuery.model.modelName}\" to return accessible records`);\n }\n\n const query = toMongoQuery(ability, subjectType, action);\n\n if (query === null) {\n return failedQuery(ability, action || 'read', subjectType, baseQuery.where());\n }\n\n return baseQuery.and([query]);\n}\n\ntype GetAccessibleRecords<T, TQueryHelpers, TMethods, TVirtuals> = <U extends AnyMongoAbility>(\n ability: U,\n action?: Normalize<Generics<U>['abilities']>[0]\n) => QueryWithHelpers<\nArray<T>,\nT,\nAccessibleRecordQueryHelpers<T, TQueryHelpers, TMethods, TVirtuals>\n>;\n\nexport type AccessibleRecordQueryHelpers<T, TQueryHelpers = {}, TMethods = {}, TVirtuals = {}> = {\n /** @deprecated use accessibleBy helper instead */\n accessibleBy: GetAccessibleRecords<\n HydratedDocument<T, TMethods, TVirtuals>,\n TQueryHelpers,\n TMethods,\n TVirtuals\n >\n};\nexport interface AccessibleRecordModel<\n T,\n TQueryHelpers = {},\n TMethods = {},\n TVirtuals = {}\n> extends Model<T,\n TQueryHelpers & AccessibleRecordQueryHelpers<T, TQueryHelpers, TMethods, TVirtuals>,\n TMethods,\n TVirtuals> {\n /** @deprecated use accessibleBy helper instead */\n accessibleBy: GetAccessibleRecords<\n HydratedDocument<T, TMethods, TVirtuals>,\n TQueryHelpers,\n TMethods,\n TVirtuals\n >\n}\n\nfunction modelAccessibleBy(this: Model<unknown>, ability: AnyMongoAbility, action?: string) {\n return accessibleBy(this.where(), ability, action);\n}\n\nfunction queryAccessibleBy(\n this: Query<unknown, unknown>,\n ability: AnyMongoAbility,\n action?: string\n) {\n return accessibleBy(this, ability, action);\n}\n\nexport function accessibleRecordsPlugin(schema: Schema<any>): void {\n (schema.query as Record<string, unknown>).accessibleBy = queryAccessibleBy;\n schema.statics.accessibleBy = modelAccessibleBy;\n}\n","import { wrapArray, Normalize, AnyMongoAbility, Generics } from '@casl/ability';\nimport { permittedFieldsOf, PermittedFieldsOptions } from '@casl/ability/extra';\nimport type { Schema, Model, Document } from 'mongoose';\n\nexport type AccessibleFieldsOptions =\n {\n getFields(schema: Schema<Document>): string[]\n } &\n ({ only: string | string[] } | { except: string | string[] });\n\nexport const getSchemaPaths: AccessibleFieldsOptions['getFields'] = schema => Object.keys((schema as { paths: object }).paths);\n\nfunction fieldsOf(schema: Schema<Document>, options: Partial<AccessibleFieldsOptions>) {\n const fields = options.getFields!(schema);\n\n if (!options || !('except' in options)) {\n return fields;\n }\n\n const excludedFields = wrapArray(options.except);\n return fields.filter(field => excludedFields.indexOf(field) === -1);\n}\n\ntype GetAccessibleFields<T> = <U extends AnyMongoAbility>(\n this: Model<T> | T,\n ability: U,\n action?: Normalize<Generics<U>['abilities']>[0]\n) => string[];\n\nexport interface AccessibleFieldsModel<\n T,\n TQueryHelpers = {},\n TMethods = {},\n TVirtuals = {}\n> extends Model<T, TQueryHelpers, TMethods & AccessibleFieldDocumentMethods<T>, TVirtuals> {\n accessibleFieldsBy: GetAccessibleFields<T>\n}\n\nexport interface AccessibleFieldDocumentMethods<T = Document> {\n accessibleFieldsBy: GetAccessibleFields<T>\n}\n\n/**\n * @deprecated Mongoose recommends against `extends Document`, prefer to use `AccessibleFieldsModel` instead.\n * See here: https://mongoosejs.com/docs/typescript.html#using-extends-document\n */\nexport interface AccessibleFieldsDocument extends Document, AccessibleFieldDocumentMethods {}\n\nfunction modelFieldsGetter() {\n let fieldsFrom: PermittedFieldsOptions<AnyMongoAbility>['fieldsFrom'];\n return (schema: Schema<any>, options: Partial<AccessibleFieldsOptions>) => {\n if (!fieldsFrom) {\n const ALL_FIELDS = options && 'only' in options\n ? wrapArray(options.only as string[])\n : fieldsOf(schema, options);\n fieldsFrom = rule => rule.fields || ALL_FIELDS;\n }\n\n return fieldsFrom;\n };\n}\n\nexport function accessibleFieldsPlugin(\n schema: Schema<any>,\n rawOptions?: Partial<AccessibleFieldsOptions>\n): void {\n const options = { getFields: getSchemaPaths, ...rawOptions };\n const fieldsFrom = modelFieldsGetter();\n\n function istanceAccessibleFields(this: Document, ability: AnyMongoAbility, action?: string) {\n return permittedFieldsOf(ability, action || 'read', this, {\n fieldsFrom: fieldsFrom(schema, options)\n });\n }\n\n function modelAccessibleFields(this: Model<unknown>, ability: AnyMongoAbility, action?: string) {\n const document = { constructor: this };\n return permittedFieldsOf(ability, action || 'read', document, {\n fieldsFrom: fieldsFrom(schema, options)\n });\n }\n\n schema.statics.accessibleFieldsBy = modelAccessibleFields;\n schema.method('accessibleFieldsBy', istanceAccessibleFields);\n}\n"],"names":["convertToMongoQuery","rule","conditions","inverted","$nor","toMongoQuery","ability","subjectType","action","rulesToQuery","accessibleBy","Proxy","_ability","_action","accessibleByProxyHandlers","get","target","query","$expr","failedQuery","modelName","where","anyQuery","pre","cb","error","ForbiddenError","from","unlessCan","baseQuery","detectSubjectType","constructor","model","TypeError","and","modelAccessibleBy","this","queryAccessibleBy","accessibleRecordsPlugin","schema","statics","getSchemaPaths","Object","keys","paths","fieldsOf","options","fields","getFields","excludedFields","wrapArray","except","filter","field","indexOf","modelFieldsGetter","fieldsFrom","ALL_FIELDS","only","accessibleFieldsPlugin","rawOptions","assign","istanceAccessibleFields","permittedFieldsOf","modelAccessibleFields","document","accessibleFieldsBy","method"],"mappings":"iFAGA,SAASA,EAAoBC,GAC3B,MAAMC,EAAaD,EAAKC,WACxB,OAAOD,EAAKE,SAAW,CAAEC,KAAM,CAACF,IAAgBA,CAClD,CAOO,SAASG,EACdC,EACAC,EACAC,EAAuC,QAEvC,OAAOC,EAAYA,aAACH,EAASE,EAAQD,EAAaP,EACpD,CAUO,SAASU,EACdJ,EACAE,EAAuC,QAEvC,OAAO,IAAIG,MAAM,CACfC,EAAUN,EACVO,EAASL,GACRM,EACL,CAEA,MAAMA,EAA0F,CAC9FC,IAAIC,EAAQT,GACV,MAAMU,EAAQR,EAAYA,aAACO,EAAOJ,EAAUI,EAAOH,EAASN,EAAaP,GACzE,OAAOiB,IAAU,KAAO,CAAEC,MAAO,OAAUD,CAC7C,GCvCF,SAASE,EACPb,EACAE,EACAY,EACAH,GAEAA,EAAMI,MAAM,CAAEH,MAAO,QACrB,MAAMI,EAAgBL,EAEtB,UAAWK,EAASC,MAAQ,WAC1BD,EAASC,KAAKC,IACZ,MAAMC,EAAQC,EAAcA,eAACC,KAAKrB,GAASsB,UAAUpB,EAAQY,GAC7DI,EAAGC,EAAM,IAIb,OAAOR,CACT,CAEA,SAASP,EACPmB,EACAvB,EACAE,GAEA,MAAMD,EAAcD,EAAQwB,kBAAkB,CAC5CC,YAAaF,EAAUG,QAGzB,IAAKzB,EACH,MAAM,IAAI0B,UAAW,kCAAiCJ,EAAUG,MAAMZ,2CAGxE,MAAMH,EAAQZ,EAAaC,EAASC,EAAaC,GAEjD,GAAIS,IAAU,KACZ,OAAOE,EAAYb,EAASE,GAAU,OAAQD,EAAasB,EAAUR,SAGvE,OAAOQ,EAAUK,IAAI,CAACjB,GACxB,CAsCA,SAASkB,EAAwC7B,EAA0BE,GACzE,OAAOE,EAAa0B,KAAKf,QAASf,EAASE,EAC7C,CAEA,SAAS6B,EAEP/B,EACAE,GAEA,OAAOE,EAAa0B,KAAM9B,EAASE,EACrC,CAEO,SAAS8B,EAAwBC,GACrCA,EAAOtB,MAAkCP,aAAe2B,EACzDE,EAAOC,QAAQ9B,aAAeyB,CAChC,CCtFaM,MAAAA,EAAuDF,GAAUG,OAAOC,KAAMJ,EAA6BK,OAExH,SAASC,EAASN,EAA0BO,GAC1C,MAAMC,EAASD,EAAQE,UAAWT,GAElC,IAAKO,KAAa,WAAYA,GAC5B,OAAOC,EAGT,MAAME,EAAiBC,EAAAA,UAAUJ,EAAQK,QACzC,OAAOJ,EAAOK,QAAOC,GAASJ,EAAeK,QAAQD,MAAY,GACnE,CA2BA,SAASE,IACP,IAAIC,EACJ,MAAO,CAACjB,EAAqBO,KAC3B,IAAKU,EAAY,CACf,MAAMC,EAAaX,GAAW,SAAUA,EACpCI,EAASA,UAACJ,EAAQY,MAClBb,EAASN,EAAQO,GACrBU,EAAavD,GAAQA,EAAK8C,QAAUU,CACtC,CAEA,OAAOD,CAAU,CAErB,CAEO,SAASG,EACdpB,EACAqB,GAEA,MAAMd,EAAOJ,OAAAmB,OAAA,CAAKb,UAAWP,GAAmBmB,GAChD,MAAMJ,EAAaD,IAEnB,SAASO,EAAwCxD,EAA0BE,GACzE,OAAOuD,EAAiBA,kBAACzD,EAASE,GAAU,OAAQ4B,KAAM,CACxDoB,WAAYA,EAAWjB,EAAQO,IAEnC,CAEA,SAASkB,EAA4C1D,EAA0BE,GAC7E,MAAMyD,EAAW,CAAElC,YAAaK,MAChC,OAAO2B,EAAiBA,kBAACzD,EAASE,GAAU,OAAQyD,EAAU,CAC5DT,WAAYA,EAAWjB,EAAQO,IAEnC,CAEAP,EAAOC,QAAQ0B,mBAAqBF,EACpCzB,EAAO4B,OAAO,qBAAsBL,EACtC"}
|
package/dist/es6m/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ForbiddenError as t,wrapArray as n}from"@casl/ability";import{rulesToQuery as r,permittedFieldsOf as
|
|
1
|
+
import{ForbiddenError as t,wrapArray as n}from"@casl/ability";import{rulesToQuery as r,permittedFieldsOf as e}from"@casl/ability/extra";function o(t){const n=t.conditions;return t.inverted?{$nor:[n]}:n}function c(t,n,e="read"){return r(t,e,n,o)}function i(t,n="read"){return new Proxy({t:t,o:n},s)}const s={get(t,n){const e=r(t.t,t.o,n,o);return e===null?{$expr:false}:e}};function u(n,r,e,o){o.where({$expr:false});const c=o;if(typeof c.pre==="function")c.pre((o=>{const c=t.from(n).unlessCan(r,e);o(c)}));return o}function f(t,n,r){const e=n.detectSubjectType({constructor:t.model});if(!e)throw new TypeError(`Cannot detect subject type of "${t.model.modelName}" to return accessible records`);const o=c(n,e,r);if(o===null)return u(n,r||"read",e,t.where());return t.and([o])}function l(t,n){return f(this.where(),t,n)}function a(t,n){return f(this,t,n)}function d(t){t.query.accessibleBy=a;t.statics.accessibleBy=l}const y=t=>Object.keys(t.paths);function b(t,r){const e=r.getFields(t);if(!r||!("except"in r))return e;const o=n(r.except);return e.filter((t=>o.indexOf(t)===-1))}function p(){let t;return(r,e)=>{if(!t){const o=e&&"only"in e?n(e.only):b(r,e);t=t=>t.fields||o}return t}}function m(t,n){const r=Object.assign({getFields:y},n);const o=p();function c(n,c){return e(n,c||"read",this,{fieldsFrom:o(t,r)})}function i(n,c){const i={constructor:this};return e(n,c||"read",i,{fieldsFrom:o(t,r)})}t.statics.accessibleFieldsBy=i;t.method("accessibleFieldsBy",c)}export{i as accessibleBy,m as accessibleFieldsPlugin,d as accessibleRecordsPlugin,y as getSchemaPaths,c as toMongoQuery};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/es6m/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../src/mongo.ts","../../src/accessible_records.ts","../../src/accessible_fields.ts"],"sourcesContent":["import { AnyMongoAbility } from '@casl/ability';\nimport { AbilityQuery, rulesToQuery } from '@casl/ability/extra';\n\nfunction convertToMongoQuery(rule: AnyMongoAbility['rules'][number]) {\n const conditions = rule.conditions!;\n return rule.inverted ? { $nor: [conditions] } : conditions;\n}\n\nexport function toMongoQuery<T extends AnyMongoAbility>(\n ability: T,\n subjectType: Parameters<T['rulesFor']>[1],\n action: Parameters<T['rulesFor']>[0] = 'read'\n): AbilityQuery | null {\n return rulesToQuery(ability, action, subjectType, convertToMongoQuery);\n}\n","import { Normalize, AnyMongoAbility, Generics, ForbiddenError } from '@casl/ability';\nimport { Schema, QueryWithHelpers, Model, Document, HydratedDocument, Query } from 'mongoose';\nimport { toMongoQuery } from './mongo';\n\nfunction failedQuery(\n ability: AnyMongoAbility,\n action: string,\n modelName: string,\n query: QueryWithHelpers<Document, Document>\n) {\n query.where({ __forbiddenByCasl__: 1 }); // eslint-disable-line\n const anyQuery: any = query;\n\n if (typeof anyQuery.pre === 'function') {\n anyQuery.pre((cb: (error?: Error) => void) => {\n const error = ForbiddenError.from(ability).unlessCan(action, modelName);\n cb(error);\n });\n }\n\n return query;\n}\n\nfunction accessibleBy<T extends AnyMongoAbility>(\n baseQuery: Query<any, any>,\n ability: T,\n action?: Normalize<Generics<T>['abilities']>[0]\n): QueryWithHelpers<Document, Document> {\n const subjectType = ability.detectSubjectType({\n constructor: baseQuery.model\n });\n\n if (!subjectType) {\n throw new TypeError(`Cannot detect subject type of \"${baseQuery.model.modelName}\" to return accessible records`);\n }\n\n const query = toMongoQuery(ability, subjectType, action);\n\n if (query === null) {\n return failedQuery(ability, action || 'read', subjectType, baseQuery.where());\n }\n\n return baseQuery.and([query]);\n}\n\ntype GetAccessibleRecords<T, TQueryHelpers, TMethods, TVirtuals> = <U extends AnyMongoAbility>(\n ability: U,\n action?: Normalize<Generics<U>['abilities']>[0]\n) => QueryWithHelpers<\nArray<T>,\nT,\nAccessibleRecordQueryHelpers<T, TQueryHelpers, TMethods, TVirtuals>\n>;\n\nexport type AccessibleRecordQueryHelpers<T, TQueryHelpers = {}, TMethods = {}, TVirtuals = {}> = {\n accessibleBy: GetAccessibleRecords<\n HydratedDocument<T, TMethods, TVirtuals>,\n TQueryHelpers,\n TMethods,\n TVirtuals\n >\n};\nexport interface AccessibleRecordModel<\n T,\n TQueryHelpers = {},\n TMethods = {},\n TVirtuals = {}\n> extends Model<T,\n TQueryHelpers & AccessibleRecordQueryHelpers<T, TQueryHelpers, TMethods, TVirtuals>,\n TMethods,\n TVirtuals> {\n accessibleBy: GetAccessibleRecords<\n HydratedDocument<T, TMethods, TVirtuals>,\n TQueryHelpers,\n TMethods,\n TVirtuals\n >\n}\n\nfunction modelAccessibleBy(this: Model<unknown>, ability: AnyMongoAbility, action?: string) {\n return accessibleBy(this.where(), ability, action);\n}\n\nfunction queryAccessibleBy(\n this: Query<unknown, unknown>,\n ability: AnyMongoAbility,\n action?: string\n) {\n return accessibleBy(this, ability, action);\n}\n\nexport function accessibleRecordsPlugin(schema: Schema<any>): void {\n (schema.query as Record<string, unknown>).accessibleBy = queryAccessibleBy;\n schema.statics.accessibleBy = modelAccessibleBy;\n}\n","import { wrapArray, Normalize, AnyMongoAbility, Generics } from '@casl/ability';\nimport { permittedFieldsOf, PermittedFieldsOptions } from '@casl/ability/extra';\nimport type { Schema, Model, Document } from 'mongoose';\n\nexport type AccessibleFieldsOptions =\n {\n getFields(schema: Schema<Document>): string[]\n } &\n ({ only: string | string[] } | { except: string | string[] });\n\nexport const getSchemaPaths: AccessibleFieldsOptions['getFields'] = schema => Object.keys((schema as { paths: object }).paths);\n\nfunction fieldsOf(schema: Schema<Document>, options: Partial<AccessibleFieldsOptions>) {\n const fields = options.getFields!(schema);\n\n if (!options || !('except' in options)) {\n return fields;\n }\n\n const excludedFields = wrapArray(options.except);\n return fields.filter(field => excludedFields.indexOf(field) === -1);\n}\n\ntype GetAccessibleFields<T> = <U extends AnyMongoAbility>(\n this: Model<T> | T,\n ability: U,\n action?: Normalize<Generics<U>['abilities']>[0]\n) => string[];\n\nexport interface AccessibleFieldsModel<\n T,\n TQueryHelpers = {},\n TMethods = {},\n TVirtuals = {}\n> extends Model<T, TQueryHelpers, TMethods & AccessibleFieldDocumentMethods<T>, TVirtuals> {\n accessibleFieldsBy: GetAccessibleFields<T>\n}\n\nexport interface AccessibleFieldDocumentMethods<T = Document> {\n accessibleFieldsBy: GetAccessibleFields<T>\n}\n\n/**\n * @deprecated Mongoose recommends against `extends Document`, prefer to use `AccessibleFieldsModel` instead.\n * See here: https://mongoosejs.com/docs/typescript.html#using-extends-document\n */\nexport interface AccessibleFieldsDocument extends Document, AccessibleFieldDocumentMethods {}\n\nfunction modelFieldsGetter() {\n let fieldsFrom: PermittedFieldsOptions<AnyMongoAbility>['fieldsFrom'];\n return (schema: Schema<any>, options: Partial<AccessibleFieldsOptions>) => {\n if (!fieldsFrom) {\n const ALL_FIELDS = options && 'only' in options\n ? wrapArray(options.only as string[])\n : fieldsOf(schema, options);\n fieldsFrom = rule => rule.fields || ALL_FIELDS;\n }\n\n return fieldsFrom;\n };\n}\n\nexport function accessibleFieldsPlugin(\n schema: Schema<any>,\n rawOptions?: Partial<AccessibleFieldsOptions>\n): void {\n const options = { getFields: getSchemaPaths, ...rawOptions };\n const fieldsFrom = modelFieldsGetter();\n\n function istanceAccessibleFields(this: Document, ability: AnyMongoAbility, action?: string) {\n return permittedFieldsOf(ability, action || 'read', this, {\n fieldsFrom: fieldsFrom(schema, options)\n });\n }\n\n function modelAccessibleFields(this: Model<unknown>, ability: AnyMongoAbility, action?: string) {\n const document = { constructor: this };\n return permittedFieldsOf(ability, action || 'read', document, {\n fieldsFrom: fieldsFrom(schema, options)\n });\n }\n\n schema.statics.accessibleFieldsBy = modelAccessibleFields;\n schema.method('accessibleFieldsBy', istanceAccessibleFields);\n}\n"],"names":["convertToMongoQuery","rule","conditions","inverted","$nor","toMongoQuery","ability","subjectType","action","rulesToQuery","failedQuery","modelName","query","where","__forbiddenByCasl__","anyQuery","pre","cb","error","ForbiddenError","from","unlessCan","accessibleBy","baseQuery","detectSubjectType","constructor","model","TypeError","and","modelAccessibleBy","this","queryAccessibleBy","accessibleRecordsPlugin","schema","statics","getSchemaPaths","Object","keys","paths","fieldsOf","options","fields","getFields","excludedFields","wrapArray","except","filter","field","indexOf","modelFieldsGetter","fieldsFrom","ALL_FIELDS","only","accessibleFieldsPlugin","rawOptions","istanceAccessibleFields","permittedFieldsOf","modelAccessibleFields","document","accessibleFieldsBy","method"],"mappings":"wIAGA,SAASA,EAAoBC,SACrBC,EAAaD,EAAKC,kBACjBD,EAAKE,SAAW,CAAEC,KAAM,CAACF,IAAgBA,EAG3C,SAASG,EACdC,EACAC,EACAC,EAAuC,eAEhCC,EAAaH,EAASE,EAAQD,EAAaP,GCTpD,SAASU,EACPJ,EACAE,EACAG,EACAC,GAEAA,EAAMC,MAAM,CAAEC,oBAAqB,UAC7BC,EAAgBH,KAEM,oBAAjBG,EAASC,IAClBD,EAASC,KAAKC,UACNC,EAAQC,EAAeC,KAAKd,GAASe,UAAUb,EAAQG,GAC7DM,EAAGC,aAIAN,EAGT,SAASU,EACPC,EACAjB,EACAE,SAEMD,EAAcD,EAAQkB,kBAAkB,CAC5CC,YAAaF,EAAUG,YAGpBnB,QACG,IAAIoB,UAAW,kCAAiCJ,EAAUG,MAAMf,iDAGlEC,EAAQP,EAAaC,EAASC,EAAaC,MAEnC,OAAVI,SACKF,EAAYJ,EAASE,GAAU,OAAQD,EAAagB,EAAUV,gBAGhEU,EAAUK,IAAI,CAAChB,IAqCxB,SAASiB,EAAwCvB,EAA0BE,UAClEc,EAAaQ,KAAKjB,QAASP,EAASE,GAG7C,SAASuB,EAEPzB,EACAE,UAEOc,EAAaQ,KAAMxB,EAASE,GAG9B,SAASwB,EAAwBC,GACrCA,EAAOrB,MAAkCU,aAAeS,EACzDE,EAAOC,QAAQZ,aAAeO,QCnFnBM,EAAuDF,GAAUG,OAAOC,KAAMJ,EAA6BK,OAExH,SAASC,EAASN,EAA0BO,SACpCC,EAASD,EAAQE,UAAWT,OAE7BO,KAAa,WAAYA,UACrBC,QAGHE,EAAiBC,EAAUJ,EAAQK,eAClCJ,EAAOK,QAAOC,IAA4C,IAAnCJ,EAAeK,QAAQD,KA4BvD,SAASE,QACHC,QACG,CAACjB,EAAqBO,SACtBU,EAAY,OACTC,EAAaX,GAAW,SAAUA,EACpCI,EAAUJ,EAAQY,MAClBb,EAASN,EAAQO,GACrBU,EAAajD,GAAQA,EAAKwC,QAAUU,SAG/BD,GAIJ,SAASG,EACdpB,EACAqB,SAEMd,iBAAYE,UAAWP,GAAmBmB,SAC1CJ,EAAaD,aAEVM,EAAwCjD,EAA0BE,UAClEgD,EAAkBlD,EAASE,GAAU,OAAQsB,KAAM,CACxDoB,WAAYA,EAAWjB,EAAQO,cAI1BiB,EAA4CnD,EAA0BE,SACvEkD,EAAW,CAAEjC,YAAaK,aACzB0B,EAAkBlD,EAASE,GAAU,OAAQkD,EAAU,CAC5DR,WAAYA,EAAWjB,EAAQO,KAInCP,EAAOC,QAAQyB,mBAAqBF,EACpCxB,EAAO2B,OAAO,qBAAsBL"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../src/mongo.ts","../../src/accessible_records.ts","../../src/accessible_fields.ts"],"sourcesContent":["import { AnyMongoAbility } from '@casl/ability';\nimport { AbilityQuery, rulesToQuery } from '@casl/ability/extra';\n\nfunction convertToMongoQuery(rule: AnyMongoAbility['rules'][number]) {\n const conditions = rule.conditions!;\n return rule.inverted ? { $nor: [conditions] } : conditions;\n}\n\n/**\n * @deprecated use accessibleBy instead\n *\n * Converts ability action + subjectType to MongoDB query\n */\nexport function toMongoQuery<T extends AnyMongoAbility>(\n ability: T,\n subjectType: Parameters<T['rulesFor']>[1],\n action: Parameters<T['rulesFor']>[0] = 'read'\n): AbilityQuery | null {\n return rulesToQuery(ability, action, subjectType, convertToMongoQuery);\n}\n\nexport interface RecordTypes {\n}\ntype StringOrKeysOf<T> = keyof T extends never ? string : keyof T;\n\n/**\n * Returns Mongo query per record type (i.e., entity type) based on provided Ability and action.\n * In case action is not allowed, it returns `{ $expr: false }`\n */\nexport function accessibleBy<T extends AnyMongoAbility>(\n ability: T,\n action: Parameters<T['rulesFor']>[0] = 'read'\n): Record<StringOrKeysOf<RecordTypes>, AbilityQuery> {\n return new Proxy({\n _ability: ability,\n _action: action\n }, accessibleByProxyHandlers) as unknown as Record<StringOrKeysOf<RecordTypes>, AbilityQuery>;\n}\n\nconst accessibleByProxyHandlers: ProxyHandler<{ _ability: AnyMongoAbility, _action: string }> = {\n get(target, subjectType) {\n const query = rulesToQuery(target._ability, target._action, subjectType, convertToMongoQuery);\n return query === null ? { $expr: false } : query;\n }\n};\n","import { Normalize, AnyMongoAbility, Generics, ForbiddenError } from '@casl/ability';\nimport { Schema, QueryWithHelpers, Model, Document, HydratedDocument, Query } from 'mongoose';\nimport { toMongoQuery } from './mongo';\n\nfunction failedQuery(\n ability: AnyMongoAbility,\n action: string,\n modelName: string,\n query: QueryWithHelpers<Document, Document>\n) {\n query.where({ $expr: false }); // rule that returns empty result set\n const anyQuery: any = query;\n\n if (typeof anyQuery.pre === 'function') {\n anyQuery.pre((cb: (error?: Error) => void) => {\n const error = ForbiddenError.from(ability).unlessCan(action, modelName);\n cb(error);\n });\n }\n\n return query;\n}\n\nfunction accessibleBy<T extends AnyMongoAbility>(\n baseQuery: Query<any, any>,\n ability: T,\n action?: Normalize<Generics<T>['abilities']>[0]\n): QueryWithHelpers<Document, Document> {\n const subjectType = ability.detectSubjectType({\n constructor: baseQuery.model\n });\n\n if (!subjectType) {\n throw new TypeError(`Cannot detect subject type of \"${baseQuery.model.modelName}\" to return accessible records`);\n }\n\n const query = toMongoQuery(ability, subjectType, action);\n\n if (query === null) {\n return failedQuery(ability, action || 'read', subjectType, baseQuery.where());\n }\n\n return baseQuery.and([query]);\n}\n\ntype GetAccessibleRecords<T, TQueryHelpers, TMethods, TVirtuals> = <U extends AnyMongoAbility>(\n ability: U,\n action?: Normalize<Generics<U>['abilities']>[0]\n) => QueryWithHelpers<\nArray<T>,\nT,\nAccessibleRecordQueryHelpers<T, TQueryHelpers, TMethods, TVirtuals>\n>;\n\nexport type AccessibleRecordQueryHelpers<T, TQueryHelpers = {}, TMethods = {}, TVirtuals = {}> = {\n /** @deprecated use accessibleBy helper instead */\n accessibleBy: GetAccessibleRecords<\n HydratedDocument<T, TMethods, TVirtuals>,\n TQueryHelpers,\n TMethods,\n TVirtuals\n >\n};\nexport interface AccessibleRecordModel<\n T,\n TQueryHelpers = {},\n TMethods = {},\n TVirtuals = {}\n> extends Model<T,\n TQueryHelpers & AccessibleRecordQueryHelpers<T, TQueryHelpers, TMethods, TVirtuals>,\n TMethods,\n TVirtuals> {\n /** @deprecated use accessibleBy helper instead */\n accessibleBy: GetAccessibleRecords<\n HydratedDocument<T, TMethods, TVirtuals>,\n TQueryHelpers,\n TMethods,\n TVirtuals\n >\n}\n\nfunction modelAccessibleBy(this: Model<unknown>, ability: AnyMongoAbility, action?: string) {\n return accessibleBy(this.where(), ability, action);\n}\n\nfunction queryAccessibleBy(\n this: Query<unknown, unknown>,\n ability: AnyMongoAbility,\n action?: string\n) {\n return accessibleBy(this, ability, action);\n}\n\nexport function accessibleRecordsPlugin(schema: Schema<any>): void {\n (schema.query as Record<string, unknown>).accessibleBy = queryAccessibleBy;\n schema.statics.accessibleBy = modelAccessibleBy;\n}\n","import { wrapArray, Normalize, AnyMongoAbility, Generics } from '@casl/ability';\nimport { permittedFieldsOf, PermittedFieldsOptions } from '@casl/ability/extra';\nimport type { Schema, Model, Document } from 'mongoose';\n\nexport type AccessibleFieldsOptions =\n {\n getFields(schema: Schema<Document>): string[]\n } &\n ({ only: string | string[] } | { except: string | string[] });\n\nexport const getSchemaPaths: AccessibleFieldsOptions['getFields'] = schema => Object.keys((schema as { paths: object }).paths);\n\nfunction fieldsOf(schema: Schema<Document>, options: Partial<AccessibleFieldsOptions>) {\n const fields = options.getFields!(schema);\n\n if (!options || !('except' in options)) {\n return fields;\n }\n\n const excludedFields = wrapArray(options.except);\n return fields.filter(field => excludedFields.indexOf(field) === -1);\n}\n\ntype GetAccessibleFields<T> = <U extends AnyMongoAbility>(\n this: Model<T> | T,\n ability: U,\n action?: Normalize<Generics<U>['abilities']>[0]\n) => string[];\n\nexport interface AccessibleFieldsModel<\n T,\n TQueryHelpers = {},\n TMethods = {},\n TVirtuals = {}\n> extends Model<T, TQueryHelpers, TMethods & AccessibleFieldDocumentMethods<T>, TVirtuals> {\n accessibleFieldsBy: GetAccessibleFields<T>\n}\n\nexport interface AccessibleFieldDocumentMethods<T = Document> {\n accessibleFieldsBy: GetAccessibleFields<T>\n}\n\n/**\n * @deprecated Mongoose recommends against `extends Document`, prefer to use `AccessibleFieldsModel` instead.\n * See here: https://mongoosejs.com/docs/typescript.html#using-extends-document\n */\nexport interface AccessibleFieldsDocument extends Document, AccessibleFieldDocumentMethods {}\n\nfunction modelFieldsGetter() {\n let fieldsFrom: PermittedFieldsOptions<AnyMongoAbility>['fieldsFrom'];\n return (schema: Schema<any>, options: Partial<AccessibleFieldsOptions>) => {\n if (!fieldsFrom) {\n const ALL_FIELDS = options && 'only' in options\n ? wrapArray(options.only as string[])\n : fieldsOf(schema, options);\n fieldsFrom = rule => rule.fields || ALL_FIELDS;\n }\n\n return fieldsFrom;\n };\n}\n\nexport function accessibleFieldsPlugin(\n schema: Schema<any>,\n rawOptions?: Partial<AccessibleFieldsOptions>\n): void {\n const options = { getFields: getSchemaPaths, ...rawOptions };\n const fieldsFrom = modelFieldsGetter();\n\n function istanceAccessibleFields(this: Document, ability: AnyMongoAbility, action?: string) {\n return permittedFieldsOf(ability, action || 'read', this, {\n fieldsFrom: fieldsFrom(schema, options)\n });\n }\n\n function modelAccessibleFields(this: Model<unknown>, ability: AnyMongoAbility, action?: string) {\n const document = { constructor: this };\n return permittedFieldsOf(ability, action || 'read', document, {\n fieldsFrom: fieldsFrom(schema, options)\n });\n }\n\n schema.statics.accessibleFieldsBy = modelAccessibleFields;\n schema.method('accessibleFieldsBy', istanceAccessibleFields);\n}\n"],"names":["convertToMongoQuery","rule","conditions","inverted","$nor","toMongoQuery","ability","subjectType","action","rulesToQuery","accessibleBy","Proxy","_ability","_action","accessibleByProxyHandlers","get","target","query","$expr","failedQuery","modelName","where","anyQuery","pre","cb","error","ForbiddenError","from","unlessCan","baseQuery","detectSubjectType","constructor","model","TypeError","and","modelAccessibleBy","this","queryAccessibleBy","accessibleRecordsPlugin","schema","statics","getSchemaPaths","Object","keys","paths","fieldsOf","options","fields","getFields","excludedFields","wrapArray","except","filter","field","indexOf","modelFieldsGetter","fieldsFrom","ALL_FIELDS","only","accessibleFieldsPlugin","rawOptions","assign","istanceAccessibleFields","permittedFieldsOf","modelAccessibleFields","document","accessibleFieldsBy","method"],"mappings":"wIAGA,SAASA,EAAoBC,GAC3B,MAAMC,EAAaD,EAAKC,WACxB,OAAOD,EAAKE,SAAW,CAAEC,KAAM,CAACF,IAAgBA,CAClD,CAOO,SAASG,EACdC,EACAC,EACAC,EAAuC,QAEvC,OAAOC,EAAaH,EAASE,EAAQD,EAAaP,EACpD,CAUO,SAASU,EACdJ,EACAE,EAAuC,QAEvC,OAAO,IAAIG,MAAM,CACfC,EAAUN,EACVO,EAASL,GACRM,EACL,CAEA,MAAMA,EAA0F,CAC9FC,IAAIC,EAAQT,GACV,MAAMU,EAAQR,EAAaO,EAAOJ,EAAUI,EAAOH,EAASN,EAAaP,GACzE,OAAOiB,IAAU,KAAO,CAAEC,MAAO,OAAUD,CAC7C,GCvCF,SAASE,EACPb,EACAE,EACAY,EACAH,GAEAA,EAAMI,MAAM,CAAEH,MAAO,QACrB,MAAMI,EAAgBL,EAEtB,UAAWK,EAASC,MAAQ,WAC1BD,EAASC,KAAKC,IACZ,MAAMC,EAAQC,EAAeC,KAAKrB,GAASsB,UAAUpB,EAAQY,GAC7DI,EAAGC,EAAM,IAIb,OAAOR,CACT,CAEA,SAASP,EACPmB,EACAvB,EACAE,GAEA,MAAMD,EAAcD,EAAQwB,kBAAkB,CAC5CC,YAAaF,EAAUG,QAGzB,IAAKzB,EACH,MAAM,IAAI0B,UAAW,kCAAiCJ,EAAUG,MAAMZ,2CAGxE,MAAMH,EAAQZ,EAAaC,EAASC,EAAaC,GAEjD,GAAIS,IAAU,KACZ,OAAOE,EAAYb,EAASE,GAAU,OAAQD,EAAasB,EAAUR,SAGvE,OAAOQ,EAAUK,IAAI,CAACjB,GACxB,CAsCA,SAASkB,EAAwC7B,EAA0BE,GACzE,OAAOE,EAAa0B,KAAKf,QAASf,EAASE,EAC7C,CAEA,SAAS6B,EAEP/B,EACAE,GAEA,OAAOE,EAAa0B,KAAM9B,EAASE,EACrC,CAEO,SAAS8B,EAAwBC,GACrCA,EAAOtB,MAAkCP,aAAe2B,EACzDE,EAAOC,QAAQ9B,aAAeyB,CAChC,CCtFaM,MAAAA,EAAuDF,GAAUG,OAAOC,KAAMJ,EAA6BK,OAExH,SAASC,EAASN,EAA0BO,GAC1C,MAAMC,EAASD,EAAQE,UAAWT,GAElC,IAAKO,KAAa,WAAYA,GAC5B,OAAOC,EAGT,MAAME,EAAiBC,EAAUJ,EAAQK,QACzC,OAAOJ,EAAOK,QAAOC,GAASJ,EAAeK,QAAQD,MAAY,GACnE,CA2BA,SAASE,IACP,IAAIC,EACJ,MAAO,CAACjB,EAAqBO,KAC3B,IAAKU,EAAY,CACf,MAAMC,EAAaX,GAAW,SAAUA,EACpCI,EAAUJ,EAAQY,MAClBb,EAASN,EAAQO,GACrBU,EAAavD,GAAQA,EAAK8C,QAAUU,CACtC,CAEA,OAAOD,CAAU,CAErB,CAEO,SAASG,EACdpB,EACAqB,GAEA,MAAMd,EAAOJ,OAAAmB,OAAA,CAAKb,UAAWP,GAAmBmB,GAChD,MAAMJ,EAAaD,IAEnB,SAASO,EAAwCxD,EAA0BE,GACzE,OAAOuD,EAAkBzD,EAASE,GAAU,OAAQ4B,KAAM,CACxDoB,WAAYA,EAAWjB,EAAQO,IAEnC,CAEA,SAASkB,EAA4C1D,EAA0BE,GAC7E,MAAMyD,EAAW,CAAElC,YAAaK,MAChC,OAAO2B,EAAkBzD,EAASE,GAAU,OAAQyD,EAAU,CAC5DT,WAAYA,EAAWjB,EAAQO,IAEnC,CAEAP,EAAOC,QAAQ0B,mBAAqBF,EACpCzB,EAAO4B,OAAO,qBAAsBL,EACtC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Normalize, AnyMongoAbility, Generics } from '@casl/ability';
|
|
2
2
|
import type { Schema, Model, Document } from 'mongoose';
|
|
3
|
-
export
|
|
3
|
+
export type AccessibleFieldsOptions = {
|
|
4
4
|
getFields(schema: Schema<Document>): string[];
|
|
5
5
|
} & ({
|
|
6
6
|
only: string | string[];
|
|
@@ -8,7 +8,7 @@ export declare type AccessibleFieldsOptions = {
|
|
|
8
8
|
except: string | string[];
|
|
9
9
|
});
|
|
10
10
|
export declare const getSchemaPaths: AccessibleFieldsOptions['getFields'];
|
|
11
|
-
|
|
11
|
+
type GetAccessibleFields<T> = <U extends AnyMongoAbility>(this: Model<T> | T, ability: U, action?: Normalize<Generics<U>['abilities']>[0]) => string[];
|
|
12
12
|
export interface AccessibleFieldsModel<T, TQueryHelpers = {}, TMethods = {}, TVirtuals = {}> extends Model<T, TQueryHelpers, TMethods & AccessibleFieldDocumentMethods<T>, TVirtuals> {
|
|
13
13
|
accessibleFieldsBy: GetAccessibleFields<T>;
|
|
14
14
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Normalize, AnyMongoAbility, Generics } from '@casl/ability';
|
|
2
2
|
import { Schema, QueryWithHelpers, Model, HydratedDocument } from 'mongoose';
|
|
3
|
-
|
|
4
|
-
export
|
|
3
|
+
type GetAccessibleRecords<T, TQueryHelpers, TMethods, TVirtuals> = <U extends AnyMongoAbility>(ability: U, action?: Normalize<Generics<U>['abilities']>[0]) => QueryWithHelpers<Array<T>, T, AccessibleRecordQueryHelpers<T, TQueryHelpers, TMethods, TVirtuals>>;
|
|
4
|
+
export type AccessibleRecordQueryHelpers<T, TQueryHelpers = {}, TMethods = {}, TVirtuals = {}> = {
|
|
5
|
+
/** @deprecated use accessibleBy helper instead */
|
|
5
6
|
accessibleBy: GetAccessibleRecords<HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, TMethods, TVirtuals>;
|
|
6
7
|
};
|
|
7
8
|
export interface AccessibleRecordModel<T, TQueryHelpers = {}, TMethods = {}, TVirtuals = {}> extends Model<T, TQueryHelpers & AccessibleRecordQueryHelpers<T, TQueryHelpers, TMethods, TVirtuals>, TMethods, TVirtuals> {
|
|
9
|
+
/** @deprecated use accessibleBy helper instead */
|
|
8
10
|
accessibleBy: GetAccessibleRecords<HydratedDocument<T, TMethods, TVirtuals>, TQueryHelpers, TMethods, TVirtuals>;
|
|
9
11
|
}
|
|
10
12
|
export declare function accessibleRecordsPlugin(schema: Schema<any>): void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export { accessibleRecordsPlugin } from './accessible_records';
|
|
|
6
6
|
export type { AccessibleRecordModel } from './accessible_records';
|
|
7
7
|
export { getSchemaPaths, accessibleFieldsPlugin } from './accessible_fields';
|
|
8
8
|
export type { AccessibleFieldsModel, AccessibleFieldsDocument, AccessibleFieldsOptions } from './accessible_fields';
|
|
9
|
-
export { toMongoQuery } from './mongo';
|
|
9
|
+
export { toMongoQuery, accessibleBy } from './mongo';
|
|
10
|
+
export type { RecordTypes } from './mongo';
|
package/dist/types/mongo.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
import { AnyMongoAbility } from '@casl/ability';
|
|
2
2
|
import { AbilityQuery } from '@casl/ability/extra';
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated use accessibleBy instead
|
|
5
|
+
*
|
|
6
|
+
* Converts ability action + subjectType to MongoDB query
|
|
7
|
+
*/
|
|
3
8
|
export declare function toMongoQuery<T extends AnyMongoAbility>(ability: T, subjectType: Parameters<T['rulesFor']>[1], action?: Parameters<T['rulesFor']>[0]): AbilityQuery | null;
|
|
9
|
+
export interface RecordTypes {
|
|
10
|
+
}
|
|
11
|
+
type StringOrKeysOf<T> = keyof T extends never ? string : keyof T;
|
|
12
|
+
/**
|
|
13
|
+
* Returns Mongo query per record type (i.e., entity type) based on provided Ability and action.
|
|
14
|
+
* In case action is not allowed, it returns `{ $expr: false }`
|
|
15
|
+
*/
|
|
16
|
+
export declare function accessibleBy<T extends AnyMongoAbility>(ability: T, action?: Parameters<T['rulesFor']>[0]): Record<StringOrKeysOf<RecordTypes>, AbilityQuery>;
|
|
17
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@casl/mongoose",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0",
|
|
4
4
|
"description": "Allows to query accessible records from MongoDB based on CASL rules",
|
|
5
5
|
"main": "dist/es6c/index.js",
|
|
6
6
|
"es2015": "dist/es6m/index.mjs",
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@casl/ability": "^6.3.2",
|
|
44
|
-
"mongoose": "^6.0.13"
|
|
44
|
+
"mongoose": "^6.0.13 || ^7.0.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@casl/ability": "^6.0.0",
|
|
48
48
|
"@casl/dx": "workspace:^1.0.0",
|
|
49
|
-
"@types/jest": "^
|
|
49
|
+
"@types/jest": "^29.0.0",
|
|
50
50
|
"chai": "^4.1.0",
|
|
51
51
|
"chai-spies": "^1.0.0",
|
|
52
|
-
"mongoose": "^
|
|
52
|
+
"mongoose": "^7.0.0"
|
|
53
53
|
},
|
|
54
54
|
"files": [
|
|
55
55
|
"dist",
|