@naturalcycles/backend-lib 5.2.0 → 5.4.0

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.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createSecureHeaderMiddleware = void 0;
4
4
  const js_lib_1 = require("@naturalcycles/js-lib");
5
+ const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
5
6
  const adminMiddleware_1 = require("./adminMiddleware");
6
7
  /**
7
8
  * Secures the endpoint by requiring a secret header to be present.
@@ -21,7 +22,7 @@ function requireSecureHeaderOrAdmin(cfg, reqPermissions) {
21
22
  return next();
22
23
  // Header provided - don't check for Admin
23
24
  if (providedHeader) {
24
- if (!secureHeaderValue || providedHeader === secureHeaderValue)
25
+ if (!secureHeaderValue || (0, nodejs_lib_1.timingSafeStringEqual)(providedHeader, secureHeaderValue))
25
26
  return next();
26
27
  return next(new js_lib_1.AppError('secureHeader or adminToken is required', {
27
28
  backendResponseStatusCode: 401,
@@ -49,25 +49,25 @@ function httpDBRequestHandler(db) {
49
49
  //
50
50
  // })
51
51
  // getByIds
52
- router.put('/getByIds', (0, __1.reqValidation)('body', getByIdsInputSchema), async (req, res) => {
53
- const { table, ids, opt } = req.body;
52
+ router.put('/getByIds', async (req, res) => {
53
+ const { table, ids, opt } = __1.validateRequest.body(req, getByIdsInputSchema);
54
54
  res.json(await db.getByIds(table, ids, opt));
55
55
  });
56
56
  // runQuery
57
- router.put('/runQuery', (0, __1.reqValidation)('body', runQueryInputSchema), async (req, res) => {
58
- const { query, opt } = req.body;
57
+ router.put('/runQuery', async (req, res) => {
58
+ const { query, opt } = __1.validateRequest.body(req, runQueryInputSchema);
59
59
  const q = db_lib_1.DBQuery.fromPlainObject(query);
60
60
  res.json(await db.runQuery(q, opt));
61
61
  });
62
62
  // runQueryCount
63
- router.put('/runQueryCount', (0, __1.reqValidation)('body', runQueryInputSchema), async (req, res) => {
64
- const { query, opt } = req.body;
63
+ router.put('/runQueryCount', async (req, res) => {
64
+ const { query, opt } = __1.validateRequest.body(req, runQueryInputSchema);
65
65
  const q = db_lib_1.DBQuery.fromPlainObject(query);
66
66
  res.json(await db.runQueryCount(q, opt));
67
67
  });
68
68
  // saveBatch
69
- router.put('/saveBatch', (0, __1.reqValidation)('body', saveBatchInputSchema), async (req, res) => {
70
- const { table, rows, opt } = req.body;
69
+ router.put('/saveBatch', async (req, res) => {
70
+ const { table, rows, opt } = __1.validateRequest.body(req, saveBatchInputSchema);
71
71
  await db.saveBatch(table, rows, opt);
72
72
  res.end();
73
73
  });
@@ -77,8 +77,8 @@ function httpDBRequestHandler(db) {
77
77
  // res.json(await db.deleteByIds(table, ids, opt))
78
78
  // })
79
79
  // deleteByQuery
80
- router.put('/deleteByQuery', (0, __1.reqValidation)('body', runQueryInputSchema), async (req, res) => {
81
- const { query, opt } = req.body;
80
+ router.put('/deleteByQuery', async (req, res) => {
81
+ const { query, opt } = __1.validateRequest.body(req, runQueryInputSchema);
82
82
  const q = db_lib_1.DBQuery.fromPlainObject(query);
83
83
  res.json(await db.deleteByQuery(q, opt));
84
84
  });
@@ -35,7 +35,7 @@ async function deployGae(opt = {}) {
35
35
  }, {
36
36
  name: 'deploy',
37
37
  maxAttempts: 2,
38
- delay: 30000,
38
+ delay: 30_000,
39
39
  // todo: this doesn't work, as the error is different from what is logged.
40
40
  // We shoud somehow capture the logged text
41
41
  predicate: err => (0, js_lib_1._anyToError)(err).message.includes('operation is already in progress'),
package/dist/index.d.ts CHANGED
@@ -20,7 +20,7 @@ export * from './server/notFoundMiddleware';
20
20
  export * from './server/okMiddleware';
21
21
  export * from './server/basicAuthMiddleware';
22
22
  export * from './server/requestTimeoutMiddleware';
23
- export * from './server/validation/reqValidationMiddleware';
23
+ export * from './server/validation/validateRequest';
24
24
  export * from './server/simpleRequestLoggerMiddleware';
25
25
  export * from './server/serverStatusMiddleware';
26
26
  export * from './server/validation/validateMiddleware';
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ tslib_1.__exportStar(require("./server/notFoundMiddleware"), exports);
25
25
  tslib_1.__exportStar(require("./server/okMiddleware"), exports);
26
26
  tslib_1.__exportStar(require("./server/basicAuthMiddleware"), exports);
27
27
  tslib_1.__exportStar(require("./server/requestTimeoutMiddleware"), exports);
28
- tslib_1.__exportStar(require("./server/validation/reqValidationMiddleware"), exports);
28
+ tslib_1.__exportStar(require("./server/validation/validateRequest"), exports);
29
29
  tslib_1.__exportStar(require("./server/simpleRequestLoggerMiddleware"), exports);
30
30
  tslib_1.__exportStar(require("./server/serverStatusMiddleware"), exports);
31
31
  tslib_1.__exportStar(require("./server/validation/validateMiddleware"), exports);
@@ -9,7 +9,7 @@ function basicAuthMiddleware(cfg) {
9
9
  const hash = (req.headers.authorization || '').split(' ')[1];
10
10
  if (hash) {
11
11
  const [login, password] = (0, js_lib_1._split)((0, nodejs_lib_1.base64ToString)(hash), ':', 2);
12
- if (login && password && cfg.loginPasswordMap[login] === password) {
12
+ if (login && password && (0, nodejs_lib_1.timingSafeStringEqual)(cfg.loginPasswordMap[login], password)) {
13
13
  return next();
14
14
  }
15
15
  }
@@ -88,7 +88,7 @@ function serverStatsMiddleware() {
88
88
  if (res.statusCode) {
89
89
  serverStatsMap[endpoint][getStatusFamily(res.statusCode)]++;
90
90
  }
91
- if (now - lastCleanup > 60000) {
91
+ if (now - lastCleanup > 60_000) {
92
92
  lastCleanup = now;
93
93
  cleanupServerStats();
94
94
  }
@@ -62,7 +62,7 @@ class BackendServer {
62
62
  const shutdownTimeout = setTimeout(() => {
63
63
  console.log((0, nodejs_lib_1.boldGrey)('Forceful shutdown after timeout'));
64
64
  process.exit(0);
65
- }, this.cfg.forceShutdownTimeout ?? 10000);
65
+ }, this.cfg.forceShutdownTimeout ?? 10_000);
66
66
  try {
67
67
  await Promise.all([
68
68
  this.server && new Promise(r => this.server.close(r)),
@@ -1,7 +1,7 @@
1
1
  import { JsonSchema, JsonSchemaBuilder } from '@naturalcycles/js-lib';
2
2
  import { AjvSchema, AjvValidationError } from '@naturalcycles/nodejs-lib';
3
3
  import { BackendRequestHandler } from '../server.model';
4
- import { ReqValidationOptions } from './reqValidationMiddleware';
4
+ import { ReqValidationOptions } from './validateRequest';
5
5
  export declare function validateBody(schema: JsonSchema | JsonSchemaBuilder | AjvSchema, opt?: ReqValidationOptions<AjvValidationError>): BackendRequestHandler;
6
6
  export declare function validateParams(schema: JsonSchema | JsonSchemaBuilder | AjvSchema, opt?: ReqValidationOptions<AjvValidationError>): BackendRequestHandler;
7
7
  export declare function validateQuery(schema: JsonSchema | JsonSchemaBuilder | AjvSchema, opt?: ReqValidationOptions<AjvValidationError>): BackendRequestHandler;
@@ -1,5 +1,5 @@
1
1
  import { AnySchema, JoiValidationError } from '@naturalcycles/nodejs-lib';
2
- import { BackendRequest, BackendRequestHandler } from '../server.model';
2
+ import { BackendRequest } from '../server.model';
3
3
  export interface ReqValidationOptions<ERR extends Error> {
4
4
  /**
5
5
  * Pass a 'dot-paths' (e.g `pw`, or `input.pw`) that needs to be redacted from the output, in case of error.
@@ -12,7 +12,6 @@ export interface ReqValidationOptions<ERR extends Error> {
12
12
  */
