@currentjs/gen 0.5.6 → 0.5.7
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/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,11 @@ export declare class ControllerGenerator {
|
|
|
11
11
|
* Check if auth config includes owner permission
|
|
12
12
|
*/
|
|
13
13
|
private hasOwnerAuth;
|
|
14
|
+
/**
|
|
15
|
+
* Determine which HTTP error classes from @currentjs/router are needed
|
|
16
|
+
* based on the auth configs of all endpoints in a controller.
|
|
17
|
+
*/
|
|
18
|
+
private getNeededHttpErrorImports;
|
|
14
19
|
/**
|
|
15
20
|
* Generate pre-fetch authentication/authorization check code.
|
|
16
21
|
* This runs before fetching the entity and validates authentication and role-based access.
|
|
@@ -78,6 +78,28 @@ class ControllerGenerator {
|
|
|
78
78
|
const roles = this.normalizeAuth(auth);
|
|
79
79
|
return roles.includes(constants_1.AUTH_ROLES.OWNER);
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Determine which HTTP error classes from @currentjs/router are needed
|
|
83
|
+
* based on the auth configs of all endpoints in a controller.
|
|
84
|
+
*/
|
|
85
|
+
getNeededHttpErrorImports(auths) {
|
|
86
|
+
const needed = new Set();
|
|
87
|
+
for (const auth of auths) {
|
|
88
|
+
const roles = this.normalizeAuth(auth);
|
|
89
|
+
if (roles.length === 0 || (roles.length === 1 && roles[0] === constants_1.AUTH_ROLES.ALL))
|
|
90
|
+
continue;
|
|
91
|
+
needed.add('UnauthorizedError');
|
|
92
|
+
const roleChecks = roles.filter(r => r !== constants_1.AUTH_ROLES.OWNER && r !== constants_1.AUTH_ROLES.ALL && r !== constants_1.AUTH_ROLES.AUTHENTICATED);
|
|
93
|
+
if (roleChecks.length > 0) {
|
|
94
|
+
needed.add('ForbiddenError');
|
|
95
|
+
}
|
|
96
|
+
if (roles.includes(constants_1.AUTH_ROLES.OWNER)) {
|
|
97
|
+
needed.add('ForbiddenError');
|
|
98
|
+
needed.add('NotFoundError');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return Array.from(needed).sort();
|
|
102
|
+
}
|
|
81
103
|
/**
|
|
82
104
|
* Generate pre-fetch authentication/authorization check code.
|
|
83
105
|
* This runs before fetching the entity and validates authentication and role-based access.
|
|
@@ -92,14 +114,14 @@ class ControllerGenerator {
|
|
|
92
114
|
// If only 'authenticated' is specified
|
|
93
115
|
if (roles.length === 1 && roles[0] === constants_1.AUTH_ROLES.AUTHENTICATED) {
|
|
94
116
|
return `if (!context.request.user) {
|
|
95
|
-
throw new
|
|
117
|
+
throw new UnauthorizedError('${constants_1.AUTH_ERRORS.REQUIRED}');
|
|
96
118
|
}`;
|
|
97
119
|
}
|
|
98
120
|
// If only 'owner' is specified - just require authentication here
|
|
99
121
|
// (owner check happens post-fetch)
|
|
100
122
|
if (roles.length === 1 && roles[0] === constants_1.AUTH_ROLES.OWNER) {
|
|
101
123
|
return `if (!context.request.user) {
|
|
102
|
-
throw new
|
|
124
|
+
throw new UnauthorizedError('${constants_1.AUTH_ERRORS.REQUIRED}');
|
|
103
125
|
}`;
|
|
104
126
|
}
|
|
105
127
|
// Filter out 'owner' and 'all' for role checks (owner is checked post-fetch)
|
|
@@ -111,16 +133,16 @@ class ControllerGenerator {
|
|
|
111
133
|
if (roleChecks.length === 0) {
|
|
112
134
|
// Only owner (and maybe authenticated) - just require auth
|
|
113
135
|
return `if (!context.request.user) {
|
|
114
|
-
throw new
|
|
136
|
+
throw new UnauthorizedError('${constants_1.AUTH_ERRORS.REQUIRED}');
|
|
115
137
|
}`;
|
|
116
138
|
}
|
|
117
139
|
if (roleChecks.length === 1 && !hasOwner) {
|
|
118
140
|
// Single role check
|
|
119
141
|
return `if (!context.request.user) {
|
|
120
|
-
throw new
|
|
142
|
+
throw new UnauthorizedError('${constants_1.AUTH_ERRORS.REQUIRED}');
|
|
121
143
|
}
|
|
122
144
|
if (context.request.user.role !== '${roleChecks[0]}') {
|
|
123
|
-
throw new
|
|
145
|
+
throw new ForbiddenError('${constants_1.AUTH_ERRORS.INSUFFICIENT_PERMISSIONS}: ${roleChecks[0]} role required');
|
|
124
146
|
}`;
|
|
125
147
|
}
|
|
126
148
|
// Multiple roles OR owner - use OR logic
|
|
@@ -129,22 +151,22 @@ class ControllerGenerator {
|
|
|
129
151
|
if (hasOwner) {
|
|
130
152
|
// With owner: require auth, role check will be combined with owner check post-fetch
|
|
131
153
|
return `if (!context.request.user) {
|
|
132
|
-
throw new
|
|
154
|
+
throw new UnauthorizedError('${constants_1.AUTH_ERRORS.REQUIRED}');
|
|
133
155
|
}`;
|
|
134
156
|
}
|
|
135
157
|
// Multiple roles without owner - check if user has ANY of the roles
|
|
136
158
|
const roleConditions = roleChecks.map(r => `context.request.user.role === '${r}'`).join(' || ');
|
|
137
159
|
return `if (!context.request.user) {
|
|
138
|
-
throw new
|
|
160
|
+
throw new UnauthorizedError('${constants_1.AUTH_ERRORS.REQUIRED}');
|
|
139
161
|
}
|
|
140
162
|
if (!(${roleConditions})) {
|
|
141
|
-
throw new
|
|
163
|
+
throw new ForbiddenError('${constants_1.AUTH_ERRORS.INSUFFICIENT_PERMISSIONS}: one of [${roleChecks.join(', ')}] role required');
|
|
142
164
|
}`;
|
|
143
165
|
}
|
|
144
166
|
// Only 'authenticated' in the mix
|
|
145
167
|
if (hasAuthenticated) {
|
|
146
168
|
return `if (!context.request.user) {
|
|
147
|
-
throw new
|
|
169
|
+
throw new UnauthorizedError('${constants_1.AUTH_ERRORS.REQUIRED}');
|
|
148
170
|
}`;
|
|
149
171
|
}
|
|
150
172
|
return '';
|
|
@@ -168,10 +190,10 @@ class ControllerGenerator {
|
|
|
168
190
|
// Owner validation (post-fetch for reads, via parent)
|
|
169
191
|
const resourceOwnerId = await this.${useCaseVar}.getResourceOwner(${resultVar}.id);
|
|
170
192
|
if (resourceOwnerId === null) {
|
|
171
|
-
throw new
|
|
193
|
+
throw new NotFoundError('Resource not found');
|
|
172
194
|
}
|
|
173
195
|
if (resourceOwnerId !== context.request.user?.id) {
|
|
174
|
-
throw new
|
|
196
|
+
throw new ForbiddenError('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
|
|
175
197
|
}`;
|
|
176
198
|
}
|
|
177
199
|
const bypassConditions = bypassRoles.map(r => `context.request.user?.role === '${r}'`).join(' || ');
|
|
@@ -179,12 +201,12 @@ class ControllerGenerator {
|
|
|
179
201
|
// Owner validation (post-fetch for reads, via parent, bypassed for: ${bypassRoles.join(', ')})
|
|
180
202
|
const resourceOwnerId = await this.${useCaseVar}.getResourceOwner(${resultVar}.id);
|
|
181
203
|
if (resourceOwnerId === null) {
|
|
182
|
-
throw new
|
|
204
|
+
throw new NotFoundError('Resource not found');
|
|
183
205
|
}
|
|
184
206
|
const isOwner = resourceOwnerId === context.request.user?.id;
|
|
185
207
|
const hasPrivilegedRole = ${bypassConditions};
|
|
186
208
|
if (!isOwner && !hasPrivilegedRole) {
|
|
187
|
-
throw new
|
|
209
|
+
throw new ForbiddenError('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
|
|
188
210
|
}`;
|
|
189
211
|
}
|
|
190
212
|
// Root entity: result has ownerId
|
|
@@ -192,7 +214,7 @@ class ControllerGenerator {
|
|
|
192
214
|
return `
|
|
193
215
|
// Owner validation (post-fetch for reads)
|
|
194
216
|
if (${resultVar}.ownerId !== context.request.user?.id) {
|
|
195
|
-
throw new
|
|
217
|
+
throw new ForbiddenError('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
|
|
196
218
|
}`;
|
|
197
219
|
}
|
|
198
220
|
const bypassConditions = bypassRoles.map(r => `context.request.user?.role === '${r}'`).join(' || ');
|
|
@@ -201,7 +223,7 @@ class ControllerGenerator {
|
|
|
201
223
|
const isOwner = ${resultVar}.ownerId === context.request.user?.id;
|
|
202
224
|
const hasPrivilegedRole = ${bypassConditions};
|
|
203
225
|
if (!isOwner && !hasPrivilegedRole) {
|
|
204
|
-
throw new
|
|
226
|
+
throw new ForbiddenError('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
|
|
205
227
|
}`;
|
|
206
228
|
}
|
|
207
229
|
/**
|
|
@@ -225,10 +247,10 @@ class ControllerGenerator {
|
|
|
225
247
|
// Pre-mutation owner validation
|
|
226
248
|
const resourceOwnerId = await this.${useCaseVar}.getResourceOwner(input.id);
|
|
227
249
|
if (resourceOwnerId === null) {
|
|
228
|
-
throw new
|
|
250
|
+
throw new NotFoundError('Resource not found');
|
|
229
251
|
}
|
|
230
252
|
if (resourceOwnerId !== context.request.user?.id) {
|
|
231
|
-
throw new
|
|
253
|
+
throw new ForbiddenError('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
|
|
232
254
|
}
|
|
233
255
|
`;
|
|
234
256
|
}
|
|
@@ -238,12 +260,12 @@ class ControllerGenerator {
|
|
|
238
260
|
// Pre-mutation owner validation (bypassed for: ${bypassRoles.join(', ')})
|
|
239
261
|
const resourceOwnerId = await this.${useCaseVar}.getResourceOwner(input.id);
|
|
240
262
|
if (resourceOwnerId === null) {
|
|
241
|
-
throw new
|
|
263
|
+
throw new NotFoundError('Resource not found');
|
|
242
264
|
}
|
|
243
265
|
const isOwner = resourceOwnerId === context.request.user?.id;
|
|
244
266
|
const hasPrivilegedRole = ${bypassConditions};
|
|
245
267
|
if (!isOwner && !hasPrivilegedRole) {
|
|
246
|
-
throw new
|
|
268
|
+
throw new ForbiddenError('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
|
|
247
269
|
}
|
|
248
270
|
`;
|
|
249
271
|
}
|
|
@@ -559,7 +581,9 @@ class ControllerGenerator {
|
|
|
559
581
|
const constructorParams = Array.from(useCaseModels)
|
|
560
582
|
.map(model => `private ${model.toLowerCase()}UseCase: ${model}UseCase`)
|
|
561
583
|
.join(',\n ');
|
|
562
|
-
|
|
584
|
+
const errorImports = this.getNeededHttpErrorImports(sortedEndpoints.map(e => e.auth));
|
|
585
|
+
const routerImports = ['Controller', 'Get', 'Post', 'Put', 'Delete', 'type IContext', ...errorImports].join(', ');
|
|
586
|
+
return `import { ${routerImports} } from '@currentjs/router';
|
|
563
587
|
${useCaseImports}
|
|
564
588
|
${dtoImportStatements}
|
|
565
589
|
|
|
@@ -626,7 +650,9 @@ ${methods.join('\n\n')}
|
|
|
626
650
|
${constructorParams.join(',\n ')}
|
|
627
651
|
) {}`
|
|
628
652
|
: 'constructor() {}';
|
|
629
|
-
|
|
653
|
+
const errorImports = this.getNeededHttpErrorImports(sortedPages.map(p => p.auth));
|
|
654
|
+
const routerImports = ['Controller', 'Get', 'Post', 'Render', 'type IContext', ...errorImports].join(', ');
|
|
655
|
+
return `import { ${routerImports} } from '@currentjs/router';
|
|
630
656
|
${useCaseImports}
|
|
631
657
|
${serviceImports.join('\n')}
|
|
632
658
|
${dtoImportStatements}
|