@opengis/fastify-table 2.0.4 → 2.0.6

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.
Files changed (27) hide show
  1. package/dist/server/migrations/users.sql +2 -0
  2. package/dist/server/plugins/auth/funcs/authorizeUser.js +16 -8
  3. package/dist/server/plugins/auth/funcs/loginFile.js +24 -19
  4. package/dist/server/plugins/auth/funcs/loginUser.js +4 -3
  5. package/dist/server/plugins/auth/index.js +6 -1
  6. package/dist/server/plugins/crud/funcs/getAccess.js +28 -27
  7. package/dist/server/plugins/hook/index.js +38 -6
  8. package/dist/server/plugins/logger/errorMessage.js +1 -1
  9. package/dist/server/plugins/logger/errorStatus.js +5 -5
  10. package/dist/server/plugins/table/funcs/getFilterSQL/util/formatValue.js +1 -1
  11. package/dist/server/routes/auth/controllers/2factor/recovery.js +1 -1
  12. package/dist/server/routes/auth/controllers/2factor/verify.js +1 -1
  13. package/dist/server/routes/auth/controllers/core/getUserInfo.js +1 -1
  14. package/dist/server/routes/auth/controllers/euSign/authByData.js +57 -43
  15. package/dist/server/routes/auth/controllers/jwt/authorize.js +43 -28
  16. package/dist/server/routes/auth/controllers/jwt/token.js +54 -29
  17. package/dist/server/routes/menu/controllers/getMenu.js +1 -1
  18. package/dist/server/routes/table/controllers/filter.js +1 -1
  19. package/dist/server/routes/table/functions/getData.js +1 -1
  20. package/dist/server/routes/widget/controllers/widget.set.js +1 -1
  21. package/dist/server/routes/widget/index.js +1 -1
  22. package/dist/utils.js +1 -3
  23. package/package.json +1 -1
  24. package/dist/server/plugins/hook/funcs/addHook.js +0 -7
  25. package/dist/server/plugins/hook/funcs/applyHook.js +0 -25
  26. package/dist/server/plugins/hook/funcs/applyHookSync.js +0 -7
  27. package/dist/server/plugins/hook/hookList.js +0 -2
@@ -27,6 +27,7 @@ ALTER TABLE admin.users add column if not exists salt text;
27
27
  ALTER TABLE admin.users add column if not exists cdate timestamp without time zone DEFAULT date_trunc('seconds'::text, now());
28
28
  ALTER TABLE admin.users add column if not exists editor_id text;
29
29
  ALTER TABLE admin.users add column if not exists editor_date timestamp without time zone;
30
+ ALTER TABLE admin.users add column if not exists twofa boolean not null DEFAULT true;
30
31
 
31
32
  ALTER TABLE admin.users add CONSTRAINT admin_user_uid_pkey PRIMARY KEY (uid);
32
33
  ALTER TABLE admin.users add CONSTRAINT admin_user_user_rnokpp UNIQUE (user_rnokpp);
@@ -46,6 +47,7 @@ COMMENT ON COLUMN admin.users.enabled IS 'On / Off';
46
47
  COMMENT ON COLUMN admin.users.last_activity_date IS 'Дата останньої активності';
47
48
  COMMENT ON COLUMN admin.users.user_type IS 'Тип користувача';
48
49
  COMMENT ON COLUMN admin.users.salt IS 'Сіль';
50
+ COMMENT ON COLUMN admin.users.twofa IS 'Двофакторна авторизація';
49
51
 
50
52
  CREATE EXTENSION if not exists pgcrypto SCHEMA public VERSION "1.3";
51
53
  CREATE OR REPLACE FUNCTION admin.crypt(text, text) RETURNS text AS '$libdir/pgcrypto', 'pg_crypt' LANGUAGE c IMMUTABLE STRICT COST 1;
@@ -1,6 +1,6 @@
1
1
  import config from "../../../../config.js";
2
2
  import logger from "../../logger/getLogger.js";
3
- import applyHook from "../../hook/funcs/applyHook.js";
3
+ import { applyHook } from "../../hook/index.js";
4
4
  import logAuth from "./logAuth.js";
