Skip to content

Commit

Permalink
feat: add casl generation with deep access (#330)
Browse files Browse the repository at this point in the history
* feat: add casl generation with deep access

* feat: add casl ownership managment

* feat: disable tests to ship casl feature fast and write it back after
  • Loading branch information
floross committed Dec 21, 2021
1 parent 090fee2 commit 4138c39
Show file tree
Hide file tree
Showing 55 changed files with 1,318 additions and 467 deletions.
28 changes: 28 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,33 @@
"request": "attach",
"port": 9229
}
// To Debug directly into vscode
// Cannot reload on code changes
// {
// "name": "Debug API",
// "type": "node",
// "request": "launch",
// "args": ["apps/api/src/main.ts"], // Path to main entry file
// "runtimeArgs": [
// "--require",
// "ts-node/register",
// "--require",
// "tsconfig-paths/register",
// "--experimental-modules"
// ],
// "cwd": "${workspaceRoot}",
// "internalConsoleOptions": "openOnSessionStart",
// "restart": true,
// "env": {
// "TS_NODE_PROJECT": "apps/api/tsconfig.json" // Specify the tsconfig to use. See content of it below.
// },
// "sourceMaps": true,
// "console": "internalConsole",
// "outputCapture": "std",
// "resolveSourceMapLocations": [
// "${workspaceFolder}/**",
// "!**/node_modules/**" // Disable the "could not read source map" error for node_modules
// ]
// }
]
}
10 changes: 10 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,25 @@
},
"cSpell.words": [
"casl",
"Concat",
"dtos",
"endfor",
"hapify",
"mailjet",
"minio",
"nestjs",
"nrwl",
"Rext",
"tractr",
"Unsubscriber",
"upsert"
],
"debug.javascript.autoAttachFilter": "smart",
"debug.javascript.autoAttachSmartPattern": [
"!**/{node_modules,npm-global,.yarn,.nvm}/**",
"**/$KNOWN_TOOLS$/**",
"'**/node_modules/@nrwl/node/**'"
],
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.eslint": true
Expand Down
20 changes: 16 additions & 4 deletions apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { ConsoleModule } from 'nestjs-console';

import { AppController } from './app.controller';
import { AppService } from './app.service';

