@naturalcycles/backend-lib 9.16.0 → 9.17.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.
- package/dist/bin/deploy-gae.js +1 -1
- package/dist/bin/deploy-health-check.js +1 -1
- package/dist/bin/deploy-prepare.js +1 -1
- package/dist/bin/undeploy-gae.js +1 -1
- package/dist/deploy/backend.cfg.util.js +5 -3
- package/dist/validation/ajv/ajvValidateRequest.d.ts +1 -1
- package/dist/validation/ajv/ajvValidateRequest.js +11 -42
- package/dist/validation/joi/joiValidateRequest.d.ts +1 -18
- package/dist/validation/joi/joiValidateRequest.js +10 -35
- package/dist/validation/validateRequest.util.d.ts +23 -0
- package/dist/validation/validateRequest.util.js +32 -0
- package/dist/validation/zod/zodValidateRequest.d.ts +1 -1
- package/dist/validation/zod/zodValidateRequest.js +10 -37
- package/package.json +2 -2
- package/src/bin/deploy-gae.ts +1 -1
- package/src/bin/deploy-health-check.ts +1 -1
- package/src/bin/deploy-prepare.ts +1 -1
- package/src/bin/undeploy-gae.ts +1 -1
- package/src/deploy/backend.cfg.util.ts +7 -7
- package/src/validation/ajv/ajvValidateRequest.ts +12 -47
- package/src/validation/joi/joiValidateRequest.ts +12 -62
- package/src/validation/validateRequest.util.ts +65 -0
- package/src/validation/zod/zodValidateRequest.ts +13 -43
package/dist/bin/deploy-gae.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runScript } from '@naturalcycles/nodejs-lib';
|
|
2
|
+
import { runScript } from '@naturalcycles/nodejs-lib/runScript';
|
|
3
3
|
import { _yargs } from '@naturalcycles/nodejs-lib/yargs';
|
|
4
4
|
import { deployGae } from '../deploy/deployGae.js';
|
|
5
5
|
import { deployHealthCheckYargsOptions } from '../deploy/deployHealthCheck.js';
|
|
@@ -7,7 +7,7 @@ yarn deploy-health-check --url https://service-dot-yourproject.appspot.com
|
|
|
7
7
|
--intervalSec 2
|
|
8
8
|
|
|
9
9
|
*/
|
|
10
|
-
import { runScript } from '@naturalcycles/nodejs-lib';
|
|
10
|
+
import { runScript } from '@naturalcycles/nodejs-lib/runScript';
|
|
11
11
|
import { _yargs } from '@naturalcycles/nodejs-lib/yargs';
|
|
12
12
|
import { deployHealthCheck, deployHealthCheckYargsOptions } from '../deploy/deployHealthCheck.js';
|
|
13
13
|
runScript(async () => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
yarn deploy-prepare
|
|
5
5
|
|
|
6
6
|
*/
|
|
7
|
-
import { runScript } from '@naturalcycles/nodejs-lib';
|
|
7
|
+
import { runScript } from '@naturalcycles/nodejs-lib/runScript';
|
|
8
8
|
import { _yargs } from '@naturalcycles/nodejs-lib/yargs';
|
|
9
9
|
import { deployPrepare, deployPrepareYargsOptions } from '../deploy/deployPrepare.js';
|
|
10
10
|
runScript(async () => {
|
package/dist/bin/undeploy-gae.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runScript } from '@naturalcycles/nodejs-lib';
|
|
2
|
+
import { runScript } from '@naturalcycles/nodejs-lib/runScript';
|
|
3
3
|
import { _yargs } from '@naturalcycles/nodejs-lib/yargs';
|
|
4
4
|
import { undeployGae } from '../deploy/deployGae.js';
|
|
5
5
|
runScript(async () => {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { _lazyValue } from '@naturalcycles/js-lib';
|
|
1
2
|
import { AjvSchema } from '@naturalcycles/nodejs-lib/ajv';
|
|
2
3
|
import { fs2 } from '@naturalcycles/nodejs-lib/fs2';
|
|
3
4
|
import { yaml2 } from '@naturalcycles/nodejs-lib/yaml2';
|
|
4
5
|
import { resourcesDir } from '../paths.cnst.js';
|
|
5
|
-
const
|
|
6
|
-
|
|
6
|
+
const getBackendCfgSchema = _lazyValue(() => {
|
|
7
|
+
const schemaJson = fs2.readJson(`${resourcesDir}/backendCfg.schema.json`);
|
|
8
|
+
return AjvSchema.create(schemaJson, { objectName: 'backend.cfg.yaml' });
|
|
7
9
|
});
|
|
8
10
|
export function getBackendCfg(projectDir = '.') {
|
|
9
11
|
const backendCfgYamlPath = `${projectDir}/backend.cfg.yaml`;
|
|
@@ -11,6 +13,6 @@ export function getBackendCfg(projectDir = '.') {
|
|
|
11
13
|
const backendCfg = {
|
|
12
14
|
...yaml2.readYaml(backendCfgYamlPath),
|
|
13
15
|
};
|
|
14
|
-
|
|
16
|
+
getBackendCfgSchema().validate(backendCfg);
|
|
15
17
|
return backendCfg;
|
|
16
18
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AjvSchema, AjvValidationError } from '@naturalcycles/nodejs-lib/ajv';
|
|
2
2
|
import type { BackendRequest } from '../../server/server.model.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type ReqValidationOptions } from '../validateRequest.util.js';
|
|
4
4
|
declare class AjvValidateRequest {
|
|
5
5
|
body<T>(req: BackendRequest, schema: AjvSchema<T>, opt?: ReqValidationOptions<AjvValidationError>): T;
|
|
6
6
|
query<T>(req: BackendRequest, schema: AjvSchema<T>, opt?: ReqValidationOptions<AjvValidationError>): T;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _get } from '@naturalcycles/js-lib/object';
|
|
1
|
+
import { handleValidationError } from '../validateRequest.util.js';
|
|
3
2
|
class AjvValidateRequest {
|
|
4
3
|
body(req, schema, opt = {}) {
|
|
5
4
|
return this.validate(req, 'body', schema, opt);
|
|
@@ -20,53 +19,23 @@ class AjvValidateRequest {
|
|
|
20
19
|
* Keep in mind that this will also remove all values that are not in the schema.
|
|
21
20
|
*/
|
|
22
21
|
headers(req, schema, opt = {}) {
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
return this.validate(req, 'headers', schema, {
|
|
23
|
+
mutate: false,
|
|
25
24
|
...opt,
|
|
26
|
-
};
|
|
27
|
-
return this.validate(req, 'headers', schema, options);
|
|
25
|
+
});
|
|
28
26
|
}
|
|
29
27
|
validate(req, reqProperty, schema, opt = {}) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
const
|
|
28
|
+
const { mutate = true } = opt;
|
|
29
|
+
const originalProperty = req[reqProperty] || {};
|
|
30
|
+
const item = mutate ? originalProperty : { ...originalProperty };
|
|
31
|
+
// Ajv mutates the input
|
|
32
|
+
const error = schema.getValidationError(item, {
|
|
33
33
|
objectName: `request ${reqProperty}`,
|
|
34
34
|
});
|
|
35
35
|
if (error) {
|
|
36
|
-
|
|
37
|
-
if (typeof opt.report === 'boolean') {
|
|
38
|
-
report = opt.report;
|
|
39
|
-
}
|
|
40
|
-
else if (typeof opt.report === 'function') {
|
|
41
|
-
report = opt.report(error);
|
|
42
|
-
}
|
|
43
|
-
if (opt.redactPaths) {
|
|
44
|
-
redact(opt.redactPaths, req[reqProperty], error);
|
|
45
|
-
}
|
|
46
|
-
throw new AppError(error.message, {
|
|
47
|
-
backendResponseStatusCode: 400,
|
|
48
|
-
report,
|
|
49
|
-
...error.data,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
// mutate req to replace the property with the value, converted by Joi
|
|
53
|
-
if (!opt.keepOriginal && reqProperty !== 'query') {
|
|
54
|
-
// query is read-only in Express 5
|
|
55
|
-
req[reqProperty] = value;
|
|
36
|
+
handleValidationError(error, originalProperty, opt);
|
|
56
37
|
}
|
|
57
|
-
return
|
|
38
|
+
return item;
|
|
58
39
|
}
|
|
59
40
|
}
|
|
60
41
|
export const ajvValidateRequest = new AjvValidateRequest();
|
|
61
|
-
const REDACTED = 'REDACTED';
|
|
62
|
-
/**
|
|
63
|
-
* Mutates error
|
|
64
|
-
*/
|
|
65
|
-
function redact(redactPaths, obj, error) {
|
|
66
|
-
redactPaths
|
|
67
|
-
.map(path => _get(obj, path))
|
|
68
|
-
.filter(Boolean)
|
|
69
|
-
.forEach(secret => {
|
|
70
|
-
error.message = error.message.replaceAll(secret, REDACTED);
|
|
71
|
-
});
|
|
72
|
-
}
|
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
import type { AnySchema, JoiValidationError } from '@naturalcycles/nodejs-lib/joi';
|
|
2
2
|
import type { BackendRequest } from '../../server/server.model.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Pass a 'dot-paths' (e.g `pw`, or `input.pw`) that needs to be redacted from the output, in case of error.
|
|
6
|
-
* Useful e.g to redact (prevent leaking) plaintext passwords in error messages.
|
|
7
|
-
*/
|
|
8
|
-
redactPaths?: string[];
|
|
9
|
-
/**
|
|
10
|
-
* Set to true, or a function that returns true/false based on the error generated.
|
|
11
|
-
* If true - `genericErrorHandler` will report it to errorReporter (aka Sentry).
|
|
12
|
-
*/
|
|
13
|
-
report?: boolean | ((err: ERR) => boolean);
|
|
14
|
-
/**
|
|
15
|
-
* When set to true, the validated object will not be replaced with the Joi-converted value.
|
|
16
|
-
*
|
|
17
|
-
* The general default is `false`, with the excepction of `headers` validation, where the default is `true`.
|
|
18
|
-
*/
|
|
19
|
-
keepOriginal?: boolean;
|
|
20
|
-
}
|
|
3
|
+
import { type ReqValidationOptions } from '../validateRequest.util.js';
|
|
21
4
|
declare class ValidateRequest {
|
|
22
5
|
body<T>(req: BackendRequest, schema: AnySchema<T>, opt?: ReqValidationOptions<JoiValidationError>): T;
|
|
23
6
|
query<T>(req: BackendRequest, schema: AnySchema<T>, opt?: ReqValidationOptions<JoiValidationError>): T;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { AppError } from '@naturalcycles/js-lib/error';
|
|
2
|
-
import { _get } from '@naturalcycles/js-lib/object';
|
|
3
1
|
import { getValidationResult } from '@naturalcycles/nodejs-lib/joi';
|
|
2
|
+
import { handleValidationError } from '../validateRequest.util.js';
|
|
4
3
|
class ValidateRequest {
|
|
5
4
|
body(req, schema, opt = {}) {
|
|
6
5
|
return this.validate(req, 'body', schema, opt);
|
|
@@ -21,51 +20,27 @@ class ValidateRequest {
|
|
|
21
20
|
* Keep in mind that this will also remove all values that are not in the schema.
|
|
22
21
|
*/
|
|
23
22
|
headers(req, schema, opt = {}) {
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
return this.validate(req, 'headers', schema, {
|
|
24
|
+
mutate: false,
|
|
26
25
|
...opt,
|
|
27
|
-
};
|
|
28
|
-
return this.validate(req, 'headers', schema, options);
|
|
26
|
+
});
|
|
29
27
|
}
|
|
30
28
|
validate(req, reqProperty, schema, opt = {}) {
|
|
31
|
-
const {
|
|
29
|
+
const { mutate = true } = opt;
|
|
30
|
+
const originalProperty = req[reqProperty] || {};
|
|
31
|
+
// Joi does not mutate the input
|
|
32
|
+
const { error, value } = getValidationResult(originalProperty, schema, `request ${reqProperty}`);
|
|
32
33
|
if (error) {
|
|
33
|
-
let report;
|
|
34
|
-
if (typeof opt.report === 'boolean') {
|
|
35
|
-
report = opt.report;
|
|
36
|
-
}
|
|
37
|
-
else if (typeof opt.report === 'function') {
|
|
38
|
-
report = opt.report(error);
|
|
39
|
-
}
|
|
40
34
|
if (opt.redactPaths) {
|
|
41
|
-
redact(opt.redactPaths, req[reqProperty], error);
|
|
42
35
|
error.data.joiValidationErrorItems.length = 0; // clears the array
|
|
43
36
|
delete error.data.annotation;
|
|
44
37
|
}
|
|
45
|
-
|
|
46
|
-
backendResponseStatusCode: 400,
|
|
47
|
-
report,
|
|
48
|
-
...error.data,
|
|
49
|
-
});
|
|
38
|
+
handleValidationError(error, originalProperty, opt);
|
|
50
39
|
}
|
|
51
|
-
|
|
52
|
-
if (!opt.keepOriginal && reqProperty !== 'query') {
|
|
53
|
-
// query is read-only in Express 5
|
|
40
|
+
if (mutate) {
|
|
54
41
|
req[reqProperty] = value;
|
|
55
42
|
}
|
|
56
43
|
return value;
|
|
57
44
|
}
|
|
58
45
|
}
|
|
59
46
|
export const validateRequest = new ValidateRequest();
|
|
60
|
-
const REDACTED = 'REDACTED';
|
|
61
|
-
/**
|
|
62
|
-
* Mutates error
|
|
63
|
-
*/
|
|
64
|
-
function redact(redactPaths, obj, error) {
|
|
65
|
-
redactPaths
|
|
66
|
-
.map(path => _get(obj, path))
|
|
67
|
-
.filter(Boolean)
|
|
68
|
-
.forEach(secret => {
|
|
69
|
-
error.message = error.message.replaceAll(secret, REDACTED);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AppError } from '@naturalcycles/js-lib/error';
|
|
2
|
+
export declare function handleValidationError<T, ERR extends AppError>(error: ERR, originalProperty: T, opt?: ReqValidationOptions<ERR>): never;
|
|
3
|
+
export interface ReqValidationOptions<ERR extends AppError> {
|
|
4
|
+
/**
|
|
5
|
+
* Pass a 'dot-paths' (e.g `pw`, or `input.pw`) that needs to be redacted from the output, in case of error.
|
|
6
|
+
* Useful e.g to redact (prevent leaking) plaintext passwords in error messages.
|
|
7
|
+
*/
|
|
8
|
+
redactPaths?: string[];
|
|
9
|
+
/**
|
|
10
|
+
* Set to true, or a function that returns true/false based on the error generated.
|
|
11
|
+
* If true - `genericErrorHandler` will report it to errorReporter (aka Sentry).
|
|
12
|
+
*/
|
|
13
|
+
report?: boolean | ((err: ERR) => boolean);
|
|
14
|
+
/**
|
|
15
|
+
* When set to true, the validated object will not be replaced with the Joi-converted value.
|
|
16
|
+
*
|
|
17
|
+
* Defaults to true.
|
|
18
|
+
* Exception is `headers` validation, where the default is `false`.
|
|
19
|
+
*
|
|
20
|
+
* To avoid mutation - shallow copy is performed.
|
|
21
|
+
*/
|
|
22
|
+
mutate?: boolean;
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AppError } from '@naturalcycles/js-lib/error';
|
|
2
|
+
import { _get } from '@naturalcycles/js-lib/object';
|
|
3
|
+
export function handleValidationError(error, originalProperty, opt = {}) {
|
|
4
|
+
// const item: T = opt.mutate ? { ...req[reqProperty] } : (req[reqProperty] || {})
|
|
5
|
+
let report;
|
|
6
|
+
if (typeof opt.report === 'boolean') {
|
|
7
|
+
report = opt.report;
|
|
8
|
+
}
|
|
9
|
+
else if (typeof opt.report === 'function') {
|
|
10
|
+
report = opt.report(error);
|
|
11
|
+
}
|
|
12
|
+
if (opt.redactPaths) {
|
|
13
|
+
redact(opt.redactPaths, originalProperty, error);
|
|
14
|
+
}
|
|
15
|
+
throw new AppError(error.message, {
|
|
16
|
+
backendResponseStatusCode: 400,
|
|
17
|
+
report,
|
|
18
|
+
...error.data,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const REDACTED = 'REDACTED';
|
|
22
|
+
/**
|
|
23
|
+
* Mutates error
|
|
24
|
+
*/
|
|
25
|
+
function redact(redactPaths, obj, error) {
|
|
26
|
+
redactPaths
|
|
27
|
+
.map(path => _get(obj, path))
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.forEach(secret => {
|
|
30
|
+
error.message = error.message.replaceAll(secret, REDACTED);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ZodType, type ZodValidationError } from '@naturalcycles/js-lib/zod';
|
|
2
2
|
import type { BackendRequest } from '../../server/server.model.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type ReqValidationOptions } from '../validateRequest.util.js';
|
|
4
4
|
declare class ZodValidateRequest {
|
|
5
5
|
body<T>(req: BackendRequest, schema: ZodType<T>, opt?: ReqValidationOptions<ZodValidationError>): T;
|
|
6
6
|
query<T>(req: BackendRequest, schema: ZodType<T>, opt?: ReqValidationOptions<ZodValidationError>): T;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { AppError } from '@naturalcycles/js-lib/error';
|
|
2
|
-
import { _get } from '@naturalcycles/js-lib/object';
|
|
3
1
|
import { zSafeValidate } from '@naturalcycles/js-lib/zod';
|
|
2
|
+
import { handleValidationError } from '../validateRequest.util.js';
|
|
4
3
|
class ZodValidateRequest {
|
|
5
4
|
body(req, schema, opt = {}) {
|
|
6
5
|
return this.validate(req, 'body', schema, opt);
|
|
@@ -21,49 +20,23 @@ class ZodValidateRequest {
|
|
|
21
20
|
* Keep in mind that this will also remove all values that are not in the schema.
|
|
22
21
|
*/
|
|
23
22
|
headers(req, schema, opt = {}) {
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
return this.validate(req, 'headers', schema, {
|
|
24
|
+
mutate: false,
|
|
26
25
|
...opt,
|
|
27
|
-
};
|
|
28
|
-
return this.validate(req, 'headers', schema, options);
|
|
26
|
+
});
|
|
29
27
|
}
|
|
30
28
|
validate(req, reqProperty, schema, opt = {}) {
|
|
31
|
-
const {
|
|
29
|
+
const { mutate = true } = opt;
|
|
30
|
+
const originalProperty = req[reqProperty] || {};
|
|
31
|
+
// Zod does not mutate the input
|
|
32
|
+
const { error, data } = zSafeValidate(originalProperty, schema);
|
|
32
33
|
if (error) {
|
|
33
|
-
|
|
34
|
-
if (typeof opt.report === 'boolean') {
|
|
35
|
-
report = opt.report;
|
|
36
|
-
}
|
|
37
|
-
else if (typeof opt.report === 'function') {
|
|
38
|
-
report = opt.report(error);
|
|
39
|
-
}
|
|
40
|
-
if (opt.redactPaths) {
|
|
41
|
-
redact(opt.redactPaths, req[reqProperty], error);
|
|
42
|
-
}
|
|
43
|
-
throw new AppError(error.message, {
|
|
44
|
-
backendResponseStatusCode: 400,
|
|
45
|
-
report,
|
|
46
|
-
...error.data,
|
|
47
|
-
});
|
|
34
|
+
handleValidationError(error, originalProperty, opt);
|
|
48
35
|
}
|
|
49
|
-
|
|
50
|
-
if (!opt.keepOriginal && reqProperty !== 'query') {
|
|
51
|
-
// query is read-only in Express 5
|
|
36
|
+
if (mutate) {
|
|
52
37
|
req[reqProperty] = data;
|
|
53
38
|
}
|
|
54
39
|
return data;
|
|
55
40
|
}
|
|
56
41
|
}
|
|
57
42
|
export const zodValidateRequest = new ZodValidateRequest();
|
|
58
|
-
const REDACTED = 'REDACTED';
|
|
59
|
-
/**
|
|
60
|
-
* Mutates error
|
|
61
|
-
*/
|
|
62
|
-
function redact(redactPaths, obj, error) {
|
|
63
|
-
redactPaths
|
|
64
|
-
.map(path => _get(obj, path))
|
|
65
|
-
.filter(Boolean)
|
|
66
|
-
.forEach(secret => {
|
|
67
|
-
error.message = error.message.replaceAll(secret, REDACTED);
|
|
68
|
-
});
|
|
69
|
-
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/backend-lib",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.17.0",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@sentry/node": "^9"
|
|
7
7
|
},
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"exports": {
|
|
35
35
|
"./cfg/tsconfig.json": "./cfg/tsconfig.json",
|
|
36
36
|
".": "./dist/index.js",
|
|
37
|
+
"./admin": "./dist/admin/index.js",
|
|
37
38
|
"./db": "./dist/db/index.js",
|
|
38
39
|
"./deploy": "./dist/deploy/index.js",
|
|
39
40
|
"./express/createDefaultApp": "./dist/express/createDefaultApp.js",
|
|
@@ -61,7 +62,6 @@
|
|
|
61
62
|
"deploy-prepare": "dist/bin/deploy-prepare.js",
|
|
62
63
|
"deploy-health-check": "dist/bin/deploy-health-check.js"
|
|
63
64
|
},
|
|
64
|
-
"main": "dist/index.js",
|
|
65
65
|
"types": "dist/index.d.ts",
|
|
66
66
|
"publishConfig": {
|
|
67
67
|
"access": "public"
|
package/src/bin/deploy-gae.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { runScript } from '@naturalcycles/nodejs-lib'
|
|
3
|
+
import { runScript } from '@naturalcycles/nodejs-lib/runScript'
|
|
4
4
|
import { _yargs } from '@naturalcycles/nodejs-lib/yargs'
|
|
5
5
|
import { deployGae } from '../deploy/deployGae.js'
|
|
6
6
|
import { deployHealthCheckYargsOptions } from '../deploy/deployHealthCheck.js'
|
|
@@ -9,7 +9,7 @@ yarn deploy-health-check --url https://service-dot-yourproject.appspot.com
|
|
|
9
9
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { runScript } from '@naturalcycles/nodejs-lib'
|
|
12
|
+
import { runScript } from '@naturalcycles/nodejs-lib/runScript'
|
|
13
13
|
import { _yargs } from '@naturalcycles/nodejs-lib/yargs'
|
|
14
14
|
import { deployHealthCheck, deployHealthCheckYargsOptions } from '../deploy/deployHealthCheck.js'
|
|
15
15
|
|
|
@@ -6,7 +6,7 @@ yarn deploy-prepare
|
|
|
6
6
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { runScript } from '@naturalcycles/nodejs-lib'
|
|
9
|
+
import { runScript } from '@naturalcycles/nodejs-lib/runScript'
|
|
10
10
|
import { _yargs } from '@naturalcycles/nodejs-lib/yargs'
|
|
11
11
|
import { deployPrepare, deployPrepareYargsOptions } from '../deploy/deployPrepare.js'
|
|
12
12
|
|
package/src/bin/undeploy-gae.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { _lazyValue } from '@naturalcycles/js-lib'
|
|
2
|
+
import type { JsonSchema } from '@naturalcycles/js-lib/json-schema'
|
|
1
3
|
import type { StringMap } from '@naturalcycles/js-lib/types'
|
|
2
4
|
import { AjvSchema } from '@naturalcycles/nodejs-lib/ajv'
|
|
3
5
|
import { fs2 } from '@naturalcycles/nodejs-lib/fs2'
|
|
@@ -38,12 +40,10 @@ export interface BackendCfg {
|
|
|
38
40
|
appYamlPassEnv?: string
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
const
|
|
42
|
-
`${resourcesDir}/backendCfg.schema.json
|
|
43
|
-
{
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
)
|
|
43
|
+
const getBackendCfgSchema = _lazyValue(() => {
|
|
44
|
+
const schemaJson = fs2.readJson<JsonSchema<BackendCfg>>(`${resourcesDir}/backendCfg.schema.json`)
|
|
45
|
+
return AjvSchema.create(schemaJson, { objectName: 'backend.cfg.yaml' })
|
|
46
|
+
})
|
|
47
47
|
|
|
48
48
|
export function getBackendCfg(projectDir = '.'): BackendCfg {
|
|
49
49
|
const backendCfgYamlPath = `${projectDir}/backend.cfg.yaml`
|
|
@@ -54,6 +54,6 @@ export function getBackendCfg(projectDir = '.'): BackendCfg {
|
|
|
54
54
|
...yaml2.readYaml(backendCfgYamlPath),
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
getBackendCfgSchema().validate(backendCfg)
|
|
58
58
|
return backendCfg
|
|
59
59
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { AppError } from '@naturalcycles/js-lib/error'
|
|
2
|
-
import { _get } from '@naturalcycles/js-lib/object'
|
|
3
1
|
import type { AjvSchema, AjvValidationError } from '@naturalcycles/nodejs-lib/ajv'
|
|
4
2
|
import type { BackendRequest } from '../../server/server.model.js'
|
|
5
|
-
import type
|
|
3
|
+
import { handleValidationError, type ReqValidationOptions } from '../validateRequest.util.js'
|
|
6
4
|
|
|
7
5
|
class AjvValidateRequest {
|
|
8
6
|
body<T>(
|
|
@@ -43,11 +41,10 @@ class AjvValidateRequest {
|
|
|
43
41
|
schema: AjvSchema<T>,
|
|
44
42
|
opt: ReqValidationOptions<AjvValidationError> = {},
|
|
45
43
|
): T {
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
return this.validate(req, 'headers', schema, {
|
|
45
|
+
mutate: false,
|
|
48
46
|
...opt,
|
|
49
|
-
}
|
|
50
|
-
return this.validate(req, 'headers', schema, options)
|
|
47
|
+
})
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
private validate<T>(
|
|
@@ -56,53 +53,21 @@ class AjvValidateRequest {
|
|
|
56
53
|
schema: AjvSchema<T>,
|
|
57
54
|
opt: ReqValidationOptions<AjvValidationError> = {},
|
|
58
55
|
): T {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
const
|
|
56
|
+
const { mutate = true } = opt
|
|
57
|
+
const originalProperty = req[reqProperty] || {}
|
|
58
|
+
const item: T = mutate ? originalProperty : { ...originalProperty }
|
|
59
|
+
|
|
60
|
+
// Ajv mutates the input
|
|
61
|
+
const error = schema.getValidationError(item, {
|
|
62
62
|
objectName: `request ${reqProperty}`,
|
|
63
63
|
})
|
|
64
64
|
|
|
65
65
|
if (error) {
|
|
66
|
-
|
|
67
|
-
if (typeof opt.report === 'boolean') {
|
|
68
|
-
report = opt.report
|
|
69
|
-
} else if (typeof opt.report === 'function') {
|
|
70
|
-
report = opt.report(error)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (opt.redactPaths) {
|
|
74
|
-
redact(opt.redactPaths, req[reqProperty], error)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
throw new AppError(error.message, {
|
|
78
|
-
backendResponseStatusCode: 400,
|
|
79
|
-
report,
|
|
80
|
-
...error.data,
|
|
81
|
-
})
|
|
66
|
+
handleValidationError(error, originalProperty, opt)
|
|
82
67
|
}
|
|
83
68
|
|
|
84
|
-
|
|
85
|
-
if (!opt.keepOriginal && reqProperty !== 'query') {
|
|
86
|
-
// query is read-only in Express 5
|
|
87
|
-
req[reqProperty] = value
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return value
|
|
69
|
+
return item
|
|
91
70
|
}
|
|
92
71
|
}
|
|
93
72
|
|
|
94
73
|
export const ajvValidateRequest = new AjvValidateRequest()
|
|
95
|
-
|
|
96
|
-
const REDACTED = 'REDACTED'
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Mutates error
|
|
100
|
-
*/
|
|
101
|
-
function redact(redactPaths: string[], obj: any, error: Error): void {
|
|
102
|
-
redactPaths
|
|
103
|
-
.map(path => _get(obj, path) as string)
|
|
104
|
-
.filter(Boolean)
|
|
105
|
-
.forEach(secret => {
|
|
106
|
-
error.message = error.message.replaceAll(secret, REDACTED)
|
|
107
|
-
})
|
|
108
|
-
}
|
|
@@ -1,29 +1,7 @@
|
|
|
1
|
-
import { AppError } from '@naturalcycles/js-lib/error'
|
|
2
|
-
import { _get } from '@naturalcycles/js-lib/object'
|
|
3
1
|
import type { AnySchema, JoiValidationError } from '@naturalcycles/nodejs-lib/joi'
|
|
4
2
|
import { getValidationResult } from '@naturalcycles/nodejs-lib/joi'
|
|
5
3
|
import type { BackendRequest } from '../../server/server.model.js'
|
|
6
|
-
|
|
7
|
-
export interface ReqValidationOptions<ERR extends Error> {
|
|
8
|
-
/**
|
|
9
|
-
* Pass a 'dot-paths' (e.g `pw`, or `input.pw`) that needs to be redacted from the output, in case of error.
|
|
10
|
-
* Useful e.g to redact (prevent leaking) plaintext passwords in error messages.
|
|
11
|
-
*/
|
|
12
|
-
redactPaths?: string[]
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Set to true, or a function that returns true/false based on the error generated.
|
|
16
|
-
* If true - `genericErrorHandler` will report it to errorReporter (aka Sentry).
|
|
17
|
-
*/
|
|
18
|
-
report?: boolean | ((err: ERR) => boolean)
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* When set to true, the validated object will not be replaced with the Joi-converted value.
|
|
22
|
-
*
|
|
23
|
-
* The general default is `false`, with the excepction of `headers` validation, where the default is `true`.
|
|
24
|
-
*/
|
|
25
|
-
keepOriginal?: boolean
|
|
26
|
-
}
|
|
4
|
+
import { handleValidationError, type ReqValidationOptions } from '../validateRequest.util.js'
|
|
27
5
|
|
|
28
6
|
class ValidateRequest {
|
|
29
7
|
body<T>(
|
|
@@ -64,11 +42,10 @@ class ValidateRequest {
|
|
|
64
42
|
schema: AnySchema<T>,
|
|
65
43
|
opt: ReqValidationOptions<JoiValidationError> = {},
|
|
66
44
|
): T {
|
|
67
|
-
|
|
68
|
-
|
|
45
|
+
return this.validate(req, 'headers', schema, {
|
|
46
|
+
mutate: false,
|
|
69
47
|
...opt,
|
|
70
|
-
}
|
|
71
|
-
return this.validate(req, 'headers', schema, options)
|
|
48
|
+
})
|
|
72
49
|
}
|
|
73
50
|
|
|
74
51
|
private validate<T>(
|
|
@@ -77,35 +54,22 @@ class ValidateRequest {
|
|
|
77
54
|
schema: AnySchema<T>,
|
|
78
55
|
opt: ReqValidationOptions<JoiValidationError> = {},
|
|
79
56
|
): T {
|
|
80
|
-
const {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
)
|
|
85
|
-
if (error) {
|
|
86
|
-
let report: boolean | undefined
|
|
87
|
-
if (typeof opt.report === 'boolean') {
|
|
88
|
-
report = opt.report
|
|
89
|
-
} else if (typeof opt.report === 'function') {
|
|
90
|
-
report = opt.report(error)
|
|
91
|
-
}
|
|
57
|
+
const { mutate = true } = opt
|
|
58
|
+
const originalProperty = req[reqProperty] || {}
|
|
59
|
+
|
|
60
|
+
// Joi does not mutate the input
|
|
61
|
+
const { error, value } = getValidationResult(originalProperty, schema, `request ${reqProperty}`)
|
|
92
62
|
|
|
63
|
+
if (error) {
|
|
93
64
|
if (opt.redactPaths) {
|
|
94
|
-
redact(opt.redactPaths, req[reqProperty], error)
|
|
95
65
|
error.data.joiValidationErrorItems.length = 0 // clears the array
|
|
96
66
|
delete error.data.annotation
|
|
97
67
|
}
|
|
98
68
|
|
|
99
|
-
|
|
100
|
-
backendResponseStatusCode: 400,
|
|
101
|
-
report,
|
|
102
|
-
...error.data,
|
|
103
|
-
})
|
|
69
|
+
handleValidationError(error, originalProperty, opt)
|
|
104
70
|
}
|
|
105
71
|
|
|
106
|
-
|
|
107
|
-
if (!opt.keepOriginal && reqProperty !== 'query') {
|
|
108
|
-
// query is read-only in Express 5
|
|
72
|
+
if (mutate) {
|
|
109
73
|
req[reqProperty] = value
|
|
110
74
|
}
|
|
111
75
|
|
|
@@ -114,17 +78,3 @@ class ValidateRequest {
|
|
|
114
78
|
}
|
|
115
79
|
|
|
116
80
|
export const validateRequest = new ValidateRequest()
|
|
117
|
-
|
|
118
|
-
const REDACTED = 'REDACTED'
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Mutates error
|
|
122
|
-
*/
|
|
123
|
-
function redact(redactPaths: string[], obj: any, error: Error): void {
|
|
124
|
-
redactPaths
|
|
125
|
-
.map(path => _get(obj, path) as string)
|
|
126
|
-
.filter(Boolean)
|
|
127
|
-
.forEach(secret => {
|
|
128
|
-
error.message = error.message.replaceAll(secret, REDACTED)
|
|
129
|
-
})
|
|
130
|
-
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AppError } from '@naturalcycles/js-lib/error'
|
|
2
|
+
import { _get } from '@naturalcycles/js-lib/object'
|
|
3
|
+
|
|
4
|
+
export function handleValidationError<T, ERR extends AppError>(
|
|
5
|
+
error: ERR,
|
|
6
|
+
originalProperty: T,
|
|
7
|
+
opt: ReqValidationOptions<ERR> = {},
|
|
8
|
+
): never {
|
|
9
|
+
// const item: T = opt.mutate ? { ...req[reqProperty] } : (req[reqProperty] || {})
|
|
10
|
+
|
|
11
|
+
let report: boolean | undefined
|
|
12
|
+
if (typeof opt.report === 'boolean') {
|
|
13
|
+
report = opt.report
|
|
14
|
+
} else if (typeof opt.report === 'function') {
|
|
15
|
+
report = opt.report(error)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (opt.redactPaths) {
|
|
19
|
+
redact(opt.redactPaths, originalProperty, error)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
throw new AppError(error.message, {
|
|
23
|
+
backendResponseStatusCode: 400,
|
|
24
|
+
report,
|
|
25
|
+
...error.data,
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const REDACTED = 'REDACTED'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Mutates error
|
|
33
|
+
*/
|
|
34
|
+
function redact(redactPaths: string[], obj: any, error: Error): void {
|
|
35
|
+
redactPaths
|
|
36
|
+
.map(path => _get(obj, path) as string)
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.forEach(secret => {
|
|
39
|
+
error.message = error.message.replaceAll(secret, REDACTED)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ReqValidationOptions<ERR extends AppError> {
|
|
44
|
+
/**
|
|
45
|
+
* Pass a 'dot-paths' (e.g `pw`, or `input.pw`) that needs to be redacted from the output, in case of error.
|
|
46
|
+
* Useful e.g to redact (prevent leaking) plaintext passwords in error messages.
|
|
47
|
+
*/
|
|
48
|
+
redactPaths?: string[]
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Set to true, or a function that returns true/false based on the error generated.
|
|
52
|
+
* If true - `genericErrorHandler` will report it to errorReporter (aka Sentry).
|
|
53
|
+
*/
|
|
54
|
+
report?: boolean | ((err: ERR) => boolean)
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* When set to true, the validated object will not be replaced with the Joi-converted value.
|
|
58
|
+
*
|
|
59
|
+
* Defaults to true.
|
|
60
|
+
* Exception is `headers` validation, where the default is `false`.
|
|
61
|
+
*
|
|
62
|
+
* To avoid mutation - shallow copy is performed.
|
|
63
|
+
*/
|
|
64
|
+
mutate?: boolean
|
|
65
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { AppError } from '@naturalcycles/js-lib/error'
|
|
2
|
-
import { _get } from '@naturalcycles/js-lib/object'
|
|
3
1
|
import { type ZodType, type ZodValidationError, zSafeValidate } from '@naturalcycles/js-lib/zod'
|
|
4
2
|
import type { BackendRequest } from '../../server/server.model.js'
|
|
5
|
-
import type
|
|
3
|
+
import { handleValidationError, type ReqValidationOptions } from '../validateRequest.util.js'
|
|
6
4
|
|
|
7
5
|
class ZodValidateRequest {
|
|
8
6
|
body<T>(
|
|
@@ -43,11 +41,10 @@ class ZodValidateRequest {
|
|
|
43
41
|
schema: ZodType<T>,
|
|
44
42
|
opt: ReqValidationOptions<ZodValidationError> = {},
|
|
45
43
|
): T {
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
return this.validate(req, 'headers', schema, {
|
|
45
|
+
mutate: false,
|
|
48
46
|
...opt,
|
|
49
|
-
}
|
|
50
|
-
return this.validate(req, 'headers', schema, options)
|
|
47
|
+
})
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
private validate<T>(
|
|
@@ -56,34 +53,21 @@ class ZodValidateRequest {
|
|
|
56
53
|
schema: ZodType<T>,
|
|
57
54
|
opt: ReqValidationOptions<ZodValidationError> = {},
|
|
58
55
|
): T {
|
|
59
|
-
const {
|
|
60
|
-
|
|
56
|
+
const { mutate = true } = opt
|
|
57
|
+
const originalProperty = req[reqProperty] || {}
|
|
58
|
+
|
|
59
|
+
// Zod does not mutate the input
|
|
60
|
+
const { error, data } = zSafeValidate(
|
|
61
|
+
originalProperty,
|
|
61
62
|
schema,
|
|
62
|
-
//
|
|
63
|
+
// opt2?.itemName,
|
|
63
64
|
)
|
|
64
65
|
|
|
65
66
|
if (error) {
|
|
66
|
-
|
|
67
|
-
if (typeof opt.report === 'boolean') {
|
|
68
|
-
report = opt.report
|
|
69
|
-
} else if (typeof opt.report === 'function') {
|
|
70
|
-
report = opt.report(error)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (opt.redactPaths) {
|
|
74
|
-
redact(opt.redactPaths, req[reqProperty], error)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
throw new AppError(error.message, {
|
|
78
|
-
backendResponseStatusCode: 400,
|
|
79
|
-
report,
|
|
80
|
-
...error.data,
|
|
81
|
-
})
|
|
67
|
+
handleValidationError(error, originalProperty, opt)
|
|
82
68
|
}
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
if (!opt.keepOriginal && reqProperty !== 'query') {
|
|
86
|
-
// query is read-only in Express 5
|
|
70
|
+
if (mutate) {
|
|
87
71
|
req[reqProperty] = data
|
|
88
72
|
}
|
|
89
73
|
|
|
@@ -92,17 +76,3 @@ class ZodValidateRequest {
|
|
|
92
76
|
}
|
|
93
77
|
|
|
94
78
|
export const zodValidateRequest = new ZodValidateRequest()
|
|
95
|
-
|
|
96
|
-
const REDACTED = 'REDACTED'
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Mutates error
|
|
100
|
-
*/
|
|
101
|
-
function redact(redactPaths: string[], obj: any, error: Error): void {
|
|
102
|
-
redactPaths
|
|
103
|
-
.map(path => _get(obj, path) as string)
|
|
104
|
-
.filter(Boolean)
|
|
105
|
-
.forEach(secret => {
|
|
106
|
-
error.message = error.message.replaceAll(secret, REDACTED)
|
|
107
|
-
})
|
|
108
|
-
}
|