@node-c/api-http 1.0.0-alpha37 → 1.0.0-alpha39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/common/definitions/common.constants.ts +0 -16
- package/src/common/definitions/common.definitions.ts +0 -10
- package/src/common/definitions/common.errors.ts +0 -13
- package/src/common/definitions/index.ts +0 -2
- package/src/common/utils/index.ts +0 -1
- package/src/common/utils/utils.checkRoutes.ts +0 -31
- package/src/exceptionFilters/http.exceptionFilters.httpException.ts +0 -16
- package/src/exceptionFilters/index.ts +0 -1
- package/src/index.ts +0 -5
- package/src/interceptors/http.interceptors.authorization.ts +0 -82
- package/src/interceptors/http.interceptors.error.ts +0 -55
- package/src/interceptors/index.ts +0 -2
- package/src/middlewares/http.middlewares.authentication.ts +0 -158
- package/src/middlewares/http.middlewares.cors.ts +0 -37
- package/src/middlewares/index.ts +0 -2
- package/src/module/http.api.module.definitions.ts +0 -16
- package/src/module/http.api.module.ts +0 -83
- package/src/module/index.ts +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node-c/api-http",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-alpha39",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@types/lodash": "^4.17.19"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@node-c/core": "^1.0.0-
|
|
29
|
-
"@node-c/domain-iam": "^1.0.0-
|
|
28
|
+
"@node-c/core": "^1.0.0-alpha39",
|
|
29
|
+
"@node-c/domain-iam": "^1.0.0-alpha39"
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -1,16 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './utils.checkRoutes';
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Checks whether a route exists in a list of HTTP routes. Supports ExpressJS-style route parameters, i.e. /users/item/:id.
|
|
3
|
-
* @param route (required) - The route to be checked.
|
|
4
|
-
* @param routes (required) - The array of routes to check in.
|
|
5
|
-
* @returns A boolean, which is the result of the check.
|
|
6
|
-
*/
|
|
7
|
-
export function checkRoutes(route: string, routes: string[]): boolean {
|
|
8
|
-
const splitRoute = route.split('/');
|
|
9
|
-
for (const i in routes) {
|
|
10
|
-
const item = routes[i],
|
|
11
|
-
splitItem = item.split('/');
|
|
12
|
-
if (item === '*' || route === item) {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
if (item.indexOf(':') !== -1 && splitItem.length === splitRoute.length) {
|
|
16
|
-
let valid = true;
|
|
17
|
-
for (const j in splitItem) {
|
|
18
|
-
const innerItem = splitItem[j],
|
|
19
|
-
routeItem = splitRoute[j];
|
|
20
|
-
if (routeItem !== innerItem && innerItem.indexOf(':') === -1) {
|
|
21
|
-
valid = false;
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
if (valid) {
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './http.exceptionFilters.httpException';
|
package/src/index.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
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 ld from 'lodash';
|
|
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
|
-
ld.set(req, key, inputDataToBeMutated[key]);
|
|
79
|
-
}
|
|
80
|
-
return next.handle();
|
|
81
|
-
}
|
|
82
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
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
|
-
context
|
|
47
|
-
.switchToHttp()
|
|
48
|
-
.getResponse()
|
|
49
|
-
.status(status)
|
|
50
|
-
.json({ error: message instanceof Array ? message.join('\n') : message });
|
|
51
|
-
return new Observable();
|
|
52
|
-
})
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import crypto from 'crypto';
|
|
2
|
-
|
|
3
|
-
import { HttpException, HttpStatus, Inject, Injectable, NestMiddleware } from '@nestjs/common';
|
|
4
|
-
|
|
5
|
-
import { AppConfigAPIHTTP, ConfigProviderService } from '@node-c/core';
|
|
6
|
-
import { DecodedTokenContent, IAMTokenManagerService, IAMUsersService, UserTokenEnityFields } from '@node-c/domain-iam';
|
|
7
|
-
|
|
8
|
-
import { NextFunction, Response } from 'express';
|
|
9
|
-
|
|
10
|
-
import { Constants, RequestWithLocals } from '../common/definitions';
|
|
11
|
-
import { checkRoutes } from '../common/utils';
|
|
12
|
-
|
|
13
|
-
@Injectable()
|
|
14
|
-
export class HTTPAuthenticationMiddleware<User extends object> implements NestMiddleware {
|
|
15
|
-
constructor(
|
|
16
|
-
// eslint-disable-next-line no-unused-vars
|
|
17
|
-
protected configProvider: ConfigProviderService,
|
|
18
|
-
@Inject(Constants.API_MODULE_NAME)
|
|
19
|
-
// eslint-disable-next-line no-unused-vars
|
|
20
|
-
protected moduleName: string,
|
|
21
|
-
@Inject(Constants.AUTHENTICATION_MIDDLEWARE_TOKEN_MANAGER_SERVICE)
|
|
22
|
-
// eslint-disable-next-line no-unused-vars
|
|
23
|
-
protected tokenManager?: IAMTokenManagerService<UserTokenEnityFields>,
|
|
24
|
-
@Inject(Constants.AUTHENTICATION_MIDDLEWARE_USERS_SERVICE)
|
|
25
|
-
// eslint-disable-next-line no-unused-vars
|
|
26
|
-
protected usersService?: IAMUsersService<User>
|
|
27
|
-
) {}
|
|
28
|
-
|
|
29
|
-
use(req: RequestWithLocals<unknown>, res: Response, next: NextFunction): void {
|
|
30
|
-
(async () => {
|
|
31
|
-
const { anonymousAccessRoutes, apiKey, apiSecret, apiSecretAlgorithm } = this.configProvider.config.api![
|
|
32
|
-
this.moduleName
|
|
33
|
-
] as AppConfigAPIHTTP;
|
|
34
|
-
const requestMethod = req.method.toLowerCase();
|
|
35
|
-
if (!req.locals) {
|
|
36
|
-
req.locals = {};
|
|
37
|
-
}
|
|
38
|
-
if (anonymousAccessRoutes && Object.keys(anonymousAccessRoutes).length) {
|
|
39
|
-
const originalUrl = req.originalUrl.split('?')[0];
|
|
40
|
-
let isAnonymous = false;
|
|
41
|
-
for (const route in anonymousAccessRoutes) {
|
|
42
|
-
if (
|
|
43
|
-
checkRoutes(originalUrl, [route]) &&
|
|
44
|
-
anonymousAccessRoutes[route].find(method => method === requestMethod)
|
|
45
|
-
) {
|
|
46
|
-
isAnonymous = true;
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (isAnonymous) {
|
|
51
|
-
req.locals.isAnonymous = true;
|
|
52
|
-
next();
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const { tokenManager, usersService } = this;
|
|
57
|
-
if (apiKey) {
|
|
58
|
-
const [apiKeyFromHeader, requestSignature] =
|
|
59
|
-
req.headers.authorization?.replace(/^ApiKey\s/, '')?.split(' ') || [];
|
|
60
|
-
if (apiKey !== apiKeyFromHeader) {
|
|
61
|
-
console.error(`${apiKeyFromHeader?.length ? 'Invalid' : 'Missing'} api key in the authorization header.`);
|
|
62
|
-
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
|
|
63
|
-
}
|
|
64
|
-
if (apiSecret && apiSecretAlgorithm) {
|
|
65
|
-
if (!requestSignature) {
|
|
66
|
-
console.error('Missing request signature in the authorization header.');
|
|
67
|
-
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
|
|
68
|
-
}
|
|
69
|
-
let signatureContent = '';
|
|
70
|
-
if (requestMethod === 'get' && req.query && Object.keys(req.query).length) {
|
|
71
|
-
signatureContent = JSON.stringify(req.query);
|
|
72
|
-
} else if (
|
|
73
|
-
(requestMethod === 'patch' || requestMethod === 'post' || requestMethod === 'put') &&
|
|
74
|
-
req.body &&
|
|
75
|
-
Object.keys(req.body).length
|
|
76
|
-
) {
|
|
77
|
-
signatureContent = JSON.stringify(req.body);
|
|
78
|
-
} else {
|
|
79
|
-
signatureContent = req.originalUrl.split('?')[0];
|
|
80
|
-
}
|
|
81
|
-
const calcualtedSignature = crypto
|
|
82
|
-
.createHmac(apiSecretAlgorithm, apiSecret)
|
|
83
|
-
.update(signatureContent)
|
|
84
|
-
.digest('hex');
|
|
85
|
-
if (calcualtedSignature !== requestSignature) {
|
|
86
|
-
console.error(
|
|
87
|
-
`Invalid request signature in the authorization header. Expected: ${calcualtedSignature}. Provided: ${requestSignature}`
|
|
88
|
-
);
|
|
89
|
-
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
next();
|
|
93
|
-
return;
|
|
94
|
-
} else if (!tokenManager) {
|
|
95
|
-
console.error('Missing api key in the configuration and no tokenManager set up.');
|
|
96
|
-
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
|
|
97
|
-
}
|
|
98
|
-
let tokens: string[] = [];
|
|
99
|
-
let authToken = req.headers.authorization;
|
|
100
|
-
let authTokenIsNew = false;
|
|
101
|
-
let refreshToken: string | undefined;
|
|
102
|
-
let tokenContent: DecodedTokenContent<UserTokenEnityFields> | undefined;
|
|
103
|
-
let useCookie = false;
|
|
104
|
-
if (typeof authToken === 'string' && authToken.length && authToken.match(/^Bearer\s/)) {
|
|
105
|
-
tokens = authToken.split(' ');
|
|
106
|
-
if (tokens.length) {
|
|
107
|
-
authToken = tokens[1];
|
|
108
|
-
refreshToken = tokens[2];
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
authToken = req.cookies['sid'];
|
|
112
|
-
useCookie = true;
|
|
113
|
-
}
|
|
114
|
-
if (!authToken) {
|
|
115
|
-
console.error('Missing auth token.');
|
|
116
|
-
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
const tokenRes = await tokenManager.verifyAccessToken(authToken, {
|
|
120
|
-
deleteFromStoreIfExpired: true,
|
|
121
|
-
identifierDataField: usersService ? 'userId' : undefined,
|
|
122
|
-
persistNewToken: true,
|
|
123
|
-
purgeStoreOnRenew: true,
|
|
124
|
-
refreshToken,
|
|
125
|
-
refreshTokenAccessTokenIdentifierDataField: 'accessToken'
|
|
126
|
-
});
|
|
127
|
-
tokenContent = tokenRes.content!;
|
|
128
|
-
if (tokenRes.newToken) {
|
|
129
|
-
authTokenIsNew = true;
|
|
130
|
-
}
|
|
131
|
-
} catch (e) {
|
|
132
|
-
console.error('Failed to parse the access or refresh token:', e);
|
|
133
|
-
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
|
|
134
|
-
}
|
|
135
|
-
if (authTokenIsNew) {
|
|
136
|
-
res.setHeader('Authorization', `Bearer ${authToken}${refreshToken ? ` ${refreshToken}` : ''}`);
|
|
137
|
-
if (useCookie) {
|
|
138
|
-
res.cookie('sid', authToken);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
if (usersService) {
|
|
142
|
-
const userId = tokenContent?.data?.userId;
|
|
143
|
-
if (!userId) {
|
|
144
|
-
console.error('Missing userId in the tokenContent data.');
|
|
145
|
-
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
|
|
146
|
-
}
|
|
147
|
-
req.locals!.user = await usersService.getUserWithPermissionsData({ filters: { id: userId } });
|
|
148
|
-
}
|
|
149
|
-
next();
|
|
150
|
-
})().then(
|
|
151
|
-
() => true,
|
|
152
|
-
err => {
|
|
153
|
-
console.error(err);
|
|
154
|
-
res.status((err && err.status) || HttpStatus.INTERNAL_SERVER_ERROR).end();
|
|
155
|
-
}
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
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-Allow-Methods', 'OPTIONS,GET,POST,PUT,PATCH,DELETE');
|
|
29
|
-
res.set('Access-Control-Allow-Credentials', 'true');
|
|
30
|
-
res.set('Access-Control-Expose-Headers', 'Authorization');
|
|
31
|
-
if (req.method.toLowerCase() === 'options') {
|
|
32
|
-
res.status(HttpStatus.OK).end();
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
next();
|
|
36
|
-
}
|
|
37
|
-
}
|
package/src/middlewares/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { ModuleMetadata } from '@nestjs/common';
|
|
2
|
-
|
|
3
|
-
import { GenericObject } from '@node-c/core';
|
|
4
|
-
|
|
5
|
-
export interface HTTPAPIModuleOptions {
|
|
6
|
-
controllers?: ModuleMetadata['controllers'];
|
|
7
|
-
exports?: ModuleMetadata['exports'];
|
|
8
|
-
folderData: GenericObject<unknown>;
|
|
9
|
-
imports?: {
|
|
10
|
-
atEnd?: ModuleMetadata['imports'];
|
|
11
|
-
atStart?: ModuleMetadata['imports'];
|
|
12
|
-
};
|
|
13
|
-
moduleClass: unknown;
|
|
14
|
-
moduleName: string;
|
|
15
|
-
providers?: ModuleMetadata['providers'];
|
|
16
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { DynamicModule, Inject, MiddlewareConsumer, ModuleMetadata, ValidationPipe } from '@nestjs/common';
|
|
2
|
-
import { APP_PIPE } from '@nestjs/core';
|
|
3
|
-
|
|
4
|
-
import { ConfigProviderService, loadDynamicModules } from '@node-c/core';
|
|
5
|
-
|
|
6
|
-
import cookieParser from 'cookie-parser';
|
|
7
|
-
import express, { Response } from 'express';
|
|
8
|
-
import morgan from 'morgan';
|
|
9
|
-
|
|
10
|
-
import { HTTPAPIModuleOptions } from './http.api.module.definitions';
|
|
11
|
-
|
|
12
|
-
import { Constants, RequestWithLocals } from '../common/definitions';
|
|
13
|
-
import { HttpExceptionFilter } from '../exceptionFilters';
|
|
14
|
-
import { HTTPAuthorizationInterceptor, HTTPErrorInterceptor } from '../interceptors';
|
|
15
|
-
import { HTTPAuthenticationMiddleware, HTTPCORSMiddleware } from '../middlewares';
|
|
16
|
-
|
|
17
|
-
export class HTTPAPIModule {
|
|
18
|
-
constructor(
|
|
19
|
-
// eslint-disable-next-line no-unused-vars
|
|
20
|
-
protected configProvider: ConfigProviderService,
|
|
21
|
-
@Inject(Constants.API_MODULE_NAME)
|
|
22
|
-
// eslint-disable-next-line no-unused-vars
|
|
23
|
-
protected moduleName: string
|
|
24
|
-
) {}
|
|
25
|
-
|
|
26
|
-
configure(consumer: MiddlewareConsumer): void {
|
|
27
|
-
consumer.apply(express.urlencoded({ verify: HTTPAPIModule.rawBodyBuffer, extended: true })).forRoutes('*');
|
|
28
|
-
consumer.apply(express.json({ verify: HTTPAPIModule.rawBodyBuffer })).forRoutes('*');
|
|
29
|
-
consumer.apply(cookieParser()).forRoutes('*');
|
|
30
|
-
// configure logging
|
|
31
|
-
consumer
|
|
32
|
-
.apply(morgan(`[${this.moduleName}]: :method :url :status :res[content-length] - :response-time ms`))
|
|
33
|
-
.forRoutes('*');
|
|
34
|
-
consumer.apply(HTTPCORSMiddleware).forRoutes('*');
|
|
35
|
-
consumer.apply(HTTPAuthenticationMiddleware).forRoutes('*');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
static rawBodyBuffer(req: RequestWithLocals<unknown>, _res: Response, buffer: Buffer): void {
|
|
39
|
-
if (buffer && buffer.length) {
|
|
40
|
-
req.rawBody = buffer.toString();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
static register(options: HTTPAPIModuleOptions): DynamicModule {
|
|
45
|
-
const { folderData, imports: additionalImports, moduleClass } = options;
|
|
46
|
-
const { atEnd: importsAtEnd, atStart: importsAtStart } = additionalImports || {};
|
|
47
|
-
const { controllers, services } = loadDynamicModules(folderData);
|
|
48
|
-
return {
|
|
49
|
-
module: moduleClass as DynamicModule['module'],
|
|
50
|
-
imports: [...(importsAtStart || []), ...(importsAtEnd || [])],
|
|
51
|
-
providers: [
|
|
52
|
-
// configure DTO validation
|
|
53
|
-
{
|
|
54
|
-
provide: APP_PIPE,
|
|
55
|
-
// useClass: ValidationPipe
|
|
56
|
-
useValue: new ValidationPipe({
|
|
57
|
-
whitelist: true
|
|
58
|
-
})
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
provide: Constants.API_MODULE_NAME,
|
|
62
|
-
useValue: options.moduleName
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
provide: Constants.AUTHORIZATION_INTERCEPTOR,
|
|
66
|
-
useClass: HTTPAuthorizationInterceptor
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
provide: Constants.ERROR_INTERCEPTOR,
|
|
70
|
-
useClass: HTTPErrorInterceptor
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
provide: Constants.HTTP_EXCEPTION_FILTER,
|
|
74
|
-
useClass: HttpExceptionFilter
|
|
75
|
-
},
|
|
76
|
-
...(options.providers || []),
|
|
77
|
-
...(services || [])
|
|
78
|
-
],
|
|
79
|
-
controllers: [...(controllers || []), ...(options.controllers || [])] as unknown as ModuleMetadata['controllers'],
|
|
80
|
-
exports: [...(services || []), ...(options.exports || [])]
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
}
|
package/src/module/index.ts
DELETED