@rapidd/build 1.0.4 → 1.0.6
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/package.json
CHANGED
package/src/commands/build.js
CHANGED
|
@@ -12,7 +12,7 @@ const { generateAllRoutes } = require('../generators/routeGenerator');
|
|
|
12
12
|
*/
|
|
13
13
|
function generateBaseModelFile(modelJsPath) {
|
|
14
14
|
const content = `const { QueryBuilder, prisma } = require("./QueryBuilder");
|
|
15
|
-
const {rls} = require('
|
|
15
|
+
const {rls} = require('../rapidd/rapidd');
|
|
16
16
|
const {ErrorResponse} = require('./Api');
|
|
17
17
|
|
|
18
18
|
class Model {
|
|
@@ -33,11 +33,11 @@ class Model {
|
|
|
33
33
|
_filter = (q) => this.queryBuilder.filter(q);
|
|
34
34
|
_include = (include) => this.queryBuilder.include(include, this.user);
|
|
35
35
|
// RLS METHODS
|
|
36
|
-
_canCreate = () => this.rls
|
|
37
|
-
_hasAccess = (data) => this.rls
|
|
38
|
-
_getAccessFilter = () => this.rls
|
|
39
|
-
_getUpdateFilter = () => this.rls
|
|
40
|
-
_getDeleteFilter = () => this.rls
|
|
36
|
+
_canCreate = () => this.rls?.canCreate?.(this.user);
|
|
37
|
+
_hasAccess = (data) => this.rls?.hasAccess?.(data, this.user) || false;
|
|
38
|
+
_getAccessFilter = () => this.rls?.getAccessFilter?.(this.user);
|
|
39
|
+
_getUpdateFilter = () => this.rls?.getUpdateFilter?.(this.user);
|
|
40
|
+
_getDeleteFilter = () => this.rls?.getDeleteFilter?.(this.user);
|
|
41
41
|
_omit = () => this.queryBuilder.omit(this.user);
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -150,33 +150,21 @@ class Model {
|
|
|
150
150
|
throw new ErrorResponse(403, "no_permission_to_update");
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
// GET DATA FIRST (also checks read access)
|
|
154
|
-
const current_data = await this._get(id, "ALL");
|
|
155
|
-
|
|
156
|
-
// If updateFilter is not empty, verify the record matches the filter
|
|
157
|
-
if (Object.keys(updateFilter).length > 0) {
|
|
158
|
-
const canUpdate = await this.prisma.findUnique({
|
|
159
|
-
'where': {
|
|
160
|
-
'id': id,
|
|
161
|
-
...updateFilter
|
|
162
|
-
},
|
|
163
|
-
'select': { 'id': true }
|
|
164
|
-
});
|
|
165
|
-
if (!canUpdate) {
|
|
166
|
-
throw new ErrorResponse(403, "no_permission_to_update");
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
153
|
// VALIDATE PASSED FIELDS AND RELATIONSHIPS
|
|
171
154
|
this.queryBuilder.update(id, data, this.user_id);
|
|
172
|
-
|
|
155
|
+
const response = await this.prisma.update({
|
|
173
156
|
'where': {
|
|
174
|
-
'id': id
|
|
157
|
+
'id': id,
|
|
158
|
+
...updateFilter
|
|
175
159
|
},
|
|
176
160
|
'data': data,
|
|
177
161
|
'include': this.include('ALL'),
|
|
178
162
|
...options
|
|
179
163
|
});
|
|
164
|
+
if(response){
|
|
165
|
+
return response;
|
|
166
|
+
}
|
|
167
|
+
throw new ErrorResponse(403, "no_permission");
|
|
180
168
|
}
|
|
181
169
|
|
|
182
170
|
/**
|
|
@@ -203,30 +191,18 @@ class Model {
|
|
|
203
191
|
throw new ErrorResponse(403, "no_permission_to_delete");
|
|
204
192
|
}
|
|
205
193
|
|
|
206
|
-
|
|
207
|
-
const current_data = await this._get(id);
|
|
208
|
-
|
|
209
|
-
// If deleteFilter is not empty, verify the record matches the filter
|
|
210
|
-
if (Object.keys(deleteFilter).length > 0) {
|
|
211
|
-
const canDelete = await this.prisma.findUnique({
|
|
212
|
-
'where': {
|
|
213
|
-
'id': id,
|
|
214
|
-
...deleteFilter
|
|
215
|
-
},
|
|
216
|
-
'select': { 'id': true }
|
|
217
|
-
});
|
|
218
|
-
if (!canDelete) {
|
|
219
|
-
throw new ErrorResponse(403, "no_permission_to_delete");
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return await this.prisma.delete({
|
|
194
|
+
const response = await this.prisma.delete({
|
|
224
195
|
'where': {
|
|
225
|
-
id: parseInt(id)
|
|
196
|
+
id: parseInt(id),
|
|
197
|
+
...deleteFilter
|
|
226
198
|
},
|
|
227
199
|
'select': this.select(),
|
|
228
200
|
...options
|
|
229
201
|
});
|
|
202
|
+
if(response){
|
|
203
|
+
return response;
|
|
204
|
+
}
|
|
205
|
+
throw new ErrorResponse(403, "no_permission");
|
|
230
206
|
}
|
|
231
207
|
|
|
232
208
|
/**
|
|
@@ -306,7 +282,7 @@ class Model {
|
|
|
306
282
|
*/
|
|
307
283
|
getAccessFilter(){
|
|
308
284
|
const filter = this._getAccessFilter()
|
|
309
|
-
if(this.user.role == "application" || filter
|
|
285
|
+
if(this.user.role == "application" || filter === true){
|
|
310
286
|
return {};
|
|
311
287
|
}
|
|
312
288
|
return this._getAccessFilter();
|
|
@@ -336,7 +312,7 @@ class Model {
|
|
|
336
312
|
*/
|
|
337
313
|
getUpdateFilter(){
|
|
338
314
|
const filter = this._getUpdateFilter();
|
|
339
|
-
if(this.user.role == "application" || filter
|
|
315
|
+
if(this.user.role == "application" || filter === true){
|
|
340
316
|
return {};
|
|
341
317
|
}
|
|
342
318
|
return filter;
|
|
@@ -348,7 +324,7 @@ class Model {
|
|
|
348
324
|
*/
|
|
349
325
|
getDeleteFilter(){
|
|
350
326
|
const filter = this._getDeleteFilter();
|
|
351
|
-
if(this.user.role == "application" || filter
|
|
327
|
+
if(this.user.role == "application" || filter === true){
|
|
352
328
|
return {};
|
|
353
329
|
}
|
|
354
330
|
return filter;
|
|
@@ -381,12 +357,96 @@ module.exports = {Model, QueryBuilder, prisma};
|
|
|
381
357
|
* Generate rapidd/rapidd.js file
|
|
382
358
|
*/
|
|
383
359
|
function generateRapiddFile(rapiddJsPath) {
|
|
384
|
-
const content = `const { PrismaClient
|
|
360
|
+
const content = `const { PrismaClient } = require('../prisma/client');
|
|
361
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
385
362
|
const rls = require('./rls');
|
|
386
363
|
|
|
387
|
-
|
|
364
|
+
// Request Context Storage
|
|
365
|
+
const requestContext = new AsyncLocalStorage();
|
|
366
|
+
|
|
367
|
+
// RLS Configuration aus Environment Variables
|
|
368
|
+
const RLS_CONFIG = {
|
|
369
|
+
namespace: process.env.RLS_NAMESPACE || 'app',
|
|
370
|
+
userId: process.env.RLS_USER_ID || 'current_user_id',
|
|
371
|
+
userRole: process.env.RLS_USER_ROLE || 'current_user_role',
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// Basis Prisma Client
|
|
375
|
+
const basePrisma = new PrismaClient({
|
|
376
|
+
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Setze RLS Session Variables in PostgreSQL
|
|
381
|
+
*/
|
|
382
|
+
async function setRLSVariables(tx, userId, userRole) {
|
|
383
|
+
const namespace = RLS_CONFIG.namespace;
|
|
384
|
+
const userIdVar = RLS_CONFIG.userId;
|
|
385
|
+
const userRoleVar = RLS_CONFIG.userRole;
|
|
386
|
+
|
|
387
|
+
await tx.$executeRawUnsafe(\`SET LOCAL \${namespace}.\${userIdVar} = '\${userId}'\`);
|
|
388
|
+
await tx.$executeRawUnsafe(\`SET LOCAL \${namespace}.\${userRoleVar} = '\${userRole}'\`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Erweiterter Prisma mit automatischer RLS
|
|
392
|
+
const prisma = basePrisma.$extends({
|
|
393
|
+
query: {
|
|
394
|
+
async $allOperations({ args, query }) {
|
|
395
|
+
const context = requestContext.getStore();
|
|
396
|
+
|
|
397
|
+
// Kein Context = keine RLS (z.B. System-Operationen)
|
|
398
|
+
if (!context?.userId || !context?.userRole) {
|
|
399
|
+
return query(args);
|
|
400
|
+
}
|
|
388
401
|
|
|
389
|
-
|
|
402
|
+
const { userId, userRole } = context;
|
|
403
|
+
|
|
404
|
+
// Query in Transaction mit RLS ausführen
|
|
405
|
+
return basePrisma.$transaction(async (tx) => {
|
|
406
|
+
// Session-Variablen setzen
|
|
407
|
+
await setRLSVariables(tx, userId, userRole);
|
|
408
|
+
|
|
409
|
+
// Original Query ausführen
|
|
410
|
+
return query(args);
|
|
411
|
+
});
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Helper: System-Operationen ohne RLS (für Cron-Jobs, etc.)
|
|
418
|
+
*/
|
|
419
|
+
async function withSystemAccess(callback) {
|
|
420
|
+
return requestContext.run(
|
|
421
|
+
{ userId: 'system', userRole: 'ADMIN' },
|
|
422
|
+
callback
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Helper: Als bestimmter User ausführen (für Tests)
|
|
428
|
+
*/
|
|
429
|
+
async function withUser(userId, userRole, callback) {
|
|
430
|
+
return requestContext.run({ userId, userRole }, callback);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Helper: Hole RLS Config (für SQL Generation)
|
|
435
|
+
*/
|
|
436
|
+
function getRLSConfig() {
|
|
437
|
+
return RLS_CONFIG;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
module.exports = {
|
|
441
|
+
prisma,
|
|
442
|
+
PrismaClient,
|
|
443
|
+
requestContext,
|
|
444
|
+
withSystemAccess,
|
|
445
|
+
withUser,
|
|
446
|
+
getRLSConfig,
|
|
447
|
+
setRLSVariables,
|
|
448
|
+
rls
|
|
449
|
+
};
|
|
390
450
|
`;
|
|
391
451
|
|
|
392
452
|
// Ensure rapidd directory exists
|
|
@@ -725,7 +785,11 @@ async function buildModels(options) {
|
|
|
725
785
|
// Parse datasource from Prisma schema to get database URL
|
|
726
786
|
const datasource = parseDatasource(schemaPath);
|
|
727
787
|
|
|
728
|
-
|
|
788
|
+
// For non-PostgreSQL databases (MySQL, SQLite, etc.), generate permissive RLS
|
|
789
|
+
if (!datasource.isPostgreSQL) {
|
|
790
|
+
console.log(`${datasource.provider || 'Non-PostgreSQL'} database detected - generating permissive RLS...`);
|
|
791
|
+
await generateRLS(models, rlsPath, null, false, options.userTable, relationships, options.debug);
|
|
792
|
+
} else if (options.model) {
|
|
729
793
|
// Update only specific model in rls.js
|
|
730
794
|
await updateRLSForModel(filteredModels, models, rlsPath, datasource, options.userTable, relationships, options.debug);
|
|
731
795
|
} else {
|
|
@@ -742,10 +806,9 @@ async function buildModels(options) {
|
|
|
742
806
|
}
|
|
743
807
|
} catch (error) {
|
|
744
808
|
console.error('Failed to generate RLS:', error.message);
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
}
|
|
809
|
+
console.log('Generating permissive RLS fallback...');
|
|
810
|
+
// Pass null for URL and false for isPostgreSQL to skip database connection
|
|
811
|
+
await generateRLS(models, rlsPath, null, false, options.userTable, relationships, options.debug);
|
|
749
812
|
}
|
|
750
813
|
}
|
|
751
814
|
|
|
@@ -13,12 +13,12 @@ function generateRouteFile(modelName) {
|
|
|
13
13
|
const {${className}, QueryBuilder, prisma} = require('../../../src/Model/${className}');
|
|
14
14
|
|
|
15
15
|
router.all('*', async (req, res, next) => {
|
|
16
|
-
if(req.
|
|
16
|
+
if(req.user){
|
|
17
17
|
req.${className} = new ${className}({'user': req.user});
|
|
18
18
|
next();
|
|
19
19
|
}
|
|
20
20
|
else{
|
|
21
|
-
return res.sendError(401,
|
|
21
|
+
return res.sendError(401, "no_valid_session");
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -82,8 +82,7 @@ router.patch('/:id', async function(req, res) {
|
|
|
82
82
|
router.delete('/:id', async (req, res)=>{
|
|
83
83
|
try{
|
|
84
84
|
await req.${className}.delete(req.params.id);
|
|
85
|
-
|
|
86
|
-
return res.sendResponse(200, message);
|
|
85
|
+
return res.sendResponse(200, "object_deleted_successfully", {modelName: "${className}"});
|
|
87
86
|
}
|
|
88
87
|
catch(error){
|
|
89
88
|
const response = QueryBuilder.errorHandler(error);
|
|
@@ -61,10 +61,19 @@ function parseDatasource(schemaPath) {
|
|
|
61
61
|
isPostgreSQL = url.startsWith('postgresql://') || url.startsWith('postgres://');
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// Explicitly detect MySQL to avoid false PostgreSQL detection
|
|
65
|
+
const isMySQL = provider === 'mysql' || (url && url.startsWith('mysql://'));
|
|
66
|
+
|
|
67
|
+
// If it's MySQL, ensure isPostgreSQL is false
|
|
68
|
+
if (isMySQL) {
|
|
69
|
+
isPostgreSQL = false;
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
return {
|
|
65
73
|
provider,
|
|
66
74
|
url,
|
|
67
|
-
isPostgreSQL
|
|
75
|
+
isPostgreSQL,
|
|
76
|
+
isMySQL
|
|
68
77
|
};
|
|
69
78
|
}
|
|
70
79
|
|