@opengis/fastify-table 1.3.66 → 1.3.67

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "1.3.66",
3
+ "version": "1.3.67",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [
@@ -3,8 +3,25 @@ import config from '../../../../config.js';
3
3
  const emailReg = /(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-zA-Z0-9-]*[a-zA-Z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/g;
4
4
 
5
5
  function checkField(key, val, options, idx, body) {
6
+ const type = options?.type?.toLowerCase?.();
7
+ const isrequired = options?.validators?.includes?.('required');
8
+
6
9
  // validators: [required]
7
- if (options?.validators?.includes('required') && !val) {
10
+ if (type === 'switcher') {
11
+ // skip nulls at non-required switchers, restrict invalid values
12
+ if (val && typeof val !== 'boolean') {
13
+ return {
14
+ error: 'not a boolean', key, idx,
15
+ };
16
+ }
17
+ // restrict nulls at required switchers
18
+ if (isrequired && typeof val !== 'boolean') {
19
+ return {
20
+ error: 'empty required', key, idx,
21
+ };
22
+ }
23
+ }
24
+ else if (isrequired && !val) {
8
25
  if (options?.conditions) {
9
26
  const allowed = JSON.parse(options?.conditions?.[2] || null);
10
27
  const check = options?.conditions?.[1] === 'in' && Array.isArray(allowed)
@@ -61,4 +78,4 @@ function checkBody({ body = {}, arr = [], idx }) {
61
78
  export default function validateData({ body = {}, schema = {} }) {
62
79
  const res = checkBody({ body, arr: Object.keys(schema).map(key => ({ ...schema[key], key })) });
63
80
  return res;
64
- }
81
+ }
@@ -3,7 +3,7 @@ import xssInjection from '../xssInjection.js';
3
3
 
4
4
  function checkXSS({ body, schema = {} }) {
5
5
  const data = typeof body === 'string' ? body : JSON.stringify(body);
6
- const stopWords = xssInjection.filter((el) => data.toLowerCase().includes(el));
6
+ const stopWords = xssInjection.filter((el) => data?.toLowerCase?.()?.includes?.(el));
7
7
 
8
8
  // check sql injection
9
9
  const stopSpecialSymbols = data.match(/\p{S}OR\p{S}|\p{P}OR\p{P}| OR |\+OR\+/gi);
@@ -13,7 +13,7 @@ function checkXSS({ body, schema = {} }) {
13
13
  const skipScreening = config.skipScreening || ['Summernote', 'Tiny', 'Ace', 'Texteditor'];
14
14
  Object.keys(body)
15
15
  .filter((key) => ['<', '>'].find((el) => body[key]?.includes?.(el))
16
- && !skipScreening.includes(schema?.[key]?.type))
16
+ && !skipScreening.includes(schema?.[key]?.type))
17
17
  ?.forEach((key) => {
18
18
  Object.assign(body, { [key]: body[key].replace(/</g, '&lt;').replace(/>/g, '&gt;') });
19
19
  });
@@ -24,9 +24,9 @@ function checkXSS({ body, schema = {} }) {
24
24
 
25
25
  const field = Object.keys(body)
26
26
  ?.find((key) => body[key]?.toLowerCase
27
- && !disabledCheckFields.includes(key)
28
- && (skipScreening.includes(schema?.[key]?.type) ? stopWords.find(el => !['href=', 'src='].includes(el)) : true)
29
- && body[key].toLowerCase().includes(stopWords[0]));
27
+ && !disabledCheckFields.includes(key)
28
+ && (skipScreening.includes(schema?.[key]?.type) ? stopWords.find(el => !['href=', 'src='].includes(el)) : true)
29
+ && body[key].toLowerCase().includes(stopWords[0]));
30
30
  if (field) {
31
31
  console.error(stopWords[0], field, body[field]);
32
32
  return { error: `rule: ${stopWords[0]} | attr: ${field} | val: ${body[field]}`, body };
@@ -3,7 +3,9 @@ import {
3
3
  } from '../../../../utils.js';
4
4
 
5
5
  export default async function deleteCrud(req, reply) {
6
- const { pg = pgClients.client, user, params = {}, headers = {} } = req || {};
6
+ const {
7
+ pg = pgClients.client, user, params = {}, headers = {},
8
+ } = req || {};
7
9
 
8
10
  const hookData = await applyHook('preDelete', {
9
11
  pg, table: params?.table, id: params?.id, user,
@@ -36,7 +38,7 @@ export default async function deleteCrud(req, reply) {
36
38
  }).catch(err => {
37
39
  if (err.message?.includes?.('foreign key' || 'unique')) {
38
40
  const constraint = err.message.match(/constraint "([^"]+)"/g);
39
- return reply.status(400).send('Видалення заборонено для збереження цілісності БД: ' + constraint);
41
+ return reply.status(400).send(`Видалення заборонено для збереження цілісності БД: ${constraint}`);
40
42
  }
41
43
  if (config.trace) console.error(err.toString());
42
44
  return err.toString();
@@ -2,13 +2,17 @@ import {
2
2
  applyHook, getAccess, getTemplate, checkXSS, dataInsert, getToken, config, pgClients, logger, validateData,
3
3
  } from '../../../../utils.js';
4
4
 
5
- export default async function insert(req) {
5
+ export default async function insert(req, reply) {
6
6
  const {
7
7
  pg = pgClients.client, user = {}, params = {}, body = {}, headers = {},
8
8
  } = req || {};
9
- if (!user) return { message: 'access restricted', status: 403 };
9
+ if (!user) {
10
+ return reply.status(403).send('access restricted');
11
+ }
10
12
 
11
- const hookData = await applyHook('preInsert', { pg, table: params?.table, user, body });
13
+ const hookData = await applyHook('preInsert', {
14
+ pg, table: params?.table, user, body,
15
+ });
12
16
 
13
17
  if (hookData?.message && hookData?.status) {
14
18
  return { message: hookData?.message, status: hookData?.status };
@@ -23,12 +27,12 @@ export default async function insert(req) {
23
27
 
24
28
  const { actions = [] } = await getAccess({ table: add, user }, pg) || {};
25
29
 
26
- if (!actions.includes('add') && !config?.local && !tokenData) {
27
- return { message: 'access restricted', status: 403 };
30
+ if (!actions.includes('add') && !config.local && !config.debug && !tokenData) {
31
+ return reply.status(403).send('access restricted');
28
32
  }
29
33
 
30
34
  if (!add) {
31
- return { message: 'table is required', status: 400 };
35
+ return reply.status(400).send('table is required');
32
36
  }
33
37
 
34
38
  const loadTemplate = await getTemplate('table', add);
@@ -43,8 +47,10 @@ export default async function insert(req) {
43
47
  const xssCheck = checkXSS({ body, schema });
44
48
 
45
49
  if (xssCheck.error && formData?.xssCheck !== false) {
46
- logger.file('injection/xss', { table, form: form || loadTemplate?.form, body, uid: user?.uid, msg: xssCheck.error });
47
- return { message: 'Дані містять заборонені символи. Приберіть їх та спробуйте ще раз', status: 409 };
50
+ logger.file('injection/xss', {
51
+ table, form: form || loadTemplate?.form, body, uid: user?.uid, msg: xssCheck.error,
52
+ });
53
+ return reply.status(409).send('Дані містять заборонені символи. Приберіть їх та спробуйте ще раз');
48
54
  }
49
55
 
50
56
  const fieldCheck = validateData({ body, schema });
@@ -56,7 +62,7 @@ export default async function insert(req) {
56
62
  uid: user?.uid,
57
63
  ...fieldCheck,
58
64
  });
59
- return { message: 'Дані не пройшли валідацію. Приберіть некоректні дані та спробуйте ще раз', status: 409 };
65
+ return reply.status(409).send('Дані не пройшли валідацію. Приберіть некоректні дані та спробуйте ще раз');
60
66
  }
61
67
 
62
68
  if (![add, table].includes('admin.users')) {
@@ -76,7 +82,10 @@ export default async function insert(req) {
76
82
  tokenData,
77
83
  referer,
78
84
  });
79
- if (!res) return { message: 'nothing added ' };
85
+
86
+ if (!res) {
87
+ return reply.status(400).send('nothing added');
88
+ }
80
89
 
81
90
  // admin.custom_column
82
91
  await applyHook('afterInsert', {
@@ -33,17 +33,20 @@ export default async function tableAPI(req) {
33
33
 
34
34
  if (tokenData && !id) return { message: {} };
35
35
  if (!tableName && !id) {
36
- return { message: 'not enough params', status: 400 };
36
+ return reply.status(400).send('not enough params');
37
37
  }
38
38
 
39
39
  const { actions = [], query: accessQuery } = await getAccess({ table: templateName, id, user }, pg) || {};
40
40
 
41
41
  if (!actions.includes('edit') && !config?.local && !tokenData) {
42
- return { message: 'access restricted', status: 403 };
42
+ return reply.status(403).send('access restricted');
43
43
  }
44
44
 
45
45
  const { pk, columns: dbColumns = [] } = await getMeta({ pg, table: tableName });
46
- if (!pk) return { message: `table not found: ${table}`, status: 404 };
46
+
47
+ if (!pk) {
48
+ return reply.status(404).send(`table not found: ${table}`);
49
+ }
47
50
 
48
51
  // const cols = columns.map((el) => el.name || el).join(',');
49
52
  const formName = hookData?.form || tokenData?.form || form;
@@ -72,7 +75,10 @@ export default async function tableAPI(req) {
72
75
  }
73
76
 
74
77
  const data = await pg.query(q.replace(/{{uid}}/, user?.uid), [id]).then(el => el.rows[0]);
75
- if (!data) return { message: 'not found', status: 404 };
78
+
79
+ if (!data) {
80
+ return reply.status(404).send(`object not found: ${id}`);
81
+ }
76
82
 
77
83
  Object.keys(schema).filter(key => schema[key]?.type === 'DataTable').forEach(key => {
78
84
  if (data[key] && !Array.isArray(data[key])) { data[key] = null; }
@@ -4,12 +4,15 @@ import {
4
4
  import config from '../../../../config.js';
5
5
  import insert from './insert.js';
6
6
 
7
- export default async function update(req) {
7
+ export default async function update(req, reply) {
8
8
  const {
9
9
  pg = pgClients.client, user, params = {}, body = {}, headers = {}, unittest,
10
10
  } = req;
11
11
 
12
- if (!user) return { message: 'access restricted', status: 403 };
12
+ if (!user) {
13
+ return reply.status(403).send('access restricted');
14
+ }
15
+
13
16
  const hookData = await applyHook('preUpdate', {
14
17
  pg, table: params?.table, id: params?.id, user,
15
18
  });
@@ -27,19 +30,20 @@ export default async function update(req) {
27
30
 
28
31
  const { actions = [] } = await getAccess({ table: edit, id, user }, pg) || {};
29
32
 
30
- if (!actions.includes('edit') && !config?.local && !tokenData) {
31
- return { message: 'access restricted', status: 403 };
33
+ if (!actions.includes('edit') && !config.local && !config.debug && !tokenData) {
34
+ return reply.status(403).send('access restricted');
32
35
  }
33
36
 
34
37
  if (!edit) {
35
- return { message: 'table is required', status: 400 };
38
+ return reply.status(400).send('table is required');
36
39
  }
37
40
 
38
41
  if (!id && tokenData?.table) {
39
42
  return insert(req);
40
43
  }
44
+
41
45
  if (!id) {
42
- return { message: 'id is required', status: 404 };
46
+ return reply.status(400).send('id is required');
43
47
  }
44
48
 
45
49
  const loadTemplate = await getTemplate('table', edit);
@@ -59,7 +63,7 @@ export default async function update(req) {
59
63
 
60
64
  if (xssCheck.error && formData?.xssCheck !== false) {
61
65
  logger.file('injection/xss', { msg: xssCheck.error, table }, req);
62
- return { message: 'Дані містять заборонені символи. Приберіть їх та спробуйте ще раз', status: 409 };
66
+ return reply.status(409).send('Дані містять заборонені символи. Приберіть їх та спробуйте ще раз');
63
67
  }
64
68
 
65
69
  const fieldCheck = validateData({ body, schema });
@@ -71,7 +75,7 @@ export default async function update(req) {
71
75
  uid: user?.uid,
72
76
  ...fieldCheck,
73
77
  });
74
- return { message: 'Дані не пройшли валідацію. Приберіть некоректні дані та спробуйте ще раз', status: 409 };
78
+ return reply.status(409).send('Дані не пройшли валідацію. Приберіть некоректні дані та спробуйте ще раз');
75
79
  }
76
80
 
77
81
  const res = await dataUpdate({
@@ -100,7 +104,9 @@ export default async function update(req) {
100
104
  // insert new extra data
101
105
  if (Array.isArray(body[key]) && body[key]?.length) {
102
106
  const extraRows = await Promise.all(body[key]?.map?.(async (row) => {
103
- const extraRes = await dataInsert({ pg, table: schema[key].table, data: { ...row, [schema[key].parent_id]: objId }, uid, tokenData, referer });
107
+ const extraRes = await dataInsert({
108
+ pg, table: schema[key].table, data: { ...row, [schema[key].parent_id]: objId }, uid, tokenData, referer,
109
+ });
104
110
  return extraRes?.rows?.[0];
105
111
  }));
106
112
  Object.assign(res.extra, { [key]: extraRows.filter((el) => el) });