5
5
  /*
6
6
  session duration by default
@@ -15,9 +15,10 @@ const getIp = (req) => (req.headers?.["x-real-ip"] ||
15
15
  "")
16
16
  .split(":")
17
17
  .pop();
18
- export default async function authorizeUser(user, req, loginType = "login", expire) {
18
+ export default async function authorizeUser(user, req, authType = "creds-user", expire) {
19
19
  if (!user?.uid)
20
20
  return "/logout";
21
+ Object.assign(user, { auth_type: authType });
21
22
  // fastify/passport
22
23
  await req.login(user);
23
24
  const st = (req.body?.keep || req.query?.keep) === "on"
@@ -29,21 +30,26 @@ export default async function authorizeUser(user, req, loginType = "login", expi
29
30
  }
30
31
  logger.file("auth", {
31
32
  level: "DEBUG",
32
- name: loginType,
33
- response: { uid: user?.uid, name: user.name },
33
+ name: authType,
34
+ response: {
35
+ uid: user?.uid,
36
+ name: [user.sur_name, user.user_name, user.name]
37
+ .filter(Boolean)
38
+ .join(" "),
39
+ },
34
40
  }, req);
35
41
  const ip = getIp(req);
36
- if (loginType === "login") {
42
+ if (authType && authType.startsWith("creds")) {
37
43
  await logAuth({
38
44
  uid: user?.uid,
39
- type: loginType,
45
+ type: authType,
40
46
  ip,
41
47
  });
42
48
  }
43
49
  const { href } = (await applyHook("afterAuth", {
44
50
  ip,
45
51
  user,
46
- name: loginType,
52
+ name: authType,
47
53
  referer: req.headers?.referer,
48
54
  })) ||
49
55
  {};
@@ -52,7 +58,9 @@ export default async function authorizeUser(user, req, loginType = "login", expi
52
58
  return { message: "ok", status: "200" };
53
59
  }
54
60
  const redirectUrl = req.headers?.referer?.match?.(/[?&]redirect=([^&]+)/)?.[1] || "/";
55
- if (config.auth?.["2factor"]) {
61
+ // by default, disable 2factor for id.gov.ua auth
62
+ const check = authType === "govid" ? config.auth?.["2factor"]?.govid : true;
63
+ if (config.auth?.["2factor"] && user?.twofa && check) {
56
64
  return ("/2factor?redirect=" +
57
65
  (href ||
58
66
  config.auth?.redirectAfter ||
@@ -1,41 +1,46 @@
1
- import path from 'node:path';
2
- import { existsSync, readFileSync } from 'node:fs';
3
- import { createHash } from 'node:crypto';
4
- import config from '../../../../config.js';
5
- import users from './users.js';
6
- import authorizeUser from './authorizeUser.js';
1
+ import path from "node:path";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import config from "../../../../config.js";
5
+ import users from "./users.js";
6
+ import authorizeUser from "./authorizeUser.js";
7
7
  export default async function loginFile(req, reply) {
8
- const { username, password } = req.method === 'POST' ? req.body : req.query;
9
- const filepath = path.join(process.cwd(), 'passwd');
8
+ const { username, password } = req.method === "POST" ? req.body : req.query;
9
+ const filepath = path.join(process.cwd(), "passwd");
10
10
  if (!users?.length) {
11
11
  const fileExists = existsSync(filepath);
12
12
  if (!fileExists) {
13
- req.log.error(req, 'passwd file not exists');
14
- return { error: 'login error', status: 500 };
13
+ req.log.error(req, "passwd file not exists");
14
+ return { error: "login error", status: 500 };
15
15
  }
16
16
  // parse file on start up
17
- const data = readFileSync(filepath, 'utf8');
18
- const separator = data.indexOf('\\r\\n') !== -1 ? '\r\n' : '\n';
17
+ const data = readFileSync(filepath, "utf8");
18
+ const separator = data.indexOf("\\r\\n") !== -1 ? "\r\n" : "\n";
19
19
  const rows = data.split(separator).map((row) => {
20
- const [name, passwd, usertype = 'regular', uid] = row.split(':');
20
+ const [name, passwd, usertype = "regular", uid] = row.split(":");
21
21
  return { username: name, password: passwd, usertype, uid };
22
22
  });
23
23
  rows.forEach((row) => users.push(row));
24
24
  }
25
25
  // check user / password
26
26
  const user = users.find((el) => el.username === username);
27
- const hashPasswd = createHash('sha1').update(`${password}${user?.salt || ''}`).digest('hex');
27
+ const hashPasswd = createHash("sha1")
28
+ .update(`${password}${user?.salt || ""}`)
29
+ .digest("hex");
28
30
  if (!user?.password || user.password !== hashPasswd) {
29
- const txt = 'Invalid user or password';
30
- return req.method === 'GET'
31
+ const txt = "Invalid user or password";
32
+ return req.method === "GET"
31
33
  ? reply.status(302).redirect(`/login?confirm=wrong_pass&message=${txt}`)
32
34
  : reply.status(400).send({ message: txt });
33
35
  }
34
36
  const resultUser = {
35
37
  uid: user?.uid || config?.auth?.uid || username,
36
38
  user_name: username,
37
- user_type: user.usertype || 'regular',
39
+ user_type: user.usertype || "regular",
38
40
  };
39
- const href = await authorizeUser(resultUser, req, 'loginFile');
40
- reply.redirect(href);
41
+ const authType = "creds-" + (user.usertype === "admin" ? "admin" : "user");
42
+ const href = await authorizeUser(resultUser, req, authType); // creds-admin / creds-admin
43
+ return req.method === "GET"
44
+ ? reply.status(302).redirect(href)
45
+ : reply.status(200).send(href);
41
46
  }
@@ -18,11 +18,11 @@ export default async function loginUser(req, reply) {
18
18
  .redirect("/login?confirm=wrong_pass&message=empty redis")
19
19
  : reply.status(400).send({ message: "empty redis" });
20
20
  }
21
- const { user, message = "invalid user" } = await verifyPassword({
21
+ const { user, message = "invalid user" } = (await verifyPassword({
22
22
  pg,
23
23
  username,
24
24
  password,
25
- }) || {};
25
+ })) || {};
26
26
  if (!user) {
27
27
  return req.method === "GET"
28
28
  ? reply
@@ -38,7 +38,8 @@ export default async function loginUser(req, reply) {
38
38
  : reply.status(400).send({ message });
39
39
  }
40
40
  logger.metrics("user.login");
41
- const href = await authorizeUser(user, req, "login");
41
+ const authType = "creds-" + (user.user_type === "admin" ? "admin" : "user");
42
+ const href = await authorizeUser(user, req, authType); // creds-admin / creds-admin
42
43
  return req.method === "GET"
43
44
  ? reply.status(302).redirect(href)
44
45
  : reply.status(200).send(href);
@@ -83,13 +83,18 @@ async function plugin(fastify) {
83
83
  !req.url.startsWith("/login")) {
84
84
  return reply.redirect(`${config?.auth?.redirect || "/login"}` + `?redirect=${req.url}`);
85
85
  }
86
+ // by default, disable 2factor for id.gov.ua auth
87
+ const check = passport.user?.auth_type === "govid"
88
+ ? config.auth?.["2factor"]?.govid
89
+ : true;
86
90
  if (passport.user?.uid &&
87
91
  passport.user?.twofa &&
88
92
  config.auth?.["2factor"] &&
89
93
  !isPublic &&
90
94
  (routeOptions?.method || "GET") === "GET" &&
91
95
  !secondFactorPassed &&
92
- !ispasswd) {
96
+ !ispasswd &&
97
+ check) {
93
98
  const href = config.auth?.["2factorRedirect"] || "/2factor";
94
99
  if (!href.includes(req.url)) {
95
100
  return reply.redirect(href);
@@ -1,7 +1,7 @@
1
- import pgClients from '../../pg/pgClients.js';
2
- import getTemplate from '../../table/funcs/getTemplate.js';
3
- import applyHook from '../../hook/funcs/applyHook.js';
4
- const allActions = ['view', 'edit', 'add', 'del'];
1
+ import pgClients from "../../pg/pgClients.js";
2
+ import getTemplate from "../../table/funcs/getTemplate.js";
3
+ import { applyHook } from "../../hook/index.js";
4
+ const allActions = ["view", "edit", "add", "del"];
5
5
  const q = `select a.route_id as id, d.actions as user_roles, d.actions as role_actions, coalesce(b.actions, array['view']) as interface_actions, b.scope, c.role_id
6
6
  from admin.routes a
7
7
  left join admin.role_access b on
@@ -30,47 +30,48 @@ where $1 in (a.route_id, a.alias, a.table_name) and $2 in (b.user_uid, d.user_ui
30
30
  export default async function getAccess({ table, form, user = {} }, pg = pgClients.client) {
31
31
  if (!table)
32
32
  return null;
33
- const hookData = await applyHook('getAccess', { table, user, pg });
33
+ const hookData = await applyHook("getAccess", { table, user, pg });
34
34
  if (hookData)
35
35
  return hookData;
36
- const { uid, user_type: userType = 'regular' } = user;
37
- if (userType === 'superadmin') {
38
- return { actions: allActions, query: '1=1' };
36
+ const { uid, user_type: userType = "regular" } = user;
37
+ if (userType === "superadmin") {
38
+ return { actions: allActions, query: "1=1" };
39
39
  }
40
- const body = await getTemplate('table', table);
40
+ const body = await getTemplate("table", table);
41
41
  const tableActions = !body && form
42
42
  ? allActions // if db table and form => full access (token)
43
- : ['view'].concat(body?.actions || body?.action_default || []);
44
- if (userType === 'admin') {
43
+ : ["view"].concat(body?.actions || body?.action_default || []);
44
+ if (userType === "admin") {
45
45
  if (!(body?.actions || body?.action_default) && (body?.form || form)) {
46
- return { actions: allActions, query: '1=1' };
46
+ return { actions: allActions, query: "1=1" };
47
47
  }
48
- return { actions: tableActions, query: '1=1' };
48
+ return { actions: tableActions, query: "1=1" };
49
49
  }
50
- if (body?.public || body?.access === 'public') {
51
- return { actions: tableActions, query: '1=1' };
50
+ if (body?.public || body?.access === "public") {
51
+ return { actions: tableActions, query: "1=1" };
52
52
  }
53
- if (body?.access === 'user' && uid) {
54
- return { actions: tableActions, query: '1=1' };
53
+ if (body?.access === "user" && uid) {
54
+ return { actions: tableActions, query: "1=1" };
55
55
  }
56
56
  if (!uid) {
57
- return { actions: [], query: '1=1' };
57
+ return { actions: [], query: "1=1" };
58
58
  }
59
- const userAccess = pg?.pk?.['admin.routes']
60
- && pg.pk?.['admin.role_access']
61
- && pg.pk?.['admin.roles']
62
- && pg.pk?.['admin.user_roles']
63
- ? await pg.query(q, [table, uid])
64
- .then((el) => ({
59
+ const userAccess = pg?.pk?.["admin.routes"] &&
60
+ pg.pk?.["admin.role_access"] &&
61
+ pg.pk?.["admin.roles"] &&
62
+ pg.pk?.["admin.user_roles"]
63
+ ? await pg.query(q, [table, uid]).then((el) => ({
65
64
  ...(el.rows[0] || {}),
66
65
  roles: el.rows?.map?.((row) => row.role_id) || [],
67
66
  actions: el.rows?.map?.((row) => row.actions).flat() || [],
68
- interface_actions: el.rows?.map?.((row) => row.interface_actions).flat() || []
67
+ interface_actions: el.rows?.map?.((row) => row.interface_actions).flat() || [],
69
68
  }))
70
69
  : {};
71
- const query = userAccess?.scope === 'my' ? `uid='${uid}'` : '1=1';
70
+ const query = userAccess?.scope === "my" ? `uid='${uid}'` : "1=1";
72
71
  const actions = userAccess?.interface_actions
73
- ?.filter((el) => userAccess?.role_actions?.length ? userAccess?.role_actions.includes(el) : true)
72
+ ?.filter((el) => userAccess?.role_actions?.length
73
+ ? userAccess?.role_actions.includes(el)
74
+ : true)
74
75
  ?.filter((el) => tableActions.includes(el))
75
76
  ?.filter?.((el, idx, arr) => arr.indexOf(el) === idx);
76
77
  return {
@@ -1,7 +1,39 @@
1
- // import addHook from './funcs/addHook.js';
2
- // import applyHook from './funcs/applyHook.js';
3
- async function plugin() {
4
- // fastify.decorate('addHook', addHook);
5
- // fastify.decorate('applyHook', applyHook);
1
+ import config from "../../../config.js";
2
+ export const hookList = {};
3
+ export async function applyHook(name, data) {
4
+ if (config.trace)
5
+ console.log("applyHook", name);
6
+ if (!hookList[name]?.length)
7
+ return null;
8
+ const result = {};
9
+ await Promise.all(hookList[name].map(async (hook) => {
10
+ const hookData = await hook({ ...data, config });
11
+ if (hookData) {
12
+ if (config.trace)
13
+ console.log("applyHook", name, hookData);
14
+ Object.assign(result, hookData);
15
+ }
16
+ })).catch((err) => {
17
+ console.error("applyHook", name, err.toString());
18
+ });
19
+ if (Object.keys(result).length) {
20
+ return result;
21
+ }
22
+ return null;
23
+ }
24
+ export function addHook(name, fn) {
25
+ if (!hookList[name]) {
26
+ hookList[name] = [];
27
+ }
28
+ if (config.trace)
29
+ console.log("addHook", name);
30
+ hookList[name].push(fn);
31
+ }
32
+ export function applyHookSync(name, data) {
33
+ if (!hookList[name]?.length)
34
+ return null;
35
+ if (config.trace)
36
+ console.log("applyHookSync", name);
37
+ const hookData = hookList[name].map((hook) => hook(data))[0];
38
+ return hookData;
6
39
  }
7
- export default plugin;
@@ -1,5 +1,5 @@
1
1
  import config from "../../../config.js";
2
- import applyHookSync from "../hook/funcs/applyHookSync.js";
2
+ import { applyHookSync } from "../hook/index.js";
3
3
  import errorStatus from "./errorStatus.js";
4
4
  const defaultMessage = {
5
5
  602: "Порушення цілісності бази даних",
@@ -1,15 +1,15 @@
1
- import applyHookSync from '../hook/funcs/applyHookSync.js';
1
+ import { applyHookSync } from "../hook/index.js";
2
2
  function errorStatus(error) {
3
- const hook = applyHookSync('errorStatus', error);
3
+ const hook = applyHookSync("errorStatus", error);
4
4
  if (hook)
5
5
  return hook;
6
- if (error.routine === 'exec_stmt_raise' && error.file === 'pl_exec.c') {
6
+ if (error.routine === "exec_stmt_raise" && error.file === "pl_exec.c") {
7
7
  return 601;
8
8
  }
9
- if (error.routine === 'ExecConstraints') {
9
+ if (error.routine === "ExecConstraints") {
10
10
  return 602;
11
11
  }
12
- if (error.type === 'DatabaseError') {
12
+ if (error.type === "DatabaseError") {
13
13
  return 600;
14
14
  }
15
15
  return 500;
@@ -1,4 +1,4 @@
1
- import applyHookSync from "../../../../hook/funcs/applyHookSync.js";
1
+ import { applyHookSync } from "../../../../hook/index.js";
2
2
  import getRangeQuery from "./getRangeQuery.js";
3
3
  export default function formatValue({ pg, table, filter = {}, name, value, dataTypeID, uid = 1, optimize, }) {
4
4
  const { extra, sql, select, strict, options /* default: defaultValue, */ } = filter;
