This is a library that offers Granular Access Control to documents in mongodb collections by maintaining access control lists
| @biocomputingup/nest-mongo-acl | @nestjs/core |
|---|---|
| ^0.0.2 | ^10.0.0 |
| ^1.0.0 | ^11.0.0 |
npm install @biocomputingup/nest-mongo-acl
yarn add @biocomputingup/nest-mongo-acl
This library can be used independently with Mongoose directly and with typegoose through @m8a/nestjs/typegoose or @nestjs/mongoose.
import { Module } from '@nestjs/common';
import { AclModule } from '@biocomputingup/nest-mongo-acl';
import { User } from './user.model'; // Import user type/model where it's defined
@Module({
imports: [
// ...
AclModule.forRoot({
groupFromUser<User>(user: User): string | string[] {
// This function should return a "tag" or an Array of "tags"
// Each tag represents a group that the user belongs to
// This function allows you to map users to tags
// Later you'll attach access rights to tags to determine access rights
return `role=${user.role}`;
}
})
// ...
]
})
export class AppModule {}
// Outside of NestJS, using this library requires this call to pass the "groupFromUser" function. This is better done before model initialization
AclModule.setupAclConfiguration({
groupFromUser<User>(user: User): string | string[] {
// Return group tag(s)
return `role=${user.role}`;
}
})Add plugin to model and implement the interface WithAcl
Extend the model type to include the methods and query helpers
import { AccessControlLists, IAcl, AclMethodsCls} from '@biocomputingup/nest-mongo-acl';
import { type } from '@typegoose/typegoose';
@plugin(AccessControlLists) // Add Plugin to Model
export class Person implements WithAcl { // Implement interface to get correct type
@prop({type: () => String})
surname: string;
acl: IAcl
}
// Update the type of the model to include methods and query helpers
type PersonModel = ReturnModelType<typeof Person & AclMethodsCls, IAclQueryHelpers>;Add plugin to schema and implement the interface WithAcl
import { Prop, Schema as SchemaDec, SchemaFactory } from '@nestjs/mongoose';
import { AccessControlLists, IAcl, IAclMethods, IAclQueryHelpers} from '@biocomputingup/nest-mongo-acl';
import { Document, Model, HydratedDocument} from 'mongoose';
@Schema()
export class Person implements WithAcl { // Implement interface to get correct type
@Prop({ type: String }) // For mongoose schema
surname: string;
acl: IAcl
}
const PersonSchema = SchemaFactory.createForClass(Person); // For mongoose schema
PersonSchema.plugin(AccessControlLists); // Add Plugin to Schema
export PersonSchema;
// extends model type to add methods and query helpers
export type PersonModel = Model<Person, IAclQueryHelpers, IAclMethods>;
// extends document type to add methods;
export type PersonDocument = HydratedDocument<Person, IAclMethods>;
// Important note:
// using document.model() to retrieve the model instance requires passing the overrided PersonModel as a generic to gain access to the relevant AclMethodsThrough a document instance
const doc: PersonDocument;
// This will mutate the state of the document to grant admins read access to the document
doc.grantAccess('admins', 'read');
// This will mutate the state of the document to revoke admins read access to the document
doc.revokeAccess('admins', 'read');Through an update query
import { grantAccessTo } from '@biocomputingup/nest-mongo-acl';
const model: PersonModel;
model.updateOne({ /* Select the relevant Document */}, grantAccessTo('admins', 'read'))
// for many modifications use grantToMany
model.updateOne({ /* Select the relevant Document */}, grantToMany([
['admins', 'read'],
['editor', 'write']
])
)
// Granting public read access to a document (when user is not passed/undefined)
model.updateOne({ /* Select the relevant Document */}, grantPublicAccess('read'));
model.updateOne({ /* Select the relevant Document */}, revokePublicAccess('read'));
// inversely methods revokeAccessTo and revokeToMany can be usedThrough Query helpers
const model: PersonModel;
// Filters documents accessible to the public with read access (user not passed)
model.find({ /* relevant criteria */ }).withAccessFor('read');
// Filters documents accessible to the passed user for read access
model.find({ /* relevant criteria */ }).withAccessFor('read', user);Through direct filter injection
const model: PersonModel;
const user: UserDocument;
model.find({
$and:[
{ /* relevant criteria */ },
accessibleBy('read', 'admins')
]
})
// a user can be passed as well
model.find({
$and:[
{ /* relevant criteria */ },
accessibleBy('read', user)
]
})const doc: PersonDocument;
const user: User; // The user model
if(doc.hasAccess('admins', 'read')){ // if the admins group has access to this document
// Do something
}
if(doc.hasAccess(user, 'read')) { // If user has read access to this document
// Do something
}