import { rolePermissions } from '@generated/casl';
import { getSelectPrismaUserQuery, rolePermissions } from '@generated/casl';
import { ModelsModule } from '@generated/nestjs-models';
import { USER_SERVICE } from '@generated/nestjs-models-common';
import {
AuthenticationModule,
JwtGlobalAuthGuard,
} from '@tractr/nestjs-authentication';
import { CaslModule } from '@tractr/nestjs-casl';
import {
CaslExceptionInterceptor,
CaslModule,
PoliciesGuard,
} from '@tractr/nestjs-casl';
import { LoggerModule } from '@tractr/nestjs-core';
import { DatabaseModule } from '@tractr/nestjs-database';
import {
FileStorageController,
Expand Down Expand Up @@ -53,10 +58,17 @@ import { MailerModule } from '@tractr/nestjs-mailer';
}),
CaslModule.register({
rolePermissions,
getSelectPrismaUserQuery,
}),
ConsoleModule,
LoggerModule,
],
controllers: [AppController, FileStorageController],
providers: [AppService, { provide: APP_GUARD, useClass: JwtGlobalAuthGuard }],
providers: [
AppService,
{ provide: APP_GUARD, useClass: JwtGlobalAuthGuard },
{ provide: APP_GUARD, useClass: PoliciesGuard },
{ provide: APP_INTERCEPTOR, useClass: CaslExceptionInterceptor },
],
})
export class AppModule {}
68 changes: 66 additions & 2 deletions hapify-models.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,67 @@
"name": "Answer",
"notes": "The answer model"
},
{
"accesses": {
"count": "owner",
"create": "owner",
"read": "owner",
"remove": "owner",
"search": "owner",
"update": "owner"
},
"fields": [
{
"meta": {},
"name": "id",
"properties": ["primary", "searchable", "sortable", "internal"],
"type": "string"
},
{
"meta": {
"backRelation": "departments",
"ownerStringPath": "user.enterprises.id"
},
"name": "enterprise",
"properties": ["ownership"],
"subtype": "oneMany",
"type": "entity",
"value": "c069cc55-f28f-dcc1-cad8-0df044631ef8"
}
],
"id": "2a523149-31f3-80bf-d32b-3b9174cc6f2c",
"name": "Department"
},
{
"accesses": {
"count": "owner",
"create": "owner",
"read": "owner",
"remove": "owner",
"search": "owner",
"update": "owner"
},
"fields": [
{
"name": "id",
"properties": ["primary", "searchable", "sortable", "internal"],
"type": "string"
},
{
"meta": {
"backRelation": "enterprises",
"ownerStringPath": "user.id"
},
"name": "employee",
"properties": ["multiple", "ownership"],
"subtype": "manyMany",
"type": "entity",
"value": "a7d0308a-49f0-3458-0975-1dce106136a1"
}
],
"id": "c069cc55-f28f-dcc1-cad8-0df044631ef8",
"name": "Enterprise"
},
{
"accesses": {
"count": "guest",
Expand Down Expand Up @@ -349,7 +410,7 @@
{
"meta": {
"backRelation": "tags",
"ownerKey": "ownerId"
"ownerStringPath": "user.id"
},
"name": "owner",
"properties": ["ownership"],
Expand All @@ -367,11 +428,14 @@
"create": "guest",
"read": "owner",
"remove": "admin",
"search": "admin",
"search": "owner",
"update": "owner"
},
"fields": [
{
"meta": {
"ownerStringPath": "user.id"
},
"name": "id",
"properties": [
"primary",
Expand Down
127 changes: 127 additions & 0 deletions libs/common/src/lib/helpers/get-concat-value-by-path.helper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import {
findAllValueByPath,
getConcatValueByPath,
} from './get-concat-value-by-path.helper';

describe('findAllValueByPath', () => {
it('should return an array of keys', () => {
const obj = {
a: {
b: [
{
c: {
d: 1,
},
},
{
c: {
d: 2,
},
},
{
c: {
d: 3,
},
},
],
},
};
const result = findAllValueByPath('a.b.c.d', obj);
expect(result).toEqual([1, 2, 3]);
});
it('should work with an array', () => {
const obj = [
{
a: {
b: [
{
c: {
d: 1,
},
},
{
c: {
d: 2,
},
},
{
c: {
d: 3,
},
},
],
},
},
];
const result = findAllValueByPath('a.b.c.d', obj);
expect(result).toEqual([1, 2, 3]);
});

it('should not throw if the path is not found', () => {
const obj = [
{
a: {
b: [
{
c: {
d: 1,
},
},
{
c: {
d: 2,
},
},
{
c: {
d: 3,
},
},
],
},
},
];
const result = findAllValueByPath('b.c.d', obj);
expect(result).toEqual([]);

const obj2 = 'foo';
const result2 = findAllValueByPath('b.c.d', obj2);
expect(result2).toEqual([]);
});

it('should return the value if the path is empty', () => {
const obj = 'foo';
const result = findAllValueByPath('', obj);
expect(result).toEqual([obj]);
});
it('should return an empty array if the current obj is undefined', () => {
const result = findAllValueByPath('', undefined);
expect(result).toEqual([]);
});
it('getConcatValueByPath should be an alias with type of findAllValueByPath', () => {
const obj = {
a: {
b: [
{
c: {
d: 1,
},
},
{
c: {
d: 2,
},
},
{
c: {
d: 3,
},
},
],
},
};
const result = findAllValueByPath('a.b.c.d', obj);
const result2 = getConcatValueByPath('a.b.c.d', obj);
expect(result).toEqual(result2);
});
});
47 changes: 47 additions & 0 deletions libs/common/src/lib/helpers/get-concat-value-by-path.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { JsonArray, JsonObject, JsonValue } from '../interfaces';

/**
* Get and concat value by path
* @param path the string path to the value
* @param obj The obj to traverse
* @returns an array of values
*/
export function findAllValueByPath(
path: string,
obj?: JsonObject | JsonValue | undefined,
): JsonArray {
const [nextPath, ...nextKeys] = path.split('.');

if (typeof obj === 'undefined') {
return [];
}

if (typeof obj !== 'object' || obj === null) {
if (nextKeys.length > 0) return [];
return [obj];
}

if (Array.isArray(obj))
return obj.flatMap((item) => findAllValueByPath(path, item));

const currentObj = obj[nextPath];

if (typeof currentObj === 'undefined') return [];

if (nextKeys.length === 0) return [currentObj];

if (Array.isArray(currentObj))
return currentObj.flatMap((item) =>
findAllValueByPath(nextKeys.join('.'), item),
);

return findAllValueByPath(nextKeys.join('.'), currentObj);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getConcatValueByPath<T extends Array<any> = any[]>(
path: string,
obj?: JsonObject | JsonValue | undefined,
): T {
return findAllValueByPath(path, obj) as T;
}
1 change: 1 addition & 0 deletions libs/common/src/lib/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './get-concat-value-by-path.helper';
export * from './is-class.helper';
export * from './unique-array.helper';
2 changes: 1 addition & 1 deletion libs/common/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"lib": ["dom", "es2018"],
"lib": ["dom", "esnext"],
"outDir": "../../dist/out-tsc",
"target": "es2015",
"types": []
Expand Down
5 changes: 5 additions & 0 deletions libs/config/eslint/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ module.exports = {
plugins: ['import', 'json-files', 'jest', '@typescript-eslint/eslint-plugin'],
rules: {
'no-console': ['error', { allow: ['info', 'warn', 'error'] }],
'lines-between-class-members': [
'error',
'always',
{ exceptAfterSingleLine: true },
],

// Not compatible with nestjs
'no-useless-constructor': 'off',
Expand Down
Loading

0 comments on commit 4138c39

Please sign in to comment.