@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.
Files changed (167) hide show
  1. package/.prettierrc +4 -0
  2. package/README.md +98 -0
  3. package/config/config.json +24 -0
  4. package/dist/bin/commit.d.ts +1 -0
  5. package/dist/bin/commit.js +136 -0
  6. package/dist/bin/commit.js.map +1 -0
  7. package/dist/bin/init.d.ts +2 -0
  8. package/dist/bin/init.js +179 -0
  9. package/dist/bin/init.js.map +1 -0
  10. package/dist/bin/migrations/1-create-permission.d.ts +6 -0
  11. package/dist/bin/migrations/1-create-permission.js +30 -0
  12. package/dist/bin/migrations/1-create-permission.js.map +1 -0
  13. package/dist/bin/migrations/2-create-role.d.ts +2 -0
  14. package/dist/bin/migrations/2-create-role.js +29 -0
  15. package/dist/bin/migrations/2-create-role.js.map +1 -0
  16. package/dist/bin/migrations/3-create-rolepermissions.d.ts +2 -0
  17. package/dist/bin/migrations/3-create-rolepermissions.js +46 -0
  18. package/dist/bin/migrations/3-create-rolepermissions.js.map +1 -0
  19. package/dist/bin/migrations/4-create-model-roles.d.ts +2 -0
  20. package/dist/bin/migrations/4-create-model-roles.js +46 -0
  21. package/dist/bin/migrations/4-create-model-roles.js.map +1 -0
  22. package/dist/bin/resource.d.ts +1 -0
  23. package/dist/bin/resource.js +91 -0
  24. package/dist/bin/resource.js.map +1 -0
  25. package/dist/bin/sentinel.d.ts +2 -0
  26. package/dist/bin/sentinel.js +52 -0
  27. package/dist/bin/sentinel.js.map +1 -0
  28. package/dist/models/SecuredResource.d.ts +8 -0
  29. package/dist/models/SecuredResource.js +9 -0
  30. package/dist/models/SecuredResource.js.map +1 -0
  31. package/dist/models/SentinelConfig.d.ts +7 -0
  32. package/dist/models/SentinelConfig.js +3 -0
  33. package/dist/models/SentinelConfig.js.map +1 -0
  34. package/dist/models/sequelize/PermissionKey.d.ts +7 -0
  35. package/dist/models/sequelize/PermissionKey.js +39 -0
  36. package/dist/models/sequelize/PermissionKey.js.map +1 -0
  37. package/dist/prisma.config.d.ts +3 -0
  38. package/dist/prisma.config.js +14 -0
  39. package/dist/prisma.config.js.map +1 -0
  40. package/dist/src/can.decorator.d.ts +2 -0
  41. package/dist/src/can.decorator.js +6 -0
  42. package/dist/src/can.decorator.js.map +1 -0
  43. package/dist/src/generated/prisma/browser.d.ts +10 -0
  44. package/dist/src/generated/prisma/browser.js +44 -0
  45. package/dist/src/generated/prisma/browser.js.map +1 -0
  46. package/dist/src/generated/prisma/client.d.ts +14 -0
  47. package/dist/src/generated/prisma/client.js +46 -0
  48. package/dist/src/generated/prisma/client.js.map +1 -0
  49. package/dist/src/generated/prisma/commonInputTypes.d.ts +263 -0
  50. package/dist/src/generated/prisma/commonInputTypes.js +3 -0
  51. package/dist/src/generated/prisma/commonInputTypes.js.map +1 -0
  52. package/dist/src/generated/prisma/enums.d.ts +1 -0
  53. package/dist/src/generated/prisma/enums.js +3 -0
  54. package/dist/src/generated/prisma/enums.js.map +1 -0
  55. package/dist/src/generated/prisma/internal/class.d.ts +50 -0
  56. package/dist/src/generated/prisma/internal/class.js +75 -0
  57. package/dist/src/generated/prisma/internal/class.js.map +1 -0
  58. package/dist/src/generated/prisma/internal/prismaNamespace.d.ts +778 -0
  59. package/dist/src/generated/prisma/internal/prismaNamespace.js +128 -0
  60. package/dist/src/generated/prisma/internal/prismaNamespace.js.map +1 -0
  61. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts +88 -0
  62. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js +112 -0
  63. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js.map +1 -0
  64. package/dist/src/generated/prisma/models/ModelHasRoles.d.ts +691 -0
  65. package/dist/src/generated/prisma/models/ModelHasRoles.js +3 -0
  66. package/dist/src/generated/prisma/models/ModelHasRoles.js.map +1 -0
  67. package/dist/src/generated/prisma/models/PermissionKeys.d.ts +547 -0
  68. package/dist/src/generated/prisma/models/PermissionKeys.js +3 -0
  69. package/dist/src/generated/prisma/models/PermissionKeys.js.map +1 -0
  70. package/dist/src/generated/prisma/models/RoleHasPermissions.d.ts +675 -0
  71. package/dist/src/generated/prisma/models/RoleHasPermissions.js +3 -0
  72. package/dist/src/generated/prisma/models/RoleHasPermissions.js.map +1 -0
  73. package/dist/src/generated/prisma/models/Roles.d.ts +582 -0
  74. package/dist/src/generated/prisma/models/Roles.js +3 -0
  75. package/dist/src/generated/prisma/models/Roles.js.map +1 -0
  76. package/dist/src/generated/prisma/models/SequelizeMeta.d.ts +289 -0
  77. package/dist/src/generated/prisma/models/SequelizeMeta.js +3 -0
  78. package/dist/src/generated/prisma/models/SequelizeMeta.js.map +1 -0
  79. package/dist/src/generated/prisma/models/Users.d.ts +572 -0
  80. package/dist/src/generated/prisma/models/Users.js +3 -0
  81. package/dist/src/generated/prisma/models/Users.js.map +1 -0
  82. package/dist/src/generated/prisma/models.d.ts +7 -0
  83. package/dist/src/generated/prisma/models.js +3 -0
  84. package/dist/src/generated/prisma/models.js.map +1 -0
  85. package/dist/src/main.d.ts +6 -0
  86. package/dist/src/main.js +23 -0
  87. package/dist/src/main.js.map +1 -0
  88. package/dist/src/models/SecuredResource.d.ts +8 -0
  89. package/dist/src/models/SecuredResource.js +9 -0
  90. package/dist/src/models/SecuredResource.js.map +1 -0
  91. package/dist/src/models/SentinelConfig.d.ts +7 -0
  92. package/dist/src/models/SentinelConfig.js +3 -0
  93. package/dist/src/models/SentinelConfig.js.map +1 -0
  94. package/dist/src/models/SentinelModel.d.ts +35 -0
  95. package/dist/src/models/SentinelModel.js +3 -0
  96. package/dist/src/models/SentinelModel.js.map +1 -0
  97. package/dist/src/models/SentinelModuleOptions.d.ts +7 -0
  98. package/dist/src/models/SentinelModuleOptions.js +3 -0
  99. package/dist/src/models/SentinelModuleOptions.js.map +1 -0
  100. package/dist/src/prisma.service.d.ts +4 -0
  101. package/dist/src/prisma.service.js +29 -0
  102. package/dist/src/prisma.service.js.map +1 -0
  103. package/dist/src/sentinel.guard.d.ts +9 -0
  104. package/dist/src/sentinel.guard.js +73 -0
  105. package/dist/src/sentinel.guard.js.map +1 -0
  106. package/dist/src/sentinel.module-definition.d.ts +2 -0
  107. package/dist/src/sentinel.module-definition.js +7 -0
  108. package/dist/src/sentinel.module-definition.js.map +1 -0
  109. package/dist/src/sentinel.module.d.ts +3 -0
  110. package/dist/src/sentinel.module.js +40 -0
  111. package/dist/src/sentinel.module.js.map +1 -0
  112. package/dist/src/sentinel.service.d.ts +39 -0
  113. package/dist/src/sentinel.service.js +146 -0
  114. package/dist/src/sentinel.service.js.map +1 -0
  115. package/dist/tsconfig.build.tsbuildinfo +1 -0
  116. package/dist/tsconfig.tsbuildinfo +1 -0
  117. package/eslint.config.mjs +34 -0
  118. package/models/index.js +43 -0
  119. package/models/permissionkey.js +31 -0
  120. package/models/role.js +23 -0
  121. package/models/rolehaspermission.js +43 -0
  122. package/nest-cli.json +8 -0
  123. package/package.json +103 -0
  124. package/prisma/migrations/20260227023704_init/migration.sql +74 -0
  125. package/prisma/migrations/migration_lock.toml +3 -0
  126. package/prisma/schema.prisma +62 -0
  127. package/prisma.config.ts +14 -0
  128. package/src/bin/commit.ts +186 -0
  129. package/src/bin/init.ts +251 -0
  130. package/src/bin/migrations/1-create-permission.js +32 -0
  131. package/src/bin/migrations/2-create-role.js +29 -0
  132. package/src/bin/migrations/3-create-rolepermissions.js +46 -0
  133. package/src/bin/migrations/4-create-model-roles.js +46 -0
  134. package/src/bin/resource.ts +107 -0
  135. package/src/bin/sentinel.ts +115 -0
  136. package/src/bin/tsconfig.json +30 -0
  137. package/src/can.decorator.ts +4 -0
  138. package/src/generated/prisma/browser.ts +49 -0
  139. package/src/generated/prisma/client.ts +69 -0
  140. package/src/generated/prisma/commonInputTypes.ts +302 -0
  141. package/src/generated/prisma/enums.ts +15 -0
  142. package/src/generated/prisma/internal/class.ts +250 -0
  143. package/src/generated/prisma/internal/prismaNamespace.ts +1213 -0
  144. package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +163 -0
  145. package/src/generated/prisma/models/ModelHasRoles.ts +1521 -0
  146. package/src/generated/prisma/models/PermissionKeys.ts +1362 -0
  147. package/src/generated/prisma/models/RoleHasPermissions.ts +1503 -0
  148. package/src/generated/prisma/models/Roles.ts +1437 -0
  149. package/src/generated/prisma/models/SequelizeMeta.ts +1032 -0
  150. package/src/generated/prisma/models/Users.ts +1402 -0
  151. package/src/generated/prisma/models.ts +17 -0
  152. package/src/main.ts +24 -0
  153. package/src/models/SecuredResource.d.ts +8 -0
  154. package/src/models/SecuredResource.ts +9 -0
  155. package/src/models/SentinelConfig.d.ts +7 -0
  156. package/src/models/SentinelConfig.ts +8 -0
  157. package/src/models/SentinelModel.ts +39 -0
  158. package/src/models/SentinelModuleOptions.ts +11 -0
  159. package/src/models/sequelize/PermissionKey.ts +22 -0
  160. package/src/models/tsconfig.json +25 -0
  161. package/src/prisma.service.ts +13 -0
  162. package/src/sentinel.guard.ts +63 -0
  163. package/src/sentinel.module-definition.ts +5 -0
  164. package/src/sentinel.module.ts +27 -0
  165. package/src/sentinel.service.ts +188 -0
  166. package/tsconfig.build.json +11 -0
  167. package/tsconfig.json +25 -0
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+ import { intro, log, select, SelectOptions } from '@clack/prompts';
3
+ import { exit } from 'process';
4
+ import { Sequelize } from 'sequelize';
5
+ import { Umzug, SequelizeStorage } from 'umzug';
6
+ import fs from 'fs';
7
+
8
+ export const init = async (
9
+ modelTable: string,
10
+ modelKey: string,
11
+ nxReadFrom?: string,
12
+ env_file?: string,
13
+ skip = false,
14
+ verbose = false,
15
+ ) => {
16
+ intro('Sentinel CLI');
17
+ log.info('Initialize - Preparing your Nest.js project for Sentinel');
18
+
19
+ let nestConfig: { [key: string]: any } | undefined = undefined;
20
+ const tsconfig = {
21
+ compilerOptions: {
22
+ module: 'nodenext',
23
+ moduleResolution: 'nodenext',
24
+ resolvePackageJsonExports: true,
25
+ esModuleInterop: true,
26
+ isolatedModules: true,
27
+ declaration: true,
28
+ removeComments: true,
29
+ emitDecoratorMetadata: true,
30
+ experimentalDecorators: true,
31
+ allowSyntheticDefaultImports: true,
32
+ target: 'ES2023',
33
+ sourceMap: true,
34
+ outDir: '.sentinel/',
35
+ baseUrl: './',
36
+ incremental: true,
37
+ skipLibCheck: true,
38
+ strictNullChecks: true,
39
+ forceConsistentCasingInFileNames: true,
40
+ noImplicitAny: false,
41
+ strictBindCallApply: false,
42
+ noFallthroughCasesInSwitch: false,
43
+ allowJs: true,
44
+ },
45
+ };
46
+
47
+ if (nxReadFrom) {
48
+ log.info(
49
+ '--nx-read-from flag specified. Sentinel CLI will try to detect all Nest projects in the provided path and generate a "nest-cli.json" file.',
50
+ );
51
+ const newNestConfig: {
52
+ projects: {
53
+ [key: string]: {
54
+ root: string;
55
+ sourceRoot: string;
56
+ };
57
+ };
58
+ monorepo: boolean;
59
+ } = {
60
+ projects: {},
61
+ monorepo: true,
62
+ };
63
+
64
+ const nxPath = process.cwd() + '\\' + nxReadFrom;
65
+
66
+ if (!fs.existsSync(nxPath)) {
67
+ log.error(
68
+ `The path specified in --nx-read-from (${nxPath}) does not exist.`,
69
+ );
70
+ exit(-1);
71
+ }
72
+
73
+ const dirs = fs.readdirSync(nxPath);
74
+
75
+ dirs.forEach((dir) => {
76
+ if (fs.existsSync(nxPath + '\\' + dir + '\\project.json')) {
77
+ log.step(
78
+ `Project detected in ${nxPath + '\\' + dir}. Assuming as Nest project with name (${dir})`,
79
+ );
80
+ newNestConfig.projects[dir] = {
81
+ root: `${nxReadFrom}/${dir}`,
82
+ sourceRoot: `${nxReadFrom}/${dir}/src`,
83
+ };
84
+ }
85
+ });
86
+ fs.writeFileSync(
87
+ process.cwd() + '/nest-cli.json',
88
+ JSON.stringify(newNestConfig, null, 2),
89
+ );
90
+ }
91
+
92
+ try {
93
+ nestConfig = (
94
+ await import('file://' + process.cwd() + '\\nest-cli.json', {
95
+ with: { type: 'json' },
96
+ })
97
+ ).default as object;
98
+ } catch (error) {
99
+ log.warn(
100
+ `Your project's "nest-cli.json" file was not found in the current working directory in ${process.cwd()}. Sentinel CLI will now treat your project as an Nx monorepo.`,
101
+ );
102
+ exit(-1);
103
+ }
104
+
105
+ if (env_file) {
106
+ try {
107
+ process.loadEnvFile(env_file);
108
+ } catch (error) {
109
+ log.error(
110
+ `Sentinel CLI failed to initialize Sentinel during ENV file loading. ${error}`,
111
+ );
112
+ exit(-1);
113
+ }
114
+ }
115
+
116
+ if (skip) {
117
+ log.warn(
118
+ "Sentinel CLI will not execute the migrations to create the tables required by Sentinel within your database. Make sure that you're doing this for good reason.",
119
+ );
120
+ } else {
121
+ try {
122
+ process.env.SENTINEL_MODEL_REFERENCE_TABLE = modelTable;
123
+ process.env.SENTINEL_MODEL_REFERENCE_KEY = modelKey;
124
+
125
+ const opts = {
126
+ SENTINEL_DB_HOST: process.env.SENTINEL_DB_HOST,
127
+ SENTINEL_DB_USER: process.env.SENTINEL_DB_USER,
128
+ SENTINEL_DB_PASS: process.env.SENTINEL_DB_PASS,
129
+ SENTINEL_DB_PORT: process.env.SENTINEL_DB_PORT as unknown as number,
130
+ SENTINEL_DB_DATABASE: process.env.SENTINEL_DB_DATABASE,
131
+ };
132
+
133
+ const missingOpts = Object.keys(opts).filter((key) => {
134
+ return opts[key] === undefined;
135
+ });
136
+
137
+ if (missingOpts.length != 0) {
138
+ log.error(
139
+ `Sentinel CLI failed to initialize Sentinel due to missing environment variables: ${missingOpts}.`,
140
+ );
141
+ log.message(
142
+ 'If these variables are defined in a file, run \`npx sentinel init --env-from="<path>"\` instead.',
143
+ );
144
+ exit(-1);
145
+ }
146
+
147
+ const sequelize = new Sequelize({
148
+ dialect: 'postgres',
149
+ host: opts.SENTINEL_DB_HOST,
150
+ username: opts.SENTINEL_DB_USER,
151
+ password: opts.SENTINEL_DB_PASS,
152
+ port: opts.SENTINEL_DB_PORT,
153
+ database: opts.SENTINEL_DB_DATABASE,
154
+ });
155
+
156
+ try {
157
+ await sequelize.authenticate();
158
+ } catch (error) {
159
+ log.error(
160
+ 'Sentinel CLI cannot connect to your database. Initialization failed.',
161
+ );
162
+ exit(-1);
163
+ }
164
+
165
+ log.success('Successfully connected to your database.');
166
+
167
+ const umzug = new Umzug({
168
+ migrations: {
169
+ glob: ['migrations/*.js', { cwd: __dirname }],
170
+ },
171
+ context: sequelize.getQueryInterface(),
172
+ storage: new SequelizeStorage({ sequelize }),
173
+ logger: verbose ? console : undefined,
174
+ });
175
+
176
+ await umzug.up();
177
+ } catch (error) {
178
+ console.log(error);
179
+ }
180
+ log.success('Successfully ran all migrations.');
181
+ }
182
+
183
+ let selectedProject: {
184
+ name: string;
185
+ sourceRoot: string;
186
+ sentinelConfigPath: string;
187
+ } = {
188
+ name: 'default',
189
+ sourceRoot: 'src',
190
+ sentinelConfigPath: 'src\\sentinel',
191
+ };
192
+
193
+ if (nestConfig.monorepo || nestConfig.projects) {
194
+ log.info(
195
+ 'Monorepo Nest.js project detected. You will be prompted to select which Application to install Sentinel in.',
196
+ );
197
+
198
+ selectedProject = (await select({
199
+ message: 'Select which Application to configure Sentinel for:',
200
+ options: Object.keys(nestConfig.projects).map((app) => ({
201
+ label: app,
202
+ value: {
203
+ name: app,
204
+ sourceRoot: nestConfig.projects[app].sourceRoot,
205
+ sentinelConfigPath:
206
+ nestConfig.projects[app].sourceRoot + '\\sentinel',
207
+ },
208
+ })),
209
+ })) as { name: string; sourceRoot: string; sentinelConfigPath: string };
210
+ } else {
211
+ log.info(
212
+ 'Standalone Nest.js project detected. Default Application defined in your "main.ts" file will be used.',
213
+ );
214
+ }
215
+
216
+ log.info(
217
+ `Sentinel CLI will configure Sentinel for application "${selectedProject.name}" in (${selectedProject.sourceRoot})`,
218
+ );
219
+
220
+ try {
221
+ fs.mkdirSync(selectedProject.sentinelConfigPath + '/resources', {
222
+ recursive: true,
223
+ });
224
+ fs.writeFileSync(
225
+ selectedProject.sentinelConfigPath + '/sentinel.config.json',
226
+ JSON.stringify(selectedProject, null, 2),
227
+ );
228
+ fs.writeFileSync(
229
+ selectedProject.sentinelConfigPath + '/resources/tsconfig.json',
230
+ JSON.stringify(tsconfig, null, 2),
231
+ );
232
+ log.success(
233
+ `Sentinel Configuration file has been created in ${selectedProject.sentinelConfigPath + '/sentinel.config.json'}`,
234
+ );
235
+ } catch (err) {
236
+ console.log(err);
237
+ log.error(err);
238
+ exit(-1);
239
+ }
240
+
241
+ log.success('Next Steps');
242
+ log.message(
243
+ `You may now run${nestConfig.projects ? ` "cd apps/${selectedProject.name}" then ` : ' '}"npx sentinel resource <name>" to create a Secured Resource.`,
244
+ );
245
+ log.message(
246
+ 'Afterwards, edit the generated file with your own list of Actions',
247
+ );
248
+ log.message(
249
+ "When you're ready, run `npx sentinel commit` to commit all of your Secured Resources to the database.",
250
+ );
251
+ };
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+ const { Sequelize } = require('sequelize');
3
+
4
+ async function up({ context: queryInterface }) {
5
+ await queryInterface.createTable('PermissionKeys', {
6
+ id: {
7
+ allowNull: false,
8
+ primaryKey: true,
9
+ type: Sequelize.STRING,
10
+ },
11
+ resource: {
12
+ type: Sequelize.STRING,
13
+ },
14
+ action: {
15
+ type: Sequelize.STRING,
16
+ },
17
+ createdAt: {
18
+ allowNull: false,
19
+ type: Sequelize.DATE,
20
+ },
21
+ updatedAt: {
22
+ allowNull: false,
23
+ type: Sequelize.DATE,
24
+ },
25
+ });
26
+ }
27
+
28
+ async function down({ context: queryInterface }) {
29
+ await queryInterface.dropTable('PermissionKeys');
30
+ }
31
+
32
+ module.exports = { up, down };
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+ const { Sequelize } = require('sequelize');
3
+ /** @type {import('sequelize-cli').Migration} */
4
+ module.exports = {
5
+ async up({ context: queryInterface }) {
6
+ await queryInterface.createTable('Roles', {
7
+ id: {
8
+ allowNull: false,
9
+ autoIncrement: true,
10
+ primaryKey: true,
11
+ type: Sequelize.INTEGER,
12
+ },
13
+ name: {
14
+ type: Sequelize.STRING,
15
+ },
16
+ createdAt: {
17
+ allowNull: false,
18
+ type: Sequelize.DATE,
19
+ },
20
+ updatedAt: {
21
+ allowNull: false,
22
+ type: Sequelize.DATE,
23
+ },
24
+ });
25
+ },
26
+ async down({ context: queryInterface }) {
27
+ await queryInterface.dropTable('Roles');
28
+ },
29
+ };
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+ const { Sequelize } = require('sequelize');
3
+ /** @type {import('sequelize-cli').Migration} */
4
+ module.exports = {
5
+ async up({ context: queryInterface }) {
6
+ await queryInterface.createTable('RoleHasPermissions', {
7
+ id: {
8
+ allowNull: false,
9
+ autoIncrement: true,
10
+ primaryKey: true,
11
+ type: Sequelize.INTEGER,
12
+ },
13
+ role: {
14
+ type: Sequelize.INTEGER,
15
+ references: {
16
+ model: {
17
+ tableName: 'Roles',
18
+ },
19
+ key: 'id',
20
+ },
21
+ allowNull: false,
22
+ },
23
+ permission: {
24
+ type: Sequelize.STRING,
25
+ references: {
26
+ model: {
27
+ tableName: 'PermissionKeys',
28
+ },
29
+ key: 'id',
30
+ },
31
+ allowNull: false,
32
+ },
33
+ createdAt: {
34
+ allowNull: false,
35
+ type: Sequelize.DATE,
36
+ },
37
+ updatedAt: {
38
+ allowNull: false,
39
+ type: Sequelize.DATE,
40
+ },
41
+ });
42
+ },
43
+ async down({ context: queryInterface }) {
44
+ await queryInterface.dropTable('RoleHasPermissions');
45
+ },
46
+ };
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+ const { Sequelize } = require('sequelize');
3
+ /** @type {import('sequelize-cli').Migration} */
4
+ module.exports = {
5
+ async up({ context: queryInterface }) {
6
+ await queryInterface.createTable('ModelHasRoles', {
7
+ id: {
8
+ allowNull: false,
9
+ autoIncrement: true,
10
+ primaryKey: true,
11
+ type: Sequelize.INTEGER,
12
+ },
13
+ role: {
14
+ type: Sequelize.INTEGER,
15
+ references: {
16
+ model: {
17
+ tableName: 'Roles',
18
+ },
19
+ key: 'id',
20
+ },
21
+ allowNull: false,
22
+ },
23
+ model: {
24
+ type: Sequelize.INTEGER,
25
+ references: {
26
+ model: {
27
+ tableName: process.env.SENTINEL_MODEL_REFERENCE_TABLE || 'Roles',
28
+ },
29
+ key: process.env.SENTINEL_MODEL_REFERENCE_KEY || 'id',
30
+ },
31
+ allowNull: false,
32
+ },
33
+ createdAt: {
34
+ allowNull: false,
35
+ type: Sequelize.DATE,
36
+ },
37
+ updatedAt: {
38
+ allowNull: false,
39
+ type: Sequelize.DATE,
40
+ },
41
+ });
42
+ },
43
+ async down({ context: queryInterface }) {
44
+ await queryInterface.dropTable('ModelHasRoles');
45
+ },
46
+ };
@@ -0,0 +1,107 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
2
+ import { intro, log } from '@clack/prompts';
3
+ import { SentinelConfig } from '@models/SentinelConfig';
4
+ import { SecuredResource } from '@models/SecuredResource';
5
+ import { Project } from 'ts-morph';
6
+ import fs from 'fs';
7
+ import { exit } from 'process';
8
+
9
+ export const resource = async (name: string, manage: boolean) => {
10
+ intro('Sentinel CLI');
11
+ log.info('Resource - Create a new Secured Resource');
12
+
13
+ let sentinelConfig: SentinelConfig = {
14
+ name: '',
15
+ sourceRoot: '',
16
+ sentinelConfigPath: '',
17
+ };
18
+ if (!fs.existsSync(process.cwd() + '\\src\\sentinel\\sentinel.config.json')) {
19
+ log.error(
20
+ `"sentinel.config.json" was not found in (${process.cwd() + '\\src\\sentinel'}). Have you initialized Sentinel using "npx sentinel init"?`,
21
+ );
22
+ }
23
+ try {
24
+ sentinelConfig = (
25
+ await import(
26
+ 'file://' + process.cwd() + '\\src\\sentinel\\sentinel.config.json',
27
+ {
28
+ with: { type: 'json' },
29
+ }
30
+ )
31
+ ).default as SentinelConfig;
32
+ } catch (error) {
33
+ console.log(error);
34
+ exit(-1);
35
+ }
36
+
37
+ const project = new Project();
38
+ const resourceFile = project.createSourceFile(
39
+ process.cwd() + '\\src\\sentinel\\resources\\' + name + '.ts',
40
+ `export class ${name} {}`,
41
+ );
42
+ resourceFile.addImportDeclaration({
43
+ moduleSpecifier: '@luminotion/sentinel',
44
+ namedImports: [
45
+ { name: 'SecuredResource' },
46
+ { name: 'SecuredResourceAction' },
47
+ ],
48
+ });
49
+ resourceFile.getClass(name)?.setExtends('SecuredResource');
50
+ resourceFile.getClass(name)?.addProperties([
51
+ { name: 'static name', initializer: `"${name}"` },
52
+ {
53
+ name: 'static actions: Record<string, SecuredResourceAction>',
54
+ initializer: (writer) => {
55
+ if (manage) {
56
+ writer.block(() => {
57
+ writer.writeLine(`"manage": `).block(() => {
58
+ writer.writeLine(
59
+ 'description: "Allows all Actions in this Secured Resource"',
60
+ );
61
+ });
62
+ });
63
+ } else writer.writeLine('{}');
64
+ },
65
+ },
66
+ ]);
67
+ resourceFile.getClass(name)?.addStaticBlock({
68
+ statements: [
69
+ `Object.keys(${name}.actions).forEach((key) => {
70
+ ${name}.actions[key].source = '${name}';
71
+ });`,
72
+ ],
73
+ });
74
+ resourceFile.saveSync();
75
+
76
+ const newResource: SecuredResource = {
77
+ name,
78
+ actions: manage
79
+ ? {
80
+ manage: {
81
+ description: 'Allows all Actions in this Secured Resource.',
82
+ },
83
+ }
84
+ : {},
85
+ };
86
+ if (sentinelConfig.resources) {
87
+ const matchIdx = sentinelConfig.resources.findIndex(
88
+ (res) => res.name == name,
89
+ );
90
+ if (matchIdx != -1) {
91
+ sentinelConfig.resources[matchIdx] = newResource;
92
+ } else sentinelConfig.resources.push(newResource);
93
+ } else sentinelConfig.resources = [newResource];
94
+
95
+ log.success(
96
+ `New Secured Resource created in ${sentinelConfig.sentinelConfigPath}\\${name}`,
97
+ );
98
+
99
+ fs.writeFileSync(
100
+ process.cwd() + '\\src\\sentinel\\sentinel.config.json',
101
+ JSON.stringify(sentinelConfig, null, 2),
102
+ );
103
+
104
+ log.success(
105
+ `${name} is ready to be committed. Edit the file with your Actions, then run "npx sentinel commit" to refresh all Secured Resource definitions.`,
106
+ );
107
+ };
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ import { intro } from '@clack/prompts';
3
+ import { Command, Option } from 'commander';
4
+ import { init } from './init';
5
+ import { resource } from './resource';
6
+ import { commit } from './commit';
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name('Sentinel CLI')
12
+ .description('RBAC implementation for Nest.js powered by CASL and Postgres')
13
+ .version('0.0.1');
14
+
15
+ program
16
+ .command('init')
17
+ .description(
18
+ 'Prepares your Nest.js project for Sentinel. Run this command where your `nest-cli.json` file is located.',
19
+ )
20
+ .requiredOption(
21
+ '--model-table <model>',
22
+ 'Determines which table to reference when logging which Roles a Model has been given with. This will often be your Users or Clients table.',
23
+ )
24
+ .option(
25
+ '--nx-read-from <path>',
26
+ 'Determines the path to read Nest projects from in an Nx monorepo. (Ie. apps/backend)',
27
+ )
28
+ .option(
29
+ '--model-key <key>',
30
+ 'The primary key of your Model Table. Defaults to "id".',
31
+ 'id',
32
+ )
33
+ .option(
34
+ '--env-from <path>',
35
+ 'Optionally configure the CLI to load required ENV values from a file.',
36
+ )
37
+ .option(
38
+ '--skip-migration',
39
+ 'Configures the CLI to skip running the migration files required by Sentinel',
40
+ )
41
+ .option('--verbose', 'If true, Sequelize logs are printed to the console.')
42
+ .action((opts) => {
43
+ init(
44
+ opts.modelTable,
45
+ opts.modelKey,
46
+ opts.nxReadFrom,
47
+ opts.envFrom,
48
+ opts.skipMigration,
49
+ opts.verbose,
50
+ );
51
+ });
52
+
53
+ program
54
+ .command('resource')
55
+ .description(
56
+ 'Creates a new Secured Resource without pushing it to your database. Afterwards, you can add Actions by editing the generated file, then run `npx sentinel commit` to save them to your database.',
57
+ )
58
+ .argument('<resource>', 'Name of the Secured Resource')
59
+ .option(
60
+ '--no-manage',
61
+ 'Skips generating the "manage" Action in the new Secured Resource',
62
+ )
63
+ .action((name, opts) => {
64
+ resource(name, opts.manage);
65
+ });
66
+
67
+ program
68
+ .command('commit')
69
+ .description(
70
+ 'Commits all new and modified Secured Resources to your database.',
71
+ )
72
+ .option(
73
+ '--env-from <path>',
74
+ 'Optionally configure the CLI to load required ENV values from a file.',
75
+ )
76
+ .option(
77
+ '--review',
78
+ 'Generate a list of all detected Secured Resources and their Actions.',
79
+ )
80
+ .option(
81
+ '--refresh',
82
+ 'Refreshes the local Secured Resource definitions in the "sentinel.config.json" without committing to the database.',
83
+ )
84
+ .option(
85
+ '--skip-prompts',
86
+ 'Skips all confirmation prompts and directly performs operations.',
87
+ )
88
+ .action((opts) => {
89
+ commit(opts.envFrom);
90
+ });
91
+
92
+ program
93
+ .command('clean')
94
+ .description(
95
+ 'Add flags to remove configurations and/or drop tables related to Sentinel. Does not modify any of your Nest.js code.',
96
+ )
97
+ .option(
98
+ '--migration',
99
+ 'Resets the internal SequelizeMeta table to allow migrations to re-run again.',
100
+ )
101
+ .option(
102
+ '--tables',
103
+ 'Drops all tables related to Sentinel (Roles, RoleHasPermissions, PermissionKeys)',
104
+ )
105
+ .option(
106
+ '--role-permissions <name>',
107
+ 'Removes all Permissions assigned to a certain Role.',
108
+ )
109
+ .option('--role <name>', 'Deletes a Role completely.')
110
+ .option(
111
+ '--all',
112
+ 'Removes all Roles, PermissionKeys, and role assignments (RoleHasPermissions)',
113
+ );
114
+
115
+ program.parse();
@@ -0,0 +1,30 @@
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
+ "allowJs": true,
25
+ "paths": {
26
+ "@models/*": ["../models/*"],
27
+ "@models/sequelize/*": ["../models/sequelize/*"]
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,4 @@
1
+ import { SecuredResourceAction } from './main';
2
+ import { Reflector } from '@nestjs/core';
3
+
4
+ export const Can = Reflector.createDecorator<SecuredResourceAction[]>();