@biorate/nestjs-tools 1.24.1 → 1.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.24.2](https://github.com/biorate/core/compare/v1.24.1...v1.24.2) (2023-02-02)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **nestjs-tools:** types ([483830d](https://github.com/biorate/core/commit/483830dee914458b547502e69134f99caa54317d))
12
+
13
+
14
+
15
+
16
+
6
17
  ## [1.24.1](https://github.com/biorate/core/compare/v1.24.0...v1.24.1) (2023-02-02)
7
18
 
8
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biorate/nestjs-tools",
3
- "version": "1.24.1",
3
+ "version": "1.24.2",
4
4
  "description": "NestJS tools",
5
5
  "main": "dist",
6
6
  "scripts": {
@@ -40,5 +40,5 @@
40
40
  "@types/express": "^4.17.16",
41
41
  "@types/on-headers": "^1.0.0"
42
42
  },
43
- "gitHead": "136827e50d8e8cd280c1e9af9e0e7d5391af9894"
43
+ "gitHead": "a092d680f5d8df4473a8f07ed038b2fabe20f9a9"
44
44
  }
@@ -0,0 +1,14 @@
1
+ import { UseGuards } from '@nestjs/common';
2
+ import { ApiBasicAuth } from '@nestjs/swagger';
3
+ import { AuthGuardProvider } from '../providers';
4
+
5
+ export const AuthBasic =
6
+ () =>
7
+ (
8
+ target: Record<string | symbol, unknown>,
9
+ propertyKey?: string | symbol,
10
+ descriptor?: TypedPropertyDescriptor<unknown>,
11
+ ) => {
12
+ ApiBasicAuth()(target, propertyKey!, descriptor!);
13
+ UseGuards(AuthGuardProvider)(target, propertyKey!, descriptor!);
14
+ };
@@ -0,0 +1,2 @@
1
+ export * from './roles';
2
+ export * from './auth';
@@ -0,0 +1,13 @@
1
+ import { SetMetadata } from '@nestjs/common';
2
+ import { UserRoles } from '../enums';
3
+
4
+ export const ROLES_KEY = Symbol('RolesKey');
5
+
6
+ export const Roles = (...roles: UserRoles[]) =>
7
+ SetMetadata(
8
+ ROLES_KEY,
9
+ roles.reduce((memo, item) => {
10
+ memo |= item;
11
+ return memo;
12
+ }, 0),
13
+ );
@@ -0,0 +1 @@
1
+ export * from './user';
@@ -0,0 +1,4 @@
1
+ export enum UserRoles {
2
+ Basic = 1 << 0,
3
+ Admin = 1 << 1,
4
+ }
@@ -0,0 +1,47 @@
1
+ import { BaseError } from '@biorate/errors';
2
+ import {
3
+ ExceptionFilter,
4
+ Catch,
5
+ ArgumentsHost,
6
+ HttpException,
7
+ HttpStatus,
8
+ BadRequestException,
9
+ } from '@nestjs/common';
10
+
11
+ @Catch()
12
+ export class AllExceptionsFilter implements ExceptionFilter {
13
+ public catch(exception: unknown, host: ArgumentsHost) {
14
+ const ctx = host.switchToHttp();
15
+ const response = ctx.getResponse();
16
+ const request = ctx.getRequest();
17
+ const status =
18
+ exception instanceof HttpException
19
+ ? exception.getStatus()
20
+ : (exception as BaseError)?.meta?.status ?? HttpStatus.INTERNAL_SERVER_ERROR;
21
+ this.log(exception);
22
+ response.status(status).json({
23
+ status,
24
+ code: this.code(exception),
25
+ hint: this.hint(exception),
26
+ path: request.url,
27
+ });
28
+ }
29
+
30
+ private code(exception: unknown) {
31
+ return (<BaseError>exception).code ?? (<Error>exception).constructor.name;
32
+ }
33
+
34
+ private hint(exception: unknown) {
35
+ return (
36
+ (
37
+ (<BadRequestException>exception)?.getResponse?.() as unknown as {
38
+ message?: [string];
39
+ }
40
+ )?.message ?? (<BaseError>exception).message
41
+ );
42
+ }
43
+
44
+ private log(exception: unknown) {
45
+ console.error(exception);
46
+ }
47
+ }
@@ -0,0 +1 @@
1
+ export * from './all-exception-filter';
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './decorators';
2
+ export * from './enums';
3
+ export * from './filters';
4
+ export * from './interceptors';
5
+ export * from './middlewares';
6
+ export * from './providers';
@@ -0,0 +1 @@
1
+ export * from './routes-interceptor';
@@ -0,0 +1,30 @@
1
+ import { join, normalize } from 'path/posix';
2
+ import { Reflector } from '@nestjs/core';
3
+ import { PATH_METADATA } from '@nestjs/common/constants';
4
+ import { Request } from 'express';
5
+ import {
6
+ Injectable,
7
+ ExecutionContext,
8
+ CallHandler,
9
+ NestInterceptor,
10
+ } from '@nestjs/common';
11
+
12
+ @Injectable()
13
+ export class RoutesInterceptor implements NestInterceptor {
14
+ public static readonly map = new WeakMap<Request, { path: string }>();
15
+
16
+ private readonly reflector = new Reflector();
17
+
18
+ public intercept(context: ExecutionContext, next: CallHandler) {
19
+ RoutesInterceptor.map.set(context.switchToHttp().getRequest(), {
20
+ path: normalize(
21
+ join(
22
+ '/',
23
+ this.reflector.get<string>(PATH_METADATA, context.getClass()),
24
+ this.reflector.get<string>(PATH_METADATA, context.getHandler()),
25
+ ),
26
+ ),
27
+ });
28
+ return next.handle();
29
+ }
30
+ }
@@ -0,0 +1,3 @@
1
+ export * from './proxy-middleware';
2
+ export * from './request-count-middleware';
3
+ export * from './response-time-middleware';
@@ -0,0 +1,69 @@
1
+ import http from 'http';
2
+ import * as httpProxy from 'http-proxy';
3
+ import { pick, omit } from 'lodash';
4
+ import { Request, Response } from 'express';
5
+ import { createProxyMiddleware } from 'http-proxy-middleware';
6
+ import { Options, RequestHandler } from 'http-proxy-middleware/dist/types';
7
+ import { Injectable } from '@nestjs/common';
8
+ import { counter, histogram, Counter, Histogram } from '@biorate/prometheus';
9
+ import { time } from '@biorate/tools';
10
+
11
+ @Injectable()
12
+ export class ProxyPrometheusMiddleware {
13
+ @counter({
14
+ name: 'http_proxy_server_requests_seconds_count',
15
+ help: 'Http proxy server requests count',
16
+ labelNames: ['method', 'url', 'status'],
17
+ })
18
+ private counter: Counter;
19
+
20
+ @histogram({
21
+ name: 'http_proxy_server_requests_seconds',
22
+ help: 'Http proxy server requests seconds bucket',
23
+ labelNames: ['method', 'url', 'status'],
24
+ buckets: [5, 10, 20, 50, 100, 300, 500, 1000, 2000, 3000, 5000, 10000],
25
+ })
26
+ private histogram: Histogram;
27
+
28
+ private handler: RequestHandler;
29
+
30
+ private time = new WeakMap<Request, { time: () => number }>();
31
+
32
+ public static create(options?: Options) {
33
+ return new this(options).handler;
34
+ }
35
+
36
+ private constructor(options?: Options) {
37
+ const { onProxyReq, onProxyRes } = pick(options, 'onProxyReq', 'onProxyRes');
38
+ this.handler = createProxyMiddleware({
39
+ ...omit(options, 'onProxyReq', 'onProxyRes'),
40
+ onProxyReq: (
41
+ proxyReq: http.ClientRequest,
42
+ req: Request,
43
+ res: Response,
44
+ options: httpProxy.ServerOptions,
45
+ ) => {
46
+ this.time.set(req, { time: time.diff() });
47
+ return onProxyReq?.(proxyReq, req, res, options);
48
+ },
49
+ onProxyRes: (proxyRes: http.IncomingMessage, req: Request, res: Response) => {
50
+ const diff = this.time.get(req);
51
+ this.counter
52
+ .labels({
53
+ method: req.method,
54
+ url: req.baseUrl,
55
+ status: res.statusCode,
56
+ })
57
+ .inc();
58
+ this.histogram
59
+ .labels({
60
+ method: req.method,
61
+ url: req.baseUrl,
62
+ status: res.statusCode,
63
+ })
64
+ .observe(diff!.time());
65
+ return onProxyRes?.(proxyRes, req, res);
66
+ },
67
+ });
68
+ }
69
+ }
@@ -0,0 +1,29 @@
1
+ import { Injectable, NestMiddleware } from '@nestjs/common';
2
+ import { counter, Counter } from '@biorate/prometheus';
3
+ import { Request, Response, NextFunction } from 'express';
4
+ import onHeaders from 'on-headers';
5
+ import { RoutesInterceptor } from '../interceptors';
6
+
7
+ @Injectable()
8
+ export class RequestCountMiddleware implements NestMiddleware {
9
+ @counter({
10
+ name: 'http_server_requests_seconds_count',
11
+ help: 'Http server requests count',
12
+ labelNames: ['method', 'url', 'status'],
13
+ })
14
+ private counter: Counter;
15
+
16
+ public use(req: Request, res: Response, next: NextFunction) {
17
+ onHeaders(res, () => {
18
+ const route = RoutesInterceptor.map.get(req);
19
+ this.counter
20
+ .labels({
21
+ method: req.method,
22
+ url: route?.path ?? (req.baseUrl || req.originalUrl),
23
+ status: res.statusCode,
24
+ })
25
+ .inc();
26
+ });
27
+ next();
28
+ }
29
+ }
@@ -0,0 +1,53 @@
1
+ import { Injectable, NestMiddleware } from '@nestjs/common';
2
+ import { inject, Types } from '@biorate/inversion';
3
+ import { IConfig } from '@biorate/config';
4
+ import { histogram, Histogram } from '@biorate/prometheus';
5
+ import { Request, Response, NextFunction } from 'express';
6
+ import onHeaders from 'on-headers';
7
+ import { RoutesInterceptor } from '../interceptors';
8
+ import { time } from '@biorate/tools';
9
+
10
+ @Injectable()
11
+ export class ResponseTimeMiddleware implements NestMiddleware {
12
+ @inject(Types.Config) private config: IConfig;
13
+
14
+ private readonly suffix: boolean;
15
+
16
+ private readonly header: string;
17
+
18
+ private readonly digits: number;
19
+
20
+ @histogram({
21
+ name: 'http_server_requests_seconds',
22
+ help: 'Http server requests seconds bucket',
23
+ labelNames: ['method', 'url', 'status'],
24
+ buckets: [5, 10, 20, 50, 100, 300, 500, 1000, 2000, 3000, 5000, 10000],
25
+ })
26
+ private histogram: Histogram;
27
+
28
+ public constructor() {
29
+ this.suffix = this.config.get<boolean>('app.middleware.ResponseTime.suffix', true);
30
+ this.header = this.config.get<string>(
31
+ 'app.middleware.ResponseTime.header',
32
+ 'x-response-time',
33
+ );
34
+ this.digits = this.config.get<number>('app.middleware.ResponseTime.digits', 3);
35
+ }
36
+
37
+ public use(req: Request, res: Response, next: NextFunction) {
38
+ const diff = time.diff();
39
+ onHeaders(res, () => {
40
+ const time = diff();
41
+ const route = RoutesInterceptor.map.get(req);
42
+ this.histogram
43
+ .labels({
44
+ method: req.method,
45
+ url: route?.path ?? (req.baseUrl || req.originalUrl),
46
+ status: res.statusCode,
47
+ })
48
+ .observe(time);
49
+ res.setHeader(this.header, time.toFixed(this.digits) + (this.suffix ? 'ms' : ''));
50
+ });
51
+ next();
52
+ }
53
+ }
@@ -0,0 +1,36 @@
1
+ import { inject, Types } from '@biorate/inversion';
2
+ import { IConfig } from '@biorate/config';
3
+ import {
4
+ Injectable,
5
+ CanActivate,
6
+ ExecutionContext,
7
+ UnauthorizedException,
8
+ } from '@nestjs/common';
9
+
10
+ @Injectable()
11
+ export class AuthGuardProvider implements CanActivate {
12
+ @inject(Types.Config) private config: IConfig;
13
+
14
+ public canActivate(context: ExecutionContext): boolean {
15
+ const request = context.switchToHttp().getRequest();
16
+ const config = this.config.get<{ [key: string]: string } | void>(
17
+ 'app.auth.basic',
18
+ undefined,
19
+ );
20
+ if (!config) return true;
21
+ let username: string, password: string;
22
+ try {
23
+ [username, password] = Buffer.from(
24
+ request.headers.authorization.replace('Basic ', ''),
25
+ 'base64',
26
+ )
27
+ .toString('utf-8')
28
+ .split(':');
29
+ } catch {
30
+ throw new UnauthorizedException();
31
+ }
32
+ if (!username || !password) throw new UnauthorizedException();
33
+ if (config[username] === password) return true;
34
+ throw new UnauthorizedException();
35
+ }
36
+ }
@@ -0,0 +1,2 @@
1
+ export * from './roles';
2
+ export * from './auth';
@@ -0,0 +1,34 @@
1
+ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
3
+ import { ROLES_KEY } from '../decorators';
4
+ import { UserRoles } from '../enums';
5
+
6
+ @Injectable()
7
+ export class RolesGuardProvider implements CanActivate {
8
+ constructor(private reflector: Reflector) {}
9
+ /**
10
+ * @description - This method for override
11
+ * @example
12
+ * ```ts
13
+ * const request = context.switchToHttp().getRequest();
14
+ * const { login, sessionId } = request.cookies;
15
+ * if (!login || !sessionId) throw new AuthorizationNotAuthorizedError();
16
+ * // get user from db, or somewhere else
17
+ * if (!user) throw new AuthorizationNotAuthorizedError();
18
+ * return user.role;
19
+ * ```
20
+ */
21
+ protected async getUser(context: ExecutionContext) {
22
+ return { role: UserRoles.Basic | UserRoles.Admin };
23
+ }
24
+
25
+ public async canActivate(context: ExecutionContext) {
26
+ const roles = this.reflector.getAllAndOverride<number>(ROLES_KEY, [
27
+ context.getHandler(),
28
+ context.getClass(),
29
+ ]);
30
+ if (roles === undefined) return true;
31
+ const user = await this.getUser(context);
32
+ return (user.role & roles) === roles;
33
+ }
34
+ }