@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 CHANGED
@@ -16,12 +16,120 @@ yarn add @casl/mongoose @casl/ability
16
16
  pnpm add @casl/mongoose @casl/ability
17
17
  ```
18
18
 
19
- ## Integration with mongoose
19
+ ## Usage
20
20
 
21
- [mongoose] is a popular JavaScript ODM for [MongoDB]. `@casl/mongoose` provides 2 plugins that allow to integrate `@casl/ability` and mongoose in few minutes:
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
- ## Integration with other MongoDB libraries
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
 
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});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(n,r,e,o){o.where({__forbiddenByCasl__:1});const c=o;if("function"===typeof c.pre)c.pre((o=>{const c=t.ForbiddenError.from(n).unlessCan(r,e);o(c)}));return o}function c(t,n,r){const c=n.detectSubjectType({constructor:t.model});if(!c)throw new TypeError(`Cannot detect subject type of "${t.model.modelName}" to return accessible records`);const s=e(n,c,r);if(null===s)return o(n,r||"read",c,t.where());return t.and([s])}function s(t,n){return c(this.where(),t,n)}function i(t,n){return c(this,t,n)}function u(t){t.query.accessibleBy=i;t.statics.accessibleBy=s}const f=t=>Object.keys(t.paths);function l(n,r){const e=r.getFields(n);if(!r||!("except"in r))return e;const o=t.wrapArray(r.except);return e.filter((t=>-1===o.indexOf(t)))}function a(){let n;return(r,e)=>{if(!n){const o=e&&"only"in e?t.wrapArray(e.only):l(r,e);n=t=>t.fields||o}return n}}function d(t,r){const e=Object.assign({getFields:f},r);const o=a();function c(r,c){return n.permittedFieldsOf(r,c||"read",this,{fieldsFrom:o(t,e)})}function s(r,c){const s={constructor:this};return n.permittedFieldsOf(r,c||"read",s,{fieldsFrom:o(t,e)})}t.statics.accessibleFieldsBy=s;t.method("accessibleFieldsBy",c)}exports.accessibleFieldsPlugin=d;exports.accessibleRecordsPlugin=u;exports.getSchemaPaths=f;exports.toMongoQuery=e;
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
@@ -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"}
@@ -1,2 +1,2 @@
1
- import{ForbiddenError as t,wrapArray as n}from"@casl/ability";import{rulesToQuery as r,permittedFieldsOf as o}from"@casl/ability/extra";function e(t){const n=t.conditions;return t.inverted?{$nor:[n]}:n}function c(t,n,o="read"){return r(t,o,n,e)}function i(n,r,o,e){e.where({__forbiddenByCasl__:1});const c=e;if("function"===typeof c.pre)c.pre((e=>{const c=t.from(n).unlessCan(r,o);e(c)}));return e}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 e=c(n,o,r);if(null===e)return i(n,r||"read",o,t.where());return t.and([e])}function u(t,n){return s(this.where(),t,n)}function f(t,n){return s(this,t,n)}function l(t){t.query.accessibleBy=f;t.statics.accessibleBy=u}const a=t=>Object.keys(t.paths);function d(t,r){const o=r.getFields(t);if(!r||!("except"in r))return o;const e=n(r.except);return o.filter((t=>-1===e.indexOf(t)))}function b(){let t;return(r,o)=>{if(!t){const e=o&&"only"in o?n(o.only):d(r,o);t=t=>t.fields||e}return t}}function y(t,n){const r=Object.assign({getFields:a},n);const e=b();function c(n,c){return o(n,c||"read",this,{fieldsFrom:e(t,r)})}function i(n,c){const i={constructor:this};return o(n,c||"read",i,{fieldsFrom:e(t,r)})}t.statics.accessibleFieldsBy=i;t.method("accessibleFieldsBy",c)}export{y as accessibleFieldsPlugin,l as accessibleRecordsPlugin,a as getSchemaPaths,c as toMongoQuery};
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
@@ -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 declare type AccessibleFieldsOptions = {
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
- declare type GetAccessibleFields<T> = <U extends AnyMongoAbility>(this: Model<T> | T, ability: U, action?: Normalize<Generics<U>['abilities']>[0]) => string[];
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
- declare 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 declare type AccessibleRecordQueryHelpers<T, TQueryHelpers = {}, TMethods = {}, TVirtuals = {}> = {
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;
@@ -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';
@@ -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.1.2",
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": "^28.0.0",
49
+ "@types/jest": "^29.0.0",
50
50
  "chai": "^4.1.0",
51
51
  "chai-spies": "^1.0.0",
52
- "mongoose": "^6.7.0"
52
+ "mongoose": "^7.0.0"
53
53
  },
54
54
  "files": [
55
55
  "dist",