@nattyjs/core 0.0.1-beta.4 → 0.0.1-beta.40
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 +172 -65
- package/dist/index.d.ts +96 -64
- package/dist/index.mjs +167 -66
- 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;
|
|
@@ -116,7 +125,7 @@ const nattyContainer = new class {
|
|
|
116
125
|
function init(config, appConfig) {
|
|
117
126
|
common.commonContainer.setupConfig(config);
|
|
118
127
|
common.commonContainer.setEnvTsDefinition(appConfig.envTsDefinition);
|
|
119
|
-
nattyContainer.setup(config, appConfig.routes, appConfig.
|
|
128
|
+
nattyContainer.setup(config, appConfig.routes, appConfig.resolver);
|
|
120
129
|
return initializeModule(config);
|
|
121
130
|
}
|
|
122
131
|
function initializeModule(config) {
|
|
@@ -125,22 +134,26 @@ function initializeModule(config) {
|
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
136
|
|
|
128
|
-
function getPreResponseBody(body) {
|
|
137
|
+
function getPreResponseBody(body, isBuffer = false) {
|
|
129
138
|
let bodyInfo;
|
|
130
139
|
if (body) {
|
|
131
|
-
if (
|
|
132
|
-
bodyInfo = {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
if (isBuffer)
|
|
141
|
+
bodyInfo = { buffer: body };
|
|
142
|
+
else {
|
|
143
|
+
if (common.isObject(body) || Array.isArray(body))
|
|
144
|
+
bodyInfo = { json: body };
|
|
145
|
+
const typeText = typeof body;
|
|
146
|
+
switch (typeText) {
|
|
147
|
+
case "string":
|
|
148
|
+
bodyInfo = { string: body };
|
|
149
|
+
break;
|
|
150
|
+
case "number":
|
|
151
|
+
bodyInfo = { number: body };
|
|
152
|
+
break;
|
|
153
|
+
case "boolean":
|
|
154
|
+
bodyInfo = { boolean: body };
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
144
157
|
}
|
|
145
158
|
}
|
|
146
159
|
return bodyInfo;
|
|
@@ -154,6 +167,7 @@ class HttpResponse {
|
|
|
154
167
|
}
|
|
155
168
|
setValues(responseInit) {
|
|
156
169
|
if (responseInit) {
|
|
170
|
+
this._isBuffer = responseInit.isBuffer;
|
|
157
171
|
if (responseInit.headers)
|
|
158
172
|
for (const [key, value] of Object.entries(responseInit.headers))
|
|
159
173
|
this.headers.append(key, value);
|
|
@@ -185,7 +199,7 @@ class HttpResponse {
|
|
|
185
199
|
return this._body;
|
|
186
200
|
}
|
|
187
201
|
set body(value) {
|
|
188
|
-
this._body = getPreResponseBody(value);
|
|
202
|
+
this._body = getPreResponseBody(value, this._isBuffer);
|
|
189
203
|
}
|
|
190
204
|
write(responseInit) {
|
|
191
205
|
this.setValues(responseInit);
|
|
@@ -207,6 +221,26 @@ var RequestPipeline = /* @__PURE__ */ ((RequestPipeline2) => {
|
|
|
207
221
|
return RequestPipeline2;
|
|
208
222
|
})(RequestPipeline || {});
|
|
209
223
|
|
|
224
|
+
function lessThan(value) {
|
|
225
|
+
return value ? value.replace(/</g, "<") : value;
|
|
226
|
+
}
|
|
227
|
+
function greaterThan(value) {
|
|
228
|
+
return value ? value.replace(/>/g, ">") : value;
|
|
229
|
+
}
|
|
230
|
+
function ampersand(value) {
|
|
231
|
+
return value ? value.replace(/&/g, "&") : value;
|
|
232
|
+
}
|
|
233
|
+
function doubleDash(value) {
|
|
234
|
+
return value ? value.replace(/--/g, "") : value;
|
|
235
|
+
}
|
|
236
|
+
function sanitizeSpecialCodes(value) {
|
|
237
|
+
value = ampersand(value);
|
|
238
|
+
value = lessThan(value);
|
|
239
|
+
value = greaterThan(value);
|
|
240
|
+
value = doubleDash(value);
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
|
|
210
244
|
function isBoolean(value) {
|
|
211
245
|
return typeof value === "boolean" || value === "1" || value === "true" || value === "0" || value === "false";
|
|
212
246
|
}
|
|
@@ -267,7 +301,7 @@ function toInt(value, radix = 0) {
|
|
|
267
301
|
}
|
|
268
302
|
function toString(value) {
|
|
269
303
|
if (isNotBlank(value))
|
|
270
|
-
return String(value);
|
|
304
|
+
return sanitizeSpecialCodes(String(value));
|
|
271
305
|
return value;
|
|
272
306
|
}
|
|
273
307
|
function whitelist(value, chars) {
|
|
@@ -348,6 +382,7 @@ class BaseResult {
|
|
|
348
382
|
}
|
|
349
383
|
|
|
350
384
|
function getResponseBodyObject(body, props) {
|
|
385
|
+
const sensitiveProps = common.commonContainer.nattyConfig?.secure?.sensitiveProps;
|
|
351
386
|
if (body instanceof common.List)
|
|
352
387
|
return getResponseBodyObject(body.values, body.props);
|
|
353
388
|
if (Array.isArray(body)) {
|
|
@@ -361,7 +396,8 @@ function getResponseBodyObject(body, props) {
|
|
|
361
396
|
const keys = Object.keys(body);
|
|
362
397
|
const getterProps = props ? Object.keys(props).map((key) => props[key]) : [];
|
|
363
398
|
for (const key of [...keys, ...getterProps])
|
|
364
|
-
|
|
399
|
+
if (!sensitiveProps || sensitiveProps.filter((t) => t == key.toLowerCase()).length == 0)
|
|
400
|
+
jObject[key] = getResponseBodyObject(body[key]);
|
|
365
401
|
return jObject;
|
|
366
402
|
}
|
|
367
403
|
return body;
|
|
@@ -388,7 +424,7 @@ class BaseResponse {
|
|
|
388
424
|
}
|
|
389
425
|
|
|
390
426
|
function getTypedErrorMessage(type, value) {
|
|
391
|
-
const message = common.commonContainer.nattyConfig.modelBinding
|
|
427
|
+
const message = common.commonContainer.nattyConfig?.modelBinding?.errorMessage?.typed ? common.commonContainer.nattyConfig?.modelBinding?.errorMessage?.typed[type] : "";
|
|
392
428
|
return parseMessage(message, [value]);
|
|
393
429
|
}
|
|
394
430
|
function parseMessage(message, value) {
|
|
@@ -464,6 +500,32 @@ const entityContainer = new class {
|
|
|
464
500
|
}
|
|
465
501
|
}();
|
|
466
502
|
|
|
503
|
+
class ForbiddenAccessException extends HttpException {
|
|
504
|
+
constructor(data) {
|
|
505
|
+
super({
|
|
506
|
+
body: data,
|
|
507
|
+
status: 403
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
class UnauthorizedAccessException extends HttpException {
|
|
513
|
+
constructor(data) {
|
|
514
|
+
super({
|
|
515
|
+
body: data,
|
|
516
|
+
status: 401
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function CreateProblemDetail(modelName, detail) {
|
|
522
|
+
return {
|
|
523
|
+
type: "https://tools.ietf.org/html/rfc7231#section-6.5.1",
|
|
524
|
+
title: `The specified ${modelName} model props are invalid.`,
|
|
525
|
+
detail
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
467
529
|
class ParameterTypeConverter extends BaseResponse {
|
|
468
530
|
constructor() {
|
|
469
531
|
super(...arguments);
|
|
@@ -524,12 +586,14 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
524
586
|
} else {
|
|
525
587
|
if (this.isArrayType(property.type) && Array.isArray(value)) {
|
|
526
588
|
let arrayValue = body[property.name] = [];
|
|
527
|
-
let arrayInvalidProps = invalidProps[property.name] = [];
|
|
528
589
|
for (const item of value) {
|
|
529
590
|
const sanitizeValue = this.sanitizer[property.type.toLowerCase()] ? this.sanitizer[property.type.toLowerCase()](item) : item;
|
|
530
|
-
if (sanitizeValue === INVALID_VALUE)
|
|
591
|
+
if (sanitizeValue === INVALID_VALUE) {
|
|
592
|
+
let arrayInvalidProps = invalidProps[property.name];
|
|
593
|
+
if (!arrayInvalidProps)
|
|
594
|
+
arrayInvalidProps = invalidProps[property.name] = [];
|
|
531
595
|
arrayInvalidProps.push(property);
|
|
532
|
-
else
|
|
596
|
+
} else
|
|
533
597
|
arrayValue.push(sanitizeValue);
|
|
534
598
|
}
|
|
535
599
|
} else
|
|
@@ -550,14 +614,17 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
550
614
|
for (const parameterInfo of methodInfo.parameters) {
|
|
551
615
|
const value = jObject[parameterInfo.name];
|
|
552
616
|
if (value !== void 0) {
|
|
553
|
-
|
|
617
|
+
const sanitizedValue = SANITIZERS[parameterInfo.type](value);
|
|
618
|
+
if (sanitizedValue === null && sanitizedValue != value)
|
|
619
|
+
throw new HttpBadRequestException(CreateProblemDetail(parameterInfo.type, [{ [parameterInfo.name]: `The supplied data type must be "${parameterInfo.type}"` }]));
|
|
620
|
+
jObject[parameterInfo.name] = sanitizedValue;
|
|
554
621
|
}
|
|
555
622
|
}
|
|
556
623
|
return jObject;
|
|
557
624
|
}
|
|
558
625
|
convertToInstance(entityName, data) {
|
|
559
626
|
const typesInfo = this.types[entityName];
|
|
560
|
-
const target = this.getClassTarget(typesInfo.path) || entityContainer.getTarget(entityName);
|
|
627
|
+
const target = this.getClassTarget(typesInfo.path, entityName) || entityContainer.getTarget(entityName);
|
|
561
628
|
let instance = null;
|
|
562
629
|
if (target) {
|
|
563
630
|
instance = new target();
|
|
@@ -566,11 +633,10 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
566
633
|
instance = data;
|
|
567
634
|
return instance;
|
|
568
635
|
}
|
|
569
|
-
getClassTarget(
|
|
570
|
-
if (
|
|
571
|
-
const classInfo = resolver();
|
|
572
|
-
|
|
573
|
-
return classInfo[name];
|
|
636
|
+
getClassTarget(path, entityName) {
|
|
637
|
+
if (path) {
|
|
638
|
+
const classInfo = nattyContainer.resolver(path);
|
|
639
|
+
return classInfo[entityName];
|
|
574
640
|
}
|
|
575
641
|
return void 0;
|
|
576
642
|
}
|
|
@@ -668,15 +734,6 @@ class RouteParser extends ParameterTypeConverter {
|
|
|
668
734
|
}
|
|
669
735
|
}
|
|
670
736
|
|
|
671
|
-
class UnauthorizedAccessException extends HttpException {
|
|
672
|
-
constructor(data) {
|
|
673
|
-
super({
|
|
674
|
-
body: data,
|
|
675
|
-
status: 401
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
737
|
const decoratorStateContainer = new class {
|
|
681
738
|
constructor() {
|
|
682
739
|
this.controllerConfig = {};
|
|
@@ -722,15 +779,6 @@ var DecoratorType = /* @__PURE__ */ ((DecoratorType2) => {
|
|
|
722
779
|
return DecoratorType2;
|
|
723
780
|
})(DecoratorType || {});
|
|
724
781
|
|
|
725
|
-
class ForbiddenAccessException extends HttpException {
|
|
726
|
-
constructor(data) {
|
|
727
|
-
super({
|
|
728
|
-
body: data,
|
|
729
|
-
status: 403
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
782
|
class AbstractExecutionContext {
|
|
735
783
|
constructor(context, routeInfo) {
|
|
736
784
|
this.context = context;
|
|
@@ -760,14 +808,6 @@ class ActionExecutingContext extends AbstractExecutionContext {
|
|
|
760
808
|
}
|
|
761
809
|
}
|
|
762
810
|
|
|
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
811
|
function runValidators(entityName, value) {
|
|
772
812
|
const typeInfo = nattyContainer.types[entityName];
|
|
773
813
|
let errors = {};
|
|
@@ -829,6 +869,8 @@ class ModelBindingContext extends ParameterTypeConverter {
|
|
|
829
869
|
else
|
|
830
870
|
this.data = body;
|
|
831
871
|
this.instance = this.convertToInstance(this.type, this.data);
|
|
872
|
+
if (!this.isValid)
|
|
873
|
+
throw new HttpBadRequestException(CreateProblemDetail(this.type, this.errors));
|
|
832
874
|
}
|
|
833
875
|
get isValid() {
|
|
834
876
|
const errors = runValidators(this.type, this.instance);
|
|
@@ -845,6 +887,17 @@ class ActionExecutedContext extends AbstractExecutionContext {
|
|
|
845
887
|
super(httpContext, routeInfo);
|
|
846
888
|
this.content = content;
|
|
847
889
|
}
|
|
890
|
+
get response() {
|
|
891
|
+
return this.context.response;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
class AuthorizationContext extends AbstractExecutionContext {
|
|
896
|
+
constructor(models, context, routeInfo, config) {
|
|
897
|
+
super(context, routeInfo);
|
|
898
|
+
this.models = models;
|
|
899
|
+
this.config = config;
|
|
900
|
+
}
|
|
848
901
|
}
|
|
849
902
|
|
|
850
903
|
class RequestProcessor extends RouteParser {
|
|
@@ -857,9 +910,6 @@ class RequestProcessor extends RouteParser {
|
|
|
857
910
|
case RequestPipeline.onAuthentication:
|
|
858
911
|
await this.onAuthentication();
|
|
859
912
|
break;
|
|
860
|
-
case RequestPipeline.onAuthorization:
|
|
861
|
-
await this.onAuthorization();
|
|
862
|
-
break;
|
|
863
913
|
}
|
|
864
914
|
}
|
|
865
915
|
resolveFilter(instance) {
|
|
@@ -880,26 +930,45 @@ class RequestProcessor extends RouteParser {
|
|
|
880
930
|
const authentication = this.getAuthenticationClass();
|
|
881
931
|
const authenticationFilter = authentication ? this.resolveFilter(authentication) : void 0;
|
|
882
932
|
const anonymousInfo = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.anonymous);
|
|
933
|
+
if (!anonymousInfo && !authenticationFilter)
|
|
934
|
+
throw new UnauthorizedAccessException(DENY_BY_DEFAULT);
|
|
883
935
|
if (authenticationFilter) {
|
|
884
936
|
const result = await authenticationFilter.onAuthentication(this.httpContext);
|
|
885
937
|
this.httpContext.user = result;
|
|
886
938
|
if (!result.isAuthenticate && !anonymousInfo.controllerConfig && !anonymousInfo.methodConfig)
|
|
887
939
|
throw new UnauthorizedAccessException(authenticationFilter.onFailedResponse());
|
|
888
|
-
await this.onAuthorization();
|
|
889
940
|
}
|
|
890
941
|
}
|
|
891
|
-
async onAuthorization() {
|
|
942
|
+
async onAuthorization(methodParameters) {
|
|
892
943
|
const authorization = common.commonContainer.globalConfig.authorization;
|
|
893
944
|
const authorizationFilter = authorization ? this.resolveFilter(authorization) : void 0;
|
|
894
945
|
const authorizeConfig = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.authorize);
|
|
895
946
|
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
|
|
947
|
+
if (this.httpContext.user?.isAuthenticate && authorizationFilter && (authorizeConfig.controllerConfig || authorizeConfig.methodConfig) && (!authenticationOnly.controllerConfig && !authenticationOnly.methodConfig)) {
|
|
948
|
+
const authorizationContext = new AuthorizationContext(
|
|
949
|
+
methodParameters.filter((t) => t instanceof ModelBindingContext),
|
|
950
|
+
this.httpContext,
|
|
951
|
+
this.routeInfo,
|
|
952
|
+
authorizeConfig.methodConfig || authorizeConfig.controllerConfig
|
|
953
|
+
);
|
|
954
|
+
const result = await authorizationFilter.onAuthorization(authorizationContext);
|
|
898
955
|
if (!result)
|
|
899
956
|
throw new ForbiddenAccessException(authorizationFilter.onFailedAuthorization());
|
|
900
957
|
}
|
|
901
958
|
}
|
|
902
|
-
async
|
|
959
|
+
async onModelBinding(methodParameters) {
|
|
960
|
+
const { actionExecutingContext, actionFilters } = this.getExecutingContext(methodParameters);
|
|
961
|
+
for (const actionFilter of actionFilters) {
|
|
962
|
+
const filter = this.resolveFilter(actionFilter);
|
|
963
|
+
this.actionFilterInstances.push(filter);
|
|
964
|
+
await filter.onActionExecuting(actionExecutingContext);
|
|
965
|
+
if (actionExecutingContext.result) {
|
|
966
|
+
return actionExecutingContext.result;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
getExecutingContext(methodParameters) {
|
|
903
972
|
let actionFilters = common.commonContainer.globalConfig.actionFilters || [];
|
|
904
973
|
const actionFiltersConfig = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.useFilter);
|
|
905
974
|
actionFilters = [...actionFilters, ...actionFiltersConfig.controllerConfig?.actionFilters || [], ...actionFiltersConfig.methodConfig?.actionFilters || []];
|
|
@@ -908,6 +977,11 @@ class RequestProcessor extends RouteParser {
|
|
|
908
977
|
this.httpContext,
|
|
909
978
|
this.routeInfo
|
|
910
979
|
);
|
|
980
|
+
return { actionFilters, actionExecutingContext };
|
|
981
|
+
}
|
|
982
|
+
async onActionExecuting(methodParameters) {
|
|
983
|
+
await this.onAuthorization(methodParameters);
|
|
984
|
+
const { actionExecutingContext, actionFilters } = this.getExecutingContext(methodParameters);
|
|
911
985
|
for (const actionFilter of actionFilters) {
|
|
912
986
|
const filter = this.resolveFilter(actionFilter);
|
|
913
987
|
this.actionFilterInstances.push(filter);
|
|
@@ -1205,6 +1279,16 @@ function badRequest(value, response) {
|
|
|
1205
1279
|
return new BadRequestResult(value || common.BLANK, response);
|
|
1206
1280
|
}
|
|
1207
1281
|
|
|
1282
|
+
class FileResult extends BaseResult {
|
|
1283
|
+
constructor(value, response) {
|
|
1284
|
+
super({ ...{ isBuffer: true, body: value }, ...{ status: HttpStatusCode.success } }, response);
|
|
1285
|
+
this.value = value;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
function fileResult(value, response) {
|
|
1289
|
+
return new FileResult(value || common.BLANK, response);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1208
1292
|
function base(params, type, additionaConfig) {
|
|
1209
1293
|
decoratorStateContainer.register(params, type, additionaConfig);
|
|
1210
1294
|
}
|
|
@@ -1231,12 +1315,31 @@ function authenticationOnly() {
|
|
|
1231
1315
|
};
|
|
1232
1316
|
}
|
|
1233
1317
|
|
|
1318
|
+
function setEnvInfo(envTsDefinition, envValueInfo) {
|
|
1319
|
+
if (envTsDefinition && envValueInfo) {
|
|
1320
|
+
common.commonContainer.setEnvTsDefinition(envTsDefinition);
|
|
1321
|
+
Object.keys(envValueInfo).forEach((key) => process.env[key] = envValueInfo[key]);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
function authorize(permission) {
|
|
1326
|
+
return function(target, propertyKey, descriptor) {
|
|
1327
|
+
base({
|
|
1328
|
+
target,
|
|
1329
|
+
propertyKey,
|
|
1330
|
+
descriptor
|
|
1331
|
+
}, DecoratorType.authorize, permission);
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1234
1335
|
exports.$request = $request;
|
|
1235
1336
|
exports.AbstractModelState = AbstractModelState;
|
|
1236
1337
|
exports.BadRequestResult = BadRequestResult;
|
|
1237
1338
|
exports.BaseController = BaseController;
|
|
1339
|
+
exports.CreateProblemDetail = CreateProblemDetail;
|
|
1238
1340
|
exports.CreatedResult = CreatedResult;
|
|
1239
1341
|
exports.Delete = Delete;
|
|
1342
|
+
exports.FileResult = FileResult;
|
|
1240
1343
|
exports.ForbiddenAccessException = ForbiddenAccessException;
|
|
1241
1344
|
exports.ForbiddenAccessInfoResult = ForbiddenAccessInfoResult;
|
|
1242
1345
|
exports.HttpBadRequestException = HttpBadRequestException;
|
|
@@ -1245,6 +1348,7 @@ exports.HttpException = HttpException;
|
|
|
1245
1348
|
exports.HttpHandler = HttpHandler;
|
|
1246
1349
|
exports.HttpNotFoundException = HttpNotFoundException;
|
|
1247
1350
|
exports.HttpResponse = HttpResponse;
|
|
1351
|
+
exports.HttpStatusCode = HttpStatusCode;
|
|
1248
1352
|
exports.ModelBindingContext = ModelBindingContext;
|
|
1249
1353
|
exports.NoContentResult = NoContentResult;
|
|
1250
1354
|
exports.NotFoundResult = NotFoundResult;
|
|
@@ -1253,10 +1357,12 @@ exports.RunOn = RunOn;
|
|
|
1253
1357
|
exports.UnauthorizedAccessException = UnauthorizedAccessException;
|
|
1254
1358
|
exports.anonymous = anonymous;
|
|
1255
1359
|
exports.authenticationOnly = authenticationOnly;
|
|
1360
|
+
exports.authorize = authorize;
|
|
1256
1361
|
exports.badRequest = badRequest;
|
|
1257
1362
|
exports.created = created;
|
|
1258
1363
|
exports.defineNattyConfig = defineNattyConfig;
|
|
1259
1364
|
exports.entityContainer = entityContainer;
|
|
1365
|
+
exports.fileResult = fileResult;
|
|
1260
1366
|
exports.filter = filter;
|
|
1261
1367
|
exports.forbiddenAccessInfo = forbiddenAccessInfo;
|
|
1262
1368
|
exports.get = get;
|
|
@@ -1269,4 +1375,5 @@ exports.post = post;
|
|
|
1269
1375
|
exports.put = put;
|
|
1270
1376
|
exports.registerDecorator = registerDecorator;
|
|
1271
1377
|
exports.route = route;
|
|
1378
|
+
exports.setEnvInfo = setEnvInfo;
|
|
1272
1379
|
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;
|
|
@@ -114,7 +123,7 @@ const nattyContainer = new class {
|
|
|
114
123
|
function init(config, appConfig) {
|
|
115
124
|
commonContainer.setupConfig(config);
|
|
116
125
|
commonContainer.setEnvTsDefinition(appConfig.envTsDefinition);
|
|
117
|
-
nattyContainer.setup(config, appConfig.routes, appConfig.
|
|
126
|
+
nattyContainer.setup(config, appConfig.routes, appConfig.resolver);
|
|
118
127
|
return initializeModule(config);
|
|
119
128
|
}
|
|
120
129
|
function initializeModule(config) {
|
|
@@ -123,22 +132,26 @@ function initializeModule(config) {
|
|
|
123
132
|
}
|
|
124
133
|
}
|
|
125
134
|
|
|
126
|
-
function getPreResponseBody(body) {
|
|
135
|
+
function getPreResponseBody(body, isBuffer = false) {
|
|
127
136
|
let bodyInfo;
|
|
128
137
|
if (body) {
|
|
129
|
-
if (
|
|
130
|
-
bodyInfo = {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
if (isBuffer)
|
|
139
|
+
bodyInfo = { buffer: body };
|
|
140
|
+
else {
|
|
141
|
+
if (isObject(body) || Array.isArray(body))
|
|
142
|
+
bodyInfo = { json: body };
|
|
143
|
+
const typeText = typeof body;
|
|
144
|
+
switch (typeText) {
|
|
145
|
+
case "string":
|
|
146
|
+
bodyInfo = { string: body };
|
|
147
|
+
break;
|
|
148
|
+
case "number":
|
|
149
|
+
bodyInfo = { number: body };
|
|
150
|
+
break;
|
|
151
|
+
case "boolean":
|
|
152
|
+
bodyInfo = { boolean: body };
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
142
155
|
}
|
|
143
156
|
}
|
|
144
157
|
return bodyInfo;
|
|
@@ -152,6 +165,7 @@ class HttpResponse {
|
|
|
152
165
|
}
|
|
153
166
|
setValues(responseInit) {
|
|
154
167
|
if (responseInit) {
|
|
168
|
+
this._isBuffer = responseInit.isBuffer;
|
|
155
169
|
if (responseInit.headers)
|
|
156
170
|
for (const [key, value] of Object.entries(responseInit.headers))
|
|
157
171
|
this.headers.append(key, value);
|
|
@@ -183,7 +197,7 @@ class HttpResponse {
|
|
|
183
197
|
return this._body;
|
|
184
198
|
}
|
|
185
199
|
set body(value) {
|
|
186
|
-
this._body = getPreResponseBody(value);
|
|
200
|
+
this._body = getPreResponseBody(value, this._isBuffer);
|
|
187
201
|
}
|
|
188
202
|
write(responseInit) {
|
|
189
203
|
this.setValues(responseInit);
|
|
@@ -205,6 +219,26 @@ var RequestPipeline = /* @__PURE__ */ ((RequestPipeline2) => {
|
|
|
205
219
|
return RequestPipeline2;
|
|
206
220
|
})(RequestPipeline || {});
|
|
207
221
|
|
|
222
|
+
function lessThan(value) {
|
|
223
|
+
return value ? value.replace(/</g, "<") : value;
|
|
224
|
+
}
|
|
225
|
+
function greaterThan(value) {
|
|
226
|
+
return value ? value.replace(/>/g, ">") : value;
|
|
227
|
+
}
|
|
228
|
+
function ampersand(value) {
|
|
229
|
+
return value ? value.replace(/&/g, "&") : value;
|
|
230
|
+
}
|
|
231
|
+
function doubleDash(value) {
|
|
232
|
+
return value ? value.replace(/--/g, "") : value;
|
|
233
|
+
}
|
|
234
|
+
function sanitizeSpecialCodes(value) {
|
|
235
|
+
value = ampersand(value);
|
|
236
|
+
value = lessThan(value);
|
|
237
|
+
value = greaterThan(value);
|
|
238
|
+
value = doubleDash(value);
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
|
|
208
242
|
function isBoolean(value) {
|
|
209
243
|
return typeof value === "boolean" || value === "1" || value === "true" || value === "0" || value === "false";
|
|
210
244
|
}
|
|
@@ -265,7 +299,7 @@ function toInt(value, radix = 0) {
|
|
|
265
299
|
}
|
|
266
300
|
function toString(value) {
|
|
267
301
|
if (isNotBlank(value))
|
|
268
|
-
return String(value);
|
|
302
|
+
return sanitizeSpecialCodes(String(value));
|
|
269
303
|
return value;
|
|
270
304
|
}
|
|
271
305
|
function whitelist(value, chars) {
|
|
@@ -346,6 +380,7 @@ class BaseResult {
|
|
|
346
380
|
}
|
|
347
381
|
|
|
348
382
|
function getResponseBodyObject(body, props) {
|
|
383
|
+
const sensitiveProps = commonContainer.nattyConfig?.secure?.sensitiveProps;
|
|
349
384
|
if (body instanceof List)
|
|
350
385
|
return getResponseBodyObject(body.values, body.props);
|
|
351
386
|
if (Array.isArray(body)) {
|
|
@@ -359,7 +394,8 @@ function getResponseBodyObject(body, props) {
|
|
|
359
394
|
const keys = Object.keys(body);
|
|
360
395
|
const getterProps = props ? Object.keys(props).map((key) => props[key]) : [];
|
|
361
396
|
for (const key of [...keys, ...getterProps])
|
|
362
|
-
|
|
397
|
+
if (!sensitiveProps || sensitiveProps.filter((t) => t == key.toLowerCase()).length == 0)
|
|
398
|
+
jObject[key] = getResponseBodyObject(body[key]);
|
|
363
399
|
return jObject;
|
|
364
400
|
}
|
|
365
401
|
return body;
|
|
@@ -386,7 +422,7 @@ class BaseResponse {
|
|
|
386
422
|
}
|
|
387
423
|
|
|
388
424
|
function getTypedErrorMessage(type, value) {
|
|
389
|
-
const message = commonContainer.nattyConfig.modelBinding
|
|
425
|
+
const message = commonContainer.nattyConfig?.modelBinding?.errorMessage?.typed ? commonContainer.nattyConfig?.modelBinding?.errorMessage?.typed[type] : "";
|
|
390
426
|
return parseMessage(message, [value]);
|
|
391
427
|
}
|
|
392
428
|
function parseMessage(message, value) {
|
|
@@ -462,6 +498,32 @@ const entityContainer = new class {
|
|
|
462
498
|
}
|
|
463
499
|
}();
|
|
464
500
|
|
|
501
|
+
class ForbiddenAccessException extends HttpException {
|
|
502
|
+
constructor(data) {
|
|
503
|
+
super({
|
|
504
|
+
body: data,
|
|
505
|
+
status: 403
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
class UnauthorizedAccessException extends HttpException {
|
|
511
|
+
constructor(data) {
|
|
512
|
+
super({
|
|
513
|
+
body: data,
|
|
514
|
+
status: 401
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function CreateProblemDetail(modelName, detail) {
|
|
520
|
+
return {
|
|
521
|
+
type: "https://tools.ietf.org/html/rfc7231#section-6.5.1",
|
|
522
|
+
title: `The specified ${modelName} model props are invalid.`,
|
|
523
|
+
detail
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
465
527
|
class ParameterTypeConverter extends BaseResponse {
|
|
466
528
|
constructor() {
|
|
467
529
|
super(...arguments);
|
|
@@ -522,12 +584,14 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
522
584
|
} else {
|
|
523
585
|
if (this.isArrayType(property.type) && Array.isArray(value)) {
|
|
524
586
|
let arrayValue = body[property.name] = [];
|
|
525
|
-
let arrayInvalidProps = invalidProps[property.name] = [];
|
|
526
587
|
for (const item of value) {
|
|
527
588
|
const sanitizeValue = this.sanitizer[property.type.toLowerCase()] ? this.sanitizer[property.type.toLowerCase()](item) : item;
|
|
528
|
-
if (sanitizeValue === INVALID_VALUE)
|
|
589
|
+
if (sanitizeValue === INVALID_VALUE) {
|
|
590
|
+
let arrayInvalidProps = invalidProps[property.name];
|
|
591
|
+
if (!arrayInvalidProps)
|
|
592
|
+
arrayInvalidProps = invalidProps[property.name] = [];
|
|
529
593
|
arrayInvalidProps.push(property);
|
|
530
|
-
else
|
|
594
|
+
} else
|
|
531
595
|
arrayValue.push(sanitizeValue);
|
|
532
596
|
}
|
|
533
597
|
} else
|
|
@@ -548,14 +612,17 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
548
612
|
for (const parameterInfo of methodInfo.parameters) {
|
|
549
613
|
const value = jObject[parameterInfo.name];
|
|
550
614
|
if (value !== void 0) {
|
|
551
|
-
|
|
615
|
+
const sanitizedValue = SANITIZERS[parameterInfo.type](value);
|
|
616
|
+
if (sanitizedValue === null && sanitizedValue != value)
|
|
617
|
+
throw new HttpBadRequestException(CreateProblemDetail(parameterInfo.type, [{ [parameterInfo.name]: `The supplied data type must be "${parameterInfo.type}"` }]));
|
|
618
|
+
jObject[parameterInfo.name] = sanitizedValue;
|
|
552
619
|
}
|
|
553
620
|
}
|
|
554
621
|
return jObject;
|
|
555
622
|
}
|
|
556
623
|
convertToInstance(entityName, data) {
|
|
557
624
|
const typesInfo = this.types[entityName];
|
|
558
|
-
const target = this.getClassTarget(typesInfo.path) || entityContainer.getTarget(entityName);
|
|
625
|
+
const target = this.getClassTarget(typesInfo.path, entityName) || entityContainer.getTarget(entityName);
|
|
559
626
|
let instance = null;
|
|
560
627
|
if (target) {
|
|
561
628
|
instance = new target();
|
|
@@ -564,11 +631,10 @@ class ParameterTypeConverter extends BaseResponse {
|
|
|
564
631
|
instance = data;
|
|
565
632
|
return instance;
|
|
566
633
|
}
|
|
567
|
-
getClassTarget(
|
|
568
|
-
if (
|
|
569
|
-
const classInfo = resolver();
|
|
570
|
-
|
|
571
|
-
return classInfo[name];
|
|
634
|
+
getClassTarget(path, entityName) {
|
|
635
|
+
if (path) {
|
|
636
|
+
const classInfo = nattyContainer.resolver(path);
|
|
637
|
+
return classInfo[entityName];
|
|
572
638
|
}
|
|
573
639
|
return void 0;
|
|
574
640
|
}
|
|
@@ -666,15 +732,6 @@ class RouteParser extends ParameterTypeConverter {
|
|
|
666
732
|
}
|
|
667
733
|
}
|
|
668
734
|
|
|
669
|
-
class UnauthorizedAccessException extends HttpException {
|
|
670
|
-
constructor(data) {
|
|
671
|
-
super({
|
|
672
|
-
body: data,
|
|
673
|
-
status: 401
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
735
|
const decoratorStateContainer = new class {
|
|
679
736
|
constructor() {
|
|
680
737
|
this.controllerConfig = {};
|
|
@@ -720,15 +777,6 @@ var DecoratorType = /* @__PURE__ */ ((DecoratorType2) => {
|
|
|
720
777
|
return DecoratorType2;
|
|
721
778
|
})(DecoratorType || {});
|
|
722
779
|
|
|
723
|
-
class ForbiddenAccessException extends HttpException {
|
|
724
|
-
constructor(data) {
|
|
725
|
-
super({
|
|
726
|
-
body: data,
|
|
727
|
-
status: 403
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
780
|
class AbstractExecutionContext {
|
|
733
781
|
constructor(context, routeInfo) {
|
|
734
782
|
this.context = context;
|
|
@@ -758,14 +806,6 @@ class ActionExecutingContext extends AbstractExecutionContext {
|
|
|
758
806
|
}
|
|
759
807
|
}
|
|
760
808
|
|
|
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
809
|
function runValidators(entityName, value) {
|
|
770
810
|
const typeInfo = nattyContainer.types[entityName];
|
|
771
811
|
let errors = {};
|
|
@@ -827,6 +867,8 @@ class ModelBindingContext extends ParameterTypeConverter {
|
|
|
827
867
|
else
|
|
828
868
|
this.data = body;
|
|
829
869
|
this.instance = this.convertToInstance(this.type, this.data);
|
|
870
|
+
if (!this.isValid)
|
|
871
|
+
throw new HttpBadRequestException(CreateProblemDetail(this.type, this.errors));
|
|
830
872
|
}
|
|
831
873
|
get isValid() {
|
|
832
874
|
const errors = runValidators(this.type, this.instance);
|
|
@@ -843,6 +885,17 @@ class ActionExecutedContext extends AbstractExecutionContext {
|
|
|
843
885
|
super(httpContext, routeInfo);
|
|
844
886
|
this.content = content;
|
|
845
887
|
}
|
|
888
|
+
get response() {
|
|
889
|
+
return this.context.response;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
class AuthorizationContext extends AbstractExecutionContext {
|
|
894
|
+
constructor(models, context, routeInfo, config) {
|
|
895
|
+
super(context, routeInfo);
|
|
896
|
+
this.models = models;
|
|
897
|
+
this.config = config;
|
|
898
|
+
}
|
|
846
899
|
}
|
|
847
900
|
|
|
848
901
|
class RequestProcessor extends RouteParser {
|
|
@@ -855,9 +908,6 @@ class RequestProcessor extends RouteParser {
|
|
|
855
908
|
case RequestPipeline.onAuthentication:
|
|
856
909
|
await this.onAuthentication();
|
|
857
910
|
break;
|
|
858
|
-
case RequestPipeline.onAuthorization:
|
|
859
|
-
await this.onAuthorization();
|
|
860
|
-
break;
|
|
861
911
|
}
|
|
862
912
|
}
|
|
863
913
|
resolveFilter(instance) {
|
|
@@ -878,26 +928,45 @@ class RequestProcessor extends RouteParser {
|
|
|
878
928
|
const authentication = this.getAuthenticationClass();
|
|
879
929
|
const authenticationFilter = authentication ? this.resolveFilter(authentication) : void 0;
|
|
880
930
|
const anonymousInfo = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.anonymous);
|
|
931
|
+
if (!anonymousInfo && !authenticationFilter)
|
|
932
|
+
throw new UnauthorizedAccessException(DENY_BY_DEFAULT);
|
|
881
933
|
if (authenticationFilter) {
|
|
882
934
|
const result = await authenticationFilter.onAuthentication(this.httpContext);
|
|
883
935
|
this.httpContext.user = result;
|
|
884
936
|
if (!result.isAuthenticate && !anonymousInfo.controllerConfig && !anonymousInfo.methodConfig)
|
|
885
937
|
throw new UnauthorizedAccessException(authenticationFilter.onFailedResponse());
|
|
886
|
-
await this.onAuthorization();
|
|
887
938
|
}
|
|
888
939
|
}
|
|
889
|
-
async onAuthorization() {
|
|
940
|
+
async onAuthorization(methodParameters) {
|
|
890
941
|
const authorization = commonContainer.globalConfig.authorization;
|
|
891
942
|
const authorizationFilter = authorization ? this.resolveFilter(authorization) : void 0;
|
|
892
943
|
const authorizeConfig = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.authorize);
|
|
893
944
|
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
|
|
945
|
+
if (this.httpContext.user?.isAuthenticate && authorizationFilter && (authorizeConfig.controllerConfig || authorizeConfig.methodConfig) && (!authenticationOnly.controllerConfig && !authenticationOnly.methodConfig)) {
|
|
946
|
+
const authorizationContext = new AuthorizationContext(
|
|
947
|
+
methodParameters.filter((t) => t instanceof ModelBindingContext),
|
|
948
|
+
this.httpContext,
|
|
949
|
+
this.routeInfo,
|
|
950
|
+
authorizeConfig.methodConfig || authorizeConfig.controllerConfig
|
|
951
|
+
);
|
|
952
|
+
const result = await authorizationFilter.onAuthorization(authorizationContext);
|
|
896
953
|
if (!result)
|
|
897
954
|
throw new ForbiddenAccessException(authorizationFilter.onFailedAuthorization());
|
|
898
955
|
}
|
|
899
956
|
}
|
|
900
|
-
async
|
|
957
|
+
async onModelBinding(methodParameters) {
|
|
958
|
+
const { actionExecutingContext, actionFilters } = this.getExecutingContext(methodParameters);
|
|
959
|
+
for (const actionFilter of actionFilters) {
|
|
960
|
+
const filter = this.resolveFilter(actionFilter);
|
|
961
|
+
this.actionFilterInstances.push(filter);
|
|
962
|
+
await filter.onActionExecuting(actionExecutingContext);
|
|
963
|
+
if (actionExecutingContext.result) {
|
|
964
|
+
return actionExecutingContext.result;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
getExecutingContext(methodParameters) {
|
|
901
970
|
let actionFilters = commonContainer.globalConfig.actionFilters || [];
|
|
902
971
|
const actionFiltersConfig = decoratorStateContainer.getInfo(this.routeInfo.controller.name, this.routeInfo.methodInfo.name, DecoratorType.useFilter);
|
|
903
972
|
actionFilters = [...actionFilters, ...actionFiltersConfig.controllerConfig?.actionFilters || [], ...actionFiltersConfig.methodConfig?.actionFilters || []];
|
|
@@ -906,6 +975,11 @@ class RequestProcessor extends RouteParser {
|
|
|
906
975
|
this.httpContext,
|
|
907
976
|
this.routeInfo
|
|
908
977
|
);
|
|
978
|
+
return { actionFilters, actionExecutingContext };
|
|
979
|
+
}
|
|
980
|
+
async onActionExecuting(methodParameters) {
|
|
981
|
+
await this.onAuthorization(methodParameters);
|
|
982
|
+
const { actionExecutingContext, actionFilters } = this.getExecutingContext(methodParameters);
|
|
909
983
|
for (const actionFilter of actionFilters) {
|
|
910
984
|
const filter = this.resolveFilter(actionFilter);
|
|
911
985
|
this.actionFilterInstances.push(filter);
|
|
@@ -1203,6 +1277,16 @@ function badRequest(value, response) {
|
|
|
1203
1277
|
return new BadRequestResult(value || BLANK$1, response);
|
|
1204
1278
|
}
|
|
1205
1279
|
|
|
1280
|
+
class FileResult extends BaseResult {
|
|
1281
|
+
constructor(value, response) {
|
|
1282
|
+
super({ ...{ isBuffer: true, body: value }, ...{ status: HttpStatusCode.success } }, response);
|
|
1283
|
+
this.value = value;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
function fileResult(value, response) {
|
|
1287
|
+
return new FileResult(value || BLANK$1, response);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1206
1290
|
function base(params, type, additionaConfig) {
|
|
1207
1291
|
decoratorStateContainer.register(params, type, additionaConfig);
|
|
1208
1292
|
}
|
|
@@ -1229,4 +1313,21 @@ function authenticationOnly() {
|
|
|
1229
1313
|
};
|
|
1230
1314
|
}
|
|
1231
1315
|
|
|
1232
|
-
|
|
1316
|
+
function setEnvInfo(envTsDefinition, envValueInfo) {
|
|
1317
|
+
if (envTsDefinition && envValueInfo) {
|
|
1318
|
+
commonContainer.setEnvTsDefinition(envTsDefinition);
|
|
1319
|
+
Object.keys(envValueInfo).forEach((key) => process.env[key] = envValueInfo[key]);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
function authorize(permission) {
|
|
1324
|
+
return function(target, propertyKey, descriptor) {
|
|
1325
|
+
base({
|
|
1326
|
+
target,
|
|
1327
|
+
propertyKey,
|
|
1328
|
+
descriptor
|
|
1329
|
+
}, DecoratorType.authorize, permission);
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
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.40",
|
|
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.40"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"unbuild": "1.2.1"
|