@node-c/api-http 1.0.0-alpha10
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/README.md +4 -0
- package/dist/common/definitions/common.constants.d.ts +9 -0
- package/dist/common/definitions/common.constants.js +14 -0
- package/dist/common/definitions/common.constants.js.map +1 -0
- package/dist/common/definitions/common.definitions.d.ts +9 -0
- package/dist/common/definitions/common.definitions.js +3 -0
- package/dist/common/definitions/common.definitions.js.map +1 -0
- package/dist/common/definitions/common.errors.d.ts +9 -0
- package/dist/common/definitions/common.errors.js +12 -0
- package/dist/common/definitions/common.errors.js.map +1 -0
- package/dist/common/definitions/index.d.ts +2 -0
- package/dist/common/definitions/index.js +19 -0
- package/dist/common/definitions/index.js.map +1 -0
- package/dist/exceptionFilters/http.exceptionFilters.httpException.d.ts +4 -0
- package/dist/exceptionFilters/http.exceptionFilters.httpException.js +26 -0
- package/dist/exceptionFilters/http.exceptionFilters.httpException.js.map +1 -0
- package/dist/exceptionFilters/index.d.ts +1 -0
- package/dist/exceptionFilters/index.js +18 -0
- package/dist/exceptionFilters/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/http.interceptors.authorization.d.ts +11 -0
- package/dist/interceptors/http.interceptors.authorization.js +87 -0
- package/dist/interceptors/http.interceptors.authorization.js.map +1 -0
- package/dist/interceptors/http.interceptors.error.d.ts +5 -0
- package/dist/interceptors/http.interceptors.error.js +66 -0
- package/dist/interceptors/http.interceptors.error.js.map +1 -0
- package/dist/interceptors/index.d.ts +2 -0
- package/dist/interceptors/index.js +19 -0
- package/dist/interceptors/index.js.map +1 -0
- package/dist/middlewares/http.middlewares.authentication.d.ts +13 -0
- package/dist/middlewares/http.middlewares.authentication.js +128 -0
- package/dist/middlewares/http.middlewares.authentication.js.map +1 -0
- package/dist/middlewares/http.middlewares.cors.d.ts +10 -0
- package/dist/middlewares/http.middlewares.cors.js +47 -0
- package/dist/middlewares/http.middlewares.cors.js.map +1 -0
- package/dist/middlewares/index.d.ts +2 -0
- package/dist/middlewares/index.js +19 -0
- package/dist/middlewares/index.js.map +1 -0
- package/dist/module/http.api.module.d.ts +13 -0
- package/dist/module/http.api.module.definitions.d.ts +14 -0
- package/dist/module/http.api.module.definitions.js +3 -0
- package/dist/module/http.api.module.definitions.js.map +1 -0
- package/dist/module/http.api.module.js +82 -0
- package/dist/module/http.api.module.js.map +1 -0
- package/dist/module/index.d.ts +2 -0
- package/dist/module/index.js +19 -0
- package/dist/module/index.js.map +1 -0
- package/package.json +29 -0
- package/src/common/definitions/common.constants.ts +16 -0
- package/src/common/definitions/common.definitions.ts +10 -0
- package/src/common/definitions/common.errors.ts +13 -0
- package/src/common/definitions/index.ts +2 -0
- package/src/exceptionFilters/http.exceptionFilters.httpException.ts +16 -0
- package/src/exceptionFilters/index.ts +1 -0
- package/src/index.ts +5 -0
- package/src/interceptors/http.interceptors.authorization.ts +82 -0
- package/src/interceptors/http.interceptors.error.ts +59 -0
- package/src/interceptors/index.ts +2 -0
- package/src/middlewares/http.middlewares.authentication.ts +111 -0
- package/src/middlewares/http.middlewares.cors.ts +37 -0
- package/src/middlewares/index.ts +2 -0
- package/src/module/http.api.module.definitions.ts +16 -0
- package/src/module/http.api.module.ts +69 -0
- package/src/module/index.ts +2 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.HTTPCORSMiddleware = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const core_1 = require("@node-c/core");
|
|
18
|
+
const definitions_1 = require("../common/definitions");
|
|
19
|
+
let HTTPCORSMiddleware = class HTTPCORSMiddleware {
|
|
20
|
+
constructor(configProvider, moduleName) {
|
|
21
|
+
this.configProvider = configProvider;
|
|
22
|
+
this.moduleName = moduleName;
|
|
23
|
+
}
|
|
24
|
+
use(req, res, next) {
|
|
25
|
+
const allowedOrigins = this.configProvider.config.api[this.moduleName].allowedOrigins;
|
|
26
|
+
const origin = req.headers.origin;
|
|
27
|
+
if (allowedOrigins === null || allowedOrigins === void 0 ? void 0 : allowedOrigins.includes(origin)) {
|
|
28
|
+
res.set('Access-Control-Allow-Origin', origin);
|
|
29
|
+
}
|
|
30
|
+
res.set('Access-Control-Allow-Headers', 'accept,accept-encoding,accept-language,authorization,connection,content-type,host,origin,referer,user-agent');
|
|
31
|
+
res.set('Access-Control-Expose-Headers', 'Authorization');
|
|
32
|
+
res.set('Access-Control-Allow-Methods', 'OPTIONS,GET,POST,PUT,PATCH,DELETE');
|
|
33
|
+
res.set('Access-Control-Allow-Credentials', 'true');
|
|
34
|
+
if (req.method.toLowerCase() === 'options') {
|
|
35
|
+
res.status(common_1.HttpStatus.OK).end();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
next();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
exports.HTTPCORSMiddleware = HTTPCORSMiddleware;
|
|
42
|
+
exports.HTTPCORSMiddleware = HTTPCORSMiddleware = __decorate([
|
|
43
|
+
(0, common_1.Injectable)(),
|
|
44
|
+
__param(1, (0, common_1.Inject)(definitions_1.Constants.API_MODULE_NAME)),
|
|
45
|
+
__metadata("design:paramtypes", [core_1.ConfigProviderService, String])
|
|
46
|
+
], HTTPCORSMiddleware);
|
|
47
|
+
//# sourceMappingURL=http.middlewares.cors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.middlewares.cors.js","sourceRoot":"","sources":["../../src/middlewares/http.middlewares.cors.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAgF;AAEhF,uCAAuE;AAGvE,uDAAqE;AAG9D,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAC7B,YAEY,cAAqC,EAGrC,UAAkB;QAHlB,mBAAc,GAAd,cAAc,CAAuB;QAGrC,eAAU,GAAV,UAAU,CAAQ;IAC3B,CAAC;IAEJ,GAAG,CAAC,GAA+B,EAAE,GAAa,EAAE,IAAkB;QACpE,MAAM,cAAc,GAAI,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,GAAI,CAAC,IAAI,CAAC,UAAU,CAAsB,CAAC,cAAc,CAAC;QAC7G,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAgB,CAAC;QAC5C,IAAI,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,GAAG,CAAC,GAAG,CAAC,6BAA6B,EAAE,MAAM,CAAC,CAAC;QACjD,CAAC;QACD,GAAG,CAAC,GAAG,CACL,8BAA8B,EAC9B,6GAA6G,CAC9G,CAAC;QACF,GAAG,CAAC,GAAG,CAAC,+BAA+B,EAAE,eAAe,CAAC,CAAC;QAC1D,GAAG,CAAC,GAAG,CAAC,8BAA8B,EAAE,mCAAmC,CAAC,CAAC;QAC7E,GAAG,CAAC,GAAG,CAAC,kCAAkC,EAAE,MAAM,CAAC,CAAC;QACpD,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,SAAS,EAAE,CAAC;YAC3C,GAAG,CAAC,MAAM,CAAC,mBAAU,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;YAChC,OAAO;QACT,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC;CACF,CAAA;AA5BY,gDAAkB;6BAAlB,kBAAkB;IAD9B,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,eAAM,EAAC,uBAAS,CAAC,eAAe,CAAC,CAAA;qCADR,4BAAqB;GAHtC,kBAAkB,CA4B9B"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./http.middlewares.authentication"), exports);
|
|
18
|
+
__exportStar(require("./http.middlewares.cors"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oEAAkD;AAClD,0DAAwC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DynamicModule, MiddlewareConsumer } from '@nestjs/common';
|
|
2
|
+
import { ConfigProviderService } from '@node-c/core';
|
|
3
|
+
import { Response } from 'express';
|
|
4
|
+
import { HTTPAPIModuleOptions } from './http.api.module.definitions';
|
|
5
|
+
import { RequestWithLocals } from '../common/definitions';
|
|
6
|
+
export declare class HTTPAPIModule {
|
|
7
|
+
protected configProvider: ConfigProviderService;
|
|
8
|
+
protected moduleName: string;
|
|
9
|
+
constructor(configProvider: ConfigProviderService, moduleName: string);
|
|
10
|
+
configure(consumer: MiddlewareConsumer): void;
|
|
11
|
+
static rawBodyBuffer(req: RequestWithLocals<unknown>, _res: Response, buffer: Buffer): void;
|
|
12
|
+
static register(options: HTTPAPIModuleOptions): DynamicModule;
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ModuleMetadata } from '@nestjs/common';
|
|
2
|
+
import { GenericObject } from '@node-c/core';
|
|
3
|
+
export interface HTTPAPIModuleOptions {
|
|
4
|
+
controllers?: ModuleMetadata['controllers'];
|
|
5
|
+
exports?: ModuleMetadata['exports'];
|
|
6
|
+
folderData: GenericObject<unknown>;
|
|
7
|
+
imports?: {
|
|
8
|
+
atEnd?: ModuleMetadata['imports'];
|
|
9
|
+
atStart?: ModuleMetadata['imports'];
|
|
10
|
+
};
|
|
11
|
+
moduleClass: unknown;
|
|
12
|
+
moduleName: string;
|
|
13
|
+
providers?: ModuleMetadata['providers'];
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.api.module.definitions.js","sourceRoot":"","sources":["../../src/module/http.api.module.definitions.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
var HTTPAPIModule_1;
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.HTTPAPIModule = void 0;
|
|
20
|
+
const common_1 = require("@nestjs/common");
|
|
21
|
+
const core_1 = require("@node-c/core");
|
|
22
|
+
const cookie_parser_1 = __importDefault(require("cookie-parser"));
|
|
23
|
+
const express_1 = __importDefault(require("express"));
|
|
24
|
+
const definitions_1 = require("../common/definitions");
|
|
25
|
+
const exceptionFilters_1 = require("../exceptionFilters");
|
|
26
|
+
const interceptors_1 = require("../interceptors");
|
|
27
|
+
const middlewares_1 = require("../middlewares");
|
|
28
|
+
let HTTPAPIModule = HTTPAPIModule_1 = class HTTPAPIModule {
|
|
29
|
+
constructor(configProvider, moduleName) {
|
|
30
|
+
this.configProvider = configProvider;
|
|
31
|
+
this.moduleName = moduleName;
|
|
32
|
+
}
|
|
33
|
+
configure(consumer) {
|
|
34
|
+
consumer.apply(express_1.default.urlencoded({ verify: HTTPAPIModule_1.rawBodyBuffer, extended: true })).forRoutes('*');
|
|
35
|
+
consumer.apply(express_1.default.json({ verify: HTTPAPIModule_1.rawBodyBuffer })).forRoutes('*');
|
|
36
|
+
consumer.apply((0, cookie_parser_1.default)()).forRoutes('*');
|
|
37
|
+
consumer.apply(middlewares_1.HTTPCORSMiddleware).forRoutes('*');
|
|
38
|
+
consumer.apply(middlewares_1.HTTPAuthenticationMiddleware).forRoutes('*');
|
|
39
|
+
}
|
|
40
|
+
static rawBodyBuffer(req, _res, buffer) {
|
|
41
|
+
if (buffer && buffer.length) {
|
|
42
|
+
req.rawBody = buffer.toString();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
static register(options) {
|
|
46
|
+
const { folderData, imports: additionalImports, moduleClass } = options;
|
|
47
|
+
const { atEnd: importsAtEnd, atStart: importsAtStart } = additionalImports || {};
|
|
48
|
+
const { controllers, services } = (0, core_1.loadDynamicModules)(folderData);
|
|
49
|
+
return {
|
|
50
|
+
module: moduleClass,
|
|
51
|
+
imports: [...(importsAtStart || []), ...(importsAtEnd || [])],
|
|
52
|
+
providers: [
|
|
53
|
+
{
|
|
54
|
+
provide: definitions_1.Constants.API_MODULE_NAME,
|
|
55
|
+
useValue: options.moduleName
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
provide: definitions_1.Constants.AUTHORIZATION_INTERCEPTOR,
|
|
59
|
+
useClass: interceptors_1.HTTPAuthorizationInterceptor
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
provide: definitions_1.Constants.ERROR_INTERCEPTOR,
|
|
63
|
+
useClass: interceptors_1.HTTPErrorInterceptor
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
provide: definitions_1.Constants.HTTP_EXCEPTION_FILTER,
|
|
67
|
+
useClass: exceptionFilters_1.HttpExceptionFilter
|
|
68
|
+
},
|
|
69
|
+
...(options.providers || []),
|
|
70
|
+
...(services || [])
|
|
71
|
+
],
|
|
72
|
+
controllers: [...(controllers || []), ...(options.controllers || [])],
|
|
73
|
+
exports: [...(services || []), ...(options.exports || [])]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
exports.HTTPAPIModule = HTTPAPIModule;
|
|
78
|
+
exports.HTTPAPIModule = HTTPAPIModule = HTTPAPIModule_1 = __decorate([
|
|
79
|
+
__param(1, (0, common_1.Inject)(definitions_1.Constants.API_MODULE_NAME)),
|
|
80
|
+
__metadata("design:paramtypes", [core_1.ConfigProviderService, String])
|
|
81
|
+
], HTTPAPIModule);
|
|
82
|
+
//# sourceMappingURL=http.api.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.api.module.js","sourceRoot":"","sources":["../../src/module/http.api.module.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./http.api.module"), exports);
|
|
18
|
+
__exportStar(require("./http.api.module.definitions"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/module/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,gEAA8C"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@node-c/api-http",
|
|
3
|
+
"version": "1.0.0-alpha10",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -p tsconfig.build.json",
|
|
8
|
+
"check-types": "tsc -p tsconfig.build.json --noEmit",
|
|
9
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
10
|
+
"publish-package": "rm -rf dist/* && rm -f *.tsbuildinfo && npm run build && npm publish --access public",
|
|
11
|
+
"test": "vitest --config src/vitest.config.ts",
|
|
12
|
+
"test:coverage": "vitest --config src/vitest.config.ts --coverage"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@nestjs/common": "^10.4.12",
|
|
16
|
+
"@ramster/general-tools": "^2.3.0",
|
|
17
|
+
"cookie-parser": "^1.4.7",
|
|
18
|
+
"express": "^4.21.2",
|
|
19
|
+
"rxjs": "^7.8.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/cookie-parser": "^1.4.8",
|
|
23
|
+
"@types/express": "^5.0.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@node-c/core": "^1.0.0-alpha10",
|
|
27
|
+
"@node-c/domain-iam": "^1.0.0-alpha10"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export enum Constants {
|
|
2
|
+
// eslint-disable-next-line no-unused-vars
|
|
3
|
+
API_MODULE_AUTHORIZATION_SERVICE = 'API_MODULE_AUTHORIZATION_SERVICE',
|
|
4
|
+
// eslint-disable-next-line no-unused-vars
|
|
5
|
+
API_MODULE_NAME = 'API_MODULE_NAME',
|
|
6
|
+
// eslint-disable-next-line no-unused-vars
|
|
7
|
+
AUTHENTICATION_MIDDLEWARE_TOKEN_MANAGER_SERVICE = 'AUTHENTICATION_MIDDLEWARE_TOKEN_MANAGER_SERVICE',
|
|
8
|
+
// eslint-disable-next-line no-unused-vars
|
|
9
|
+
AUTHENTICATION_MIDDLEWARE_USERS_SERVICE = 'AUTHENTICATION_MIDDLEWARE_USERS_SERVICE',
|
|
10
|
+
// eslint-disable-next-line no-unused-vars
|
|
11
|
+
AUTHORIZATION_INTERCEPTOR = 'AUTHORIZATION_INTERCEPTOR',
|
|
12
|
+
// eslint-disable-next-line no-unused-vars
|
|
13
|
+
ERROR_INTERCEPTOR = 'ERROR_INTERCEPTOR',
|
|
14
|
+
// eslint-disable-next-line no-unused-vars
|
|
15
|
+
HTTP_EXCEPTION_FILTER = 'HTTP_EXCEPTION_FILTER'
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { GenericObject } from '@node-c/core';
|
|
2
|
+
|
|
3
|
+
export class ServerError implements Error {
|
|
4
|
+
data: { statusCode: number } | GenericObject;
|
|
5
|
+
message: string;
|
|
6
|
+
name: string;
|
|
7
|
+
|
|
8
|
+
constructor(message: string, data?: GenericObject) {
|
|
9
|
+
this.message = message;
|
|
10
|
+
this.name = 'ServerError';
|
|
11
|
+
this.data = data || {};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
|
|
2
|
+
import { Response } from 'express';
|
|
3
|
+
|
|
4
|
+
// The purpose of the class is to handle HttpExceptions that are not caught by the HTTPErrorInterceptor.
|
|
5
|
+
@Catch(HttpException)
|
|
6
|
+
export class HttpExceptionFilter implements ExceptionFilter {
|
|
7
|
+
catch(exception: HttpException, host: ArgumentsHost): void {
|
|
8
|
+
const ctx = host.switchToHttp();
|
|
9
|
+
const response = ctx.getResponse<Response>();
|
|
10
|
+
const status = exception.getStatus();
|
|
11
|
+
response.status(status).json({
|
|
12
|
+
statusCode: status,
|
|
13
|
+
message: exception.message
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './http.exceptionFilters.httpException';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CallHandler,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
HttpException,
|
|
5
|
+
HttpStatus,
|
|
6
|
+
Inject,
|
|
7
|
+
Injectable,
|
|
8
|
+
NestInterceptor
|
|
9
|
+
} from '@nestjs/common';
|
|
10
|
+
|
|
11
|
+
import { ConfigProviderService, EndpointSecurityMode } from '@node-c/core';
|
|
12
|
+
import { AuthorizationPoint, IAMAuthorizationService, UserWithPermissionsData } from '@node-c/domain-iam';
|
|
13
|
+
|
|
14
|
+
import { setNested } from '@ramster/general-tools';
|
|
15
|
+
import { Observable } from 'rxjs';
|
|
16
|
+
|
|
17
|
+
import { Constants, RequestWithLocals } from '../common/definitions';
|
|
18
|
+
|
|
19
|
+
@Injectable()
|
|
20
|
+
export class HTTPAuthorizationInterceptor<User extends UserWithPermissionsData<unknown, unknown>>
|
|
21
|
+
implements NestInterceptor
|
|
22
|
+
{
|
|
23
|
+
constructor(
|
|
24
|
+
@Inject(Constants.API_MODULE_AUTHORIZATION_SERVICE)
|
|
25
|
+
// eslint-disable-next-line no-unused-vars
|
|
26
|
+
protected authorizationService: IAMAuthorizationService<AuthorizationPoint<unknown>>,
|
|
27
|
+
// eslint-disable-next-line no-unused-vars
|
|
28
|
+
protected configProvider: ConfigProviderService,
|
|
29
|
+
@Inject(Constants.API_MODULE_NAME)
|
|
30
|
+
// eslint-disable-next-line no-unused-vars
|
|
31
|
+
protected moduleName: string
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<unknown>> {
|
|
35
|
+
const [req]: [RequestWithLocals<User>, unknown] = context.getArgs();
|
|
36
|
+
const locals = req.locals!;
|
|
37
|
+
if (!locals) {
|
|
38
|
+
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
|
|
39
|
+
} else if (locals.isAnonymous) {
|
|
40
|
+
return next.handle();
|
|
41
|
+
}
|
|
42
|
+
const { moduleName } = this;
|
|
43
|
+
const controllerName = context.getClass().name;
|
|
44
|
+
const handlerName = context.getHandler().name;
|
|
45
|
+
// TODO: cache this in-memory
|
|
46
|
+
const authorizationData = await this.authorizationService.mapAuthorizationPoints(moduleName);
|
|
47
|
+
let controllerData = authorizationData![controllerName];
|
|
48
|
+
if (!controllerData) {
|
|
49
|
+
controllerData = authorizationData.__all;
|
|
50
|
+
}
|
|
51
|
+
const user = locals.user!; // we'll always have this, otherwise the system has not been configured properly
|
|
52
|
+
let handlerData = controllerData[handlerName];
|
|
53
|
+
if (!handlerData) {
|
|
54
|
+
handlerData = controllerData.__all;
|
|
55
|
+
if (!Object.keys(handlerData).length) {
|
|
56
|
+
const { endpointSecurityMode } = this.configProvider.config.api[moduleName];
|
|
57
|
+
if (!endpointSecurityMode || endpointSecurityMode === EndpointSecurityMode.Strict) {
|
|
58
|
+
console.info(
|
|
59
|
+
`[${moduleName}][HTTPAuthorizationInterceptor]: No authorization point data for handler ${controllerName}.${handlerName}.`
|
|
60
|
+
);
|
|
61
|
+
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
|
|
62
|
+
}
|
|
63
|
+
return next.handle();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const { hasAccess, inputDataToBeMutated } = IAMAuthorizationService.checkAccess(
|
|
67
|
+
handlerData,
|
|
68
|
+
{ body: req.body, headers: req.headers, params: req.params, query: req.query },
|
|
69
|
+
user
|
|
70
|
+
);
|
|
71
|
+
if (!hasAccess) {
|
|
72
|
+
console.info(
|
|
73
|
+
`[${moduleName}][HTTPAuthorizationInterceptor]: No user access to handler ${controllerName}.${handlerName}.`
|
|
74
|
+
);
|
|
75
|
+
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
|
|
76
|
+
}
|
|
77
|
+
for (const key in inputDataToBeMutated) {
|
|
78
|
+
setNested(req, key, inputDataToBeMutated[key]);
|
|
79
|
+
}
|
|
80
|
+
return next.handle();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { ApplicationError } from '@node-c/core';
|
|
4
|
+
|
|
5
|
+
import { Observable } from 'rxjs';
|
|
6
|
+
import { catchError } from 'rxjs/operators';
|
|
7
|
+
|
|
8
|
+
import { ServerError } from '../common/definitions/common.errors';
|
|
9
|
+
|
|
10
|
+
@Injectable()
|
|
11
|
+
export class HTTPErrorInterceptor implements NestInterceptor {
|
|
12
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
|
13
|
+
return next.handle().pipe(
|
|
14
|
+
catchError(error => {
|
|
15
|
+
console.error(error);
|
|
16
|
+
let message: string | string[] = 'An error has occurred.';
|
|
17
|
+
let status = 500;
|
|
18
|
+
if (error instanceof ApplicationError || error instanceof ServerError) {
|
|
19
|
+
if (error.message) {
|
|
20
|
+
message = error.message;
|
|
21
|
+
}
|
|
22
|
+
if (error.data) {
|
|
23
|
+
if ('errorCode' in error.data) {
|
|
24
|
+
status = error.data.errorCode as number;
|
|
25
|
+
} else if ('statusCode' in error.data) {
|
|
26
|
+
status = error.data.statusCode as number;
|
|
27
|
+
} else {
|
|
28
|
+
status = 400;
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
status = 400;
|
|
32
|
+
}
|
|
33
|
+
} else if (error.response) {
|
|
34
|
+
const { response } = error;
|
|
35
|
+
if (response.statusCode) {
|
|
36
|
+
status = response.statusCode;
|
|
37
|
+
}
|
|
38
|
+
if (response.message) {
|
|
39
|
+
message = response.message;
|
|
40
|
+
}
|
|
41
|
+
} else if (error instanceof Error) {
|
|
42
|
+
if (error.message) {
|
|
43
|
+
message = error.message;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// TODO: fix this, as we're still getting error 500 when throwing the exception
|
|
47
|
+
context
|
|
48
|
+
.switchToHttp()
|
|
49
|
+
.getResponse()
|
|
50
|
+
.status(status)
|
|
51
|
+
.json({ error: message instanceof Array ? message.join('\n') : message });
|
|
52
|
+
return [];
|
|
53
|
+
// return throwError(
|
|
54
|
+
// () => new HttpException({ error: message instanceof Array ? message.join('\n') : message }, status)
|
|
55
|
+
// );
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { HttpException, HttpStatus, Inject, Injectable, NestMiddleware } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { AppConfigAPIHTTP, ConfigProviderService } from '@node-c/core';
|
|
4
|
+
import { DecodedTokenContent, IAMTokenManagerService, IAMUsersService, UserTokenEnityFields } from '@node-c/domain-iam';
|
|
5
|
+
|
|
6
|
+
import { checkRoutes } from '@ramster/general-tools';
|
|
7
|
+
|
|
8
|
+
import { NextFunction, Response } from 'express';
|
|
9
|
+
|
|
10
|
+
import { Constants, RequestWithLocals } from '../common/definitions';
|
|
11
|
+
|
|
12
|
+
@Injectable()
|
|
13
|
+
export class HTTPAuthenticationMiddleware<User extends object> implements NestMiddleware {
|
|
14
|
+
constructor(
|
|
15
|
+
// eslint-disable-next-line no-unused-vars
|
|
16
|
+
protected configProvider: ConfigProviderService,
|
|
17
|
+
@Inject(Constants.API_MODULE_NAME)
|
|
18
|
+
// eslint-disable-next-line no-unused-vars
|
|
19
|
+
protected moduleName: string,
|
|
20
|
+
@Inject(Constants.AUTHENTICATION_MIDDLEWARE_TOKEN_MANAGER_SERVICE)
|
|
21
|
+
// eslint-disable-next-line no-unused-vars
|
|
22
|
+
protected tokenManager: IAMTokenManagerService<UserTokenEnityFields>,
|
|
23
|
+
@Inject(Constants.AUTHENTICATION_MIDDLEWARE_USERS_SERVICE)
|
|
24
|
+
// eslint-disable-next-line no-unused-vars
|
|
25
|
+
protected usersService: IAMUsersService<User>
|
|
26
|
+
) {}
|
|
27
|
+
|
|
28
|
+
use(req: RequestWithLocals<unknown>, res: Response, next: NextFunction): void {
|
|
29
|
+
(async () => {
|
|
30
|
+
const { anonymousAccessRoutes } = this.configProvider.config.api![this.moduleName] as AppConfigAPIHTTP;
|
|
31
|
+
if (!req.locals) {
|
|
32
|
+
req.locals = {};
|
|
33
|
+
}
|
|
34
|
+
if (anonymousAccessRoutes && Object.keys(anonymousAccessRoutes).length) {
|
|
35
|
+
const originalUrl = req.originalUrl.split('?')[0];
|
|
36
|
+
let isAnonymous = false;
|
|
37
|
+
for (const route in anonymousAccessRoutes) {
|
|
38
|
+
if (
|
|
39
|
+
checkRoutes(originalUrl, [route]) &&
|
|
40
|
+
anonymousAccessRoutes[route].find(method => method === req.method.toLowerCase())
|
|
41
|
+
) {
|
|
42
|
+
isAnonymous = true;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (isAnonymous) {
|
|
47
|
+
req.locals.isAnonymous = true;
|
|
48
|
+
next();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const { tokenManager, usersService } = this;
|
|
53
|
+
let tokens: string[] = [];
|
|
54
|
+
let authToken = req.headers.authorization;
|
|
55
|
+
let authTokenIsNew = false;
|
|
56
|
+
let refreshToken: string | undefined;
|
|
57
|
+
let tokenContent: DecodedTokenContent<UserTokenEnityFields> | undefined;
|
|
58
|
+
let useCookie = false;
|
|
59
|
+
if (typeof authToken === 'string' && authToken.length && authToken.match(/^Bearer\s/)) {
|
|
60
|
+
tokens = authToken.split(' ');
|
|
61
|
+
if (tokens.length) {
|
|
62
|
+
authToken = tokens[1];
|
|
63
|
+
refreshToken = tokens[2];
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
authToken = req.cookies['sid'];
|
|
67
|
+
useCookie = true;
|
|
68
|
+
}
|
|
69
|
+
if (!authToken) {
|
|
70
|
+
console.error('Missing auth token.');
|
|
71
|
+
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const tokenRes = await tokenManager.verifyAccessToken(authToken, {
|
|
75
|
+
deleteFromStoreIfExpired: true,
|
|
76
|
+
identifierDataField: 'userId',
|
|
77
|
+
persistNewToken: true,
|
|
78
|
+
purgeStoreOnRenew: true,
|
|
79
|
+
refreshToken,
|
|
80
|
+
refreshTokenAccessTokenIdentifierDataField: 'accessToken'
|
|
81
|
+
});
|
|
82
|
+
tokenContent = tokenRes.content!;
|
|
83
|
+
if (tokenRes.newToken) {
|
|
84
|
+
authTokenIsNew = true;
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.error('Failed to parse the access or refresh token:', e);
|
|
88
|
+
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
|
|
89
|
+
}
|
|
90
|
+
if (authTokenIsNew) {
|
|
91
|
+
res.setHeader('Authorization', `Bearer ${authToken}${refreshToken ? ` ${refreshToken}` : ''}`);
|
|
92
|
+
if (useCookie) {
|
|
93
|
+
res.cookie('sid', authToken);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const userId = tokenContent?.data?.userId;
|
|
97
|
+
if (!userId) {
|
|
98
|
+
console.error('Missing userId in the tokenContent data.');
|
|
99
|
+
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
|
|
100
|
+
}
|
|
101
|
+
req.locals!.user = await usersService.getUserWithPermissionsData({ filters: { id: userId } });
|
|
102
|
+
next();
|
|
103
|
+
})().then(
|
|
104
|
+
() => true,
|
|
105
|
+
err => {
|
|
106
|
+
console.error(err);
|
|
107
|
+
res.status((err && err.status) || HttpStatus.INTERNAL_SERVER_ERROR).end();
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { HttpStatus, Inject, Injectable, NestMiddleware } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { AppConfigAPIHTTP, ConfigProviderService } from '@node-c/core';
|
|
4
|
+
import { NextFunction, Response } from 'express';
|
|
5
|
+
|
|
6
|
+
import { Constants, RequestWithLocals } from '../common/definitions';
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class HTTPCORSMiddleware implements NestMiddleware {
|
|
10
|
+
constructor(
|
|
11
|
+
// eslint-disable-next-line no-unused-vars
|
|
12
|
+
protected configProvider: ConfigProviderService,
|
|
13
|
+
@Inject(Constants.API_MODULE_NAME)
|
|
14
|
+
// eslint-disable-next-line no-unused-vars
|
|
15
|
+
protected moduleName: string
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
use(req: RequestWithLocals<unknown>, res: Response, next: NextFunction): void {
|
|
19
|
+
const allowedOrigins = (this.configProvider.config.api![this.moduleName] as AppConfigAPIHTTP).allowedOrigins;
|
|
20
|
+
const origin = req.headers.origin as string;
|
|
21
|
+
if (allowedOrigins?.includes(origin)) {
|
|
22
|
+
res.set('Access-Control-Allow-Origin', origin);
|
|
23
|
+
}
|
|
24
|
+
res.set(
|
|
25
|
+
'Access-Control-Allow-Headers',
|
|
26
|
+
'accept,accept-encoding,accept-language,authorization,connection,content-type,host,origin,referer,user-agent'
|
|
27
|
+
);
|
|
28
|
+
res.set('Access-Control-Expose-Headers', 'Authorization');
|
|
29
|
+
res.set('Access-Control-Allow-Methods', 'OPTIONS,GET,POST,PUT,PATCH,DELETE');
|
|
30
|
+
res.set('Access-Control-Allow-Credentials', 'true');
|
|
31
|
+
if (req.method.toLowerCase() === 'options') {
|
|
32
|
+
res.status(HttpStatus.OK).end();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
next();
|
|
36
|
+
}
|
|
37
|
+
}
|