@opengis/fastify-table 1.1.43 → 1.1.44

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
@@ -1,9 +1,10 @@
1
1
  # fastify-table
2
2
 
3
- ## 1.1.43 - 21.10.2024
3
+ ## 1.1.44 - 21.10.2024
4
4
 
5
5
  - addHook params refactor
6
6
  - add handlebars to utils
7
+ - refactor getAccess for CRUD
7
8
 
8
9
  ## 1.1.40 - 18.10.2024
9
10
 
@@ -1,5 +1,6 @@
1
- import dataDelete from '../funcs/dataDelete.js';
2
- import { getTemplate, getAccess, applyHook } from '../../utils.js';
1
+ import {
2
+ dataDelete, getTemplate, getAccess, applyHook,
3
+ } from '../../utils.js';
3
4
 
4
5
  export default async function deleteCrud(req) {
5
6
  const { user, params = {} } = req || {};
@@ -11,7 +12,7 @@ export default async function deleteCrud(req) {
11
12
  }
12
13
 
13
14
  const { table: del, id } = hookData || req.params || {};
14
- const { actions = [], scope, my } = await getAccess(req, del, id) || {};
15
+ const { actions = [], scope, my } = await getAccess({ table: del, id, user }) || {};
15
16
 
16
17
  if (!actions.includes('del') || (scope === 'my' && !my)) {
17
18
  return { message: 'access restricted', status: 403 };
@@ -4,7 +4,7 @@ import {
4
4
 
5
5
  export default async function insert(req) {
6
6
  const {
7
- pg, user, params = {}, body = {},
7
+ user, params = {}, body = {},
8
8
  } = req || {};
9
9
  const hookData = await applyHook('preInsert', { table: params?.table, user });
10
10
  if (hookData?.message && hookData?.status) {
@@ -13,7 +13,7 @@ export default async function insert(req) {
13
13
 
14
14
  const { form, table: add } = hookData || req.params || {};
15
15
 
16
- const { actions = [] } = await getAccess(req, add) || {};
16
+ const { actions = [] } = await getAccess({ table: add, user }) || {};
17
17
 
18
18
  if (!actions.includes('edit')) {
19
19
  return { message: 'access restricted', status: 403 };
@@ -4,7 +4,7 @@ import {
4
4
 
5
5
  export default async function update(req) {
6
6
  const {
7
- pg, user, params = {}, body = {},
7
+ user, params = {}, body = {},
8
8
  } = req || {};
9
9
  const hookData = await applyHook('preUpdate', {
10
10
  table: params?.table, id: params?.id, user,
@@ -15,7 +15,7 @@ export default async function update(req) {
15
15
 
16
16
  const { form, table: edit, id } = hookData || req.params;
17
17
 
18
- const { actions = [], scope, my } = await getAccess(req, edit, id) || {};
18
+ const { actions = [], scope, my } = await getAccess({ table: edit, id, user }) || {};
19
19
 
20
20
  if (!actions.includes('edit') || (scope === 'my' && !my)) {
21
21
  return { message: 'access restricted', status: 403 };
@@ -1,8 +1,9 @@
1
1
  import getMeta from '../../pg/funcs/getMeta.js';
2
2
  import getTemplate from '../../table/controllers/utils/getTemplate.js';
3
3
  import config from '../../config.js';
4
+ import pgClients from '../../pg/pgClients.js';
4
5
 
5
- const q = `select a.route_id as id, b.actions, b.scope
6
+ const q = `select a.route_id as id, coalesce(b.actions,array['get']) as actions, b.scope
6
7
  from admin.routes a
7
8
  left join admin.access b on
8
9
  a.route_id=b.route_id
@@ -16,36 +17,39 @@ left join admin.user_roles d on
16
17
  then d.expiration > CURRENT_DATE
17
18
  else 1=1
18
19
  end )
19
- where a.route_id=$1 and $2 in (b.user_uid, d.user_uid)`;
20
+ where $1 in (a.route_id, a.alias) and $2 in (b.user_uid, d.user_uid)`;
20
21
 
21
- export default async function getAccess(req, template, id = null) {
22
- if (config.disableAccessRestriction || true) {
22
+ export default async function getAccess({ table, id, user }) {
23
+ if (config.auth?.disable || user?.user_type?.includes('admin')) {
23
24
  return { actions: ['get', 'edit', 'del'], my: true, query: '1=1' };
24
25
  }
25
- const { pg, session = {} } = req;
26
- const { uid, user_type: userType } = session.passport?.user || {};
27
- if (!uid || !template) return null;
26
+
27
+ const { client: pg } = pgClients || {};
28
+ const { uid, user_type: userType } = user || {};
29
+
30
+ if (!uid || !table) return null;
28
31
 
29
32
  if (!pg.pk?.['admin.access']) return null;
30
33
 
31
- const { table } = await getTemplate('table', template) || {};
32
- if (!table) return null;
34
+ const body = await getTemplate('table', table) || {};
35
+ if (!body?.table) return null;
33
36
 
34
- const { scope = 'my', actions = [] } = await pg.one(q, [template, uid]);
35
- // console.log(scope, actions);
37
+ const { scope = 'my', actions = [] } = await pg.query(q, [table, uid]).then((res) => res.rows?.[0] || {});
36
38
 
37
- const { columns = [] } = await getMeta(table);
38
- const columnList = columns.map((el) => el.name || el).join(',');
39
+ const { columns = [] } = await getMeta({ table: body?.table });
39
40
 
40
41
  const query = userType?.includes('admin') ? '1=1' : {
41
42
  my: `uid='${uid}'`,
42
- responsible: columnList.includes('responsible_id')
43
+ responsible: columns.map((el) => el?.name || el).includes('responsible_id')
43
44
  ? `responsible_id='${uid}'`
44
45
  : `uid='${uid}'`,
45
46
  all: '1=1',
46
47
  }[scope];
47
48
 
48
- const { my } = pg.pk?.[table] && id ? await pg.one(`select uid=$1 as my from ${table} where ${pg.pk?.[table]}=$2`, [uid, id]) : {};
49
+ const { my } = pg.pk?.[body?.table] && id
50
+ ? await pg.query(`select uid=$1 as my from ${body?.table} where ${pg.pk?.[body?.table]}=$2`, [uid, id])
51
+ .then((res) => res.rows?.[0] || {})
52
+ : {};
49
53
 
50
54
  return {
51
55
  scope, actions, query, my,
package/index.js CHANGED
@@ -20,7 +20,7 @@ import userPlugin from './user/index.js';
20
20
 
21
21
  // import pgClients from './pg/pgClients.js';
22
22
 
23
- import { addTemplateDir, execMigrations } from './utils.js';
23
+ import { addTemplateDir, execMigrations, logger } from './utils.js';
24
24
 
25
25
  async function plugin(fastify, opt) {
26
26
  // console.log(opt);
@@ -65,8 +65,14 @@ async function plugin(fastify, opt) {
65
65
  return reply.status(error.statusCode || 500).send(config.local ? error.toString() : 'ServerError');
66
66
  });
67
67
 
68
+ execMigrations().catch(err => console.log(err));
68
69
  // core migrations
69
- await execMigrations();
70
+ /* try {
71
+
72
+ }
73
+ catch (err) {
74
+ logger.error(err);
75
+ } */
70
76
 
71
77
  if (!fastify.funcs) {
72
78
  fastify.addHook('onRequest', async (req) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "1.1.43",
3
+ "version": "1.1.44",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "main": "index.js",
@@ -1,14 +1,35 @@
1
1
  import getTemplate from './utils/getTemplate.js';
2
2
  import getMeta from '../../pg/funcs/getMeta.js';
3
3
  import metaFormat from '../funcs/metaFormat/index.js';
4
+ import applyHook from '../../hook/funcs/applyHook.js';
5
+ import getAccess from '../../crud/funcs/getAccess.js';
4
6
 
5
7
  export default async function card(req) {
6
8
  const time = Date.now();
7
9
  const {
8
- pg, params = {}, query = {}, opt = {},
10
+ pg, user, params = {}, query = {},
9
11
  } = req;
10
12
 
11
- const loadTable = await getTemplate('table', params.table);
13
+ const hookData = await applyHook('preCard', {
14
+ table: params?.table, id: params?.id, user,
15
+ });
16
+
17
+ if (hookData?.message && hookData?.status) {
18
+ return { message: hookData?.message, status: hookData?.status };
19
+ }
20
+
21
+ const {
22
+ actions = [], scope, my,
23
+ } = await getAccess({
24
+ table: hookData?.table || params.table,
25
+ id: hookData?.id || params?.id,
26
+ user,
27
+ });
28
+ if (!actions.includes('get') || (scope === 'my' && !my)) {
29
+ return { message: 'access restricted', status: 403 };
30
+ }
31
+
32
+ const loadTable = await getTemplate('table', hookData?.table || params.table);
12
33
 
13
34
  if (!loadTable) { return { message: 'template not found', status: 404 }; }
14
35
 
@@ -23,7 +44,7 @@ export default async function card(req) {
23
44
  const cols = columns.map((el) => el.name || el).join(',');
24
45
  const columnList = dbColumns.map((el) => el.name || el).join(',');
25
46
  const sqlTable = sql?.filter?.((el) => !el?.disabled && el?.sql?.replace).map((el, i) => ` left join lateral (${el.sql}) ${el.name || `t${i}`} on 1=1 `)?.join('') || '';
26
- const cardSqlFiltered = opt.id || params.id ? cardSql?.filter?.((el) => !el?.disabled && el?.name && el?.sql?.replace) : [];
47
+ const cardSqlFiltered = hookData?.id || params.id ? cardSql?.filter?.((el) => !el?.disabled && el?.name && el?.sql?.replace) : [];
27
48
  const cardSqlTable = cardSqlFiltered?.length ? cardSqlFiltered.map((el, i) => ` left join lateral (select json_agg(row_to_json(q)) as ${el.name} from (${el.sql})q) ct${i} on 1=1 `).join('') || '' : '';
28
49
 
29
50
  const where = [`"${pk}" = $1`, loadTable.query].filter((el) => el);
@@ -33,12 +54,21 @@ export default async function card(req) {
33
54
 
34
55
  if (query.sql === '1') { return q; }
35
56
 
36
- const { rows } = await pg.query(q, [opt.id || params.id]);
57
+ const { rows } = await pg.query(q, [hookData?.id || params.id]);
37
58
 
38
- await metaFormat({ rows, table: params.table });
59
+ await metaFormat({ rows, table: hookData?.table || params.table });
39
60
 
40
61
  const data = meta.card?.length ? meta.card.reduce((acc, curr) => Object.assign(acc, { [columns.find((col) => col.name === curr)?.ua || '']: rows[0][curr] }), {}) : {};
41
- return {
62
+
63
+ const afterHookData = await applyHook('afterCard', {
64
+ table: hookData?.table || params.table,
65
+ id: hookData?.id || params?.id,
66
+ user,
67
+ payload: {
68
+ time: Date.now() - time, data,
69
+ },
70
+ });
71
+ return afterHookData || {
42
72
  time: Date.now() - time, data,
43
73
  };
44
74
  }
@@ -5,13 +5,13 @@ import metaFormat from '../funcs/metaFormat/index.js';
5
5
  import getAccess from '../../crud/funcs/getAccess.js';
6
6
  import setToken from '../../crud/funcs/setToken.js';
7
7
  import gisIRColumn from './utils/gisIRColumn.js';
8
-
9
- import { applyHook, config } from '../../utils.js';
8
+ import applyHook from '../../hook/funcs/applyHook.js';
9
+ import config from '../../config.js';
10
10
 
11
11
  const maxLimit = 100;
12
12
  export default async function dataAPI(req) {
13
13
  const {
14
- pg, params, query = {}, opt = {}, user,
14
+ pg, params, query = {}, user,
15
15
  } = req;
16
16
  const time = Date.now();
17
17
 
@@ -24,6 +24,17 @@ export default async function dataAPI(req) {
24
24
  return { message: hookData?.message, status: hookData?.status };
25
25
  }
26
26
 
27
+ const {
28
+ actions = [], scope, my, query: access,
29
+ } = await getAccess({
30
+ table: hookData?.table || params.table,
31
+ id: hookData?.id || params?.id,
32
+ user,
33
+ });
34
+ if (!actions.includes('get') || (scope === 'my' && !my)) {
35
+ return { message: 'access restricted', status: 403 };
36
+ }
37
+
27
38
  const loadTable = await getTemplate('table', hookData?.table || params.table);
28
39
 
29
40
  if (!loadTable) { return { message: 'template not found', status: 404 }; }
@@ -46,7 +57,7 @@ export default async function dataAPI(req) {
46
57
  : '';
47
58
  const columnList = dbColumns.map((el) => el.name || el).join(',');
48
59
  const sqlTable = sql?.filter?.((el) => !el?.disabled && el?.sql?.replace).map((el, i) => ` left join lateral (${el.sql.replace('{{uid}}', uid)}) ${el.name || `t${i}`} on 1=1 `)?.join('') || '';
49
- const cardSqlFiltered = opt?.id || params.id ? (cardSql?.filter?.((el) => !el?.disabled && el?.name && el?.sql?.replace) || []) : [];
60
+ const cardSqlFiltered = hookData?.id || params.id ? (cardSql?.filter?.((el) => !el?.disabled && el?.name && el?.sql?.replace) || []) : [];
50
61
  const cardSqlTable = cardSqlFiltered.length ? cardSqlFiltered.map((el, i) => ` left join lateral (select json_agg(row_to_json(q)) as ${el.name} from (${el.sql})q) ct${i} on 1=1 `).join('') || '' : '';
51
62
 
52
63
  if (params.id && columnList.includes(params.id)) {
@@ -65,7 +76,7 @@ export default async function dataAPI(req) {
65
76
  json: 1,
66
77
  }) : {};
67
78
 
68
- const keyQuery = query.key && loadTable.key && !(opt?.id || params.id) ? `${loadTable.key}=$1` : null;
79
+ const keyQuery = query.key && loadTable.key && !(hookData?.id || params.id) ? `${loadTable.key}=$1` : null;
69
80
 
70
81
  const limit = Math.min(maxLimit, +(query.limit || 20));
71
82
 
@@ -79,16 +90,15 @@ export default async function dataAPI(req) {
79
90
  const queryPolyline = meta?.bbox && query?.polyline ? `ST_Contains(ST_MakePolygon(ST_LineFromEncodedPolyline('${query?.polyline}')),${meta.bbox})` : undefined;
80
91
  const bbox = meta?.bbox && queryBbox.filter((el) => !Number.isNaN(el))?.length === 4 ? `${meta.bbox} && 'box(${queryBbox[0]} ${queryBbox[1]},${queryBbox[2]} ${queryBbox[3]})'::box2d ` : undefined;
81
92
 
82
- const access = await getAccess(req, params.table);
83
- const where = [(opt?.id || params.id ? ` "${pk}" = $1` : null), keyQuery, loadTable.query, fData.q, search, access?.query || '1=1', bbox, queryPolyline].filter((el) => el);
93
+ const where = [(hookData?.id || params.id ? ` "${pk}" = $1` : null), keyQuery, loadTable.query, fData.q, search, access?.query || '1=1', bbox, queryPolyline].filter((el) => el);
84
94
  const cardColumns = cardSqlFiltered.length ? `,${cardSqlFiltered.map((el) => el.name)}` : '';
85
95
  const q = `select ${pk ? `"${pk}" as id,` : ''} ${columnList.includes('geom') ? 'st_asgeojson(geom)::json as geom,' : ''} ${query.id || query.key ? '*' : sqlColumns || cols || '*'} ${metaCols} ${cardColumns} from ${table} t ${sqlTable} ${cardSqlTable} where ${where.join(' and ') || 'true'} ${order} ${offset} limit ${limit}`;
86
96
 
87
97
  if (query.sql === '1') { return q; }
88
98
 
89
- const { rows } = await pg.query(q, (opt?.id || params.id ? [opt?.id || params.id] : null) || (query.key && loadTable.key ? [query.key] : []));
99
+ const { rows } = await pg.query(q, (hookData?.id || params.id ? [hookData?.id || params.id] : null) || (query.key && loadTable.key ? [query.key] : []));
90
100
 
91
- const total = keyQuery || opt?.id || params.id ? rows.length : await pg.queryCache(`select count(*) from ${table} t ${sqlTable} where ${where.join(' and ') || 'true'}`).then((el) => (el?.rows[0]?.count || 0) - 0);
101
+ const total = keyQuery || hookData?.id || params.id ? rows.length : await pg.queryCache(`select count(*) from ${table} t ${sqlTable} where ${where.join(' and ') || 'true'}`).then((el) => (el?.rows[0]?.count || 0) - 0);
92
102
 
93
103
  await metaFormat({ rows, table: params.table });
94
104
  const res = {
@@ -1,7 +1,7 @@
1
1
  import getTemplate from './utils/getTemplate.js';
2
2
  import getMeta from '../../pg/funcs/getMeta.js';
3
-
4
- import { applyHook } from '../../utils.js';
3
+ import applyHook from '../../hook/funcs/applyHook.js';
4
+ import getAccess from '../../crud/funcs/getAccess.js';
5
5
 
6
6
  export default async function tableAPI(req) {
7
7
  const {
@@ -24,6 +24,17 @@ export default async function tableAPI(req) {
24
24
  if (!pg.pk?.[hookData?.table || params.table]) { return { message: 'not found', status: 404 }; }
25
25
  }
26
26
 
27
+ const {
28
+ actions = [], scope, my,
29
+ } = await getAccess({
30
+ table: hookData?.table || params.table,
31
+ id: hookData?.id || params?.id,
32
+ user,
33
+ });
34
+ if (!actions.includes('get') || (scope === 'my' && !my)) {
35
+ return { message: 'access restricted', status: 403 };
36
+ }
37
+
27
38
  const {
28
39
  table, /* columns, */ form,
29
40
  } = loadTable;
@@ -8,9 +8,10 @@ import config from '../config.js';
8
8
 
9
9
  test('api crud xss', async (t) => {
10
10
  const app = await build(t);
11
- const session = { passport: { user: { uid: '1' } } };
11
+ const session = { passport: { user: { uid: '1', user_type: 'admin' } } };
12
12
  app.addHook('onRequest', async (req) => {
13
13
  req.session = session;
14
+ req.user = session.passport.user;
14
15
  });
15
16
  // app.decorateRequest('session', session);
16
17
 
@@ -42,10 +43,7 @@ test('api crud xss', async (t) => {
42
43
  url: `${prefix}/table/${addTokens[0]}`,
43
44
  body: { dataset_name: '<a onClick="alert("XSS Injection")">xss injection</a>', dataset_id: '5400000' },
44
45
  });
45
-
46
- const rep = JSON.parse(res?.body);
47
- console.log(rep);
48
- assert.ok(rep.status, 409);
46
+ assert.equal(res?.json()?.status || res?.statusCode, 409);
49
47
  });
50
48
 
51
49
  await t.test('PUT /update', async () => {
@@ -54,19 +52,13 @@ test('api crud xss', async (t) => {
54
52
  url: `${prefix}/table/${editTokens[0]}/${editTokens[0]}`,
55
53
  body: { editor_id: '11', dataset_name: '<a onClick="alert("XSS Injection")">xss injection</a>' },
56
54
  });
57
-
58
- const rep = JSON.parse(res?.body);
59
- console.log(rep);
60
- assert.equal(rep.status, 409);
55
+ assert.equal(res.json()?.status || res?.statusCode, 409);
61
56
  });
62
57
  await t.test('DELETE /delete', async () => {
63
58
  const res = await app.inject({
64
59
  method: 'DELETE',
65
60
  url: `${prefix}/table/gis.dataset/5400000`,
66
61
  });
67
-
68
- const rep = JSON.parse(res?.body);
69
- console.log(rep);
70
- assert.ok(rep);
62
+ assert.equal(res?.json()?.status || res?.statusCode, 200);
71
63
  });
72
64
  });