13
13
  report?: boolean | ((err: ERR) => boolean);
14
14
  }
15
- export declare function reqValidation(reqProperty: 'body' | 'params' | 'query', schema: AnySchema, opt?: ReqValidationOptions<JoiValidationError>): BackendRequestHandler;
16
15
  declare class ValidateRequest {
17
16
  body<T>(req: BackendRequest, schema: AnySchema<T>, opt?: ReqValidationOptions<JoiValidationError>): T;
18
17
  query<T>(req: BackendRequest, schema: AnySchema<T>, opt?: ReqValidationOptions<JoiValidationError>): T;
@@ -1,32 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateRequest = exports.reqValidation = void 0;
3
+ exports.validateRequest = void 0;
4
4
  const js_lib_1 = require("@naturalcycles/js-lib");
5
5
  const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
6
6
  const REDACTED = 'REDACTED';
7
- function reqValidation(reqProperty, schema, opt = {}) {
8
- const reportPredicate = typeof opt.report === 'function' ? opt.report : () => opt.report;
9
- return (req, res, next) => {
10
- const { value, error } = (0, nodejs_lib_1.getValidationResult)(req[reqProperty], schema, `request ${reqProperty}`);
11
- if (error) {
12
- const report = reportPredicate(error);
13
- if (opt.redactPaths) {
14
- redact(opt.redactPaths, req[reqProperty], error);
15
- error.data.joiValidationErrorItems.length = 0; // clears the array
16
- delete error.data.annotation;
17
- }
18
- return next(new js_lib_1.AppError(error.message, {
19
- backendResponseStatusCode: 400,
20
- report,
21
- ...error.data,
22
- }));
23
- }
24
- // mutate req to replace the property with the value, converted by Joi
25
- req[reqProperty] = value;
26
- next();
27
- };
28
- }
29
- exports.reqValidation = reqValidation;
30
7
  /**
31
8
  * Mutates error
32
9
  */
@@ -1,6 +1,6 @@
1
1
  import { ZodSchema, ZodValidationError } from '@naturalcycles/js-lib';
2
2
  import { BackendRequestHandler } from '../server.model';
3
- import { ReqValidationOptions } from './reqValidationMiddleware';
3
+ import { ReqValidationOptions } from './validateRequest';
4
4
  /**
5
5
  * Validates req property (body, params or query).
6
6
  * Supports Joi schema or AjvSchema (from nodejs-lib).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/backend-lib",
3
- "version": "5.2.0",
3
+ "version": "5.4.0",
4
4
  "scripts": {
5
5
  "prepare": "husky",
6
6
  "dev": "APP_ENV=dev nodemon",
@@ -36,7 +36,7 @@
36
36
  "yargs": "^17.0.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@naturalcycles/bench-lib": "^1.0.7",
39
+ "@naturalcycles/bench-lib": "^2.0.0",
40
40
  "@naturalcycles/dev-lib": "^13.0.0",
41
41
  "@sentry/node": "^7.0.0",
42
42
  "@types/ejs": "^3.0.0",
@@ -1,4 +1,5 @@
1
1
  import { AppError } from '@naturalcycles/js-lib'
2
+ import { timingSafeStringEqual } from '@naturalcycles/nodejs-lib'
2
3
  import { BackendRequestHandler } from '../server/server.model'
3
4
  import { AdminMiddleware, RequireAdminCfg, requireAdminPermissions } from './adminMiddleware'
4
5
  import { BaseAdminService } from './base.admin.service'
@@ -41,7 +42,8 @@ function requireSecureHeaderOrAdmin(
41
42
 
42
43
  // Header provided - don't check for Admin
43
44
  if (providedHeader) {
44
- if (!secureHeaderValue || providedHeader === secureHeaderValue) return next()
45
+ if (!secureHeaderValue || timingSafeStringEqual(providedHeader, secureHeaderValue))
46
+ return next()
45
47
 
46
48
  return next(
47
49
  new AppError('secureHeader or adminToken is required', {
@@ -12,7 +12,7 @@ import {
12
12
  } from '@naturalcycles/db-lib/dist/validation'
13
13
  import { ObjectWithId } from '@naturalcycles/js-lib'
14
14
  import { anyObjectSchema, arraySchema, objectSchema, stringSchema } from '@naturalcycles/nodejs-lib'
15
- import { BackendRouter, getDefaultRouter, reqValidation } from '..'
15
+ import { BackendRouter, getDefaultRouter, validateRequest } from '..'
16
16
 
17
17
  export interface GetByIdsInput {
18
18
  table: string
@@ -84,28 +84,28 @@ export function httpDBRequestHandler(db: CommonDB): BackendRouter {
84
84
  // })
85
85
 
86
86
  // getByIds
87
- router.put('/getByIds', reqValidation('body', getByIdsInputSchema), async (req, res) => {
88
- const { table, ids, opt } = req.body as GetByIdsInput
87
+ router.put('/getByIds', async (req, res) => {
88
+ const { table, ids, opt } = validateRequest.body(req, getByIdsInputSchema)
89
89
  res.json(await db.getByIds(table, ids, opt))
90
90
  })
91
91
 
92
92
  // runQuery
93
- router.put('/runQuery', reqValidation('body', runQueryInputSchema), async (req, res) => {
94
- const { query, opt } = req.body as RunQueryInput
93
+ router.put('/runQuery', async (req, res) => {
94
+ const { query, opt } = validateRequest.body(req, runQueryInputSchema)
95
95
  const q = DBQuery.fromPlainObject(query)
96
96
  res.json(await db.runQuery(q, opt))
97
97
  })
98
98
 
99
99
  // runQueryCount
100
- router.put('/runQueryCount', reqValidation('body', runQueryInputSchema), async (req, res) => {
101
- const { query, opt } = req.body as RunQueryInput
100
+ router.put('/runQueryCount', async (req, res) => {
101
+ const { query, opt } = validateRequest.body(req, runQueryInputSchema)
102
102
  const q = DBQuery.fromPlainObject(query)
103
103
  res.json(await db.runQueryCount(q, opt))
104
104
  })
105
105
 
106
106
  // saveBatch
107
- router.put('/saveBatch', reqValidation('body', saveBatchInputSchema), async (req, res) => {
108
- const { table, rows, opt } = req.body as SaveBatchInput
107
+ router.put('/saveBatch', async (req, res) => {
108
+ const { table, rows, opt } = validateRequest.body(req, saveBatchInputSchema)
109
109
  await db.saveBatch(table, rows, opt)
110
110
  res.end()
111
111
  })
@@ -117,8 +117,8 @@ export function httpDBRequestHandler(db: CommonDB): BackendRouter {
117
117
  // })
118
118
 
119
119
  // deleteByQuery
120
- router.put('/deleteByQuery', reqValidation('body', runQueryInputSchema), async (req, res) => {
121
- const { query, opt } = req.body as RunQueryInput
120
+ router.put('/deleteByQuery', async (req, res) => {
121
+ const { query, opt } = validateRequest.body(req, runQueryInputSchema)
122
122
  const q = DBQuery.fromPlainObject(query)
123
123
  res.json(await db.deleteByQuery(q, opt))
124
124
  })
package/src/index.ts CHANGED
@@ -20,7 +20,7 @@ export * from './server/notFoundMiddleware'
20
20
  export * from './server/okMiddleware'
21
21
  export * from './server/basicAuthMiddleware'
22
22
  export * from './server/requestTimeoutMiddleware'
23
- export * from './server/validation/reqValidationMiddleware'
23
+ export * from './server/validation/validateRequest'
24
24
  export * from './server/simpleRequestLoggerMiddleware'
25
25
  export * from './server/serverStatusMiddleware'
26
26
  export * from './server/validation/validateMiddleware'
@@ -1,5 +1,5 @@
1
1
  import { _split, StringMap } from '@naturalcycles/js-lib'
2
- import { base64ToString } from '@naturalcycles/nodejs-lib'
2
+ import { base64ToString, timingSafeStringEqual } from '@naturalcycles/nodejs-lib'
3
3
  import { BackendRequestHandler } from './server.model'
4
4
 
5
5
  export interface BasicAuthMiddlewareCfg {
@@ -21,7 +21,7 @@ export function basicAuthMiddleware(cfg: BasicAuthMiddlewareCfg): BackendRequest
21
21
  const hash = (req.headers.authorization || '').split(' ')[1]
22
22
  if (hash) {
23
23
  const [login, password] = _split(base64ToString(hash), ':', 2)
24
- if (login && password && cfg.loginPasswordMap[login] === password) {
24
+ if (login && password && timingSafeStringEqual(cfg.loginPasswordMap[login], password)) {
25
25
  return next()
26
26
  }
27
27
  }
@@ -1,7 +1,7 @@
1
1
  import { JsonSchema, JsonSchemaBuilder, _get, AppError } from '@naturalcycles/js-lib'
2
2
  import { AjvSchema, AjvValidationError } from '@naturalcycles/nodejs-lib'
3
3
  import { BackendRequestHandler } from '../server.model'
4
- import { ReqValidationOptions } from './reqValidationMiddleware'
4
+ import { ReqValidationOptions } from './validateRequest'
5
5
 
6
6
  const REDACTED = 'REDACTED'
7
7
 
@@ -1,6 +1,6 @@
1
1
  import { _get, AppError } from '@naturalcycles/js-lib'
2
2
  import { AnySchema, getValidationResult, JoiValidationError } from '@naturalcycles/nodejs-lib'
3
- import { BackendRequest, BackendRequestHandler } from '../server.model'
3
+ import { BackendRequest } from '../server.model'
4
4
 
5
5
  const REDACTED = 'REDACTED'
6
6
 
@@ -18,40 +18,6 @@ export interface ReqValidationOptions<ERR extends Error> {
18
18
  report?: boolean | ((err: ERR) => boolean)
19
19
  }
20
20
 
21
- export function reqValidation(
22
- reqProperty: 'body' | 'params' | 'query',
23
- schema: AnySchema,
24
- opt: ReqValidationOptions<JoiValidationError> = {},
25
- ): BackendRequestHandler {
26
- const reportPredicate =
27
- typeof opt.report === 'function' ? opt.report : () => opt.report as boolean | undefined
28
-
29
- return (req, res, next) => {
30
- const { value, error } = getValidationResult(req[reqProperty], schema, `request ${reqProperty}`)
31
- if (error) {
32
- const report = reportPredicate(error)
33
-
34
- if (opt.redactPaths) {
35
- redact(opt.redactPaths, req[reqProperty], error)
36
- error.data.joiValidationErrorItems.length = 0 // clears the array
37
- delete error.data.annotation
38
- }
39
-
40
- return next(
41
- new AppError(error.message, {
42
- backendResponseStatusCode: 400,
43
- report,
44
- ...error.data,
45
- }),
46
- )
47
- }
48
-
49
- // mutate req to replace the property with the value, converted by Joi
50
- req[reqProperty] = value
51
- next()
52
- }
53
- }
54
-
55
21
  /**
56
22
  * Mutates error
57
23
  */
@@ -1,6 +1,6 @@
1
1
  import { _get, AppError, ZodSchema, ZodValidationError, zSafeValidate } from '@naturalcycles/js-lib'
2
2
  import { BackendRequestHandler } from '../server.model'
3
- import { ReqValidationOptions } from './reqValidationMiddleware'
3
+ import { ReqValidationOptions } from './validateRequest'
4
4
 
5
5
  const REDACTED = 'REDACTED'
6
6