@@ -43,7 +43,7 @@ export default async function recoveryFunction(req, reply) {
43
43
  // return reply.status(400).send('not enough params');
44
44
  const customPt = await getTemplate("pt", template);
45
45
  const pt = customPt ||
46
- (await readFile(path.join(dirname, `../templates/pt/${template}.html`), "utf8"));
46
+ (await readFile(path.join(dirname, `../../../../../templates/pt/${template}.html`), "utf8"));
47
47
  const recoveryCodes = await pg
48
48
  .query(`select social_auth_obj->'codesArray' as "recoveryCodes" from admin.users_social_auth
49
49
  where uid = $1 and social_auth_type = $2`, [uid, "TOTP"])
@@ -55,7 +55,7 @@ export default async function verifyFunction(req, reply) {
55
55
  ?.then((el) => el.rows?.[0] || {});
56
56
  const customPt = await getTemplate("pt", template);
57
57
  const pt = customPt ||
58
- (await readFile(path.join(dirname, `../templates/pt/${template}.html`), "utf8"));
58
+ (await readFile(path.join(dirname, `../../../../../templates/pt/${template}.html`), "utf8"));
59
59
  const html = await handlebars.compile(pt)({
60
60
  recoveryCodes,
61
61
  domain: `${req.protocol || "https"}://${req.hostname}`,
@@ -1,5 +1,5 @@
1
1
  import config from "../../../../../config.js";
2
- import applyHook from "../../../../plugins/hook/funcs/applyHook.js";
2
+ import { applyHook } from "../../../../../utils.js";
3
3
  import getRedis from "../../../../plugins/redis/funcs/getRedis.js";
4
4
  const rclient2 = getRedis({ db: 2 });
5
5
  export default async function getUserInfo(req) {
@@ -1,29 +1,29 @@
1
- import { Agent } from 'undici';
2
- import config from '../../../../../config.js';
3
- import applyHook from '../../../../plugins/hook/funcs/applyHook.js';
4
- import logger from '../../../../plugins/logger/getLogger.js';
5
- import pgClients from '../../../../plugins/pg/pgClients.js';
6
- import checkReferer from '../../../../plugins/auth/funcs/checkReferer.js';
7
- import getQuery from '../../../../plugins/auth/funcs/getQuery.js';
8
- import logAuth from '../../../../plugins/auth/funcs/logAuth.js';
9
- import authorizeUser from '../../../../plugins/auth/funcs/authorizeUser.js';
1
+ import { Agent } from "undici";
2
+ import config from "../../../../../config.js";
3
+ import { applyHook } from "../../../../../utils.js";
4
+ import logger from "../../../../plugins/logger/getLogger.js";
5
+ import pgClients from "../../../../plugins/pg/pgClients.js";
6
+ import checkReferer from "../../../../plugins/auth/funcs/checkReferer.js";
7
+ import getQuery from "../../../../plugins/auth/funcs/getQuery.js";
8
+ import logAuth from "../../../../plugins/auth/funcs/logAuth.js";
9
+ import authorizeUser from "../../../../plugins/auth/funcs/authorizeUser.js";
10
10
  function fetchWithoutSSL(url, options) {
11
11
  const httpsAgent = new Agent({
12
12
  connect: {
13
- rejectUnauthorized: false
14
- }
13
+ rejectUnauthorized: false,
14
+ },
15
15
  });
16
16
  return fetch(url, {
17
- ...options || {},
18
- dispatcher: httpsAgent
17
+ ...(options || {}),
18
+ dispatcher: httpsAgent,
19
19
  });
20
20
  }
21
- const getIp = (req) => (req.headers?.['x-real-ip']
22
- || req.headers?.['x-forwarded-for']
23
- || req.ip
24
- || req.connection?.remoteAddress
25
- || '')
26
- .split(':')
21
+ const getIp = (req) => (req.headers?.["x-real-ip"] ||
22
+ req.headers?.["x-forwarded-for"] ||
23
+ req.ip ||
24
+ req.connection?.remoteAddress ||
25
+ "")
26
+ .split(":")
27
27
  .pop();
28
28
  export default async function authByData(req, reply) {
29
29
  const { pg = pgClients.client, query = {}, headers = {} } = req;
@@ -33,22 +33,22 @@ export default async function authByData(req, reply) {
33
33
  const ip = getIp(req);
34
34
  const { data: code, type } = query; // token
35
35
  const { security } = config;
36
- const authType = type
37
- || (referer?.includes('softpro.ua') ? 'google' : null)
38
- || (referer?.includes('nsdi.gov.ua') ? 'govid' : null)
39
- || (referer?.includes('admin.nsdi.gki.com.ua') ? 'govid' : null); // fix admin auth
36
+ const authType = type ||
37
+ (referer?.includes("softpro.ua") ? "google" : null) ||
38
+ (referer?.includes("nsdi.gov.ua") ? "govid" : null) ||
39
+ (referer?.includes("admin.nsdi.gki.com.ua") ? "govid" : null); // fix admin auth
40
40
  const hostOauth = {
41
- google: security?.social_auth_host_local || 'https://id.softpro.ua',
42
- govid: security?.id_gov_ua_host_local || 'https://nsdi.gov.ua',
43
- }[authType || ''];
41
+ google: security?.social_auth_host_local || "https://id.softpro.ua",
42
+ govid: security?.id_gov_ua_host_local || "https://nsdi.gov.ua",
43
+ }[authType || ""];
44
44
  if (!hostOauth) {
45
- logger.file('auth/by_data/warn', {
46
- error: 'invalid oauth params',
45
+ logger.file("auth/by_data/warn", {
46
+ error: "invalid oauth params",
47
47
  referer,
48
48
  authType,
49
49
  hostOauth,
50
50
  });
51
- return reply.status(400).send('Невалідний парметр тип авторизації');
51
+ return reply.status(400).send("Невалідний парметр тип авторизації");
52
52
  }
53
53
  // referer + token check
54
54
  const invalidReferer = await checkReferer({
@@ -57,39 +57,48 @@ export default async function authByData(req, reply) {
57
57
  hostOauth,
58
58
  });
59
59
  if (!code || invalidReferer) {
60
- logger.file('auth/by_data/warn', {
61
- error: 'invalid request', referer, code,
60
+ logger.file("auth/by_data/warn", {
61
+ error: "invalid request",
62
+ referer,
63
+ code,
62
64
  });
63
- return reply.status(403).send('Параметри data / code / state мають невірний формат, або Ви перейшли за прямим посиланням.');
65
+ return reply
66
+ .status(403)
67
+ .send("Параметри data / code / state мають невірний формат, або Ви перейшли за прямим посиланням.");
64
68
  }
65
- const url = authType === 'govid'
69
+ const url = authType === "govid"
66
70
  ? `${hostOauth}/api-user/auth_data?token=${code}`
67
71
  : `${hostOauth}/oauth/token?code=${code}`;
68
72
  try {
69
73
  const response = await fetchWithoutSSL(url);
70
74
  const body = await response.text();
71
- console.log(`${authType} login: ${code} ${config.debug ? body : ''}`);
72
- if (response.status !== 200 || !body?.startsWith?.('{')) {
75
+ console.log(`${authType} login: ${code} ${config.debug ? body : ""}`);
76
+ if (response.status !== 200 || !body?.startsWith?.("{")) {
73
77
  return reply.status(response.status).send(body);
74
78
  }
75
79
  const data = JSON.parse(body);
76
- const hookData = await applyHook('onAuthByData', { req, pg, data, authType });
80
+ const hookData = (await applyHook("onAuthByData", {
81
+ req,
82
+ pg,
83
+ data,
84
+ authType,
85
+ }));
77
86
  if (hookData?.href) {
78
87
  return reply.redirect(hookData.href);
79
88
  }
80
89
  // get user / create new user
81
90
  const _user = config.pg && !hookData?.user
82
91
  ? await getQuery({ pg, data })
83
- : (hookData?.user || {});
92
+ : hookData?.user || {};
84
93
  await logAuth({
85
94
  uid: _user?.uid,
86
- type: 'byData',
95
+ type: "byData",
87
96
  data,
88
97
  ip,
89
98
  }, pg);
90
- console.log('register user sql');
99
+ console.log("register user sql");
91
100
  timeList.push(Date.now());
92
- logger.file('auth/by_data', {
101
+ logger.file("auth/by_data", {
93
102
  msec: Date.now() - d1,
94
103
  time: {
95
104
  total: timeList[5] - timeList[0],
@@ -104,12 +113,17 @@ export default async function authByData(req, reply) {
104
113
  ip,
105
114
  });
106
115
  // console.log(user)
107
- const href = await authorizeUser(_user, req, authType);
116
+ const href = await authorizeUser(_user, req, authType); // govid / google(social)
108
117
  // console.log(href)
109
118
  return reply.redirect(href);
110
119
  }
111
120
  catch (err) {
112
- logger.file('auth/by_data/error', { error: err.toString(), stack: err.stack });
113
- return reply.status(500).send(`Помилка авторизації через ${authType === 'govid' ? 'id.gov.ua' : 'google'}. Зверніться до Адміністратора!`);
121
+ logger.file("auth/by_data/error", {
122
+ error: err.toString(),
123
+ stack: err.stack,
124
+ });
125
+ return reply
126
+ .status(500)
127
+ .send(`Помилка авторизації через ${authType === "govid" ? "id.gov.ua" : "google"}. Зверніться до Адміністратора!`);
114
128
  }
115
129
  }
@@ -1,56 +1,69 @@
1
- import pgClients from '../../../../plugins/pg/pgClients.js';
2
- import dataInsert from '../../../../plugins/crud/funcs/dataInsert.js';
3
- import { sign, scryptHash } from '../../../../plugins/auth/funcs/jwt.js';
4
- import authorizeUser from '../../../../plugins/auth/funcs/authorizeUser.js';
5
- const getIp = (req) => (req.headers?.['x-real-ip']
6
- || req.headers?.['x-forwarded-for']
7
- || req.ip
8
- || req.connection?.remoteAddress
9
- || '')
10
- .split(':')
1
+ import pgClients from "../../../../plugins/pg/pgClients.js";
2
+ import dataInsert from "../../../../plugins/crud/funcs/dataInsert.js";
3
+ import { sign, scryptHash } from "../../../../plugins/auth/funcs/jwt.js";
4
+ import authorizeUser from "../../../../plugins/auth/funcs/authorizeUser.js";
5
+ const getIp = (req) => (req.headers?.["x-real-ip"] ||
6
+ req.headers?.["x-forwarded-for"] ||
7
+ req.ip ||
8
+ req.connection?.remoteAddress ||
9
+ "")
10
+ .split(":")
11
11
  .pop();
12
12
  const expireMsec = 1000 * 60 * 60;
13
13
  // ? Request example: http://localhost:3000/oauth/authorize?response_type=code&client_id=2835134164020233999&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Foauth%2Ftoken%3Fclient_id%3D2835134164020233999%26noredirect%3D1%26grant_type%3Dauthorization_code
14
14
  // ? add &noredirect=1 to retrieve code via GET request or simply send POST requests
15
15
  export default async function authorize(req, reply) {
16
16
  const { pg = pgClients.client, query, body } = req;
17
- const payload = req.method === 'POST' ? body : query;
17
+ const payload = req.method === "POST" ? body : query;
18
18
  const { response_type, client_id, redirect_uri, scope } = payload;
19
19
  if (response_type !== "code") {
20
- return reply.code(400).send({ message: "unsupported response_type", code: 400 });
20
+ return reply
21
+ .code(400)
22
+ .send({ message: "unsupported response_type", code: 400 });
21
23
  }
22
24
  if (!client_id) {
23
- return reply.code(400).send({ message: "not enough query params: client_id", code: 400 });
25
+ return reply
26
+ .code(400)
27
+ .send({ message: "not enough query params: client_id", code: 400 });
24
28
  }
25
- const q = `select owner_user_id, client_secret_hash, redirect_uris from oauth.clients where client_id=$1 and token_endpoint_auth_method=$2 and ${scope ? '$1=any(scopes)' : '1=1'}`;
26
- const { owner_user_id: userId, client_secret_hash: secret, redirect_uris = [] } = pg.pk?.['oauth.clients']
27
- ? await pg.query(q, [client_id, 'private_key_jwt']).then((el) => el.rows?.[0] || {})
29
+ const q = `select owner_user_id, client_secret_hash, redirect_uris from oauth.clients where client_id=$1 and token_endpoint_auth_method=$2 and ${scope ? "$1=any(scopes)" : "1=1"}`;
30
+ const { owner_user_id: userId, client_secret_hash: secret, redirect_uris = [], } = pg.pk?.["oauth.clients"]
31
+ ? await pg
32
+ .query(q, [client_id, "private_key_jwt"])
33
+ .then((el) => el.rows?.[0] || {})
28
34
  : {};
29
35
  if (!userId) {
30
36
  return reply.code(400).send({ message: "invalid client id", code: 400 });
31
37
  }
32
- if (redirect_uri && Array.isArray(redirect_uris) && !redirect_uris.includes(redirect_uri)) {
38
+ if (redirect_uri &&
39
+ Array.isArray(redirect_uris) &&
40
+ !redirect_uris.includes(redirect_uri)) {
33
41
  return reply.code(400).send({ message: "invalid redirect_uri", code: 400 });
34
42
  }
35
- const user = pg.pk?.['admin.users'] ? await pg.query('select * from admin.users where uid=$1 and enabled limit 1', [userId]).then((el) => el.rows[0]) : null;
43
+ const user = pg.pk?.["admin.users"]
44
+ ? await pg
45
+ .query("select * from admin.users where uid=$1 and enabled limit 1", [
46
+ userId,
47
+ ])
48
+ .then((el) => el.rows[0])
49
+ : null;
36
50
  if (!user) {
37
51
  return reply.code(404).send({ message: "user not found", code: 404 });
38
52
  }
39
- Object.assign(user, { auth_type: 'jwt' });
40
- const href1 = await authorizeUser(user, req, 'jwt', expireMsec);
53
+ const href1 = await authorizeUser(user, req, "jwt", expireMsec);
41
54
  // Generate authorization code
42
55
  const code = sign(userId, secret, expireMsec);
43
56
  const tokenHash = await scryptHash(code);
44
57
  const ip = getIp(req);
45
58
  // disable access via old tokens
46
- if (pg.pk?.['oauth.tokens']) {
47
- await pg.query('update oauth.tokens set revoked_at = now(), revocation_reason=\'refresh\' where client_id=$1', [client_id]);
59
+ if (pg.pk?.["oauth.tokens"]) {
60
+ await pg.query("update oauth.tokens set revoked_at = now(), revocation_reason='refresh' where client_id=$1", [client_id]);
48
61
  }
49
62
  await dataInsert({
50
63
  pg,
51
- table: 'oauth.tokens',
64
+ table: "oauth.tokens",
52
65
  data: {
53
- token_type: 'access',
66
+ token_type: "access",
54
67
  token_hash: tokenHash,
55
68
  token_hint: code.slice(-6),
56
69
  // jti: undefined,
@@ -67,11 +80,13 @@ export default async function authorize(req, reply) {
67
80
  },
68
81
  uid: userId,
69
82
  });
70
- const backUrl = redirect_uri ? `${redirect_uri}?code=${code}` : '';
71
- const href = redirect_uri && !redirect_uri.includes('?')
83
+ const backUrl = redirect_uri ? `${redirect_uri}?code=${code}` : "";
84
+ const href = redirect_uri && !redirect_uri.includes("?")
72
85
  ? backUrl
73
- : (backUrl?.replace?.(/\?code=/, '&code=') || href1);
74
- if (req.method === 'POST' || payload.noredirect || process.env.NODE_ENV === 'test') {
86
+ : backUrl?.replace?.(/\?code=/, "&code=") || href1;
87
+ if (req.method === "POST" ||
88
+ payload.noredirect ||
89
+ process.env.NODE_ENV === "test") {
75
90
  return reply.code(200).send(code);
76
91
  }
77
92
  return reply.redirect(href);
@@ -1,67 +1,92 @@
1
1
  import { verify, scryptVerify } from "../../../../plugins/auth/funcs/jwt.js";
2
- import pgClients from '../../../../plugins/pg/pgClients.js';
3
- import authorizeUser from '../../../../plugins/auth/funcs/authorizeUser.js';
2
+ import pgClients from "../../../../plugins/pg/pgClients.js";
3
+ import authorizeUser from "../../../../plugins/auth/funcs/authorizeUser.js";
4
4
  const expireMsec = 1000 * 60 * 60;
5
- const getIp = (req) => (req.headers?.['x-real-ip']
6
- || req.headers?.['x-forwarded-for']
7
- || req.ip
8
- || req.connection?.remoteAddress
9
- || '')
10
- .split(':')
5
+ const getIp = (req) => (req.headers?.["x-real-ip"] ||
6
+ req.headers?.["x-forwarded-for"] ||
7
+ req.ip ||
8
+ req.connection?.remoteAddress ||
9
+ "")
10
+ .split(":")
11
11
  .pop();
12
12
  export default async function oauthToken(req, reply) {
13
13
  const { pg = pgClients.client, query, body } = req;
14
- const payload = req.method === 'POST' ? body : query;
14
+ const payload = req.method === "POST" ? body : query;
15
15
  const { grant_type, client_id, code, redirect_uri, code_verifier } = payload;
16
16
  if (grant_type !== "authorization_code") {
17
- return reply.code(400).send({ message: "unsupported grant_type", code: 400 });
17
+ return reply
18
+ .code(400)
19
+ .send({ message: "unsupported grant_type", code: 400 });
18
20
  }
19
21
  if (!client_id) {
20
- return reply.code(400).send({ message: "not enough params: client_id", code: 400 });
22
+ return reply
23
+ .code(400)
24
+ .send({ message: "not enough params: client_id", code: 400 });
21
25
  }
22
26
  if (!code) {
23
- return reply.code(400).send({ message: "not enough params: code", code: 400 });
27
+ return reply
28
+ .code(400)
29
+ .send({ message: "not enough params: code", code: 400 });
24
30
  }
25
31
  const q = `select owner_user_id, client_secret_hash, redirect_uris from oauth.clients where client_id=$1 and token_endpoint_auth_method=$2`;
26
- const { owner_user_id: userId, client_secret_hash: secret, redirect_uris = [] } = pg.pk?.['oauth.clients']
27
- ? await pg.query(q, [client_id, 'private_key_jwt']).then((el) => el.rows?.[0] || {})
32
+ const { owner_user_id: userId, client_secret_hash: secret, redirect_uris = [], } = pg.pk?.["oauth.clients"]
33
+ ? await pg
34
+ .query(q, [client_id, "private_key_jwt"])
35
+ .then((el) => el.rows?.[0] || {})
28
36
  : {};
29
37
  const isCodeValid = verify(code, secret);
30
- const q1 = 'select token_hash, expires_at, ip from oauth.tokens where client_id=$1 and revoked_at is null and expires_at > now()';
31
- const { token_hash: stored, expires_at, ip: storedIp } = await pg.query(q1, [client_id]).then((el) => el.rows?.[0] || {});
38
+ const q1 = "select token_hash, expires_at, ip from oauth.tokens where client_id=$1 and revoked_at is null and expires_at > now()";
39
+ const { token_hash: stored, expires_at, ip: storedIp, } = await pg.query(q1, [client_id]).then((el) => el.rows?.[0] || {});
32
40
  const ip = getIp(req);
33
41
  if (storedIp !== ip) {
34
- return reply.code(403).send({ message: "access restricted: wrong IP address", code: 403 });
42
+ return reply
43
+ .code(403)
44
+ .send({ message: "access restricted: wrong IP address", code: 403 });
35
45
  }
36
46
  if (!stored) {
37
- return reply.code(403).send({ message: "access restricted: code expired", code: 403 });
47
+ return reply
48
+ .code(403)
49
+ .send({ message: "access restricted: code expired", code: 403 });
38
50
  }
39
51
  const isValid = await scryptVerify(stored, code);
40
52
  if (!isValid) {
41
- return reply.code(403).send({ message: "access restricted: stored code mismatch", code: 403 });
53
+ return reply
54
+ .code(403)
55
+ .send({ message: "access restricted: stored code mismatch", code: 403 });
42
56
  }
43
57
  if (!isCodeValid) {
44
- return reply.code(403).send({ message: "access restricted: invalid code", code: 403 });
58
+ return reply
59
+ .code(403)
60
+ .send({ message: "access restricted: invalid code", code: 403 });
45
61
  }
46
62
  if (!userId) {
47
63
  return reply.code(400).send({ message: "invalid client id", code: 400 });
48
64
  }
49
- if (redirect_uri && Array.isArray(redirect_uris) && !redirect_uris.includes(redirect_uri)) {
65
+ if (redirect_uri &&
66
+ Array.isArray(redirect_uris) &&
67
+ !redirect_uris.includes(redirect_uri)) {
50
68
  return reply.code(400).send({ message: "invalid redirect_uri", code: 400 });
51
69
  }
52
- const user = pg.pk?.['admin.users'] ? await pg.query('select * from admin.users where uid=$1 and enabled limit 1', [userId]).then((el) => el.rows[0]) : null;
70
+ const user = pg.pk?.["admin.users"]
71
+ ? await pg
72
+ .query("select * from admin.users where uid=$1 and enabled limit 1", [
73
+ userId,
74
+ ])
75
+ .then((el) => el.rows[0])
76
+ : null;
53
77
  if (!user) {
54
78
  return reply.code(404).send({ message: "user not found", code: 404 });
55
79
  }
56
- Object.assign(user, { auth_type: 'jwt' });
57
80
  const expire = expires_at ? expires_at - Date.now() : expireMsec;
58
- const href1 = await authorizeUser(user, req, 'jwt', expire);
59
- const backUrl = redirect_uri ? `${redirect_uri}?code=${code}` : '';
60
- const href = redirect_uri && !redirect_uri.includes('?')
81
+ const href1 = await authorizeUser(user, req, "jwt", expire);
82
+ const backUrl = redirect_uri ? `${redirect_uri}?code=${code}` : "";
83
+ const href = redirect_uri && !redirect_uri.includes("?")
61
84
  ? backUrl
62
- : (backUrl?.replace?.(/\?code=/, '&code=') || href1);
63
- if (req.method === 'POST' || payload.noredirect || process.env.NODE_ENV === 'test') {
64
- return reply.code(200).send('auth success');
85
+ : backUrl?.replace?.(/\?code=/, "&code=") || href1;
86
+ if (req.method === "POST" ||
87
+ payload.noredirect ||
88
+ process.env.NODE_ENV === "test") {
89
+ return reply.code(200).send("auth success");
65
90
  }
66
91
  return reply.redirect(href);
67
92
  }
@@ -5,7 +5,7 @@ import { join } from "node:path";
5
5
  import { existsSync, readdirSync, readFileSync } from "node:fs";
6
6
  import config from "../../../../config.js";
7
7
  import pgClients from "../../../plugins/pg/pgClients.js";
8
- import applyHook from "../../../plugins/hook/funcs/applyHook.js";
8
+ import { applyHook } from "../../../../utils.js";
9
9
  import menuDirs from "../../../plugins/table/funcs/menuDirs.js";
10
10
  const menuCache = [];
11
11
  // check module dir
@@ -4,7 +4,7 @@ import autoIndex from "../../../plugins/pg/funcs/autoIndex.js";
4
4
  import getSelect from "../../../plugins/table/funcs/getSelect.js";
5
5
  import getSelectVal from "../../../plugins/table/funcs/metaFormat/getSelectVal.js";
6
6
  import pgClients from "../../../plugins/pg/pgClients.js";
7
- import applyHook from "../../../plugins/hook/funcs/applyHook.js";
7
+ import { applyHook } from "../../../../utils.js";
8
8
  import getTemplate from "../../../plugins/table/funcs/getTemplate.js";
9
9
  export default async function filterAPI(req, reply, iscalled) {
10
10
  const time = Date.now();
@@ -4,7 +4,7 @@ import getFilterSQL from "../../../plugins/table/funcs/getFilterSQL/index.js";
4
4
  import getAccess from "../../../plugins/crud/funcs/getAccess.js";
5
5
  import setToken from "../../../plugins/crud/funcs/setToken.js";
6
6
  import gisIRColumn from "../../../plugins/table/funcs/gisIRColumn.js";
7
- import applyHook from "../../../plugins/hook/funcs/applyHook.js";
7
+ import { applyHook } from "../../../../utils.js";
8
8
  import getSelect from "../../../plugins/table/funcs/getSelect.js";
9
9
  import setOpt from "../../../plugins/crud/funcs/setOpt.js";
10
10
  import getOpt from "../../../plugins/crud/funcs/getOpt.js";
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import getMeta from "../../../plugins/pg/funcs/getMeta.js";
3
3
  import dataInsert from "../../../plugins/crud/funcs/dataInsert.js";
4
4
  import dataUpdate from "../../../plugins/crud/funcs/dataUpdate.js";
5
- import applyHook from "../../../plugins/hook/funcs/applyHook.js";
5
+ import { applyHook } from "../../../../utils.js";
6
6
  import uploadMultiPart from "../../../plugins/file/uploadMultiPart.js";
7
7
  const tableList = {
8
8
  comment: "crm.communications",
@@ -1,4 +1,4 @@
1
- import addHook from "../../plugins/hook/funcs/addHook.js";
1
+ import { addHook } from "../../../utils.js";
2
2
  import widgetDel from "./controllers/widget.del.js";
3
3
  import widgetSet from "./controllers/widget.set.js";
4
4
  import widgetGet from "./controllers/widget.get.js";
package/dist/utils.js CHANGED
@@ -52,9 +52,7 @@ export { default as validateData } from "./server/plugins/crud/funcs/validateDat
52
52
  // policy
53
53
  export { default as checkXSS } from "./server/plugins/policy/funcs/checkXSS.js";
54
54
  // hook
55
- export { default as applyHook } from "./server/plugins/hook/funcs/applyHook.js";
56
- export { default as applyHookSync } from "./server/plugins/hook/funcs/applyHookSync.js";
57
- export { default as addHook } from "./server/plugins/hook/funcs/addHook.js";
55
+ export * from "./server/plugins/hook/index.js";
58
56
  export { default as execMigrations } from "./server/plugins/migration/exec.migrations.js";
59
57
  export { default as execSql } from "./server/plugins/migration/exec.sql.js";
60
58
  // cron
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [
@@ -1,7 +0,0 @@
1
- import hookList from "../hookList.js";
2
- export default function addHook(name, fn) {
3
- if (!hookList[name]) {
4
- hookList[name] = [];
5
- }
6
- hookList[name].push(fn);
7
- }
@@ -1,25 +0,0 @@
1
- /* eslint-disable no-console */
2
- import config from "../../../../config.js";
3
- import hookList from "../hookList.js";
4
- export default async function applyHook(name, data) {
5
- const { trace } = config;
6
- if (trace)
7
- console.log("applyHook", name);
8
- if (!hookList[name]?.length)
9
- return null;
10
- const result = {};
11
- await Promise.all(hookList[name].map(async (hook) => {
12
- const hookData = await hook({ ...data, config });
13
- if (hookData) {
14
- if (trace)
15
- console.log("applyHook", name, hookData);
16
- Object.assign(result, hookData);
17
- }
18
- })).catch((err) => {
19
- console.error("applyHook", name, err.toString());
20
- });
21
- if (Object.keys(result).length) {
22
- return result;
23
- }
24
- return null;
25
- }
@@ -1,7 +0,0 @@
1
- import hookList from "../hookList.js";
2
- export default function applyHookSync(name, data) {
3
- if (!hookList[name]?.length)
4
- return null;
5
- const hookData = hookList[name].map((hook) => hook(data))[0];
6
- return hookData;
7
- }
@@ -1,2 +0,0 @@
1
- const hookList = {};
2
- export default hookList;