@nattyjs/core 0.0.1-beta.5 → 0.0.1-beta.51
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/dist/index.cjs +194 -66
- package/dist/index.d.ts +96 -64
- package/dist/index.mjs +189 -67
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,12 @@ function route(path) {
|
|
|
27
27
|
const CONTROLLER = "controller";
|
|
28
28
|
const INVALID_VALUE = "INVALID_VALUE";
|
|
29
29
|
const BLANK = "";
|
|
30
|
+
const DENY_BY_DEFAULT = {
|
|
31
|
+
type: "https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html#deny-by-default",
|
|
32
|
+
title: "Deny-by-default",
|
|
33
|
+
description: `Zero Trust Architecture (NIST SP 800-207) \u2014 Core principle is "never trust, always verify," which translates to deny-by-default behavior. OWASP Secure Defaults Principle \u2014 \u201CSecurity should be a default setting, and everything should be locked down unless explicitly permitted.\u201D`,
|
|
34
|
+
solution: `Please implement proper authentication and authorization checks. If this API needs to be accessed anonymously, add the @anonymous() decorator above the HTTP action.`
|
|
35
|
+
};
|
|
30
36
|
|
|
31
37
|
function get(path) {
|
|
32
38
|
return function(target, propertyKey, descriptor) {
|
|
@@ -60,10 +66,13 @@ const nattyContainer = new class {
|
|
|
60
66
|
this.container = /* @__PURE__ */ new Map();
|
|
61
67
|
this.containerState = /* @__PURE__ */ new Map();
|
|
62
68
|
}
|
|
63
|
-
|
|
69
|
+
get types() {
|
|
70
|
+
return common.commonContainer.types;
|
|
71
|
+
}
|
|
72
|
+
setup(config, routes, resolver) {
|
|
64
73
|
this.config = config;
|
|
65
74
|
this.routes = routes;
|
|
66
|
-
this.
|
|
75
|
+
this.resolver = resolver;
|
|
67
76
|
}
|
|
68
77
|
getTypes() {
|
|
69
78
|
return StaticContainer.types;
|
|
@@ -113,34 +122,91 @@ const nattyContainer = new class {
|
|
|
113
122
|
}
|
|
114
123
|
}();
|
|
115
124
|
|
|
125
|
+
function startWebSchedules(schedules) {
|
|
126
|
+
if (schedules && Array.isArray(schedules)) {
|
|
127
|
+
for (const schedule of schedules)
|
|
128
|
+
startWebSchedule(schedule);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function startWebSchedule(config) {
|
|
132
|
+
if (config) {
|
|
133
|
+
const interval = setInterval(async () => {
|
|
134
|
+
try {
|
|
135
|
+
clearInterval(interval);
|
|
136
|
+
await config.scheduleFunction();
|
|
137
|
+
startWebSchedule(config);
|
|
138
|
+
} catch (ex) {
|
|
139
|
+
startWebSchedule(config);
|
|
140
|
+
}
|
|
141
|
+
}, config.interval);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
116
145
|
function init(config, appConfig) {
|
|
117
146
|
common.commonContainer.setupConfig(config);
|
|
118
147
|
common.commonContainer.setEnvTsDefinition(appConfig.envTsDefinition);
|
|
119
|
-
nattyContainer.setup(config, appConfig.routes, appConfig.
|
|
120
|
-
|
|
148
|
+
nattyContainer.setup(config, appConfig.routes, appConfig.resolver);
|
|
149
|
+
callLifeCycleEvents(config, true);
|
|
150
|
+
const result = initializeModule(config);
|
|
151
|
+
callLifeCycleEvents(config);
|
|
152
|
+
startWebSchedules(config.webSchedules);
|
|
153
|
+
return result;
|
|
121
154
|
}
|
|
122
155
|
function initializeModule(config) {
|
|
123
156
|
if (config.app) {
|
|
124
157
|
return config.app.init(config);
|
|
125
158
|
}
|
|
126
159
|
}
|
|
160
|
+
async function callLifeCycleEvents(config, isPreInit = false) {
|
|
161
|
+
if (config.lifeCycle) {
|
|
162
|
+
if (config.lifeCycle.preInit && isPreInit) {
|
|
163
|
+
const preInit = config.lifeCycle.preInit();
|
|
164
|
+
if (preInit) {
|
|
165
|
+
if (preInit.cors) {
|
|
166
|
+
if (!config.cors) {
|
|
167
|
+
const jObject = { origin: [] };
|
|
168
|
+
config.cors = jObject;
|
|
169
|
+
} else if (!config.cors.origin)
|
|
170
|
+
config.cors.origin = [];
|
|
171
|
+
if (preInit.cors.origin)
|
|
172
|
+
preInit.cors.origin.forEach((t) => {
|
|
173
|
+
config.cors?.origin.push(t);
|
|
174
|
+
});
|
|
175
|
+
if (preInit.cors.methods)
|
|
176
|
+
config.cors.methods = preInit.cors.methods;
|
|
177
|
+
if (preInit.cors.optionsSuccessStatus)
|
|
178
|
+
config.cors.optionsSuccessStatus = preInit.cors.optionsSuccessStatus;
|
|
179
|
+
if (preInit.cors.preflightContinue)
|
|
180
|
+
config.cors.preflightContinue = preInit.cors.preflightContinue;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (config.lifeCycle.onStart) {
|
|
185
|
+
config.lifeCycle.onStart();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
127
189
|
|
|
128
|
-
function getPreResponseBody(body) {
|
|
190
|
+
function getPreResponseBody(body, isBuffer = false) {
|
|
129
191
|
let bodyInfo;
|
|
130
192
|
if (body) {
|
|
131
|
-
if (
|
|
132
|
-
bodyInfo = {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
193
|
+
if (isBuffer)
|
|
194
|
+
bodyInfo = { buffer: body };
|
|
195
|
+
else {
|
|
196
|
+
if (common.isObject(body) || Array.isArray(body))
|
|
197
|
+
bodyInfo = { json: body };
|
|
198
|
+
const typeText = typeof body;
|
|
199
|
+
switch (typeText) {
|
|
200
|
+
case "string":
|
|
201
|
+
bodyInfo = { string: body };
|
|
202
|
+
break;
|
|
203
|
+
case "number":
|
|
204
|
+
bodyInfo = { number: body };
|
|
205
|
+
break;
|
|
206
|
+
case "boolean":
|
|
207
|
+
bodyInfo = { boolean: body };
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
144
210
|
}
|
|
145
211
|
}
|
|
146
212
|
return bodyInfo;
|
|
@@ -154,6 +220,7 @@ class HttpResponse {
|
|
|
154
220
|
}
|
|
155
221
|
setValues(responseInit) {
|
|
156
222
|
if (responseInit) {
|
|
223
|
+
this._isBuffer = responseInit.isBuffer;
|
|
157
224
|
if (responseInit.headers)
|
|
158
225
|
for (const [key, value] of Object.entries(responseInit.headers))
|
|
159
226
|
this.headers.append(key, value);
|
|
@@ -185,7 +252,7 @@ class HttpResponse {
|
|
|
185
252
|
return this._body;
|
|
186
253
|
}
|
|
187
254
|
set body(value) {
|
|
188
|
-
this._body = getPreResponseBody(value);
|
|
255
|
+
this._body = getPreResponseBody(value, this._isBuffer);
|
|
189
256
|
}
|
|
190
257
|
write(responseInit) {
|
|
191
258
|
this.setValues(responseInit);
|
|
@@ -207,6 +274,10 @@ var RequestPipeline = /* @__PURE__ */ ((RequestPipeline2) => {
|
|
|
207
274
|
return RequestPipeline2;
|
|
208
275
|
})(RequestPipeline || {});
|
|
209
276
|
|
|
277
|
+
function sanitizeSpecialCodes(value) {
|
|
278
|
+
return value;
|
|
279
|
+
}
|
|
280
|
+
|
|
210
281
|
function isBoolean(value) {
|
|
211
282
|
return typeof value === "boolean" || value === "1" || value === "true" || value === "0" || value === "false";
|
|
212
283
|
}
|
|
@@ -267,7 +338,7 @@ function toInt(value, radix = 0) {
|
|
|
267
338
|
}
|
|
268
339
|
function toString(value) {
|
|
269
340
|
if (isNotBlank(value))
|
|
270
|
-
return String(value);
|
|
341
|
+
return sanitizeSpecialCodes(String(value));
|
|
271
342
|
return value;
|
|
272
343
|
}
|
|
273
344
|
function whitelist(value, chars) {
|
|
@@ -348,6 +419,7 @@ class BaseResult {
|
|
|
348
419
|
}
|
|
349
420
|
|
|
350
421
|
function getResponseBodyObject(body, props) {
|
|
422
|
+
const sensitiveProps = common.commonContainer.nattyConfig?.secure?.sensitiveProps;
|
|
351
423
|
if (body instanceof common.List)
|
|
352
424
|
return getResponseBodyObject(body.values, body.props);
|
|
353
425
|
if (Array.isArray(body)) {
|
|
@@ -361,7 +433,8 @@ function getResponseBodyObject(body, props) {
|
|
|
361
433
|
const keys = Object.keys(body);
|
|
362
434
|
const getterProps = props ? Object.keys(props).map((key) => props[key]) : [];
|
|
363
435
|
for (const key of [...keys, ...getterProps])
|
|
364
|
-
|
|
436
|
+
if (!sensitiveProps || sensitiveProps.filter((t) => t == key.toLowerCase()).length == 0)
|
|
437
|
+
jObject[key] = getResponseBodyObject(body[key]);
|
|
365
438
|
return jObject;
|
|
366
439
|
}
|
|
367
440
|
return body;
|
|
@@ -388,7 +461,7 @@ class BaseResponse {
|
|
|
388
461
|
}
|
|
389
462
|
|
|
390
463
|
function getTypedErrorMessage(type, value) {
|
|
391
|
-
const message = common.commonContainer.nattyConfig.modelBinding
|
|
464
|
+
const message = common.commonContainer.nattyConfig?.modelBinding?.errorMessage?.typed ? common.commonContainer.nattyConfig?.modelBinding?.errorMessage?.typed[type] : "";
|
|
392
465
|
return parseMessage(message, [value]);
|
|
393
466
|
}
|
|
394
467
|
function parseMessage(message, value) {
|
|
@@ -455,7 +528,7 @@ const entityContainer = new class {
|
|
|
455
528
|
}
|
|
456
529
|
getPropertyValidators(entityName, propName) {
|
|
457
530
|
const entityInfo = this.entityConfig[entityName];
|
|
458
|
-
const propertyInfo = entityInfo.properties[propName];
|
|
531
|
+
const propertyInfo = entityInfo ? entityInfo.properties[propName] : void 0;
|
|
459
532
|
return propertyInfo ? propertyInfo.validators : {};
|
|
460
533
|
}
|
|
461
534
|
getProperties(entityName) {
|
|
@@ -464,6 +537,32 @@ const entityContainer = new class {
|
|
|
464
537
|
}
|
|
465
538
|
}();
|
|
466
539
|
|
|
540
|
+
class ForbiddenAccessException extends HttpException {
|
|
541
|
+
constructor(data) {
|
|
542
|
+
super({
|
|
543
|
+
body: data,
|
|
544
|
+
status: 403
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
class UnauthorizedAccessException extends HttpException {
|
|
550
|
+
constructor(data) {
|
|
551
|
+
super({
|
|
552
|
+
body: data,
|
|
553
|
+
status: 401
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function CreateProblemDetail(modelName, detail) {
|
|
559
|
+
return {
|
|
560
|
+
type: "https://tools.ietf.org/html/rfc7231#section-6.5.1",
|
|
561
|
+
title: `The specified ${modelName} model props are invalid.`,
|
|
562
|
+
detail
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
467
566
|
class ParameterTypeConverter extends BaseResponse {
|
|
468
567
|
constructor() {
|
|
469
568
|
super(...arguments);
|
|
@@ -524,12 +623,14 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
524
623
|
} else {
|
|
525
624
|
if (this.isArrayType(property.type) && Array.isArray(value)) {
|
|
526
625
|
let arrayValue = body[property.name] = [];
|
|
527
|
-
let arrayInvalidProps = invalidProps[property.name] = [];
|
|
528
626
|
for (const item of value) {
|
|
529
627
|
const sanitizeValue = this.sanitizer[property.type.toLowerCase()] ? this.sanitizer[property.type.toLowerCase()](item) : item;
|
|
530
|
-
if (sanitizeValue === INVALID_VALUE)
|
|
628
|
+
if (sanitizeValue === INVALID_VALUE) {
|
|
629
|
+
let arrayInvalidProps = invalidProps[property.name];
|
|
630
|
+
if (!arrayInvalidProps)
|
|
631
|
+
arrayInvalidProps = invalidProps[property.name] = [];
|
|
531
632
|
arrayInvalidProps.push(property);
|
|
532
|
-
else
|
|
633
|
+
} else
|
|
533
634
|
arrayValue.push(sanitizeValue);
|
|
534
635
|
}
|
|
535
636
|
} else
|
|
@@ -550,14 +651,17 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
550
651
|
for (const parameterInfo of methodInfo.parameters) {
|
|
551
652
|
const value = jObject[parameterInfo.name];
|
|
552
653
|
if (value !== void 0) {
|
|
553
|
-
|
|
654
|
+
const sanitizedValue = SANITIZERS[parameterInfo.type](value);
|
|
655
|
+
if (sanitizedValue === null && sanitizedValue != value)
|
|
656
|
+
throw new HttpBadRequestException(CreateProblemDetail(parameterInfo.type, [{ [parameterInfo.name]: `The supplied data type must be "${parameterInfo.type}"` }]));
|
|
657
|
+
jObject[parameterInfo.name] = sanitizedValue;
|
|
554
658
|
}
|
|
555
659
|
}
|
|
556
660
|
return jObject;
|
|
557
661
|
}
|
|
558
662
|
convertToInstance(entityName, data) {
|
|
559
663
|
const typesInfo = this.types[entityName];
|
|
560
|
-
const target = this.getClassTarget(typesInfo.path) || entityContainer.getTarget(entityName);
|
|
664
|
+
const target = this.getClassTarget(typesInfo.path, entityName) || entityContainer.getTarget(entityName);
|
|
561
665
|
let instance = null;
|
|
562
666
|
if (target) {
|
|
563
667
|
instance = new target();
|
|
@@ -566,11 +670,10 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
566
670
|
instance = data;
|
|
567
671
|
return instance;
|
|
568
672
|
}
|
|
569
|
-
getClassTarget(
|
|
570
|
-
if (
|
|
571
|
-
const classInfo = resolver();
|
|
572
|
-
|
|
573
|
-
return classInfo[name];
|
|
673
|
+
getClassTarget(path, entityName) {
|
|
674
|
+
if (path) {
|
|
675
|
+
const classInfo = nattyContainer.resolver(path);
|
|
676
|
+
return classInfo[entityName];
|
|
574
677
|
}
|
|
575
678
|
return void 0;
|
|
576
679
|
}
|
|
@@ -668,15 +771,6 @@ class RouteParser extends ParameterTypeConverter {
|
|
|
668
771
|
}
|
|
669
772
|
}
|
|
670
773
|
|
|
671
|
-
class UnauthorizedAccessException extends HttpException {
|
|
672
|
-
constructor(data) {
|
|
673
|
-
super({
|
|
674
|
-
body: data,
|
|
675
|
-
status: 401
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
774
|
const decoratorStateContainer = new class {
|
|
681
775
|
constructor() {
|
|
682
776
|
this.controllerConfig = {};
|
|
@@ -722,15 +816,6 @@ var DecoratorType = /* @__PURE__ */ ((DecoratorType2) => {
|
|
|
722
816
|
return DecoratorType2;
|
|
723
817
|
})(DecoratorType || {});
|
|
724
818
|
|
|
725
|
-
class ForbiddenAccessException extends HttpException {
|
|
726
|
-
constructor(data) {
|
|
727
|
-
super({
|
|
728
|
-
body: data,
|
|
729
|
-
status: 403
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
819
|
class AbstractExecutionContext {
|
|
735
820
|
constructor(context, routeInfo) {
|
|
736
821
|
this.context = context;
|
|
@@ -760,14 +845,6 @@ class ActionExecutingContext extends AbstractExecutionContext {
|
|
|
760
845
|
}
|
|
761
846
|
}
|
|
762
847
|
|
|
763
|
-
function CreateProblemDetail(modelName, detail) {
|
|
764
|
-
return {
|
|
765
|
-
type: "https://tools.ietf.org/html/rfc7231#section-6.5.1",
|
|
766
|
-
title: `The specified ${modelName} model props are invalid.`,
|
|
767
|
-
detail
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
|
|
771
848
|
function runValidators(entityName, value) {
|
|
772
849
|
const typeInfo = nattyContainer.types[entityName];
|
|
773
850
|
let errors = {};
|
|
@@ -829,6 +906,8 @@ class ModelBindingContext extends ParameterTypeConverter {
|
|
|
829
906
|
else
|
|
830
907
|
this.data = body;
|
|
831
908
|
this.instance = this.convertToInstance(this.type, this.data);
|
|
909
|
+
if (!this.isValid)
|
|
910
|
+
throw new HttpBadRequestException(CreateProblemDetail(this.type, this.errors));
|
|
832
911
|
}
|
|
833
912
|
get isValid() {
|
|
834
913
|
const errors = runValidators(this.type, this.instance);
|
|
@@ -845,6 +924,17 @@ class ActionExecutedContext extends AbstractExecutionContext {
|
|
|
845
924
|
super(httpContext, routeInfo);
|
|
846
925
|
this.content = content;
|
|
847
926
|
}
|
|
927
|
+
get response() {
|
|
928
|
+
return this.context.response;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
class AuthorizationContext extends AbstractExecutionContext {
|
|
933
|
+
constructor(models, context, routeInfo, config) {
|
|
934
|
+
super(context, routeInfo);
|
|
935
|
+
this.models = models;
|
|
936
|
+
this.config = config;
|
|
937
|
+
}
|
|
848
938
|
}
|
|
849
939
|
|
|
850
940
|
class RequestProcessor extends RouteParser {
|
|
@@ -857,9 +947,6 @@ class RequestProcessor extends RouteParser {
|
|
|
857
947
|
case RequestPipeline.onAuthentication:
|
|
858
948
|
await this.onAuthentication();
|
|
859
949
|
break;
|
|
860
|
-
case RequestPipeline.onAuthorization:
|
|
861
|
-
await this.onAuthorization();
|
|
862
|
-
break;
|
|
863
950
|
}
|
|
864
951
|
}
|
|
865
952
|
resolveFilter(instance) {
|
|
@@ -880,26 +967,34 @@ class RequestProcessor extends RouteParser {
|
|
|
880
967
|
const authentication = this.getAuthenticationClass();
|
|
881
968
|
const authenticationFilter = authentication ? this.resolveFilter(authentication) : void 0;
|
|
882
969
|
const anonymousInfo = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.anonymous);
|
|
970
|
+
if (!anonymousInfo.controllerConfig && !anonymousInfo.methodConfig && !authenticationFilter)
|
|
971
|
+
throw new UnauthorizedAccessException(DENY_BY_DEFAULT);
|
|
883
972
|
if (authenticationFilter) {
|
|
884
973
|
const result = await authenticationFilter.onAuthentication(this.httpContext);
|
|
885
974
|
this.httpContext.user = result;
|
|
886
975
|
if (!result.isAuthenticate && !anonymousInfo.controllerConfig && !anonymousInfo.methodConfig)
|
|
887
976
|
throw new UnauthorizedAccessException(authenticationFilter.onFailedResponse());
|
|
888
|
-
await this.onAuthorization();
|
|
889
977
|
}
|
|
890
978
|
}
|
|
891
|
-
async onAuthorization() {
|
|
979
|
+
async onAuthorization(methodParameters) {
|
|
892
980
|
const authorization = common.commonContainer.globalConfig.authorization;
|
|
893
981
|
const authorizationFilter = authorization ? this.resolveFilter(authorization) : void 0;
|
|
894
982
|
const authorizeConfig = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.authorize);
|
|
895
983
|
const authenticationOnly = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.authenticationOnly);
|
|
896
|
-
if (this.httpContext.user?.isAuthenticate && authorizationFilter && (!authenticationOnly.controllerConfig && !authenticationOnly.methodConfig)) {
|
|
897
|
-
const
|
|
984
|
+
if (this.httpContext.user?.isAuthenticate && authorizationFilter && (authorizeConfig.controllerConfig || authorizeConfig.methodConfig) && (!authenticationOnly.controllerConfig && !authenticationOnly.methodConfig)) {
|
|
985
|
+
const authorizationContext = new AuthorizationContext(
|
|
986
|
+
methodParameters.filter((t) => t instanceof ModelBindingContext),
|
|
987
|
+
this.httpContext,
|
|
988
|
+
this.routeInfo,
|
|
989
|
+
authorizeConfig.methodConfig || authorizeConfig.controllerConfig
|
|
990
|
+
);
|
|
991
|
+
const result = await authorizationFilter.onAuthorization(authorizationContext);
|
|
898
992
|
if (!result)
|
|
899
993
|
throw new ForbiddenAccessException(authorizationFilter.onFailedAuthorization());
|
|
900
994
|
}
|
|
901
995
|
}
|
|
902
996
|
async onActionExecuting(methodParameters) {
|
|
997
|
+
await this.onAuthorization(methodParameters);
|
|
903
998
|
let actionFilters = common.commonContainer.globalConfig.actionFilters || [];
|
|
904
999
|
const actionFiltersConfig = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.useFilter);
|
|
905
1000
|
actionFilters = [...actionFilters, ...actionFiltersConfig.controllerConfig?.actionFilters || [], ...actionFiltersConfig.methodConfig?.actionFilters || []];
|
|
@@ -1205,6 +1300,16 @@ function badRequest(value, response) {
|
|
|
1205
1300
|
return new BadRequestResult(value || common.BLANK, response);
|
|
1206
1301
|
}
|
|
1207
1302
|
|
|
1303
|
+
class FileResult extends BaseResult {
|
|
1304
|
+
constructor(value, response) {
|
|
1305
|
+
super({ ...{ isBuffer: true, body: value }, ...{ status: HttpStatusCode.success } }, response);
|
|
1306
|
+
this.value = value;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
function fileResult(value, response) {
|
|
1310
|
+
return new FileResult(value || common.BLANK, response);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1208
1313
|
function base(params, type, additionaConfig) {
|
|
1209
1314
|
decoratorStateContainer.register(params, type, additionaConfig);
|
|
1210
1315
|
}
|
|
@@ -1231,12 +1336,31 @@ function authenticationOnly() {
|
|
|
1231
1336
|
};
|
|
1232
1337
|
}
|
|
1233
1338
|
|
|
1339
|
+
function setEnvInfo(envTsDefinition, envValueInfo) {
|
|
1340
|
+
if (envTsDefinition && envValueInfo) {
|
|
1341
|
+
common.commonContainer.setEnvTsDefinition(envTsDefinition);
|
|
1342
|
+
Object.keys(envValueInfo).forEach((key) => process.env[key] = envValueInfo[key]);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function authorize(permission) {
|
|
1347
|
+
return function(target, propertyKey, descriptor) {
|
|
1348
|
+
base({
|
|
1349
|
+
target,
|
|
1350
|
+
propertyKey,
|
|
1351
|
+
descriptor
|
|
1352
|
+
}, DecoratorType.authorize, permission);
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1234
1356
|
exports.$request = $request;
|
|
1235
1357
|
exports.AbstractModelState = AbstractModelState;
|
|
1236
1358
|
exports.BadRequestResult = BadRequestResult;
|
|
1237
1359
|
exports.BaseController = BaseController;
|
|
1360
|
+
exports.CreateProblemDetail = CreateProblemDetail;
|
|
1238
1361
|
exports.CreatedResult = CreatedResult;
|
|
1239
1362
|
exports.Delete = Delete;
|
|
1363
|
+
exports.FileResult = FileResult;
|
|
1240
1364
|
exports.ForbiddenAccessException = ForbiddenAccessException;
|
|
1241
1365
|
exports.ForbiddenAccessInfoResult = ForbiddenAccessInfoResult;
|
|
1242
1366
|
exports.HttpBadRequestException = HttpBadRequestException;
|
|
@@ -1245,6 +1369,7 @@ exports.HttpException = HttpException;
|
|
|
1245
1369
|
exports.HttpHandler = HttpHandler;
|
|
1246
1370
|
exports.HttpNotFoundException = HttpNotFoundException;
|
|
1247
1371
|
exports.HttpResponse = HttpResponse;
|
|
1372
|
+
exports.HttpStatusCode = HttpStatusCode;
|
|
1248
1373
|
exports.ModelBindingContext = ModelBindingContext;
|
|
1249
1374
|
exports.NoContentResult = NoContentResult;
|
|
1250
1375
|
exports.NotFoundResult = NotFoundResult;
|
|
@@ -1253,10 +1378,12 @@ exports.RunOn = RunOn;
|
|
|
1253
1378
|
exports.UnauthorizedAccessException = UnauthorizedAccessException;
|
|
1254
1379
|
exports.anonymous = anonymous;
|
|
1255
1380
|
exports.authenticationOnly = authenticationOnly;
|
|
1381
|
+
exports.authorize = authorize;
|
|
1256
1382
|
exports.badRequest = badRequest;
|
|
1257
1383
|
exports.created = created;
|
|
1258
1384
|
exports.defineNattyConfig = defineNattyConfig;
|
|
1259
1385
|
exports.entityContainer = entityContainer;
|
|
1386
|
+
exports.fileResult = fileResult;
|
|
1260
1387
|
exports.filter = filter;
|
|
1261
1388
|
exports.forbiddenAccessInfo = forbiddenAccessInfo;
|
|
1262
1389
|
exports.get = get;
|
|
@@ -1269,4 +1396,5 @@ exports.post = post;
|
|
|
1269
1396
|
exports.put = put;
|
|
1270
1397
|
exports.registerDecorator = registerDecorator;
|
|
1271
1398
|
exports.route = route;
|
|
1399
|
+
exports.setEnvInfo = setEnvInfo;
|
|
1272
1400
|
exports.useFilter = useFilter;
|
package/dist/index.d.ts
CHANGED
|
@@ -53,12 +53,70 @@ declare function route(path: string): (target: any, propertyKey?: string, parame
|
|
|
53
53
|
|
|
54
54
|
declare function get(path: string): (target: any, propertyKey?: string, descriptor?: any) => void;
|
|
55
55
|
|
|
56
|
-
declare function post(path
|
|
56
|
+
declare function post(path?: string): (target: any, propertyKey?: string, descriptor?: any) => void;
|
|
57
57
|
|
|
58
|
-
declare function put(path
|
|
58
|
+
declare function put(path?: string): (target: any, propertyKey?: string, descriptor?: any) => void;
|
|
59
59
|
|
|
60
60
|
declare function Delete(): (target: any, propertyKey?: string, descriptor?: any) => void;
|
|
61
61
|
|
|
62
|
+
interface RouteConfig {
|
|
63
|
+
controller: Function;
|
|
64
|
+
parameters: Array<{
|
|
65
|
+
name: string;
|
|
66
|
+
type: string;
|
|
67
|
+
}>;
|
|
68
|
+
get: {
|
|
69
|
+
[key: string]: {
|
|
70
|
+
name: string;
|
|
71
|
+
parameters: Array<{
|
|
72
|
+
name: string;
|
|
73
|
+
type: string;
|
|
74
|
+
}>;
|
|
75
|
+
returnType: string;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
post: {
|
|
79
|
+
[key: string]: {
|
|
80
|
+
name: string;
|
|
81
|
+
parameters: Array<{
|
|
82
|
+
name: string;
|
|
83
|
+
type: string;
|
|
84
|
+
}>;
|
|
85
|
+
returnType: string;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
put: {
|
|
89
|
+
[key: string]: {
|
|
90
|
+
name: string;
|
|
91
|
+
parameters: Array<{
|
|
92
|
+
name: string;
|
|
93
|
+
type: string;
|
|
94
|
+
}>;
|
|
95
|
+
returnType: string;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
delete: {
|
|
99
|
+
[key: string]: {
|
|
100
|
+
name: string;
|
|
101
|
+
parameters: Array<{
|
|
102
|
+
name: string;
|
|
103
|
+
type: string;
|
|
104
|
+
}>;
|
|
105
|
+
returnType: string;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
declare function init(config: NattyConfig, appConfig: {
|
|
111
|
+
routes: {
|
|
112
|
+
[key: string]: RouteConfig;
|
|
113
|
+
};
|
|
114
|
+
envTsDefinition: {
|
|
115
|
+
[key: string]: string;
|
|
116
|
+
};
|
|
117
|
+
resolver: (path: string) => {};
|
|
118
|
+
}): any;
|
|
119
|
+
|
|
62
120
|
interface DecoratorInfo {
|
|
63
121
|
httpMethod: string;
|
|
64
122
|
route: string;
|
|
@@ -81,6 +139,7 @@ type HttpRequestBodyInfo = {
|
|
|
81
139
|
number?: number | number[];
|
|
82
140
|
boolean?: boolean | boolean[];
|
|
83
141
|
FormData?: FormData;
|
|
142
|
+
buffer?: any;
|
|
84
143
|
ArrayBuffer?: ArrayBuffer;
|
|
85
144
|
Blob?: Blob;
|
|
86
145
|
json?: {
|
|
@@ -120,6 +179,7 @@ interface HttpResponseInit {
|
|
|
120
179
|
headers?: HeadersInit;
|
|
121
180
|
cookies?: Cookie[];
|
|
122
181
|
enableContentNegotiation?: boolean;
|
|
182
|
+
isBuffer?: boolean;
|
|
123
183
|
}
|
|
124
184
|
|
|
125
185
|
interface ProblemDetail {
|
|
@@ -190,64 +250,6 @@ interface IHttpResult {
|
|
|
190
250
|
getResponse(): HttpResponseInit;
|
|
191
251
|
}
|
|
192
252
|
|
|
193
|
-
interface RouteConfig {
|
|
194
|
-
controller: Function;
|
|
195
|
-
parameters: Array<{
|
|
196
|
-
name: string;
|
|
197
|
-
type: string;
|
|
198
|
-
}>;
|
|
199
|
-
get: {
|
|
200
|
-
[key: string]: {
|
|
201
|
-
name: string;
|
|
202
|
-
parameters: Array<{
|
|
203
|
-
name: string;
|
|
204
|
-
type: string;
|
|
205
|
-
}>;
|
|
206
|
-
returnType: string;
|
|
207
|
-
};
|
|
208
|
-
};
|
|
209
|
-
post: {
|
|
210
|
-
[key: string]: {
|
|
211
|
-
name: string;
|
|
212
|
-
parameters: Array<{
|
|
213
|
-
name: string;
|
|
214
|
-
type: string;
|
|
215
|
-
}>;
|
|
216
|
-
returnType: string;
|
|
217
|
-
};
|
|
218
|
-
};
|
|
219
|
-
put: {
|
|
220
|
-
[key: string]: {
|
|
221
|
-
name: string;
|
|
222
|
-
parameters: Array<{
|
|
223
|
-
name: string;
|
|
224
|
-
type: string;
|
|
225
|
-
}>;
|
|
226
|
-
returnType: string;
|
|
227
|
-
};
|
|
228
|
-
};
|
|
229
|
-
delete: {
|
|
230
|
-
[key: string]: {
|
|
231
|
-
name: string;
|
|
232
|
-
parameters: Array<{
|
|
233
|
-
name: string;
|
|
234
|
-
type: string;
|
|
235
|
-
}>;
|
|
236
|
-
returnType: string;
|
|
237
|
-
};
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
declare function init(config: NattyConfig, appConfig: {
|
|
242
|
-
routes: {
|
|
243
|
-
[key: string]: RouteConfig;
|
|
244
|
-
};
|
|
245
|
-
envTsDefinition: {
|
|
246
|
-
[key: string]: string;
|
|
247
|
-
};
|
|
248
|
-
types?: TypesInfo;
|
|
249
|
-
}): any;
|
|
250
|
-
|
|
251
253
|
declare class HttpRequest {
|
|
252
254
|
private httpRequest;
|
|
253
255
|
constructor(http: HttpRequestInit);
|
|
@@ -264,6 +266,7 @@ declare class HttpResponse {
|
|
|
264
266
|
private _headers;
|
|
265
267
|
private _body;
|
|
266
268
|
private _status;
|
|
269
|
+
private _isBuffer;
|
|
267
270
|
constructor(response?: HttpResponseInit);
|
|
268
271
|
private setValues;
|
|
269
272
|
addCookie(cookie: Cookie): void;
|
|
@@ -341,9 +344,9 @@ declare abstract class ParameterTypeConverter extends BaseResponse {
|
|
|
341
344
|
};
|
|
342
345
|
get types(): TypesInfo;
|
|
343
346
|
getObjectTypeInfo(typeName: string): {
|
|
344
|
-
path?: any;
|
|
345
|
-
props?: TypeInfo
|
|
346
|
-
values?: any
|
|
347
|
+
path?: string | any | Function;
|
|
348
|
+
props?: Array<TypeInfo>;
|
|
349
|
+
values?: Array<any>;
|
|
347
350
|
};
|
|
348
351
|
isArrayType(typeName: string): boolean;
|
|
349
352
|
getTypeName(typeName: string): string;
|
|
@@ -435,6 +438,12 @@ declare class BadRequestResult extends BaseResult implements IHttpResult {
|
|
|
435
438
|
}
|
|
436
439
|
declare function badRequest(value?: ExceptionTypeInfo, response?: Pick<HttpResponseInit, "headers" | "cookies">): BadRequestResult;
|
|
437
440
|
|
|
441
|
+
declare class FileResult extends BaseResult {
|
|
442
|
+
value: any;
|
|
443
|
+
constructor(value: any, response?: Pick<HttpResponseInit, "headers" | "cookies">);
|
|
444
|
+
}
|
|
445
|
+
declare function fileResult(value?: any, response?: Pick<HttpResponseInit, "headers" | "cookies">): FileResult;
|
|
446
|
+
|
|
438
447
|
declare class HttpException {
|
|
439
448
|
private httpResponse;
|
|
440
449
|
constructor(response: HttpResponseInit);
|
|
@@ -463,4 +472,27 @@ declare function anonymous(): (target: any, propertyKey?: string, descriptor?: a
|
|
|
463
472
|
|
|
464
473
|
declare function authenticationOnly(): (target: any, propertyKey?: string, descriptor?: any) => void;
|
|
465
474
|
|
|
466
|
-
|
|
475
|
+
declare function setEnvInfo(envTsDefinition: {
|
|
476
|
+
[key: string]: string;
|
|
477
|
+
}, envValueInfo: {
|
|
478
|
+
[key: string]: any;
|
|
479
|
+
}): void;
|
|
480
|
+
|
|
481
|
+
declare enum HttpStatusCode {
|
|
482
|
+
success = 200,
|
|
483
|
+
created = 201,
|
|
484
|
+
noContent = 204,
|
|
485
|
+
notFound = 404,
|
|
486
|
+
unAuthorized = 401,
|
|
487
|
+
forbiddenAccess = 403,
|
|
488
|
+
badRequest = 400,
|
|
489
|
+
serverError = 500
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
declare function authorize(permission: {
|
|
493
|
+
[key: string]: any;
|
|
494
|
+
}): (target: any, propertyKey?: string, descriptor?: any) => void;
|
|
495
|
+
|
|
496
|
+
declare function CreateProblemDetail(modelName: string, detail: any): ProblemDetail;
|
|
497
|
+
|
|
498
|
+
export { $request, AbstractModelState, BadRequestResult, BaseController, BuildOptions, ClassTypeInfo, CreateProblemDetail, CreatedResult, Delete, FileResult, ForbiddenAccessException, ForbiddenAccessInfoResult, HttpBadRequestException, HttpContext, HttpException, HttpHandler, HttpModule, HttpNotFoundException, HttpResponse, HttpStatusCode, MethodInfo$1 as MethodInfo, ModelBindingContext, NoContentResult, NotFoundResult, OkResult, ParameterInfo, RunOn, TypeInfo$1 as TypeInfo, UnauthorizedAccessException, anonymous, authenticationOnly, authorize, badRequest, created, defineNattyConfig, entityContainer, fileResult, filter, forbiddenAccessInfo, get, init, injectable, noContent, notFound, ok, post, put, registerDecorator, route, setEnvInfo, useFilter };
|
package/dist/index.mjs
CHANGED
|
@@ -25,6 +25,12 @@ function route(path) {
|
|
|
25
25
|
const CONTROLLER = "controller";
|
|
26
26
|
const INVALID_VALUE = "INVALID_VALUE";
|
|
27
27
|
const BLANK = "";
|
|
28
|
+
const DENY_BY_DEFAULT = {
|
|
29
|
+
type: "https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html#deny-by-default",
|
|
30
|
+
title: "Deny-by-default",
|
|
31
|
+
description: `Zero Trust Architecture (NIST SP 800-207) \u2014 Core principle is "never trust, always verify," which translates to deny-by-default behavior. OWASP Secure Defaults Principle \u2014 \u201CSecurity should be a default setting, and everything should be locked down unless explicitly permitted.\u201D`,
|
|
32
|
+
solution: `Please implement proper authentication and authorization checks. If this API needs to be accessed anonymously, add the @anonymous() decorator above the HTTP action.`
|
|
33
|
+
};
|
|
28
34
|
|
|
29
35
|
function get(path) {
|
|
30
36
|
return function(target, propertyKey, descriptor) {
|
|
@@ -58,10 +64,13 @@ const nattyContainer = new class {
|
|
|
58
64
|
this.container = /* @__PURE__ */ new Map();
|
|
59
65
|
this.containerState = /* @__PURE__ */ new Map();
|
|
60
66
|
}
|
|
61
|
-
|
|
67
|
+
get types() {
|
|
68
|
+
return commonContainer.types;
|
|
69
|
+
}
|
|
70
|
+
setup(config, routes, resolver) {
|
|
62
71
|
this.config = config;
|
|
63
72
|
this.routes = routes;
|
|
64
|
-
this.
|
|
73
|
+
this.resolver = resolver;
|
|
65
74
|
}
|
|
66
75
|
getTypes() {
|
|
67
76
|
return StaticContainer.types;
|
|
@@ -111,34 +120,91 @@ const nattyContainer = new class {
|
|
|
111
120
|
}
|
|
112
121
|
}();
|
|
113
122
|
|
|
123
|
+
function startWebSchedules(schedules) {
|
|
124
|
+
if (schedules && Array.isArray(schedules)) {
|
|
125
|
+
for (const schedule of schedules)
|
|
126
|
+
startWebSchedule(schedule);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function startWebSchedule(config) {
|
|
130
|
+
if (config) {
|
|
131
|
+
const interval = setInterval(async () => {
|
|
132
|
+
try {
|
|
133
|
+
clearInterval(interval);
|
|
134
|
+
await config.scheduleFunction();
|
|
135
|
+
startWebSchedule(config);
|
|
136
|
+
} catch (ex) {
|
|
137
|
+
startWebSchedule(config);
|
|
138
|
+
}
|
|
139
|
+
}, config.interval);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
114
143
|
function init(config, appConfig) {
|
|
115
144
|
commonContainer.setupConfig(config);
|
|
116
145
|
commonContainer.setEnvTsDefinition(appConfig.envTsDefinition);
|
|
117
|
-
nattyContainer.setup(config, appConfig.routes, appConfig.
|
|
118
|
-
|
|
146
|
+
nattyContainer.setup(config, appConfig.routes, appConfig.resolver);
|
|
147
|
+
callLifeCycleEvents(config, true);
|
|
148
|
+
const result = initializeModule(config);
|
|
149
|
+
callLifeCycleEvents(config);
|
|
150
|
+
startWebSchedules(config.webSchedules);
|
|
151
|
+
return result;
|
|
119
152
|
}
|
|
120
153
|
function initializeModule(config) {
|
|
121
154
|
if (config.app) {
|
|
122
155
|
return config.app.init(config);
|
|
123
156
|
}
|
|
124
157
|
}
|
|
158
|
+
async function callLifeCycleEvents(config, isPreInit = false) {
|
|
159
|
+
if (config.lifeCycle) {
|
|
160
|
+
if (config.lifeCycle.preInit && isPreInit) {
|
|
161
|
+
const preInit = config.lifeCycle.preInit();
|
|
162
|
+
if (preInit) {
|
|
163
|
+
if (preInit.cors) {
|
|
164
|
+
if (!config.cors) {
|
|
165
|
+
const jObject = { origin: [] };
|
|
166
|
+
config.cors = jObject;
|
|
167
|
+
} else if (!config.cors.origin)
|
|
168
|
+
config.cors.origin = [];
|
|
169
|
+
if (preInit.cors.origin)
|
|
170
|
+
preInit.cors.origin.forEach((t) => {
|
|
171
|
+
config.cors?.origin.push(t);
|
|
172
|
+
});
|
|
173
|
+
if (preInit.cors.methods)
|
|
174
|
+
config.cors.methods = preInit.cors.methods;
|
|
175
|
+
if (preInit.cors.optionsSuccessStatus)
|
|
176
|
+
config.cors.optionsSuccessStatus = preInit.cors.optionsSuccessStatus;
|
|
177
|
+
if (preInit.cors.preflightContinue)
|
|
178
|
+
config.cors.preflightContinue = preInit.cors.preflightContinue;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (config.lifeCycle.onStart) {
|
|
183
|
+
config.lifeCycle.onStart();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
125
187
|
|
|
126
|
-
function getPreResponseBody(body) {
|
|
188
|
+
function getPreResponseBody(body, isBuffer = false) {
|
|
127
189
|
let bodyInfo;
|
|
128
190
|
if (body) {
|
|
129
|
-
if (
|
|
130
|
-
bodyInfo = {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
191
|
+
if (isBuffer)
|
|
192
|
+
bodyInfo = { buffer: body };
|
|
193
|
+
else {
|
|
194
|
+
if (isObject(body) || Array.isArray(body))
|
|
195
|
+
bodyInfo = { json: body };
|
|
196
|
+
const typeText = typeof body;
|
|
197
|
+
switch (typeText) {
|
|
198
|
+
case "string":
|
|
199
|
+
bodyInfo = { string: body };
|
|
200
|
+
break;
|
|
201
|
+
case "number":
|
|
202
|
+
bodyInfo = { number: body };
|
|
203
|
+
break;
|
|
204
|
+
case "boolean":
|
|
205
|
+
bodyInfo = { boolean: body };
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
142
208
|
}
|
|
143
209
|
}
|
|
144
210
|
return bodyInfo;
|
|
@@ -152,6 +218,7 @@ class HttpResponse {
|
|
|
152
218
|
}
|
|
153
219
|
setValues(responseInit) {
|
|
154
220
|
if (responseInit) {
|
|
221
|
+
this._isBuffer = responseInit.isBuffer;
|
|
155
222
|
if (responseInit.headers)
|
|
156
223
|
for (const [key, value] of Object.entries(responseInit.headers))
|
|
157
224
|
this.headers.append(key, value);
|
|
@@ -183,7 +250,7 @@ class HttpResponse {
|
|
|
183
250
|
return this._body;
|
|
184
251
|
}
|
|
185
252
|
set body(value) {
|
|
186
|
-
this._body = getPreResponseBody(value);
|
|
253
|
+
this._body = getPreResponseBody(value, this._isBuffer);
|
|
187
254
|
}
|
|
188
255
|
write(responseInit) {
|
|
189
256
|
this.setValues(responseInit);
|
|
@@ -205,6 +272,10 @@ var RequestPipeline = /* @__PURE__ */ ((RequestPipeline2) => {
|
|
|
205
272
|
return RequestPipeline2;
|
|
206
273
|
})(RequestPipeline || {});
|
|
207
274
|
|
|
275
|
+
function sanitizeSpecialCodes(value) {
|
|
276
|
+
return value;
|
|
277
|
+
}
|
|
278
|
+
|
|
208
279
|
function isBoolean(value) {
|
|
209
280
|
return typeof value === "boolean" || value === "1" || value === "true" || value === "0" || value === "false";
|
|
210
281
|
}
|
|
@@ -265,7 +336,7 @@ function toInt(value, radix = 0) {
|
|
|
265
336
|
}
|
|
266
337
|
function toString(value) {
|
|
267
338
|
if (isNotBlank(value))
|
|
268
|
-
return String(value);
|
|
339
|
+
return sanitizeSpecialCodes(String(value));
|
|
269
340
|
return value;
|
|
270
341
|
}
|
|
271
342
|
function whitelist(value, chars) {
|
|
@@ -346,6 +417,7 @@ class BaseResult {
|
|
|
346
417
|
}
|
|
347
418
|
|
|
348
419
|
function getResponseBodyObject(body, props) {
|
|
420
|
+
const sensitiveProps = commonContainer.nattyConfig?.secure?.sensitiveProps;
|
|
349
421
|
if (body instanceof List)
|
|
350
422
|
return getResponseBodyObject(body.values, body.props);
|
|
351
423
|
if (Array.isArray(body)) {
|
|
@@ -359,7 +431,8 @@ function getResponseBodyObject(body, props) {
|
|
|
359
431
|
const keys = Object.keys(body);
|
|
360
432
|
const getterProps = props ? Object.keys(props).map((key) => props[key]) : [];
|
|
361
433
|
for (const key of [...keys, ...getterProps])
|
|
362
|
-
|
|
434
|
+
if (!sensitiveProps || sensitiveProps.filter((t) => t == key.toLowerCase()).length == 0)
|
|
435
|
+
jObject[key] = getResponseBodyObject(body[key]);
|
|
363
436
|
return jObject;
|
|
364
437
|
}
|
|
365
438
|
return body;
|
|
@@ -386,7 +459,7 @@ class BaseResponse {
|
|
|
386
459
|
}
|
|
387
460
|
|
|
388
461
|
function getTypedErrorMessage(type, value) {
|
|
389
|
-
const message = commonContainer.nattyConfig.modelBinding
|
|
462
|
+
const message = commonContainer.nattyConfig?.modelBinding?.errorMessage?.typed ? commonContainer.nattyConfig?.modelBinding?.errorMessage?.typed[type] : "";
|
|
390
463
|
return parseMessage(message, [value]);
|
|
391
464
|
}
|
|
392
465
|
function parseMessage(message, value) {
|
|
@@ -453,7 +526,7 @@ const entityContainer = new class {
|
|
|
453
526
|
}
|
|
454
527
|
getPropertyValidators(entityName, propName) {
|
|
455
528
|
const entityInfo = this.entityConfig[entityName];
|
|
456
|
-
const propertyInfo = entityInfo.properties[propName];
|
|
529
|
+
const propertyInfo = entityInfo ? entityInfo.properties[propName] : void 0;
|
|
457
530
|
return propertyInfo ? propertyInfo.validators : {};
|
|
458
531
|
}
|
|
459
532
|
getProperties(entityName) {
|
|
@@ -462,6 +535,32 @@ const entityContainer = new class {
|
|
|
462
535
|
}
|
|
463
536
|
}();
|
|
464
537
|
|
|
538
|
+
class ForbiddenAccessException extends HttpException {
|
|
539
|
+
constructor(data) {
|
|
540
|
+
super({
|
|
541
|
+
body: data,
|
|
542
|
+
status: 403
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
class UnauthorizedAccessException extends HttpException {
|
|
548
|
+
constructor(data) {
|
|
549
|
+
super({
|
|
550
|
+
body: data,
|
|
551
|
+
status: 401
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function CreateProblemDetail(modelName, detail) {
|
|
557
|
+
return {
|
|
558
|
+
type: "https://tools.ietf.org/html/rfc7231#section-6.5.1",
|
|
559
|
+
title: `The specified ${modelName} model props are invalid.`,
|
|
560
|
+
detail
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
465
564
|
class ParameterTypeConverter extends BaseResponse {
|
|
466
565
|
constructor() {
|
|
467
566
|
super(...arguments);
|
|
@@ -522,12 +621,14 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
522
621
|
} else {
|
|
523
622
|
if (this.isArrayType(property.type) && Array.isArray(value)) {
|
|
524
623
|
let arrayValue = body[property.name] = [];
|
|
525
|
-
let arrayInvalidProps = invalidProps[property.name] = [];
|
|
526
624
|
for (const item of value) {
|
|
527
625
|
const sanitizeValue = this.sanitizer[property.type.toLowerCase()] ? this.sanitizer[property.type.toLowerCase()](item) : item;
|
|
528
|
-
if (sanitizeValue === INVALID_VALUE)
|
|
626
|
+
if (sanitizeValue === INVALID_VALUE) {
|
|
627
|
+
let arrayInvalidProps = invalidProps[property.name];
|
|
628
|
+
if (!arrayInvalidProps)
|
|
629
|
+
arrayInvalidProps = invalidProps[property.name] = [];
|
|
529
630
|
arrayInvalidProps.push(property);
|
|
530
|
-
else
|
|
631
|
+
} else
|
|
531
632
|
arrayValue.push(sanitizeValue);
|
|
532
633
|
}
|
|
533
634
|
} else
|
|
@@ -548,14 +649,17 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
548
649
|
for (const parameterInfo of methodInfo.parameters) {
|
|
549
650
|
const value = jObject[parameterInfo.name];
|
|
550
651
|
if (value !== void 0) {
|
|
551
|
-
|
|
652
|
+
const sanitizedValue = SANITIZERS[parameterInfo.type](value);
|
|
653
|
+
if (sanitizedValue === null && sanitizedValue != value)
|
|
654
|
+
throw new HttpBadRequestException(CreateProblemDetail(parameterInfo.type, [{ [parameterInfo.name]: `The supplied data type must be "${parameterInfo.type}"` }]));
|
|
655
|
+
jObject[parameterInfo.name] = sanitizedValue;
|
|
552
656
|
}
|
|
553
657
|
}
|
|
554
658
|
return jObject;
|
|
555
659
|
}
|
|
556
660
|
convertToInstance(entityName, data) {
|
|
557
661
|
const typesInfo = this.types[entityName];
|
|
558
|
-
const target = this.getClassTarget(typesInfo.path) || entityContainer.getTarget(entityName);
|
|
662
|
+
const target = this.getClassTarget(typesInfo.path, entityName) || entityContainer.getTarget(entityName);
|
|
559
663
|
let instance = null;
|
|
560
664
|
if (target) {
|
|
561
665
|
instance = new target();
|
|
@@ -564,11 +668,10 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
564
668
|
instance = data;
|
|
565
669
|
return instance;
|
|
566
670
|
}
|
|
567
|
-
getClassTarget(
|
|
568
|
-
if (
|
|
569
|
-
const classInfo = resolver();
|
|
570
|
-
|
|
571
|
-
return classInfo[name];
|
|
671
|
+
getClassTarget(path, entityName) {
|
|
672
|
+
if (path) {
|
|
673
|
+
const classInfo = nattyContainer.resolver(path);
|
|
674
|
+
return classInfo[entityName];
|
|
572
675
|
}
|
|
573
676
|
return void 0;
|
|
574
677
|
}
|
|
@@ -666,15 +769,6 @@ class RouteParser extends ParameterTypeConverter {
|
|
|
666
769
|
}
|
|
667
770
|
}
|
|
668
771
|
|
|
669
|
-
class UnauthorizedAccessException extends HttpException {
|
|
670
|
-
constructor(data) {
|
|
671
|
-
super({
|
|
672
|
-
body: data,
|
|
673
|
-
status: 401
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
772
|
const decoratorStateContainer = new class {
|
|
679
773
|
constructor() {
|
|
680
774
|
this.controllerConfig = {};
|
|
@@ -720,15 +814,6 @@ var DecoratorType = /* @__PURE__ */ ((DecoratorType2) => {
|
|
|
720
814
|
return DecoratorType2;
|
|
721
815
|
})(DecoratorType || {});
|
|
722
816
|
|
|
723
|
-
class ForbiddenAccessException extends HttpException {
|
|
724
|
-
constructor(data) {
|
|
725
|
-
super({
|
|
726
|
-
body: data,
|
|
727
|
-
status: 403
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
817
|
class AbstractExecutionContext {
|
|
733
818
|
constructor(context, routeInfo) {
|
|
734
819
|
this.context = context;
|
|
@@ -758,14 +843,6 @@ class ActionExecutingContext extends AbstractExecutionContext {
|
|
|
758
843
|
}
|
|
759
844
|
}
|
|
760
845
|
|
|
761
|
-
function CreateProblemDetail(modelName, detail) {
|
|
762
|
-
return {
|
|
763
|
-
type: "https://tools.ietf.org/html/rfc7231#section-6.5.1",
|
|
764
|
-
title: `The specified ${modelName} model props are invalid.`,
|
|
765
|
-
detail
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
|
|
769
846
|
function runValidators(entityName, value) {
|
|
770
847
|
const typeInfo = nattyContainer.types[entityName];
|
|
771
848
|
let errors = {};
|
|
@@ -827,6 +904,8 @@ class ModelBindingContext extends ParameterTypeConverter {
|
|
|
827
904
|
else
|
|
828
905
|
this.data = body;
|
|
829
906
|
this.instance = this.convertToInstance(this.type, this.data);
|
|
907
|
+
if (!this.isValid)
|
|
908
|
+
throw new HttpBadRequestException(CreateProblemDetail(this.type, this.errors));
|
|
830
909
|
}
|
|
831
910
|
get isValid() {
|
|
832
911
|
const errors = runValidators(this.type, this.instance);
|
|
@@ -843,6 +922,17 @@ class ActionExecutedContext extends AbstractExecutionContext {
|
|
|
843
922
|
super(httpContext, routeInfo);
|
|
844
923
|
this.content = content;
|
|
845
924
|
}
|
|
925
|
+
get response() {
|
|
926
|
+
return this.context.response;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
class AuthorizationContext extends AbstractExecutionContext {
|
|
931
|
+
constructor(models, context, routeInfo, config) {
|
|
932
|
+
super(context, routeInfo);
|
|
933
|
+
this.models = models;
|
|
934
|
+
this.config = config;
|
|
935
|
+
}
|
|
846
936
|
}
|
|
847
937
|
|
|
848
938
|
class RequestProcessor extends RouteParser {
|
|
@@ -855,9 +945,6 @@ class RequestProcessor extends RouteParser {
|
|
|
855
945
|
case RequestPipeline.onAuthentication:
|
|
856
946
|
await this.onAuthentication();
|
|
857
947
|
break;
|
|
858
|
-
case RequestPipeline.onAuthorization:
|
|
859
|
-
await this.onAuthorization();
|
|
860
|
-
break;
|
|
861
948
|
}
|
|
862
949
|
}
|
|
863
950
|
resolveFilter(instance) {
|
|
@@ -878,26 +965,34 @@ class RequestProcessor extends RouteParser {
|
|
|
878
965
|
const authentication = this.getAuthenticationClass();
|
|
879
966
|
const authenticationFilter = authentication ? this.resolveFilter(authentication) : void 0;
|
|
880
967
|
const anonymousInfo = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.anonymous);
|
|
968
|
+
if (!anonymousInfo.controllerConfig && !anonymousInfo.methodConfig && !authenticationFilter)
|
|
969
|
+
throw new UnauthorizedAccessException(DENY_BY_DEFAULT);
|
|
881
970
|
if (authenticationFilter) {
|
|
882
971
|
const result = await authenticationFilter.onAuthentication(this.httpContext);
|
|
883
972
|
this.httpContext.user = result;
|
|
884
973
|
if (!result.isAuthenticate && !anonymousInfo.controllerConfig && !anonymousInfo.methodConfig)
|
|
885
974
|
throw new UnauthorizedAccessException(authenticationFilter.onFailedResponse());
|
|
886
|
-
await this.onAuthorization();
|
|
887
975
|
}
|
|
888
976
|
}
|
|
889
|
-
async onAuthorization() {
|
|
977
|
+
async onAuthorization(methodParameters) {
|
|
890
978
|
const authorization = commonContainer.globalConfig.authorization;
|
|
891
979
|
const authorizationFilter = authorization ? this.resolveFilter(authorization) : void 0;
|
|
892
980
|
const authorizeConfig = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.authorize);
|
|
893
981
|
const authenticationOnly = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.authenticationOnly);
|
|
894
|
-
if (this.httpContext.user?.isAuthenticate && authorizationFilter && (!authenticationOnly.controllerConfig && !authenticationOnly.methodConfig)) {
|
|
895
|
-
const
|
|
982
|
+
if (this.httpContext.user?.isAuthenticate && authorizationFilter && (authorizeConfig.controllerConfig || authorizeConfig.methodConfig) && (!authenticationOnly.controllerConfig && !authenticationOnly.methodConfig)) {
|
|
983
|
+
const authorizationContext = new AuthorizationContext(
|
|
984
|
+
methodParameters.filter((t) => t instanceof ModelBindingContext),
|
|
985
|
+
this.httpContext,
|
|
986
|
+
this.routeInfo,
|
|
987
|
+
authorizeConfig.methodConfig || authorizeConfig.controllerConfig
|
|
988
|
+
);
|
|
989
|
+
const result = await authorizationFilter.onAuthorization(authorizationContext);
|
|
896
990
|
if (!result)
|
|
897
991
|
throw new ForbiddenAccessException(authorizationFilter.onFailedAuthorization());
|
|
898
992
|
}
|
|
899
993
|
}
|
|
900
994
|
async onActionExecuting(methodParameters) {
|
|
995
|
+
await this.onAuthorization(methodParameters);
|
|
901
996
|
let actionFilters = commonContainer.globalConfig.actionFilters || [];
|
|
902
997
|
const actionFiltersConfig = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.useFilter);
|
|
903
998
|
actionFilters = [...actionFilters, ...actionFiltersConfig.controllerConfig?.actionFilters || [], ...actionFiltersConfig.methodConfig?.actionFilters || []];
|
|
@@ -1203,6 +1298,16 @@ function badRequest(value, response) {
|
|
|
1203
1298
|
return new BadRequestResult(value || BLANK$1, response);
|
|
1204
1299
|
}
|
|
1205
1300
|
|
|
1301
|
+
class FileResult extends BaseResult {
|
|
1302
|
+
constructor(value, response) {
|
|
1303
|
+
super({ ...{ isBuffer: true, body: value }, ...{ status: HttpStatusCode.success } }, response);
|
|
1304
|
+
this.value = value;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
function fileResult(value, response) {
|
|
1308
|
+
return new FileResult(value || BLANK$1, response);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1206
1311
|
function base(params, type, additionaConfig) {
|
|
1207
1312
|
decoratorStateContainer.register(params, type, additionaConfig);
|
|
1208
1313
|
}
|
|
@@ -1229,4 +1334,21 @@ function authenticationOnly() {
|
|
|
1229
1334
|
};
|
|
1230
1335
|
}
|
|
1231
1336
|
|
|
1232
|
-
|
|
1337
|
+
function setEnvInfo(envTsDefinition, envValueInfo) {
|
|
1338
|
+
if (envTsDefinition && envValueInfo) {
|
|
1339
|
+
commonContainer.setEnvTsDefinition(envTsDefinition);
|
|
1340
|
+
Object.keys(envValueInfo).forEach((key) => process.env[key] = envValueInfo[key]);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function authorize(permission) {
|
|
1345
|
+
return function(target, propertyKey, descriptor) {
|
|
1346
|
+
base({
|
|
1347
|
+
target,
|
|
1348
|
+
propertyKey,
|
|
1349
|
+
descriptor
|
|
1350
|
+
}, DecoratorType.authorize, permission);
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
export { $request, AbstractModelState, BadRequestResult, BaseController, CreateProblemDetail, CreatedResult, Delete, FileResult, ForbiddenAccessException, ForbiddenAccessInfoResult, HttpBadRequestException, HttpContext, HttpException, HttpHandler, HttpNotFoundException, HttpResponse, HttpStatusCode, ModelBindingContext, NoContentResult, NotFoundResult, OkResult, RunOn, UnauthorizedAccessException, anonymous, authenticationOnly, authorize, badRequest, created, defineNattyConfig, entityContainer, fileResult, filter, forbiddenAccessInfo, get, init, injectable, noContent, notFound, ok, post, put, registerDecorator, route, setEnvInfo, useFilter };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nattyjs/core",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.51",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"author": "ajayojha",
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
"build": "unbuild"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
+
"reflect-metadata": "0.2.2",
|
|
18
19
|
"tsyringe": "^4.7.0",
|
|
19
20
|
"path-to-regexp": "6.2.1",
|
|
20
|
-
"@nattyjs/common": "0.0.1-beta.
|
|
21
|
+
"@nattyjs/common": "0.0.1-beta.51"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"unbuild": "1.2.1"
|