@illustrisinteractive/sentinel-nest 0.0.1
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/.prettierrc +4 -0
- package/README.md +98 -0
- package/config/config.json +24 -0
- package/dist/bin/commit.d.ts +1 -0
- package/dist/bin/commit.js +136 -0
- package/dist/bin/commit.js.map +1 -0
- package/dist/bin/init.d.ts +2 -0
- package/dist/bin/init.js +179 -0
- package/dist/bin/init.js.map +1 -0
- package/dist/bin/migrations/1-create-permission.d.ts +6 -0
- package/dist/bin/migrations/1-create-permission.js +30 -0
- package/dist/bin/migrations/1-create-permission.js.map +1 -0
- package/dist/bin/migrations/2-create-role.d.ts +2 -0
- package/dist/bin/migrations/2-create-role.js +29 -0
- package/dist/bin/migrations/2-create-role.js.map +1 -0
- package/dist/bin/migrations/3-create-rolepermissions.d.ts +2 -0
- package/dist/bin/migrations/3-create-rolepermissions.js +46 -0
- package/dist/bin/migrations/3-create-rolepermissions.js.map +1 -0
- package/dist/bin/migrations/4-create-model-roles.d.ts +2 -0
- package/dist/bin/migrations/4-create-model-roles.js +46 -0
- package/dist/bin/migrations/4-create-model-roles.js.map +1 -0
- package/dist/bin/resource.d.ts +1 -0
- package/dist/bin/resource.js +91 -0
- package/dist/bin/resource.js.map +1 -0
- package/dist/bin/sentinel.d.ts +2 -0
- package/dist/bin/sentinel.js +52 -0
- package/dist/bin/sentinel.js.map +1 -0
- package/dist/models/SecuredResource.d.ts +8 -0
- package/dist/models/SecuredResource.js +9 -0
- package/dist/models/SecuredResource.js.map +1 -0
- package/dist/models/SentinelConfig.d.ts +7 -0
- package/dist/models/SentinelConfig.js +3 -0
- package/dist/models/SentinelConfig.js.map +1 -0
- package/dist/models/sequelize/PermissionKey.d.ts +7 -0
- package/dist/models/sequelize/PermissionKey.js +39 -0
- package/dist/models/sequelize/PermissionKey.js.map +1 -0
- package/dist/prisma.config.d.ts +3 -0
- package/dist/prisma.config.js +14 -0
- package/dist/prisma.config.js.map +1 -0
- package/dist/src/can.decorator.d.ts +2 -0
- package/dist/src/can.decorator.js +6 -0
- package/dist/src/can.decorator.js.map +1 -0
- package/dist/src/generated/prisma/browser.d.ts +10 -0
- package/dist/src/generated/prisma/browser.js +44 -0
- package/dist/src/generated/prisma/browser.js.map +1 -0
- package/dist/src/generated/prisma/client.d.ts +14 -0
- package/dist/src/generated/prisma/client.js +46 -0
- package/dist/src/generated/prisma/client.js.map +1 -0
- package/dist/src/generated/prisma/commonInputTypes.d.ts +263 -0
- package/dist/src/generated/prisma/commonInputTypes.js +3 -0
- package/dist/src/generated/prisma/commonInputTypes.js.map +1 -0
- package/dist/src/generated/prisma/enums.d.ts +1 -0
- package/dist/src/generated/prisma/enums.js +3 -0
- package/dist/src/generated/prisma/enums.js.map +1 -0
- package/dist/src/generated/prisma/internal/class.d.ts +50 -0
- package/dist/src/generated/prisma/internal/class.js +75 -0
- package/dist/src/generated/prisma/internal/class.js.map +1 -0
- package/dist/src/generated/prisma/internal/prismaNamespace.d.ts +778 -0
- package/dist/src/generated/prisma/internal/prismaNamespace.js +128 -0
- package/dist/src/generated/prisma/internal/prismaNamespace.js.map +1 -0
- package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts +88 -0
- package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js +112 -0
- package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js.map +1 -0
- package/dist/src/generated/prisma/models/ModelHasRoles.d.ts +691 -0
- package/dist/src/generated/prisma/models/ModelHasRoles.js +3 -0
- package/dist/src/generated/prisma/models/ModelHasRoles.js.map +1 -0
- package/dist/src/generated/prisma/models/PermissionKeys.d.ts +547 -0
- package/dist/src/generated/prisma/models/PermissionKeys.js +3 -0
- package/dist/src/generated/prisma/models/PermissionKeys.js.map +1 -0
- package/dist/src/generated/prisma/models/RoleHasPermissions.d.ts +675 -0
- package/dist/src/generated/prisma/models/RoleHasPermissions.js +3 -0
- package/dist/src/generated/prisma/models/RoleHasPermissions.js.map +1 -0
- package/dist/src/generated/prisma/models/Roles.d.ts +582 -0
- package/dist/src/generated/prisma/models/Roles.js +3 -0
- package/dist/src/generated/prisma/models/Roles.js.map +1 -0
- package/dist/src/generated/prisma/models/SequelizeMeta.d.ts +289 -0
- package/dist/src/generated/prisma/models/SequelizeMeta.js +3 -0
- package/dist/src/generated/prisma/models/SequelizeMeta.js.map +1 -0
- package/dist/src/generated/prisma/models/Users.d.ts +572 -0
- package/dist/src/generated/prisma/models/Users.js +3 -0
- package/dist/src/generated/prisma/models/Users.js.map +1 -0
- package/dist/src/generated/prisma/models.d.ts +7 -0
- package/dist/src/generated/prisma/models.js +3 -0
- package/dist/src/generated/prisma/models.js.map +1 -0
- package/dist/src/main.d.ts +6 -0
- package/dist/src/main.js +23 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/models/SecuredResource.d.ts +8 -0
- package/dist/src/models/SecuredResource.js +9 -0
- package/dist/src/models/SecuredResource.js.map +1 -0
- package/dist/src/models/SentinelConfig.d.ts +7 -0
- package/dist/src/models/SentinelConfig.js +3 -0
- package/dist/src/models/SentinelConfig.js.map +1 -0
- package/dist/src/models/SentinelModel.d.ts +35 -0
- package/dist/src/models/SentinelModel.js +3 -0
- package/dist/src/models/SentinelModel.js.map +1 -0
- package/dist/src/models/SentinelModuleOptions.d.ts +7 -0
- package/dist/src/models/SentinelModuleOptions.js +3 -0
- package/dist/src/models/SentinelModuleOptions.js.map +1 -0
- package/dist/src/prisma.service.d.ts +4 -0
- package/dist/src/prisma.service.js +29 -0
- package/dist/src/prisma.service.js.map +1 -0
- package/dist/src/sentinel.guard.d.ts +9 -0
- package/dist/src/sentinel.guard.js +73 -0
- package/dist/src/sentinel.guard.js.map +1 -0
- package/dist/src/sentinel.module-definition.d.ts +2 -0
- package/dist/src/sentinel.module-definition.js +7 -0
- package/dist/src/sentinel.module-definition.js.map +1 -0
- package/dist/src/sentinel.module.d.ts +3 -0
- package/dist/src/sentinel.module.js +40 -0
- package/dist/src/sentinel.module.js.map +1 -0
- package/dist/src/sentinel.service.d.ts +39 -0
- package/dist/src/sentinel.service.js +146 -0
- package/dist/src/sentinel.service.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/eslint.config.mjs +34 -0
- package/models/index.js +43 -0
- package/models/permissionkey.js +31 -0
- package/models/role.js +23 -0
- package/models/rolehaspermission.js +43 -0
- package/nest-cli.json +8 -0
- package/package.json +103 -0
- package/prisma/migrations/20260227023704_init/migration.sql +74 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +62 -0
- package/prisma.config.ts +14 -0
- package/src/bin/commit.ts +186 -0
- package/src/bin/init.ts +251 -0
- package/src/bin/migrations/1-create-permission.js +32 -0
- package/src/bin/migrations/2-create-role.js +29 -0
- package/src/bin/migrations/3-create-rolepermissions.js +46 -0
- package/src/bin/migrations/4-create-model-roles.js +46 -0
- package/src/bin/resource.ts +107 -0
- package/src/bin/sentinel.ts +115 -0
- package/src/bin/tsconfig.json +30 -0
- package/src/can.decorator.ts +4 -0
- package/src/generated/prisma/browser.ts +49 -0
- package/src/generated/prisma/client.ts +69 -0
- package/src/generated/prisma/commonInputTypes.ts +302 -0
- package/src/generated/prisma/enums.ts +15 -0
- package/src/generated/prisma/internal/class.ts +250 -0
- package/src/generated/prisma/internal/prismaNamespace.ts +1213 -0
- package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +163 -0
- package/src/generated/prisma/models/ModelHasRoles.ts +1521 -0
- package/src/generated/prisma/models/PermissionKeys.ts +1362 -0
- package/src/generated/prisma/models/RoleHasPermissions.ts +1503 -0
- package/src/generated/prisma/models/Roles.ts +1437 -0
- package/src/generated/prisma/models/SequelizeMeta.ts +1032 -0
- package/src/generated/prisma/models/Users.ts +1402 -0
- package/src/generated/prisma/models.ts +17 -0
- package/src/main.ts +24 -0
- package/src/models/SecuredResource.d.ts +8 -0
- package/src/models/SecuredResource.ts +9 -0
- package/src/models/SentinelConfig.d.ts +7 -0
- package/src/models/SentinelConfig.ts +8 -0
- package/src/models/SentinelModel.ts +39 -0
- package/src/models/SentinelModuleOptions.ts +11 -0
- package/src/models/sequelize/PermissionKey.ts +22 -0
- package/src/models/tsconfig.json +25 -0
- package/src/prisma.service.ts +13 -0
- package/src/sentinel.guard.ts +63 -0
- package/src/sentinel.module-definition.ts +5 -0
- package/src/sentinel.module.ts +27 -0
- package/src/sentinel.service.ts +188 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
// biome-ignore-all lint: generated file
|
|
5
|
+
// @ts-nocheck
|
|
6
|
+
/*
|
|
7
|
+
* This is a barrel export file for all models and their related types.
|
|
8
|
+
*
|
|
9
|
+
* 🟢 You can import this file directly.
|
|
10
|
+
*/
|
|
11
|
+
export type * from './models/ModelHasRoles.js'
|
|
12
|
+
export type * from './models/PermissionKeys.js'
|
|
13
|
+
export type * from './models/RoleHasPermissions.js'
|
|
14
|
+
export type * from './models/Roles.js'
|
|
15
|
+
export type * from './models/SequelizeMeta.js'
|
|
16
|
+
export type * from './models/Users.js'
|
|
17
|
+
export type * from './commonInputTypes.js'
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export * from './sentinel.module';
|
|
2
|
+
export * from './sentinel.service';
|
|
3
|
+
export * from './sentinel.guard';
|
|
4
|
+
export * from './can.decorator';
|
|
5
|
+
export * from './models/SecuredResource';
|
|
6
|
+
export * from './models/SentinelConfig';
|
|
7
|
+
|
|
8
|
+
// import { Logger } from '@nestjs/common';
|
|
9
|
+
// import { NestFactory } from '@nestjs/core';
|
|
10
|
+
// import { SentinelModule } from './sentinel.module';
|
|
11
|
+
|
|
12
|
+
// async function bootstrap() {
|
|
13
|
+
// const app = await NestFactory.create(SentinelModule);
|
|
14
|
+
// const globalPrefix = 'api';
|
|
15
|
+
// app.setGlobalPrefix(globalPrefix);
|
|
16
|
+
// app.enableCors();
|
|
17
|
+
// const port = 8080;
|
|
18
|
+
// await app.listen(port);
|
|
19
|
+
// Logger.log(
|
|
20
|
+
// `🚀Main Backend Application is running on: http://localhost:${port}/${globalPrefix}`,
|
|
21
|
+
// );
|
|
22
|
+
// }
|
|
23
|
+
|
|
24
|
+
// bootstrap();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { PermissionKeysModel } from '../generated/prisma/models';
|
|
2
|
+
export interface SentinelPermissionKey {
|
|
3
|
+
id: string;
|
|
4
|
+
resource: string;
|
|
5
|
+
action: string;
|
|
6
|
+
createdAt: string | Date;
|
|
7
|
+
updatedAt: string | Date;
|
|
8
|
+
description: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SentinelModelRoleHasPermissions {
|
|
12
|
+
id: number;
|
|
13
|
+
role: number;
|
|
14
|
+
permission: string;
|
|
15
|
+
createdAt: string | Date;
|
|
16
|
+
updatedAt: string | Date;
|
|
17
|
+
PermissionKeys: SentinelPermissionKey;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SentinelModelRole {
|
|
21
|
+
id: number;
|
|
22
|
+
name: string;
|
|
23
|
+
createdAt: string | Date;
|
|
24
|
+
updatedAt: string | Date;
|
|
25
|
+
RoleHasPermissions: SentinelModelRoleHasPermissions[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SentinelModelHasPermissions {
|
|
29
|
+
id: number;
|
|
30
|
+
role: number;
|
|
31
|
+
model: number;
|
|
32
|
+
createdAt: string | Date;
|
|
33
|
+
updatedAt: string | Date;
|
|
34
|
+
Roles: SentinelModelRole;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SentinelModel {
|
|
38
|
+
permissions: PermissionKeysModel[];
|
|
39
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SecuredResource } from './SecuredResource';
|
|
2
|
+
|
|
3
|
+
export interface SentinelModuleOptions {
|
|
4
|
+
resources: SecuredResource[];
|
|
5
|
+
modelTable: string;
|
|
6
|
+
modelKey: string;
|
|
7
|
+
/**
|
|
8
|
+
* Defines which custom header Sentinel should look for when validating gRPC calls via the SentinelGuard
|
|
9
|
+
*/
|
|
10
|
+
header: string;
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Column,
|
|
3
|
+
DataType,
|
|
4
|
+
Model,
|
|
5
|
+
PrimaryKey,
|
|
6
|
+
Table,
|
|
7
|
+
} from 'sequelize-typescript';
|
|
8
|
+
|
|
9
|
+
@Table({ tableName: 'PermissionKeys' })
|
|
10
|
+
export class PermissionKey extends Model {
|
|
11
|
+
@Column({ primaryKey: true, type: DataType.STRING })
|
|
12
|
+
declare id: string;
|
|
13
|
+
|
|
14
|
+
@Column({ type: DataType.STRING })
|
|
15
|
+
resource: string;
|
|
16
|
+
|
|
17
|
+
@Column({ type: DataType.STRING })
|
|
18
|
+
action: string;
|
|
19
|
+
|
|
20
|
+
@Column({ type: DataType.STRING })
|
|
21
|
+
description: string;
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "nodenext",
|
|
4
|
+
"moduleResolution": "nodenext",
|
|
5
|
+
"resolvePackageJsonExports": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"removeComments": true,
|
|
10
|
+
"emitDecoratorMetadata": true,
|
|
11
|
+
"experimentalDecorators": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"target": "ES2023",
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"outDir": "../../dist/models/sentinel",
|
|
16
|
+
"baseUrl": "./",
|
|
17
|
+
"incremental": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"strictNullChecks": true,
|
|
20
|
+
"forceConsistentCasingInFileNames": true,
|
|
21
|
+
"noImplicitAny": false,
|
|
22
|
+
"strictBindCallApply": false,
|
|
23
|
+
"noFallthroughCasesInSwitch": false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { PrismaClient } from './generated/prisma/client';
|
|
3
|
+
import { PrismaPg } from '@prisma/adapter-pg';
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class PrismaService extends PrismaClient {
|
|
7
|
+
constructor() {
|
|
8
|
+
const adapter = new PrismaPg({
|
|
9
|
+
connectionString: `postgres://${process.env.SENTINEL_DB_USER}:${process.env.SENTINEL_DB_PASS}@${process.env.SENTINEL_DB_HOST}:${process.env.SENTINEL_DB_PORT}/${process.env.SENTINEL_DB_DATABASE}`,
|
|
10
|
+
});
|
|
11
|
+
super({ adapter });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CanActivate,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
Inject,
|
|
5
|
+
Injectable,
|
|
6
|
+
} from '@nestjs/common';
|
|
7
|
+
import { SentinelService } from './sentinel.service';
|
|
8
|
+
import { type Metadata } from '@grpc/grpc-js';
|
|
9
|
+
import { type Request } from 'express';
|
|
10
|
+
import { SentinelModel } from './models/SentinelModel';
|
|
11
|
+
import { Reflector } from '@nestjs/core';
|
|
12
|
+
import { Can, SecuredResourceAction } from './main';
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class SentinelGuard implements CanActivate {
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly sentinelService: SentinelService,
|
|
18
|
+
@Inject(Reflector) private reflector: Reflector,
|
|
19
|
+
) {}
|
|
20
|
+
canActivate(context: ExecutionContext): boolean {
|
|
21
|
+
const requiredActions = this.reflector.getAllAndOverride<
|
|
22
|
+
SecuredResourceAction[]
|
|
23
|
+
>(Can, [context.getHandler(), context.getClass()]);
|
|
24
|
+
|
|
25
|
+
if (requiredActions.length == 0) return true;
|
|
26
|
+
|
|
27
|
+
let sentinelAuth = '';
|
|
28
|
+
if (context.getType() == 'rpc') {
|
|
29
|
+
const rpcContext: Metadata = context.switchToRpc().getContext();
|
|
30
|
+
sentinelAuth = rpcContext.get(
|
|
31
|
+
this.sentinelService.options.header || 'x-sentinel-key',
|
|
32
|
+
)[0] as string;
|
|
33
|
+
} else if (context.getType() == 'http') {
|
|
34
|
+
const httpContext: Request = context.switchToHttp().getRequest();
|
|
35
|
+
const header =
|
|
36
|
+
httpContext.headers[
|
|
37
|
+
this.sentinelService.options.header || 'x-sentinel-key'
|
|
38
|
+
];
|
|
39
|
+
if (header && typeof header == 'string') {
|
|
40
|
+
sentinelAuth = header;
|
|
41
|
+
}
|
|
42
|
+
} else return false;
|
|
43
|
+
|
|
44
|
+
if (!sentinelAuth) return false;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const payload: SentinelModel = this.sentinelService.verify(sentinelAuth);
|
|
48
|
+
if (!payload) return false;
|
|
49
|
+
|
|
50
|
+
const abilities = this.sentinelService.parseModelAbilities(payload);
|
|
51
|
+
for (const action of requiredActions) {
|
|
52
|
+
if (!action.source) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (abilities.can(Object.keys(action)[0], action.source)) return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { SentinelService } from './sentinel.service';
|
|
3
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
4
|
+
import { PrismaService } from './prisma.service';
|
|
5
|
+
import { ConfigurableModuleClass } from './sentinel.module-definition';
|
|
6
|
+
import { JwtModule } from '@nestjs/jwt';
|
|
7
|
+
import { SentinelGuard } from './sentinel.guard';
|
|
8
|
+
|
|
9
|
+
@Module({
|
|
10
|
+
imports: [
|
|
11
|
+
ConfigModule.forRoot({ isGlobal: true }),
|
|
12
|
+
JwtModule.registerAsync({
|
|
13
|
+
imports: [ConfigModule],
|
|
14
|
+
inject: [ConfigService],
|
|
15
|
+
useFactory: (configService: ConfigService) => ({
|
|
16
|
+
secret: configService.getOrThrow('SENTINEL_KEY'),
|
|
17
|
+
signOptions: {
|
|
18
|
+
expiresIn: '30m',
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
}),
|
|
22
|
+
],
|
|
23
|
+
controllers: [],
|
|
24
|
+
providers: [PrismaService, SentinelService, SentinelGuard],
|
|
25
|
+
exports: [PrismaService, SentinelService, SentinelGuard],
|
|
26
|
+
})
|
|
27
|
+
export class SentinelModule extends ConfigurableModuleClass {}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Inject,
|
|
3
|
+
Injectable,
|
|
4
|
+
Logger,
|
|
5
|
+
NotFoundException,
|
|
6
|
+
OnModuleInit,
|
|
7
|
+
} from '@nestjs/common';
|
|
8
|
+
import { Prisma } from './generated/prisma/client';
|
|
9
|
+
import { PrismaService } from './prisma.service';
|
|
10
|
+
import { MODULE_OPTIONS_TOKEN } from './sentinel.module-definition';
|
|
11
|
+
import type { SentinelModuleOptions } from './models/SentinelModuleOptions';
|
|
12
|
+
import { SecuredResource } from './main';
|
|
13
|
+
import { PermissionKeysModel } from './generated/prisma/models';
|
|
14
|
+
import {
|
|
15
|
+
AbilityBuilder,
|
|
16
|
+
createMongoAbility,
|
|
17
|
+
ExtractSubjectType,
|
|
18
|
+
InferSubjects,
|
|
19
|
+
MongoAbility,
|
|
20
|
+
} from '@casl/ability';
|
|
21
|
+
import { ConfigService } from '@nestjs/config';
|
|
22
|
+
import { JwtService } from '@nestjs/jwt';
|
|
23
|
+
import { SentinelModel } from './models/SentinelModel';
|
|
24
|
+
|
|
25
|
+
@Injectable()
|
|
26
|
+
export class SentinelService implements OnModuleInit {
|
|
27
|
+
resources: SecuredResource[];
|
|
28
|
+
options: SentinelModuleOptions;
|
|
29
|
+
constructor(
|
|
30
|
+
@Inject(MODULE_OPTIONS_TOKEN) private _options: SentinelModuleOptions,
|
|
31
|
+
private prisma: PrismaService,
|
|
32
|
+
private jwt: JwtService,
|
|
33
|
+
private config: ConfigService,
|
|
34
|
+
) {
|
|
35
|
+
this.options = this._options;
|
|
36
|
+
this.resources = this.options.resources;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
verify(token: string): SentinelModel {
|
|
40
|
+
return this.jwt.verify(token);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
packModel(model: PermissionKeysModel[]) {
|
|
44
|
+
return this.jwt.sign({
|
|
45
|
+
permissions: model,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
parseModelAbilities(model: SentinelModel): MongoAbility {
|
|
50
|
+
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
|
|
51
|
+
|
|
52
|
+
for (const resource of this.resources) {
|
|
53
|
+
for (const action of Object.keys(resource.actions)) {
|
|
54
|
+
if (
|
|
55
|
+
model.permissions.find(
|
|
56
|
+
(permissions) => permissions.action == resource.actions[action],
|
|
57
|
+
)
|
|
58
|
+
) {
|
|
59
|
+
can(action, resource.name);
|
|
60
|
+
} else cannot(action, resource.name);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type Subjects = InferSubjects<any>;
|
|
65
|
+
|
|
66
|
+
return build({
|
|
67
|
+
detectSubjectType: (item) =>
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
69
|
+
item.constructor as ExtractSubjectType<Subjects>,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async buildModelAbilities(modelId: number) {
|
|
74
|
+
const rawModel = await this.getModelRoles(modelId);
|
|
75
|
+
if (!rawModel)
|
|
76
|
+
return new NotFoundException(
|
|
77
|
+
'The provided model ID did not return any Role assigned to it.',
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const model: {
|
|
81
|
+
roles: string[];
|
|
82
|
+
permissions: PermissionKeysModel[];
|
|
83
|
+
} = {
|
|
84
|
+
roles: [],
|
|
85
|
+
permissions: [],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// for (const role of rawModel) {
|
|
89
|
+
// model.roles.push(role.Roles.name || '');
|
|
90
|
+
// role.Roles.RoleHasPermissions.forEach((permission) => {
|
|
91
|
+
// model.permissions.push(permission);
|
|
92
|
+
// });
|
|
93
|
+
// }
|
|
94
|
+
|
|
95
|
+
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
|
|
96
|
+
|
|
97
|
+
for (const resource of this.resources) {
|
|
98
|
+
for (const action of Object.keys(resource.actions)) {
|
|
99
|
+
if (
|
|
100
|
+
model.permissions.find(
|
|
101
|
+
(permissions) => permissions.action == resource.actions[action],
|
|
102
|
+
)
|
|
103
|
+
) {
|
|
104
|
+
can(action, resource.name);
|
|
105
|
+
} else cannot(action, resource.name);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type Subjects = InferSubjects<any>;
|
|
110
|
+
|
|
111
|
+
return build({
|
|
112
|
+
detectSubjectType: (item) =>
|
|
113
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
114
|
+
item.constructor as ExtractSubjectType<Subjects>,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async onModuleInit() {
|
|
119
|
+
// check if Sentinel key has been generated
|
|
120
|
+
const key: string | undefined = this.config.get('SENTINEL_KEY');
|
|
121
|
+
if (!key) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
'SENTINEL_KEY was not found during Sentinel runtime. Have you ran `npx sentinel init`?',
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// check if actions exist in database
|
|
128
|
+
for (const resource of this.options.resources) {
|
|
129
|
+
const matchingActions = await this.prisma.permissionKeys.findMany({
|
|
130
|
+
where: {
|
|
131
|
+
resource: resource.name,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
Object.keys(resource.actions).forEach((_key) => {
|
|
136
|
+
if (!matchingActions.find((mAction) => mAction.action == _key)) {
|
|
137
|
+
Logger.warn(
|
|
138
|
+
`Action "${_key}" in Secured Resource "${resource.name}" does not exist in the database. If these Actions are new, have you persisted them with "npx sentinel commit"?`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async getRole(role: Prisma.RolesWhereUniqueInput) {
|
|
146
|
+
return this.prisma.roles.findUnique({
|
|
147
|
+
where: role,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async getModelRoles(modelId: number): Promise<PermissionKeysModel[]> {
|
|
152
|
+
try {
|
|
153
|
+
const result = await this.prisma.modelHasRoles.findMany({
|
|
154
|
+
where: {
|
|
155
|
+
model: modelId,
|
|
156
|
+
},
|
|
157
|
+
include: {
|
|
158
|
+
Roles: {
|
|
159
|
+
include: {
|
|
160
|
+
RoleHasPermissions: {
|
|
161
|
+
include: {
|
|
162
|
+
PermissionKeys: true,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const flatResult: PermissionKeysModel[] = result.flatMap((rhp) =>
|
|
171
|
+
rhp.Roles.RoleHasPermissions.map((rhp) => rhp.PermissionKeys),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return flatResult;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
177
|
+
throw new Error(error);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async getPermission(id: 'fds' | 'fdsk') {
|
|
182
|
+
return await this.prisma.permissionKeys.findFirst({
|
|
183
|
+
where: {
|
|
184
|
+
id,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "nodenext",
|
|
4
|
+
"moduleResolution": "nodenext",
|
|
5
|
+
"resolvePackageJsonExports": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"removeComments": true,
|
|
10
|
+
"emitDecoratorMetadata": true,
|
|
11
|
+
"experimentalDecorators": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"target": "ES2023",
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"outDir": "./dist",
|
|
16
|
+
"baseUrl": "./",
|
|
17
|
+
"incremental": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"strictNullChecks": true,
|
|
20
|
+
"forceConsistentCasingInFileNames": true,
|
|
21
|
+
"noImplicitAny": false,
|
|
22
|
+
"strictBindCallApply": false,
|
|
23
|
+
"noFallthroughCasesInSwitch": false
|
|
24
|
+
}
|
|
25
|
+
}
|