@naturalcycles/backend-lib 9.20.0 → 9.22.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/cfg/tsconfig.json +2 -2
- package/dist/deploy/backend.cfg.util.js +1 -1
- package/dist/deploy/deployInfo.util.d.ts +1 -2
- package/dist/validation/ajv/ajvValidateRequest.d.ts +0 -9
- package/dist/validation/ajv/ajvValidateRequest.js +7 -17
- package/dist/validation/joi/joiValidateRequest.js +3 -3
- package/dist/validation/validateRequest.util.d.ts +4 -5
- package/dist/validation/validateRequest.util.js +0 -1
- package/dist/validation/zod/zodValidateRequest.js +3 -3
- package/package.json +2 -2
- package/src/deploy/backend.cfg.util.ts +1 -1
- package/src/validation/ajv/ajvValidateRequest.ts +7 -17
- package/src/validation/joi/joiValidateRequest.ts +3 -3
- package/src/validation/validateRequest.util.ts +4 -7
- package/src/validation/zod/zodValidateRequest.ts +3 -3
- package/dist/admin/login.html +0 -150
- package/dist/deploy/files-default/.gcloudignore +0 -7
package/cfg/tsconfig.json
CHANGED
|
@@ -5,7 +5,7 @@ import { yaml2 } from '@naturalcycles/nodejs-lib/yaml2';
|
|
|
5
5
|
import { resourcesDir } from '../paths.cnst.js';
|
|
6
6
|
const getBackendCfgSchema = _lazyValue(() => {
|
|
7
7
|
const schemaJson = fs2.readJson(`${resourcesDir}/backendCfg.schema.json`);
|
|
8
|
-
return AjvSchema.create(schemaJson, {
|
|
8
|
+
return AjvSchema.create(schemaJson, { inputName: 'backend.cfg.yaml' });
|
|
9
9
|
});
|
|
10
10
|
export function getBackendCfg(projectDir = '.') {
|
|
11
11
|
const backendCfgYamlPath = `${projectDir}/backend.cfg.yaml`;
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const getDeployInfo: ((projectDir: string) => DeployInfo) & import("@naturalcycles/js-lib/decorators/memoFn.js").MemoizedFunction;
|
|
1
|
+
export declare const getDeployInfo: any;
|
|
@@ -5,15 +5,6 @@ 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;
|
|
7
7
|
params<T>(req: BackendRequest, schema: AjvSchema<T>, opt?: ReqValidationOptions<AjvValidationError>): T;
|
|
8
|
-
/**
|
|
9
|
-
* Validates `req.headers` against the provided schema.
|
|
10
|
-
*
|
|
11
|
-
* Note: as opposed to other methods, this method does not mutate `req.headers` in case of success,
|
|
12
|
-
* i.e. schemas that cast values will not have any effect.
|
|
13
|
-
*
|
|
14
|
-
* If you wish to mutate `req.headers` with the validated value, use `keepOriginal: false` option.
|
|
15
|
-
* Keep in mind that this will also remove all values that are not in the schema.
|
|
16
|
-
*/
|
|
17
8
|
headers<T>(req: BackendRequest, schema: AjvSchema<T>, opt?: ReqValidationOptions<AjvValidationError>): T;
|
|
18
9
|
private validate;
|
|
19
10
|
}
|
|
@@ -9,30 +9,20 @@ class AjvValidateRequest {
|
|
|
9
9
|
params(req, schema, opt = {}) {
|
|
10
10
|
return this.validate(req, 'params', schema, opt);
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Validates `req.headers` against the provided schema.
|
|
14
|
-
*
|
|
15
|
-
* Note: as opposed to other methods, this method does not mutate `req.headers` in case of success,
|
|
16
|
-
* i.e. schemas that cast values will not have any effect.
|
|
17
|
-
*
|
|
18
|
-
* If you wish to mutate `req.headers` with the validated value, use `keepOriginal: false` option.
|
|
19
|
-
* Keep in mind that this will also remove all values that are not in the schema.
|
|
20
|
-
*/
|
|
21
12
|
headers(req, schema, opt = {}) {
|
|
22
13
|
return this.validate(req, 'headers', schema, opt);
|
|
23
14
|
}
|
|
24
15
|
validate(req, reqProperty, schema, opt = {}) {
|
|
25
|
-
const {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
objectName: `request ${reqProperty}`,
|
|
16
|
+
const { mutateInput = true } = opt;
|
|
17
|
+
const input = req[reqProperty] || {};
|
|
18
|
+
const [error, output] = schema.getValidationResult(input, {
|
|
19
|
+
mutateInput,
|
|
20
|
+
inputName: `request ${reqProperty}`,
|
|
31
21
|
});
|
|
32
22
|
if (error) {
|
|
33
|
-
handleValidationError(error,
|
|
23
|
+
handleValidationError(error, input, opt);
|
|
34
24
|
}
|
|
35
|
-
return
|
|
25
|
+
return output;
|
|
36
26
|
}
|
|
37
27
|
}
|
|
38
28
|
export const ajvValidateRequest = new AjvValidateRequest();
|
|
@@ -23,10 +23,10 @@ class ValidateRequest {
|
|
|
23
23
|
return this.validate(req, 'headers', schema, opt);
|
|
24
24
|
}
|
|
25
25
|
validate(req, reqProperty, schema, opt = {}) {
|
|
26
|
-
const {
|
|
26
|
+
const { mutateInput } = opt;
|
|
27
27
|
const originalProperty = req[reqProperty] || {};
|
|
28
28
|
// Joi does not mutate the input
|
|
29
|
-
const
|
|
29
|
+
const [error, value] = getValidationResult(originalProperty, schema, `request ${reqProperty}`);
|
|
30
30
|
if (error) {
|
|
31
31
|
if (opt.redactPaths) {
|
|
32
32
|
error.data.joiValidationErrorItems.length = 0; // clears the array
|
|
@@ -34,7 +34,7 @@ class ValidateRequest {
|
|
|
34
34
|
}
|
|
35
35
|
handleValidationError(error, originalProperty, opt);
|
|
36
36
|
}
|
|
37
|
-
if (
|
|
37
|
+
if (mutateInput) {
|
|
38
38
|
req[reqProperty] = value;
|
|
39
39
|
}
|
|
40
40
|
return value;
|
|
@@ -12,11 +12,10 @@ export interface ReqValidationOptions<ERR extends AppError> {
|
|
|
12
12
|
*/
|
|
13
13
|
report?: boolean | ((err: ERR) => boolean);
|
|
14
14
|
/**
|
|
15
|
-
* Defaults to
|
|
15
|
+
* Defaults to true.
|
|
16
16
|
*
|
|
17
|
-
* When set to true,
|
|
18
|
-
*
|
|
19
|
-
* To avoid mutation - shallow copy is performed.
|
|
17
|
+
* When set to true, input (body, query, headers or params) will be mutated.
|
|
18
|
+
* So, e.g req.body will already contain mutated data post-validation.
|
|
20
19
|
*/
|
|
21
|
-
|
|
20
|
+
mutateInput?: boolean;
|
|
22
21
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AppError } from '@naturalcycles/js-lib/error/error.util.js';
|
|
2
2
|
import { _get } from '@naturalcycles/js-lib/object/object.util.js';
|
|
3
3
|
export function handleValidationError(error, originalProperty, opt = {}) {
|
|
4
|
-
// const item: T = opt.mutate ? { ...req[reqProperty] } : (req[reqProperty] || {})
|
|
5
4
|
let report;
|
|
6
5
|
if (typeof opt.report === 'boolean') {
|
|
7
6
|
report = opt.report;
|
|
@@ -23,14 +23,14 @@ class ZodValidateRequest {
|
|
|
23
23
|
return this.validate(req, 'headers', schema, opt);
|
|
24
24
|
}
|
|
25
25
|
validate(req, reqProperty, schema, opt = {}) {
|
|
26
|
-
const {
|
|
26
|
+
const { mutateInput } = opt;
|
|
27
27
|
const originalProperty = req[reqProperty] || {};
|
|
28
28
|
// Zod does not mutate the input
|
|
29
|
-
const
|
|
29
|
+
const [error, data] = zSafeValidate(originalProperty, schema);
|
|
30
30
|
if (error) {
|
|
31
31
|
handleValidationError(error, originalProperty, opt);
|
|
32
32
|
}
|
|
33
|
-
if (
|
|
33
|
+
if (mutateInput) {
|
|
34
34
|
req[reqProperty] = data;
|
|
35
35
|
}
|
|
36
36
|
return data;
|
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.22.0",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@sentry/node": "^9"
|
|
7
7
|
},
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@sentry/node": "^9",
|
|
30
30
|
"@types/ejs": "^3",
|
|
31
31
|
"fastify": "^5",
|
|
32
|
-
"@naturalcycles/dev-lib": "
|
|
32
|
+
"@naturalcycles/dev-lib": "19.22.0"
|
|
33
33
|
},
|
|
34
34
|
"exports": {
|
|
35
35
|
".": "./dist/index.js",
|
|
@@ -42,7 +42,7 @@ export interface BackendCfg {
|
|
|
42
42
|
|
|
43
43
|
const getBackendCfgSchema = _lazyValue(() => {
|
|
44
44
|
const schemaJson = fs2.readJson<JsonSchema<BackendCfg>>(`${resourcesDir}/backendCfg.schema.json`)
|
|
45
|
-
return AjvSchema.create(schemaJson, {
|
|
45
|
+
return AjvSchema.create(schemaJson, { inputName: 'backend.cfg.yaml' })
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
export function getBackendCfg(projectDir = '.'): BackendCfg {
|
|
@@ -27,15 +27,6 @@ class AjvValidateRequest {
|
|
|
27
27
|
return this.validate(req, 'params', schema, opt)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
* Validates `req.headers` against the provided schema.
|
|
32
|
-
*
|
|
33
|
-
* Note: as opposed to other methods, this method does not mutate `req.headers` in case of success,
|
|
34
|
-
* i.e. schemas that cast values will not have any effect.
|
|
35
|
-
*
|
|
36
|
-
* If you wish to mutate `req.headers` with the validated value, use `keepOriginal: false` option.
|
|
37
|
-
* Keep in mind that this will also remove all values that are not in the schema.
|
|
38
|
-
*/
|
|
39
30
|
headers<T>(
|
|
40
31
|
req: BackendRequest,
|
|
41
32
|
schema: AjvSchema<T>,
|
|
@@ -50,20 +41,19 @@ class AjvValidateRequest {
|
|
|
50
41
|
schema: AjvSchema<T>,
|
|
51
42
|
opt: ReqValidationOptions<AjvValidationError> = {},
|
|
52
43
|
): T {
|
|
53
|
-
const {
|
|
54
|
-
const
|
|
55
|
-
const item: T = mutate ? originalProperty : { ...originalProperty }
|
|
44
|
+
const { mutateInput = true } = opt
|
|
45
|
+
const input: T = req[reqProperty] || {}
|
|
56
46
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
const [error, output] = schema.getValidationResult(input, {
|
|
48
|
+
mutateInput,
|
|
49
|
+
inputName: `request ${reqProperty}`,
|
|
60
50
|
})
|
|
61
51
|
|
|
62
52
|
if (error) {
|
|
63
|
-
handleValidationError(error,
|
|
53
|
+
handleValidationError(error, input, opt)
|
|
64
54
|
}
|
|
65
55
|
|
|
66
|
-
return
|
|
56
|
+
return output
|
|
67
57
|
}
|
|
68
58
|
}
|
|
69
59
|
|
|
@@ -51,11 +51,11 @@ class ValidateRequest {
|
|
|
51
51
|
schema: AnySchema<T>,
|
|
52
52
|
opt: ReqValidationOptions<JoiValidationError> = {},
|
|
53
53
|
): T {
|
|
54
|
-
const {
|
|
54
|
+
const { mutateInput } = opt
|
|
55
55
|
const originalProperty = req[reqProperty] || {}
|
|
56
56
|
|
|
57
57
|
// Joi does not mutate the input
|
|
58
|
-
const
|
|
58
|
+
const [error, value] = getValidationResult(originalProperty, schema, `request ${reqProperty}`)
|
|
59
59
|
|
|
60
60
|
if (error) {
|
|
61
61
|
if (opt.redactPaths) {
|
|
@@ -66,7 +66,7 @@ class ValidateRequest {
|
|
|
66
66
|
handleValidationError(error, originalProperty, opt)
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
if (
|
|
69
|
+
if (mutateInput) {
|
|
70
70
|
req[reqProperty] = value
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -6,8 +6,6 @@ export function handleValidationError<T, ERR extends AppError>(
|
|
|
6
6
|
originalProperty: T,
|
|
7
7
|
opt: ReqValidationOptions<ERR> = {},
|
|
8
8
|
): never {
|
|
9
|
-
// const item: T = opt.mutate ? { ...req[reqProperty] } : (req[reqProperty] || {})
|
|
10
|
-
|
|
11
9
|
let report: boolean | undefined
|
|
12
10
|
if (typeof opt.report === 'boolean') {
|
|
13
11
|
report = opt.report
|
|
@@ -54,11 +52,10 @@ export interface ReqValidationOptions<ERR extends AppError> {
|
|
|
54
52
|
report?: boolean | ((err: ERR) => boolean)
|
|
55
53
|
|
|
56
54
|
/**
|
|
57
|
-
* Defaults to
|
|
58
|
-
*
|
|
59
|
-
* When set to true, the validated object will be replaced with the converted value.
|
|
55
|
+
* Defaults to true.
|
|
60
56
|
*
|
|
61
|
-
*
|
|
57
|
+
* When set to true, input (body, query, headers or params) will be mutated.
|
|
58
|
+
* So, e.g req.body will already contain mutated data post-validation.
|
|
62
59
|
*/
|
|
63
|
-
|
|
60
|
+
mutateInput?: boolean
|
|
64
61
|
}
|
|
@@ -50,11 +50,11 @@ class ZodValidateRequest {
|
|
|
50
50
|
schema: ZodType<T>,
|
|
51
51
|
opt: ReqValidationOptions<ZodValidationError> = {},
|
|
52
52
|
): T {
|
|
53
|
-
const {
|
|
53
|
+
const { mutateInput } = opt
|
|
54
54
|
const originalProperty = req[reqProperty] || {}
|
|
55
55
|
|
|
56
56
|
// Zod does not mutate the input
|
|
57
|
-
const
|
|
57
|
+
const [error, data] = zSafeValidate(
|
|
58
58
|
originalProperty,
|
|
59
59
|
schema,
|
|
60
60
|
// opt2?.itemName,
|
|
@@ -64,7 +64,7 @@ class ZodValidateRequest {
|
|
|
64
64
|
handleValidationError(error, originalProperty, opt)
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
if (
|
|
67
|
+
if (mutateInput) {
|
|
68
68
|
req[reqProperty] = data
|
|
69
69
|
}
|
|
70
70
|
|
package/dist/admin/login.html
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>Login</title>
|
|
5
|
-
|
|
6
|
-
<meta charset="utf-8" />
|
|
7
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
|
8
|
-
|
|
9
|
-
<!-- CSS only -->
|
|
10
|
-
<link
|
|
11
|
-
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.2/dist/css/bootstrap.min.css"
|
|
12
|
-
rel="stylesheet"
|
|
13
|
-
crossorigin="anonymous"
|
|
14
|
-
/>
|
|
15
|
-
</head>
|
|
16
|
-
<body>
|
|
17
|
-
<div id="app" style="padding: 40px 50px">
|
|
18
|
-
<pre v-if="loading">Checking login...</pre>
|
|
19
|
-
<div v-else>
|
|
20
|
-
<div v-if="user">
|
|
21
|
-
<p>Logged in as:</p>
|
|
22
|
-
<pre><mark>{{ user.email }}</mark></pre>
|
|
23
|
-
<pre><mark>{{ user.token }}</mark></pre>
|
|
24
|
-
<button class="btn btn-primary btn-lg" @click="logout">Logout</button>
|
|
25
|
-
</div>
|
|
26
|
-
<div v-else>
|
|
27
|
-
<button class="btn btn-primary btn-lg" @click="login" id="loginBtn">Login</button>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
<script type="module">
|
|
33
|
-
import { createApp } from 'https://cdn.jsdelivr.net/npm/vue@3.2.20/dist/vue.esm-browser.prod.js'
|
|
34
|
-
import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.1.2/firebase-app.js'
|
|
35
|
-
import {
|
|
36
|
-
getAuth,
|
|
37
|
-
GoogleAuthProvider,
|
|
38
|
-
onAuthStateChanged,
|
|
39
|
-
signInWithRedirect,
|
|
40
|
-
} from 'https://www.gstatic.com/firebasejs/9.1.2/firebase-auth.js'
|
|
41
|
-
|
|
42
|
-
const apiKey = '<%= firebaseApiKey %>'
|
|
43
|
-
const authDomain = '<%= firebaseAuthDomain %>'
|
|
44
|
-
// const authProvider = '<%= firebaseAuthProvider %>'
|
|
45
|
-
|
|
46
|
-
if (!apiKey || !authDomain) {
|
|
47
|
-
alert(`Error: 'apiKey' or 'authDomain' is missing!`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Initialize Firebase
|
|
51
|
-
initializeApp({
|
|
52
|
-
apiKey,
|
|
53
|
-
authDomain,
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
const auth = getAuth()
|
|
57
|
-
const provider = new GoogleAuthProvider()
|
|
58
|
-
|
|
59
|
-
onAuthStateChanged(auth, user => {
|
|
60
|
-
// console.log('onAuthStateChanged, user: ', JSON.stringify(user, null, 2))
|
|
61
|
-
// console.log('onAuthStateChanged, user: ', user)
|
|
62
|
-
onUser(user)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
const qs = Object.fromEntries(new URLSearchParams(location.search))
|
|
66
|
-
const { autoLogin, logout, returnUrl, adminTokenKey = 'admin_token' } = qs
|
|
67
|
-
// console.log(qs)
|
|
68
|
-
|
|
69
|
-
const app = createApp({
|
|
70
|
-
data() {
|
|
71
|
-
return {
|
|
72
|
-
loading: 'Loading...',
|
|
73
|
-
user: undefined,
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
methods: {
|
|
77
|
-
login: async function () {
|
|
78
|
-
try {
|
|
79
|
-
await signInWithRedirect(auth, provider)
|
|
80
|
-
} catch (err) {
|
|
81
|
-
logError(err)
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
logout: async function () {
|
|
86
|
-
try {
|
|
87
|
-
await auth.signOut()
|
|
88
|
-
|
|
89
|
-
await postToken('logout') // magic string
|
|
90
|
-
|
|
91
|
-
if (logout && returnUrl) {
|
|
92
|
-
alert('Logged out, redurecting back...')
|
|
93
|
-
location.href = returnUrl
|
|
94
|
-
}
|
|
95
|
-
} catch (err) {
|
|
96
|
-
logError(err)
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
}).mount('#app')
|
|
101
|
-
|
|
102
|
-
if (logout) app.logout()
|
|
103
|
-
|
|
104
|
-
////
|
|
105
|
-
|
|
106
|
-
async function onUser(user) {
|
|
107
|
-
try {
|
|
108
|
-
// alert('onUser')
|
|
109
|
-
app.user = user
|
|
110
|
-
app.loading = false
|
|
111
|
-
if (!user) {
|
|
112
|
-
if (autoLogin) app.login()
|
|
113
|
-
} else {
|
|
114
|
-
const token = await auth.currentUser.getIdToken()
|
|
115
|
-
|
|
116
|
-
// alert('idToken')
|
|
117
|
-
// console.log(idToken)
|
|
118
|
-
app.user = Object.assign({}, app.user, {
|
|
119
|
-
token,
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
await postToken(token)
|
|
123
|
-
|
|
124
|
-
// Redirect if needed
|
|
125
|
-
if (returnUrl) {
|
|
126
|
-
// alert(`Logged in as ${app.user.email}, redirecting back...`)
|
|
127
|
-
location.href = returnUrl
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
} catch (err) {
|
|
131
|
-
logError(err)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function logError(err) {
|
|
136
|
-
console.error(err)
|
|
137
|
-
alert('Error\n ' + JSON.stringify(err, null, 2))
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async function postToken(token) {
|
|
141
|
-
await fetch(`/admin/login`, {
|
|
142
|
-
method: 'post',
|
|
143
|
-
headers: {
|
|
144
|
-
Authentication: token,
|
|
145
|
-
},
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
</script>
|
|
149
|
-
</body>
|
|
150
|
-
</html>
|