You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I've answered a few questions from this repo's discussion board, and it led me to build the following example that I thought I'd show off here. I built a pretty robust and simple example of permissions from mongoose that includes static permissions as well it uses typegoose :D.
import{AbilityBuilder,createMongoAbility,SubjectRawRule,}from"@casl/ability";importmongoosefrom"mongoose";import{prop,getModelForClass,DocumentType,Severity,}from"@typegoose/typegoose";import{AnyParamConstructor,IModelOptions,BeAnObject,Ref,}from"@typegoose/typegoose/lib/types";import{accessibleRecordsPlugin,AccessibleRecordModel}from"@casl/mongoose";// Usually you'd use a real mongodb database this is just an in memory version.import{MongoMemoryServer}from"mongodb-memory-server";// This will create an new instance of "MongoMemoryServer" and automatically start itconstmongod=awaitMongoMemoryServer.create();consturi=mongod.getUri();mongoose.set("strictQuery",false);awaitmongoose.connect(uri);/// Setup CASL's pluginmongoose.plugin(accessibleRecordsPlugin);enumStatusEnum{review="review",published="published",draft="draft",}classUser{}classAccount{}classAccountSubject{
@prop({required: true,ref: User,})user!: Ref<User>;
@prop({ref: Account,required: true,})account!: Ref<Account>;}// https://casl.js.org/v6/en/guide/define-rules#json-objectsclassPermission{
@prop({required: true,allowMixed: Severity.ALLOW,})action!: string|string[];
@prop({default: undefined,required: false,allowMixed: Severity.ALLOW,})subject?: string|string[];/** an array of fields to which user has (or not) access */
@prop({default: undefined,required: false,allowMixed: Severity.ALLOW,})fields?: string[];/** an object of conditions which restricts the rule scope */
@prop({default: undefined,required: false,allowMixed: Severity.ALLOW,})conditions?: any;/** indicates whether rule allows or forbids something */
@prop({default: undefined,required: false,})inverted?: boolean;/** message which explains why rule is forbidden */
@prop({default: undefined,required: false,})reason?: string;}/// Setup CASL's plugin to work with typegoose's getModelForClass according to:/// https://github.com/stalniy/casl/discussions/549#discussioncomment-3796299constgetMyModelForClass=<UextendsAnyParamConstructor<any>,QueryHelpers=BeAnObject>(cl: U,options?: IModelOptions): AccessibleRecordModel<InstanceType<U>&DocumentType<U,QueryHelpers>,QueryHelpers>&U=>getModelForClass(cl,options)asany;/// Use our new getMyModelForClass which provides the CASL typesconstUserModel=getMyModelForClass(User);constAccountModel=getMyModelForClass(Account);constAccountSubjectModel=getMyModelForClass(AccountSubject);constPermissionModel=getMyModelForClass(Permission);constuser1=awaitUserModel.create({});constuser2=awaitUserModel.create({});constaccount1=awaitAccountModel.create({});constaccount2=awaitAccountModel.create({});constaccount3=awaitAccountModel.create({});constaccountSubject1=awaitAccountSubjectModel.create({user: user1._id,account: account1._id,});constaccountSubject2=awaitAccountSubjectModel.create({user: user1._id,account: account2._id,});constaccountSubject3=awaitAccountSubjectModel.create({user: user2._id,account: account3._id,});awaitPromise.all([PermissionModel.create({conditions: accountSubject1,action: "manage",subject: AccountSubject.name,}),PermissionModel.create({conditions: accountSubject1,action: "read",subject: AccountSubject.name,}),PermissionModel.create({conditions: accountSubject1,action: "create",subject: AccountSubject.name,}),PermissionModel.create({conditions: accountSubject1,action: "update",subject: AccountSubject.name,}),PermissionModel.create({conditions: accountSubject1,action: "delete",subject: AccountSubject.name,}),PermissionModel.create({conditions: accountSubject2,action: "read",subject: AccountSubject.name,}),PermissionModel.create({conditions: accountSubject3,action: "read",subject: AccountSubject.name,}),]asconst);functionPermissionModelAbility(user: DocumentType<User>){const{ can, cannot, build }=newAbilityBuilder(createMongoAbility);can("read","Permission",{"conditions.user": user._id,}asany);returnbuild();}functionPreDBAbility(user: DocumentType<User>){const{ can, cannot, build }=newAbilityBuilder(createMongoAbility);// Do some static permissions that happen before the DB Permissions model// e.g. Eplicitly granting users certain permissions that are statically available// to all users.// can("manage", User, user);returnbuild();}functionPostDBAbility(user: DocumentType<User>){const{ can, cannot, build }=newAbilityBuilder(createMongoAbility);// Do some static permissions that happen after the DB Permission model// e.g. Explicitly denying users certain things they would never have access too// regardless of DB Model.returnbuild();}asyncfunctionabilityForUser(user: DocumentType<User>){constpermissionAbility=PermissionModelAbility(user);constpreDBAbility=PreDBAbility(user);constDBRules=awaitPermissionModel.accessibleBy(permissionAbility);constpostDBAbility=PostDBAbility(user);// Combine them with DB rules/permissions here:returncreateMongoAbility(permissionAbility.rules.concat(preDBAbility.rules,DBRules,postDBAbility.rules));}console.log("USERS",user1,user2);console.log("--- User 1: Single account record check! ---");/// Single account checks hopefully work :Dconstuser1Ability=awaitabilityForUser(user1);console.log("user1 can read readable data",user1Ability.can("read",accountSubject1));// logs: trueconsole.log("user1 can read readable data",user1Ability.can("read",accountSubject2));// logs: trueconsole.log("user1 can manage unmanageable data",user1Ability.can("manage",accountSubject2));// logs: falseconsole.log("user1 can read unreadable data",user1Ability.can("read",accountSubject3));// logs: falseconsole.log("--- User 2: Single account record check! ---");constuser2Ability=awaitabilityForUser(user2);console.log("user2 can read unreadable data",user2Ability.can("read",accountSubject1));// logs: false (because user ids don't match)console.log("user2 can read unreadable data",user2Ability.can("read",accountSubject2));// logs: false (because user ids don't match)console.log("user2 can manage unmanageable data",user2Ability.can("manage",accountSubject2));// logs: false (because user ids don't match)console.log("user2 can read readable data",user2Ability.can("read",accountSubject3));// logs: true/// Now for something different and interesting/// querying the DB for Accounts that can only/// be read by a userconsole.log("---- USER 1's data ----");constuser1accountSubjects=awaitAccountSubjectModel.accessibleBy(user1Ability).populate("account");console.log(user1accountSubjects);console.log("---- USER 2's data ----");constuser2accountSubjects=awaitAccountSubjectModel.accessibleBy(user2Ability).populate("account");console.log(user2accountSubjects);// Stop the mongoose server, so the script doesn't hang.// You may not need to do this if you are using a server :Dawaitmongod.stop();
The above logs
USERS { _id: new ObjectId("64121d2b5790e478093f36d8"), __v: 0 } { _id: new ObjectId("64121d2b5790e478093f36da"), __v: 0 }
--- User 1: Single account record check! ---
user1 can read readable data true
user1 can read readable data true
user1 can manage unmanageable data false
user1 can read unreadable data false
--- User 2: Single account record check! ---
user2 can read unreadable data false
user2 can read unreadable data false
user2 can manage unmanageable data false
user2 can read readable data true
---- USER 1's data ----
[
{
_id: new ObjectId("64121d2b5790e478093f36e4"),
user: new ObjectId("64121d2b5790e478093f36d8"),
account: { _id: new ObjectId("64121d2b5790e478093f36de"), __v: 0 },
__v: 0
},
{
_id: new ObjectId("64121d2b5790e478093f36e2"),
user: new ObjectId("64121d2b5790e478093f36d8"),
account: { _id: new ObjectId("64121d2b5790e478093f36dc"), __v: 0 },
__v: 0
}
]
---- USER 2's data ----
[
{
_id: new ObjectId("64121d2b5790e478093f36e6"),
user: new ObjectId("64121d2b5790e478093f36da"),
account: { _id: new ObjectId("64121d2b5790e478093f36e0"), __v: 0 },
__v: 0
}
]
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
I've answered a few questions from this repo's discussion board, and it led me to build the following example that I thought I'd show off here. I built a pretty robust and simple example of permissions from mongoose that includes static permissions as well it uses typegoose :D.
The above logs
Beta Was this translation helpful? Give feedback.
All reactions