From 72843e818e129c64cfcee3aecfa185a302b2fd69 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 20 Oct 2023 09:28:11 +0100 Subject: [PATCH 01/44] logs --- mod/templates/_templates.js | 10 ++++++++++ mod/user/admin.js | 2 ++ 2 files changed, 12 insertions(+) diff --git a/mod/templates/_templates.js b/mod/templates/_templates.js index 8230f234e2..76885321c3 100644 --- a/mod/templates/_templates.js +++ b/mod/templates/_templates.js @@ -73,6 +73,16 @@ module.exports = async (key, language = 'en', params = {}) => { return key; } + if (key === 'login_view') { + + console.log(templates.login_view) + } + + if (key === 'user_admin_view') { + + console.log(templates.user_admin_view) + } + let template = templates[key]?.[language] || templates[key]?.en if (!template) { diff --git a/mod/user/admin.js b/mod/user/admin.js index 98f8c15433..7fbd4f4ecb 100644 --- a/mod/user/admin.js +++ b/mod/user/admin.js @@ -2,6 +2,8 @@ const templates = require('../templates/_templates') module.exports = async (req, res) => { + console.log(req.params.user.email) + // Get admin view template. const adminView = await templates('user_admin_view', 'en', { dir: process.env.DIR, From 333b23b46fd80a5b7ef4ef67389f7ff3a75f6b36 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 20 Oct 2023 17:21:41 +0100 Subject: [PATCH 02/44] getTemplate method poc --- api/api.js | 9 +++- mod/templates/_templates.js | 26 +++-------- mod/user/admin.js | 2 - mod/workspace/_workspace.js | 39 ++++++++++++++-- mod/workspace/assignDefaults.js | 2 + mod/workspace/assignTemplates.js | 2 + mod/workspace/cache.js | 11 +++-- mod/workspace/getTemplate.js | 78 ++++++++++++++++++++++++++++++++ 8 files changed, 138 insertions(+), 31 deletions(-) create mode 100644 mod/workspace/getTemplate.js diff --git a/api/api.js b/api/api.js index 58e24a7930..effd19d03a 100644 --- a/api/api.js +++ b/api/api.js @@ -10,6 +10,8 @@ const saml = process.env.SAML_ENTITY_ID && require('../mod/user/saml') const workspaceCache = require('../mod/workspace/cache') +const getTemplate = require('../mod/workspace/getTemplate') + const routes = { layer: require('../mod/layer/_layer'), location: require('../mod/location/_location'), @@ -194,9 +196,12 @@ module.exports = async (req, res) => { // Retrieve query or view template from workspace if (req.params.template) { - const template = workspace.templates[req.params.template] + if (!Object.hasOwn(workspace.templates, req.params.template)) { + + return res.status(404).send('Template not found.') + } - if (!template) return res.status(404).send('Template not found.') + const template = await getTemplate(workspace.templates[req.params.template]) if (template.err) return res.status(500).send(template.err.message) diff --git a/mod/templates/_templates.js b/mod/templates/_templates.js index 76885321c3..d11c7cd0f9 100644 --- a/mod/templates/_templates.js +++ b/mod/templates/_templates.js @@ -37,13 +37,6 @@ const getFrom = { cloudfront: ref => cloudfront(ref.split(':')[1]), } -const custom_templates = new Promise(async (resolve, reject)=>{ - - if (!process.env.CUSTOM_TEMPLATES) return resolve({}) - - resolve(await getFrom[process.env.CUSTOM_TEMPLATES.split(':')[0]](process.env.CUSTOM_TEMPLATES)) -}) - module.exports = async (key, language = 'en', params = {}) => { if (key === undefined) return; @@ -58,13 +51,18 @@ module.exports = async (key, language = 'en', params = {}) => { // Prevent prototype polluting assignment. if (/__proto__/.test(key)) return; - const _templates = await custom_templates; + const custom_templates = await new Promise(async (resolve, reject)=>{ + + if (!process.env.CUSTOM_TEMPLATES) return resolve({}) + + resolve(await getFrom[process.env.CUSTOM_TEMPLATES.split(':')[0]](process.env.CUSTOM_TEMPLATES)) + }) const templates = merge({}, view_templates, mail_templates, msg_templates, - _templates) + custom_templates) if (!Object.hasOwn(templates, key)) { @@ -73,16 +71,6 @@ module.exports = async (key, language = 'en', params = {}) => { return key; } - if (key === 'login_view') { - - console.log(templates.login_view) - } - - if (key === 'user_admin_view') { - - console.log(templates.user_admin_view) - } - let template = templates[key]?.[language] || templates[key]?.en if (!template) { diff --git a/mod/user/admin.js b/mod/user/admin.js index 7fbd4f4ecb..98f8c15433 100644 --- a/mod/user/admin.js +++ b/mod/user/admin.js @@ -2,8 +2,6 @@ const templates = require('../templates/_templates') module.exports = async (req, res) => { - console.log(req.params.user.email) - // Get admin view template. const adminView = await templates('user_admin_view', 'en', { dir: process.env.DIR, diff --git a/mod/workspace/_workspace.js b/mod/workspace/_workspace.js index 5ba2b46c9c..dff8b13efd 100644 --- a/mod/workspace/_workspace.js +++ b/mod/workspace/_workspace.js @@ -2,6 +2,10 @@ const clone = require('../utils/clone.js') const Roles = require('../utils/roles.js') +const merge = require('../utils/merge') + +const getTemplate = require('./getTemplate') + module.exports = async (req, res) => { const keys = { @@ -26,11 +30,13 @@ module.exports = async (req, res) => { async function getLayer(req, res) { - if (!Object.hasOwn(req.params.workspace.locales, req.params.locale)) { + const workspace = req.params.workspace + + if (!Object.hasOwn(workspace.locales, req.params.locale)) { return res.status(400).send(`Unable to validate locale param.`) } - const locale = req.params.workspace.locales[req.params.locale] + const locale = workspace.locales[req.params.locale] const roles = req.params.user?.roles || [] @@ -42,7 +48,34 @@ async function getLayer(req, res) { return res.status(400).send(`Unable to validate layer param.`) } - const layer = clone(locale.layers[req.params.layer]) + let layer = locale.layers[req.params.layer] + + // Assign key value as key on layer object. + layer.key ??= req.params.layer + + if (Object.hasOwn(workspace.templates, layer.template || layer.key)) { + + merge(layer, await getTemplate(workspace.templates[layer.template || layer.key])) + } + + if (Array.isArray(layer.templates)) { + + // Merge templates from templates array into layer. + layer.templates.forEach(async template => { + merge(layer, await getTemplate(workspace.templates[template])) + }) + } + + // Check for layer geom[s]. + if ((layer.table || layer.tables) && (!layer.geom && !layer.geoms)) { + + console.warn(`Layer: ${layer.key},has a table or tables defined, but no geom or geoms.`) + } + + // Assign layer key as name with no existing name on layer object. + layer.name ??= layer.key + + //const layer = clone(locale.layers[req.params.layer]) if (!Roles.check(layer, roles)) { return res.status(403).send('Role access denied.') diff --git a/mod/workspace/assignDefaults.js b/mod/workspace/assignDefaults.js index 04e19e3a1a..833478bdf8 100644 --- a/mod/workspace/assignDefaults.js +++ b/mod/workspace/assignDefaults.js @@ -36,6 +36,8 @@ module.exports = async workspace => { // Assign locale key as name with no existing name on locale object. locale.name = locale.name || locale_key + return; + // Loop through layer keys in locale. Object.keys(locale.layers).forEach(layer_key => { diff --git a/mod/workspace/assignTemplates.js b/mod/workspace/assignTemplates.js index 49a5c9542c..0bbd20c04e 100644 --- a/mod/workspace/assignTemplates.js +++ b/mod/workspace/assignTemplates.js @@ -73,6 +73,8 @@ module.exports = async (workspace) => { workspace.templates ); + return; + const templatePromises = Object.entries(workspace.templates) .map((entry) => new Promise((resolve, reject) => { diff --git a/mod/workspace/cache.js b/mod/workspace/cache.js index ade8e79d38..b36b43ffac 100644 --- a/mod/workspace/cache.js +++ b/mod/workspace/cache.js @@ -17,6 +17,8 @@ const assignTemplates = require('./assignTemplates') const assignDefaults = require('./assignDefaults') +process.env.WORKSPACE_AGE ??= 3600000 + let workspace = null const logger = require('../utils/logger') @@ -32,10 +34,10 @@ module.exports = async () => { } // Logically assign timestamp. - workspace.timestamp = workspace.timestamp || timestamp + workspace.timestamp ??= timestamp // Cache workspace if expired. - if ((timestamp - workspace.timestamp) > (+process.env.WORKSPACE_AGE || 3600000)) { + if ((timestamp - workspace.timestamp) > +process.env.WORKSPACE_AGE) { await cache() logger(`Workspace cache expired; Time to cache: ${Date.now()-timestamp}`, 'workspace') @@ -48,9 +50,8 @@ module.exports = async () => { async function cache() { // Get workspace from source. - workspace = process.env.WORKSPACE - && await getFrom[process.env.WORKSPACE.split(':')[0]](process.env.WORKSPACE) - || {} + workspace = process.env.WORKSPACE ? + await getFrom[process.env.WORKSPACE.split(':')[0]](process.env.WORKSPACE) : {} // Return error if source failed. if (workspace instanceof Error) return workspace diff --git a/mod/workspace/getTemplate.js b/mod/workspace/getTemplate.js new file mode 100644 index 0000000000..fff08e0c14 --- /dev/null +++ b/mod/workspace/getTemplate.js @@ -0,0 +1,78 @@ +const cloudfront = require('../provider/cloudfront'); + +const file = require('../provider/file'); + +const http = require('./httpsAgent'); + +const getFrom = { + https: (ref) => http(ref), + file: (ref) => file(ref.split(':')[1]), + cloudfront: (ref) => cloudfront(ref.split(':')[1]), +}; + +const merge = require('../utils/merge') + +module.exports = async (template) => { + + if (!template.src) { + + return template + } + + if (template.loaded) { + + return template + } + + // Substitute parameter in src string. + template.src = template.src.replace(/\$\{(.*?)\}/g, + (matched) => process.env[`SRC_${matched.replace(/\$|\{|\}/g, '')}`] || matched); + + + if (!Object.hasOwn(getFrom, template.src.split(':')[0])) { + + // Unable to determine getFrom method. + console.warn(`Cannot get: "${template.src}"`); + return template + } + + const response = await getFrom[template.src.split(':')[0]](template.src) + + if (response instanceof Error) { + + template.err = response + + return template + } + + // Template is a module. + if (template.module || template.type === 'module') { + try { + + // Attempt to construct module from string. + const module_constructor = module.constructor; + const Module = new module_constructor(); + Module._compile(response, template.src); + + template.render = Module.exports + + } catch (err) { + template.err = err + return template + } + } + + if (typeof response === 'object') { + + // Get template from src. + merge(template, response) + + } else if (typeof response === 'string') { + + template.template = response + } + + template.loaded = true + + return template +} \ No newline at end of file From 7699ab696960f86838cdc2f6f3d595460e32f1fd Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 20 Oct 2023 17:49:19 +0100 Subject: [PATCH 03/44] getLayer for query --- mod/query.js | 6 +++- mod/workspace/getLayer.js | 61 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 mod/workspace/getLayer.js diff --git a/mod/query.js b/mod/query.js index f2cc7f671b..14e5ba6047 100644 --- a/mod/query.js +++ b/mod/query.js @@ -6,6 +6,8 @@ const Roles = require('./utils/roles.js') const logger = require('./utils/logger'); +const getLayer = require('./workspace/getLayer'); + module.exports = async (req, res) => { const template = req.params.template @@ -30,8 +32,10 @@ module.exports = async (req, res) => { return res.status(400).send('Layer not found.') } + let layer = await getLayer(req) + // Get layer from locale. - const layer = Roles.check(locale.layers[req.params.layer], req.params.user?.roles) + layer = Roles.check(layer, req.params.user?.roles) if (!layer) { diff --git a/mod/workspace/getLayer.js b/mod/workspace/getLayer.js new file mode 100644 index 0000000000..9dcc77f2fe --- /dev/null +++ b/mod/workspace/getLayer.js @@ -0,0 +1,61 @@ +const Roles = require('../utils/roles.js') + +const merge = require('../utils/merge') + +const getTemplate = require('./getTemplate') + +module.exports = async (req) => { + + const workspace = req.params.workspace + + if (!Object.hasOwn(workspace.locales, req.params.locale)) { + return new Error('Unable to validate locale param.') //400 + } + + const locale = workspace.locales[req.params.locale] + + const roles = req.params.user?.roles || [] + + if (!Roles.check(locale, roles)) { + return new Error('Role access denied.') //403 + } + + if (!Object.hasOwn(locale.layers, req.params.layer)) { + return new Error('Unable to validate layer param.') //400 + } + + let layer = locale.layers[req.params.layer] + + // Assign key value as key on layer object. + layer.key ??= req.params.layer + + if (Object.hasOwn(workspace.templates, layer.template || layer.key)) { + + merge(layer, await getTemplate(workspace.templates[layer.template || layer.key])) + } + + if (Array.isArray(layer.templates)) { + + // Merge templates from templates array into layer. + layer.templates.forEach(async template => { + merge(layer, await getTemplate(workspace.templates[template])) + }) + } + + // Check for layer geom[s]. + if ((layer.table || layer.tables) && (!layer.geom && !layer.geoms)) { + + console.warn(`Layer: ${layer.key},has a table or tables defined, but no geom or geoms.`) + } + + // Assign layer key as name with no existing name on layer object. + layer.name ??= layer.key + + //const layer = clone(locale.layers[req.params.layer]) + + // if (!Roles.check(layer, roles)) { + // return res.status(403).send('Role access denied.') + // } + + return layer +} \ No newline at end of file From 356c6cac4d45bbf887c3e99fcc0e79f42736b535 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 20 Oct 2023 17:50:56 +0100 Subject: [PATCH 04/44] remove layer process from assignDefaults --- mod/workspace/assignDefaults.js | 42 --------------------------------- 1 file changed, 42 deletions(-) diff --git a/mod/workspace/assignDefaults.js b/mod/workspace/assignDefaults.js index 833478bdf8..2f4715eff7 100644 --- a/mod/workspace/assignDefaults.js +++ b/mod/workspace/assignDefaults.js @@ -35,48 +35,6 @@ module.exports = async workspace => { // Assign locale key as name with no existing name on locale object. locale.name = locale.name || locale_key - - return; - - // Loop through layer keys in locale. - Object.keys(locale.layers).forEach(layer_key => { - - // Get layer object from key. - let layer = locale.layers[layer_key] - - // Assign key value as key on layer object. - layer.key = layer_key - - // Merge layer object with layer template object. - layer = merge({}, - - // Assign layer template implicit or from key lookup. - workspace.templates[layer.template || layer.key] || {}, - - // Layer entries must override template entries. - layer) - - if (Array.isArray(layer.templates)) { - - // Merge templates from templates array into layer. - layer.templates.forEach(template => { - merge(layer, workspace.templates[template] || {}) - }) - } - - // Check for layer geom[s]. - if ((layer.table || layer.tables) && (!layer.geom && !layer.geoms)) { - - console.warn(`Layer: ${layer.key},has a table or tables defined, but no geom or geoms.`) - } - - // Assign layer key as name with no existing name on layer object. - layer.name = layer.name || layer_key - - // Assign layer to workspace locale - locale.layers[layer_key] = layer - }) - }) } \ No newline at end of file From cd4224d6b9d8e467a4204602c21051eab2bb5d7a Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 20 Oct 2023 17:53:36 +0100 Subject: [PATCH 05/44] remove get[From] assignTemplates --- mod/workspace/assignTemplates.js | 122 ------------------------------- 1 file changed, 122 deletions(-) diff --git a/mod/workspace/assignTemplates.js b/mod/workspace/assignTemplates.js index 0bbd20c04e..799e04177d 100644 --- a/mod/workspace/assignTemplates.js +++ b/mod/workspace/assignTemplates.js @@ -1,17 +1,3 @@ -const cloudfront = require('../provider/cloudfront'); - -const file = require('../provider/file'); - -const http = require('./httpsAgent'); - -const getFrom = { - https: (ref) => http(ref), - file: (ref) => file(ref.split(':')[1]), - cloudfront: (ref) => cloudfront(ref.split(':')[1]), -}; - -const logger = require('../utils/logger'); - module.exports = async (workspace) => { // Assign default view and query templates to workspace. @@ -72,112 +58,4 @@ module.exports = async (workspace) => { }, workspace.templates ); - - return; - - const templatePromises = Object.entries(workspace.templates) - .map((entry) => new Promise((resolve, reject) => { - - // Entries without a src value must not be fetched. - if (!entry[1].src) { - return resolve({ - [entry[0]]: entry[1] - }); - } - - // Substitute parameter in src string. - entry[1].src = entry[1].src.replace( - /\$\{(.*?)\}/g, - (matched) => process.env[`SRC_${matched.replace(/\$|\{|\}/g, '')}`] || matched - ); - - if (!Object.hasOwn(getFrom, entry[1].src.split(':')[0])) { - - // Unable to determine getFrom method. - console.warn(`${entry[0]} template cannot be retrieved from src:"${entry[1].src}"`); - return reject({[entry[0]]: entry[1]}); - } - - // Get template from src. - getFrom[entry[1].src.split(':')[0]](entry[1].src).then(resolveFrom); - - function resolveFrom(_template) { - - // Failed to fetch template from src. - if (_template instanceof Error) { - return reject({ - [entry[0]]: Object.assign(entry[1], { - err: _template, - }), - }); - } - - // Template is a module. - if (entry[1].module || (entry[1].type && entry[1].type === 'module')) { - try { - - // Attempt to construct module from string. - const module_constructor = module.constructor; - const Module = new module_constructor(); - Module._compile(_template, entry[1].src); - - // Assign module exports as template function - return resolve({ - [entry[0]]: Object.assign(entry[1], { - render: Module.exports, - }), - }); - - } catch (err) { - return resolve({ - [entry[0]]: Object.assign(entry[1], { - err: err, - }), - }); - } - } - - if (typeof _template === 'object') { - - // Assign template object to the entry - return resolve({ - [entry[0]]: Object.assign(_template, entry[1]) - }); - } - - // Resolve template as string. - resolve({ - [entry[0]]: Object.assign(entry[1], { - template: _template, - }), - }); - - } - }) - ); - - return new Promise((resolve, reject) => { - Promise.allSettled(templatePromises) - .then((arr) => { - - // Log set of template objects from resolved promises. - logger(arr - .filter(o => o.value instanceof Object) - .map(o => `${Object.keys(o.value)[0]} - ${o.status}`), 'templates'); - - let assign = arr - .filter(o => o.value instanceof Object) - .map(o => o.value) - - // Spread array of template objects and assign to workspace. - Object.assign(workspace.templates, ...assign); - - // Resolve Promise for all template promises. - resolve(); - }) - .catch((error) => { - console.error(error); - reject(); - }); - }); }; From e9f1badf6553953b28455ff955db3eccd67c1124 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 20 Oct 2023 17:56:48 +0100 Subject: [PATCH 06/44] remove assignTemplates --- mod/workspace/assignTemplates.js | 61 ------------------------------ mod/workspace/cache.js | 65 +++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 67 deletions(-) delete mode 100644 mod/workspace/assignTemplates.js diff --git a/mod/workspace/assignTemplates.js b/mod/workspace/assignTemplates.js deleted file mode 100644 index 799e04177d..0000000000 --- a/mod/workspace/assignTemplates.js +++ /dev/null @@ -1,61 +0,0 @@ -module.exports = async (workspace) => { - - // Assign default view and query templates to workspace. - workspace.templates = Object.assign( - { - // Query templates: - gaz_query: { - template: require('./templates/gaz_query'), - }, - get_last_location: { - template: require('./templates/get_last_location'), - }, - distinct_values: { - template: require('./templates/distinct_values'), - }, - field_stats: { - template: require('./templates/field_stats'), - }, - field_min: { - template: require('./templates/field_min'), - }, - field_max: { - template: require('./templates/field_max'), - }, - get_nnearest: { - render: require('./templates/get_nnearest'), - }, - geojson: { - render: require('./templates/geojson'), - }, - cluster: { - render: require('./templates/cluster'), - reduce: true - }, - cluster_hex: { - render: require('./templates/cluster_hex'), - reduce: true - }, - wkt: { - render: require('./templates/wkt'), - reduce: true - }, - infotip: { - render: require('./templates/infotip'), - }, - layer_extent: { - template: require('./templates/layer_extent'), - }, - mvt_cache: { - admin: true, - render: require('./templates/mvt_cache'), - }, - mvt_cache_delete_intersects: { - template: require('./templates/mvt_cache_delete_intersects'), - }, - - // Default templates can be overridden by assigning a template with the same name. - }, - workspace.templates - ); -}; diff --git a/mod/workspace/cache.js b/mod/workspace/cache.js index b36b43ffac..bfab1d0004 100644 --- a/mod/workspace/cache.js +++ b/mod/workspace/cache.js @@ -13,8 +13,6 @@ const getFrom = { 'mongodb': ref => mongodb(ref.split(/:(.*)/s)[1]) } -const assignTemplates = require('./assignTemplates') - const assignDefaults = require('./assignDefaults') process.env.WORKSPACE_AGE ??= 3600000 @@ -30,7 +28,7 @@ module.exports = async () => { // Cache workspace if empty. if (!workspace) { await cache() - logger(`Workspace empty; Time to cache: ${Date.now()-timestamp}`, 'workspace') + logger(`Workspace empty; Time to cache: ${Date.now() - timestamp}`, 'workspace') } // Logically assign timestamp. @@ -38,9 +36,9 @@ module.exports = async () => { // Cache workspace if expired. if ((timestamp - workspace.timestamp) > +process.env.WORKSPACE_AGE) { - + await cache() - logger(`Workspace cache expired; Time to cache: ${Date.now()-timestamp}`, 'workspace') + logger(`Workspace cache expired; Time to cache: ${Date.now() - timestamp}`, 'workspace') workspace.timestamp = timestamp } @@ -56,7 +54,62 @@ async function cache() { // Return error if source failed. if (workspace instanceof Error) return workspace - await assignTemplates(workspace) + // Assign default view and query templates to workspace. + workspace.templates = { + // Query templates: + gaz_query: { + template: require('./templates/gaz_query'), + }, + get_last_location: { + template: require('./templates/get_last_location'), + }, + distinct_values: { + template: require('./templates/distinct_values'), + }, + field_stats: { + template: require('./templates/field_stats'), + }, + field_min: { + template: require('./templates/field_min'), + }, + field_max: { + template: require('./templates/field_max'), + }, + get_nnearest: { + render: require('./templates/get_nnearest'), + }, + geojson: { + render: require('./templates/geojson'), + }, + cluster: { + render: require('./templates/cluster'), + reduce: true + }, + cluster_hex: { + render: require('./templates/cluster_hex'), + reduce: true + }, + wkt: { + render: require('./templates/wkt'), + reduce: true + }, + infotip: { + render: require('./templates/infotip'), + }, + layer_extent: { + template: require('./templates/layer_extent'), + }, + mvt_cache: { + admin: true, + render: require('./templates/mvt_cache'), + }, + mvt_cache_delete_intersects: { + template: require('./templates/mvt_cache_delete_intersects'), + }, + + // Default templates can be overridden by assigning a template with the same name. + ...workspace.templates + } await assignDefaults(workspace) From e5ace68e60900f5dd8cf4b1a4aaff26c73b10cd0 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 20 Oct 2023 18:02:07 +0100 Subject: [PATCH 07/44] remove assignDefaults --- mod/workspace/assignDefaults.js | 40 --------------------------------- mod/workspace/cache.js | 37 ++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 42 deletions(-) delete mode 100644 mod/workspace/assignDefaults.js diff --git a/mod/workspace/assignDefaults.js b/mod/workspace/assignDefaults.js deleted file mode 100644 index 2f4715eff7..0000000000 --- a/mod/workspace/assignDefaults.js +++ /dev/null @@ -1,40 +0,0 @@ -const merge = require('../utils/merge') - -module.exports = async workspace => { - - workspace.locale ??= { - layers: {} - } - - workspace.locales ??= { - locale: workspace.locale - } - - // Loop through locale keys in workspace. - Object.keys(workspace.locales).forEach(locale_key => { - - // Get locale object from key. - const locale = workspace.locales[locale_key] - - // A default locale has been defined in the workspace. - if (typeof workspace.locale === 'object') { - - // Merge the workspace template into workspace. - merge(locale, workspace.locale) - } - - // A template exists for the locale key. - if (Object.hasOwn(workspace.templates, locale_key) && typeof workspace.templates[locale_key] === 'object') { - - // Merge the workspace template into workspace. - merge(locale, workspace.templates[locale_key]) - } - - // Assign key value as key on locale object. - locale.key = locale_key - - // Assign locale key as name with no existing name on locale object. - locale.name = locale.name || locale_key - }) - -} \ No newline at end of file diff --git a/mod/workspace/cache.js b/mod/workspace/cache.js index bfab1d0004..53a2ffc460 100644 --- a/mod/workspace/cache.js +++ b/mod/workspace/cache.js @@ -13,7 +13,7 @@ const getFrom = { 'mongodb': ref => mongodb(ref.split(/:(.*)/s)[1]) } -const assignDefaults = require('./assignDefaults') +const merge = require('../utils/merge') process.env.WORKSPACE_AGE ??= 3600000 @@ -111,7 +111,40 @@ async function cache() { ...workspace.templates } - await assignDefaults(workspace) + workspace.locale ??= { + layers: {} + } + + workspace.locales ??= { + locale: workspace.locale + } + + // Loop through locale keys in workspace. + Object.keys(workspace.locales).forEach(locale_key => { + + // Get locale object from key. + const locale = workspace.locales[locale_key] + + // A default locale has been defined in the workspace. + if (typeof workspace.locale === 'object') { + + // Merge the workspace template into workspace. + merge(locale, workspace.locale) + } + + // A template exists for the locale key. + if (Object.hasOwn(workspace.templates, locale_key) && typeof workspace.templates[locale_key] === 'object') { + + // Merge the workspace template into workspace. + merge(locale, workspace.templates[locale_key]) + } + + // Assign key value as key on locale object. + locale.key = locale_key + + // Assign locale key as name with no existing name on locale object. + locale.name = locale.name || locale_key + }) if (workspace.plugins) { From d771f95690e64acca8ee5fbd4f775ae8ef95051c Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 20 Oct 2023 18:10:55 +0100 Subject: [PATCH 08/44] getLayer method for workspace and query --- mod/workspace/_workspace.js | 31 +++---------------------------- mod/workspace/getLayer.js | 2 +- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/mod/workspace/_workspace.js b/mod/workspace/_workspace.js index dff8b13efd..2275e6e86f 100644 --- a/mod/workspace/_workspace.js +++ b/mod/workspace/_workspace.js @@ -4,6 +4,8 @@ const Roles = require('../utils/roles.js') const merge = require('../utils/merge') +const _getLayer = require('./getLayer') + const getTemplate = require('./getTemplate') module.exports = async (req, res) => { @@ -48,34 +50,7 @@ async function getLayer(req, res) { return res.status(400).send(`Unable to validate layer param.`) } - let layer = locale.layers[req.params.layer] - - // Assign key value as key on layer object. - layer.key ??= req.params.layer - - if (Object.hasOwn(workspace.templates, layer.template || layer.key)) { - - merge(layer, await getTemplate(workspace.templates[layer.template || layer.key])) - } - - if (Array.isArray(layer.templates)) { - - // Merge templates from templates array into layer. - layer.templates.forEach(async template => { - merge(layer, await getTemplate(workspace.templates[template])) - }) - } - - // Check for layer geom[s]. - if ((layer.table || layer.tables) && (!layer.geom && !layer.geoms)) { - - console.warn(`Layer: ${layer.key},has a table or tables defined, but no geom or geoms.`) - } - - // Assign layer key as name with no existing name on layer object. - layer.name ??= layer.key - - //const layer = clone(locale.layers[req.params.layer]) + const layer = await _getLayer(req) if (!Roles.check(layer, roles)) { return res.status(403).send('Role access denied.') diff --git a/mod/workspace/getLayer.js b/mod/workspace/getLayer.js index 9dcc77f2fe..7d7a593040 100644 --- a/mod/workspace/getLayer.js +++ b/mod/workspace/getLayer.js @@ -24,7 +24,7 @@ module.exports = async (req) => { return new Error('Unable to validate layer param.') //400 } - let layer = locale.layers[req.params.layer] + const layer = locale.layers[req.params.layer] // Assign key value as key on layer object. layer.key ??= req.params.layer From 9795478d9d6f5f20bc8d73445fe81929af5cf43b Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 20 Oct 2023 18:14:32 +0100 Subject: [PATCH 09/44] remove async from promise execution --- mod/templates/_templates.js | 4 ++-- mod/workspace/_workspace.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mod/templates/_templates.js b/mod/templates/_templates.js index d11c7cd0f9..330b08fa5a 100644 --- a/mod/templates/_templates.js +++ b/mod/templates/_templates.js @@ -51,11 +51,11 @@ module.exports = async (key, language = 'en', params = {}) => { // Prevent prototype polluting assignment. if (/__proto__/.test(key)) return; - const custom_templates = await new Promise(async (resolve, reject)=>{ + const custom_templates = await new Promise((resolve)=>{ if (!process.env.CUSTOM_TEMPLATES) return resolve({}) - resolve(await getFrom[process.env.CUSTOM_TEMPLATES.split(':')[0]](process.env.CUSTOM_TEMPLATES)) + resolve(getFrom[process.env.CUSTOM_TEMPLATES.split(':')[0]](process.env.CUSTOM_TEMPLATES)) }) const templates = merge({}, diff --git a/mod/workspace/_workspace.js b/mod/workspace/_workspace.js index 2275e6e86f..98d186cf93 100644 --- a/mod/workspace/_workspace.js +++ b/mod/workspace/_workspace.js @@ -2,11 +2,11 @@ const clone = require('../utils/clone.js') const Roles = require('../utils/roles.js') -const merge = require('../utils/merge') +//const merge = require('../utils/merge') const _getLayer = require('./getLayer') -const getTemplate = require('./getTemplate') +//const getTemplate = require('./getTemplate') module.exports = async (req, res) => { From ec436e0bb305b150aaf4a1c0d20b765a5e5f92c1 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 20 Oct 2023 18:16:13 +0100 Subject: [PATCH 10/44] remove commented code --- mod/workspace/_workspace.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mod/workspace/_workspace.js b/mod/workspace/_workspace.js index 98d186cf93..fa26af4dd4 100644 --- a/mod/workspace/_workspace.js +++ b/mod/workspace/_workspace.js @@ -2,12 +2,8 @@ const clone = require('../utils/clone.js') const Roles = require('../utils/roles.js') -//const merge = require('../utils/merge') - const _getLayer = require('./getLayer') -//const getTemplate = require('./getTemplate') - module.exports = async (req, res) => { const keys = { From 707a8eb39a988661ae0ff47b4b0b63e4d724fb37 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Tue, 24 Oct 2023 12:23:59 +0100 Subject: [PATCH 11/44] template caching --- mod/utils/languageTemplates.js | 17 ++ mod/view.js | 64 +++--- mod/workspace/cache.js | 15 ++ mod/workspace/templates/mails.js | 329 +++++++++++++++++++++++++++++++ mod/workspace/templates/msgs.js | 116 +++++++++++ mod/workspace/templates/views.js | 35 ++++ 6 files changed, 546 insertions(+), 30 deletions(-) create mode 100644 mod/utils/languageTemplates.js create mode 100644 mod/workspace/templates/mails.js create mode 100644 mod/workspace/templates/msgs.js create mode 100644 mod/workspace/templates/views.js diff --git a/mod/utils/languageTemplates.js b/mod/utils/languageTemplates.js new file mode 100644 index 0000000000..0ea78c2119 --- /dev/null +++ b/mod/utils/languageTemplates.js @@ -0,0 +1,17 @@ +const getTemplate = require('../workspace/getTemplate') + +module.exports = async (req, params) => { + + let template = await getTemplate({ + src: req.params.workspace.templates[params.template]?.[params.language] + }) + + + template = template.template.replace(/[{]{2}([A-Za-z][A-Za-z0-9]*)[}]{2}/g, matched => { + + // regex matches {{ or }} + return params[matched.replace(/[{]{2}|[}]{2}/g, '')] || ''; + }); + + return template +} diff --git a/mod/view.js b/mod/view.js index d0a2113fec..f26cea79f8 100644 --- a/mod/view.js +++ b/mod/view.js @@ -1,11 +1,11 @@ -const templates = require('./templates/_templates'); - const Roles = require('./utils/roles.js') const login = require('./user/login') const logger = require('./utils/logger') +const languageTemplates = require('./utils/languageTemplates') + module.exports = async (req, res) => { logger(req.url, 'view-req-url') @@ -24,6 +24,7 @@ module.exports = async (req, res) => { return login(req, res, 'no_locales') } + // Encode stringified user for template. const user = req.params.user && encodeURI(JSON.stringify({ email: req.params.user.email, admin: req.params.user.admin, @@ -31,37 +32,40 @@ module.exports = async (req, res) => { language: req.params.user.language })); - const params = Object.assign( - req.params || {}, - { - title: process.env.TITLE, - dir: process.env.DIR, - user: user, - language: req.params.language, - locale: req.params.locale, - login: (process.env.PRIVATE || process.env.PUBLIC) && 'true', - }, - // regex matches SRC_ at the start of string - Object.fromEntries(Object.entries(process.env).filter(entry => entry[0].match(/^SRC_/))) - ); - - // Template is provided from workspace - if (req.params.template?.template) { - - // regex captures characters inside {{ }} - return res.send(req.params.template?.template.replace(/[{]{2}([A-Za-z][A-Za-z0-9]*)[}]{2}/g, matched => { - - // regex matches {{ or }} - return params[matched.replace(/[{]{2}|[}]{2}/g, '')] || ''; - })); + const params = { + //...req.params, + language: req.params.user?.language || 'en', + title: process.env.TITLE, + dir: process.env.DIR, + user: user, + login: (process.env.PRIVATE || process.env.PUBLIC) && 'true', } + // Object.entries(process.env) + // .filter(entry => entry[0].match(/^SRC_/)) + // .forEach(entry => params[entry[0].replace(/^SRC_/, '')]=entry[1]) + + params.template ??= 'default_view' + + // // Template is provided from workspace + // if (req.params.template?.template) { + + // // regex captures characters inside {{ }} + // return res.send(req.params.template?.template.replace(/[{]{2}([A-Za-z][A-Za-z0-9]*)[}]{2}/g, matched => { + + // // regex matches {{ or }} + // return params[matched.replace(/[{]{2}|[}]{2}/g, '')] || ''; + // })); + // } + // Get view template. - const view = await templates( - 'default_view', - req.params.language || req.params.user?.language, - params - ); + // const view = await templates( + // 'default_view', + // req.params.language || req.params.user?.language, + // params + // ); + + const view = await languageTemplates(req, params) res.send(view); } \ No newline at end of file diff --git a/mod/workspace/cache.js b/mod/workspace/cache.js index 53a2ffc460..b620950a8c 100644 --- a/mod/workspace/cache.js +++ b/mod/workspace/cache.js @@ -45,6 +45,12 @@ module.exports = async () => { return workspace } +const view_templates = require('./templates/views') + +const mail_templates = require('./templates/mails') + +const msg_templates = require('./templates/msgs') + async function cache() { // Get workspace from source. @@ -54,8 +60,17 @@ async function cache() { // Return error if source failed. if (workspace instanceof Error) return workspace + const custom_templates = process.env.CUSTOM_TEMPLATES + && await getFrom[process.env.CUSTOM_TEMPLATES.split(':')[0]](process.env.CUSTOM_TEMPLATES) + // Assign default view and query templates to workspace. workspace.templates = { + + ...view_templates, + ...mail_templates, + ...msg_templates, + ...custom_templates, + // Query templates: gaz_query: { template: require('./templates/gaz_query'), diff --git a/mod/workspace/templates/mails.js b/mod/workspace/templates/mails.js new file mode 100644 index 0000000000..4da51f4e51 --- /dev/null +++ b/mod/workspace/templates/mails.js @@ -0,0 +1,329 @@ +module.exports = { + verify_password_reset: { + en: { + subject: `Please verify your password reset for \${host}`, + text: `A new password has been set for this account. + Please verify that you are the account holder: \${link} + The reset occured from this remote address \${remote_address} + This wasn't you? Please let your manager know.` + }, + fr: { + subject: `Vérifiez votre mot de passe réinitialisé sur \${host}.`, + text: `Le nouveau mot de passe a été défini pour ce compte. + Vérifiez que vous disposez des droits d'accès du compte \${link} + La réinitialisation a été exécutée par \${remote_address} + Vous ne l’avez pas demandé? Veuillez informer votre directeur.` + }, + pl: { + subject: `Zweryfikuj nowe hasło \${host}.`, + text: `Dla tego konta ustawiono nowe hasło. + Potwierdź swoje prawa dostępu do \${link} + Proces zmiany hasła rozpoczęto z tego adresu \${remote_address} + To nie Ty? Zgłoś to osobie odpowiedzialnej.` + }, + ja: { + subject: `リセットするパスワードを検証してください \${host}`, + text: `このアカウントに新規パスワードが設定されました. + アカウントホールダーであることを検証してください \${link} + このリモートアドレスによりリセットがされました \${remote_address} + あなたではなかった場合、マネージャーに連絡をして下さい` + }, + ko: { + subject: `비밀번호 재설정을 확인해주십시오. \${host}`, + text: `이 계정의 새로운 비밀번호가 설정되었습니다. + 계정 소유자임을 확인해주십시오. \${link} + 기기의 고유주소로부터 재설정이 되었습니다. \${remote_address} + 본인이 아니면 담당매니저에게 알려주십시오.` + }, + zh: { + subject: `请验证您的密码重置 \${host}`, + text: `为此帐户设置了新密码 + 请确认您是帐户持有人 \${link} + 账户重置发生于远程地址 \${remote_address} + 如果这不是您本人的操作,请告知您的相关负责人` + } + }, + verify_account: { + en: { + subject: `Please verify your account on \${host}`, + text: `A new account for this email address has been registered with \${host} + Please verify that you are the account holder: \${link} + A site administrator must approve the account before you are able to login. + You will be notified via email once an administrator has approved your account. + The account was registered from this remote address \${remote_address}\n + This wasn't you? Do NOT verify the account and let your manager know.` + }, + de: { + subject: `Bitte verifizieren Sie ihr Benutzerkonto für \${host}`, + text: `A new account for this email address has been registered with \${host} + Please verify that you are the account holder: \${link} + A site administrator must approve the account before you are able to login. + You will be notified via email once an administrator has approved your account. + The account was registered from this remote address \${remote_address}\n + This wasn't you? Do NOT verify the account and let your manager know.` + }, + fr: { + subject: `Vérifiez votre compte sur \${host}`, + text: `Un nouveau compte a été enregistré sur \${host}. + Vérifiez que vous disposez des droits d'accès du compte \${link} + L'adminstrateur doit approuver votre compte avant de vous connecter. + Vous recevrez un e-mail lorsque votre compte sera approuvé. + L'enregistrement a été exécuté par \${remote_address}\n + Vous ne l’avez pas demandé? Veuillez informer votre directeur.` + }, + pl: { + subject: `Zweryfikuj konto \${host}`, + text: `Nowe konto dla tego adresu e-mail zostało zarejestrowane z \${host} + Potwierdź swoje prawa dostępu do \${link} + Administrator musi zatwierdzić konto przed logowaniem. + Otrzymasz powiadomienie na swój adres e-mail. + Proces rejestracji konta rozpoczęto z tego adresu \${remote_address}\n + To nie Ty? Zignoruj ten link i zgłoś to osobie odpowiedzialnej.` + }, + ja: { + subject: `\${host} についてアカウントを検証して下さい`, + text: `このE-メールアドレスの新規アカウントは\${host}に登録されています + アカウントホールダーであることを検証してください \${link} + サイトアドミニストレーター承認後にログインが可能となります + サイトアドミニストレーターによるアカウント承認後通知メールが送信されます + アカウントはこのリモートアドレスより登録されました。 \${remote_address}\n + これがあなたでなかった場合はアカウントの検証は行わずマネージャーに連絡をしてください` + }, + ko: { + subject: `계정 확인바랍니다 \${host}`, + text: `이 이메일주소의 새로운 계정이 등록되었습니다. \${host} + 계정 소유자임을 확인해주십시오. \${protocol}\${link} + 로그인전에 입지 관리자가 계정을 승인해야만 합니다. + 관리자가 계정 승인을 하면 공지 이메일을 받게됩니다. + 기기의 고유주소로부터 계정 등록이 되었습니다. \${remote_address}\n + 본인이 아니면 계정 확인을 하지말고 관리자에게 알려주십시오.` + }, + zh: { + subject: `请验证您的帐户 \${host}`, + text: `已为此电子邮件在\${host}上注册了新账户 + 请确认您是帐户持有人 \${protocol}\${link} + 等待网站管理员批准该帐户,然后才能登录。 + 一旦管理员批准了您的帐户,就会通过电子邮件通知您。 + 该帐户是从该远程地址注册的 \${remote_address}\n + 如果这不是您本人的操作,请不要进行验证;同时请告知您的相关负责人` + } + }, + approved_account: { + en: { + subject: `This account has been approved on \${host}.`, + text: `You are now able to log on to \${protocol}\${host}` + }, + fr: { + text: `Ce compte a été approuvé sur \${host}.`, + subject: `Maintenant vous pouvez vous connecter à \${protocol}\${host}` + }, + pl: { + text: `Konto na \${host} zostało zatwierdzone.`, + subject: `Teraz możesz się zalogować na \${protocol}\${host}` + }, + ja: { + subject: `アカウントは承認されました \${host}`, + text: `これで、\${protocol}\${host}にログオンできます。` + }, + ko: { + subject: `계정이 승인되었습니다. \${host}`, + text: `로그인이 가능합니다. \${protocol}\${host}` + }, + zh: { + subject: `该帐户已得到批准 \${host}`, + text: `您现在已可登录 \${protocol}\${host}` + } + }, + deleted_account: { + en: { + subject: `This \${host} account has been deleted.`, + text: `You will no longer be able to log in to \${protocol}\${host}` + }, + de: { + subject: `Diese Benutzerkonto für \${host} wurde entfernt.`, + text: `Einloggen ist nicht länger möglich \${protocol}\${host}` + }, + fr: { + subject: `Ce compte sur \${host} a été supprimé.`, + text: `Vous ne pouvez plus vous connecter à \${protocol}\${host}` + }, + pl: { + subject: `Konto na \${host} zostało usunięte.`, + text: `Nie możesz się już logować na \${protocol}\${host}` + }, + ja: { + subject: `\${host}のこのアカウントは削除されました`, + text: `\${protocol}\${host}にログインできなくなります削除されました` + }, + ko: { + subject: `계정이 삭제되었습니다. \${host}`, + text: `더 이상 로그인이 불가합니다. \${protocol}\${host}` + }, + zh: { + subject: `此帐户已被删除 \${host}`, + text: `您将不再能登录 \${protocol}\${host}` + } + }, + failed_login: { + en: { + subject: `A failed login attempt was made on \${host}`, + text: `The failed attempt occured from this remote address \${remote_address} + This wasn't you? Please let your manager know.` + }, + fr: { + subject: `Une tentative de connexion a échouée sur \${host}`, + text: `La tentative de connexion échouée a été exécuté par \${remote_address}\n + Vous ne l’avez pas exécuté? Veuillez informer votre directeur.` + }, + pl: { + subject: `Nieudana próba logowania na \${host}`, + text: `Nieudaną próbę logowania rozpoczęto z tego adresu \${remote_address} + To nie Ty? Zgłoś to osobie odpowiedzialnej.` + }, + ja: { + subject: `ログインに失敗しました \${host}にログインしようとしました`, + text: `このリモートアドレスから試されましたが失敗しました \${remote_address} + これがあなたではなかった場合、マネージャーに連絡をして下さい` + }, + ko: { + subject: `잘못된 로그인 시도입니다. \${host}`, + text: `기기의 고유주소로부터 잘못된 로그인 시도가 발생했습니다. \${remote_address} + 본인이 아니면 담당매니저에게 알려주십시오.` + }, + zh: { + subject: `尝试登录失败 \${host}`, + text: `操作失败。该尝试发生于这个远程地址 \${remote_address} + 如果这不是您本人的操作,请告知您的相关负责人` + } + }, + locked_account: { + en: { + subject: `Too many failed login attempts occured on \${host}`, + text: `\${failed_attempts} failed login attempts have been recorded on this account. + This account has now been locked until verified. + Please verify that you are the account holder: \${protocol}\${host}/api/user/verify/\${verificationtoken} + Verifying the account will reset the failed login attempts. + The failed attempt occured from this remote address \${remote_address} + This wasn't you? Please let your manager know.` + }, + fr: { + subject: `Trop d'échecs de tentatives de connexions ont été exécutés sur \${host}`, + text: `\${failed_attempts} échecs de tentatives de connexions ont été exécutés par ce compte. + Il a été verrouillé jusqu’à ce qu’il soit vérifié de nouveau. + Vérifiez que vous disposez des droits d'accès du compte \${protocol}\${host}/api/user/verify/\${verificationtoken} + La vérification du compte réinitialisera des droits d'accès. + La tentative de connexion échouée a été exécuté par \${remote_address}\n + Vous ne l’avez pas exécuté? Veuillez informer votre directeur.` + }, + pl: { + subject: `Zbyt wiele nieudanych prób logowania na \${host}`, + text: `Na tym koncie zarejestrowano \${failed_attempts} \${failed_attempts === 1 ? 'nieudaną próbę' : 'nieudane próby'} + logowania. To konto zostało zablokowane do czasu powtórnej weryfikacji. + Potwierdź swoje prawa dostępu do \${protocol}\${host}/api/user/verify/\${verificationtoken} + Powtórna weryfikacja odświeży dozwoloną liczbę nieudanych prób. + Nieudaną próbę logowania rozpoczęto z tego adresu \${remote_address} + To nie Ty? Zgłoś to osobie odpowiedzialnej.` + }, + ja: { + subject: `\${host}ログインに多数失敗しました`, + text: `このアカウントによるログインが\${failed_attempts}回失敗しました + このアカウントは検証されるまでロックされます + アカウントホールダーであることを検証してください \${protocol}\${host}/api/user/verify/\${verificationtoken} + アカウント検証によりログイン失敗がリセットされます + このリモートアドレスから試されましたが失敗しました \${remote_address} + これがあなたではなかった場合、マネージャーに連絡をして下さい` + }, + ko: { + subject: `다수의 잘못된 로그인 시도가 발생했습니다. \${host}`, + text: `이 계정에 \${failed_attempts} 번의 잘못된 로그인 시도가 발생했습니다. + 이 계정은 확인될때까지 봉쇄되었습니다. + 계정 소유자임을 확인해주십시오. \${protocol}\${host}/api/user/verify/\${verificationtoken} + 계정 확인은 잘못된 로그인 시도를 재설정합니다. + 기기의 고유주소로부터 잘못된 로그인 시도가 발생했습니다. \${remote_address} + 본인이 아니면 담당매니저에게 알려주십시오.` + }, + zh: { + subject: `发生太多失败的登录尝试 \${host}`, + text: `此帐户登录尝试失败已发生\${failed_attempts}次 + 此帐户现已锁定,等待通过验证。 + 请确认您是帐户持有人:\${protocol}\${host}/api/user/verify/\${verificationtoken} + 验证帐户将重置失败的登录尝试。 + 操作失败。该尝试发生于这个远程地址 \${remote_address} + 如果这不是您本人的操作,请告知您的相关负责人` + } + }, + login_incorrect: { + en: { + subject: `A failed login attempt was made on \${host}`, + text: `An incorrect password was entered. + The failed attempt occured from this remote address \${remote_address} + This wasn't you? Please let your manager know.` + }, + fr: { + subject: `Une tentative de connexion a échouée sur \${host}`, + text: `Le mot de passe entré est incorrect. + Vous ne l’avez pas exécuté? Veuillez informer votre directeur.` + }, + pl: { + subject: `Nieudana próba logowania na \${host}`, + text: `Podano błędne hasło. + Nieudaną próbę logowania rozpoczęto z tego adresu \${remote_address} + To nie Ty? Zgłoś to osobie odpowiedzialnej.` + }, + ja: { + subject: `ログインに失敗しました \${host}にログインしようとしました`, + text: `間違ったパスワードが入力されました + このリモートアドレスから試されましたが失敗しました \${remote_address} + これがあなたではなかった場合、マネージャーに連絡をして下さい` + }, + ko: { + subject: `잘못된 로그인 시도입니다. \${host}`, + text: `잘못된 비밀번호입니다. + 기기의 고유주소로부터 잘못된 로그인 시도가 발생했습니다. \${remote_address} + 본인이 아니면 담당매니저에게 알려주십시오.` + }, + zh: { + subject: `尝试登录失败 \${host}`, + text: `密码输入错误 + 操作失败。该尝试发生于这个远程地址 \${remote_address} + 如果这不是您本人的操作,请告知您的相关负责人` + } + }, + admin_email: { + en: { + subject: `A new account has been verified on \${host}`, + text: `Please log into the admin panel \${protocol}\${host}/api/user/admin to approve \${email} + You can also approve the account by following this link: \${protocol}\${host}/api/user/admin?email=\${email}` + }, + de: { + subject: `A neues Benutzerkonto wurde erstellt fuer \${host}`, + text: `Please log into the admin panel \${protocol}\${host}/api/user/admin to approve \${email} + You can also approve the account by following this link: \${protocol}\${host}/api/user/admin?email=\${email}` + }, + fr: { + subject: `Un nouveau compte a été verifié sur \${host}`, + text: `Veuillez vous connecter à votre compte administrateur \${protocol}\${host}/api/user/admin pour approuver \${email} + Vous pouvez également l'approuver en suivant ce lien \${protocol}\${host}/api/user/admin?email=\${email}` + }, + pl: { + subject: `Nowe konto zostało zweryfikowane na \${host}`, + text: `Zaloguj się do panelu administratora \${protocol}\${host}/api/user/admin aby zatwierdzić \${email} + Możesz też zatwierdzić nowego konto za pomocą tego linku \${protocol}\${host}/api/user/admin?email=\${email}` + }, + ja: { + subject: `\${host}についてアカウントを検証されました`, + text: `\${email} を承認するには、管理パネル \${protocol}\${host}/api/user/admin にログインしてください + このリンクからもアカウントを承認することができます \${protocol}\${host}/api/user/admin?email=\${email}` + }, + ko: { + subject: `새로운 계정이 확인되었습니다. \${host}`, + text: `\${email} 을 승인하려면 관리자 패널 \${protocol}\${host}/api/user/admin 에 로그인하세요. + 다음의 링크로 또한 계정 승인을 할 수 있습니다. \${protocol}\${host}/api/user/admin?email=\${email}` + }, + zh: { + subject: `新帐户已通过验证 \${host}`, + text: `请登录管理控制台 \${protocol}\${host}/api/user/admin 批准 \${email} + You can also approve the account by following this link: \${protocol}\${host}/api/user/admin?email=\${email}` + } + } +} \ No newline at end of file diff --git a/mod/workspace/templates/msgs.js b/mod/workspace/templates/msgs.js new file mode 100644 index 0000000000..6c40c940da --- /dev/null +++ b/mod/workspace/templates/msgs.js @@ -0,0 +1,116 @@ +module.exports = { + login_required: { + en: `Login required.` + }, + admin_required: { + en: `Admin priviliges required.` + }, + token_not_found: { + en: `Token not found. The token has probably been resolved already.`, + fr: `Token n’a pas été trouvé. Il a probablement déjà été utilisé.`, + pl: `Token wygasł. Prawdopodobnie został już wykorzystany.`, + ja: `トークンが見つかりません。 トークンはおそらくすでに解決されています。`, + ko: `토근이 발견되지 않았습니다. 이미 해결된 것 같습니다.`, + zh: `未找到相关令牌, 该令牌可能已解析` + }, + no_locales: { + en: `No accessible locales for user account.`, + de: `Keine Locale zugreifbar fuer den User.` + }, + password_reset_verification: { + en: `Password will be reset after email verification.`, + fr: `Le mot de passe sera réinitialisé après la vérification par e-mail.`, + pl: `Hasło zostanie ustawione po weryfikacji konta przez wiadomości e-mail.`, + ja: `E-メール検証完了後にパスワードはリセットされます`, + ko: `이메일 확인 후 비밀번호가 재설정됩니다`, + zh: `电子邮件验证后,密码将被重置。` + }, + new_account_registered: { + en: `A new account has been registered and is awaiting email verification.`, + fr: `Un nouveau compte a été enregistré et il attend la vérification par e-mail.`, + pl: `Nowe konto zostało zarejestrowane i czeka na weryfikację przez wiadomość email.`, + ja: `新規アカウントの登録完了にてE-メール検証待ち`, + ko: `새로운 계정이 등록되었고 이메일 확인을 기다리고 있습니다.`, + zh: `已注册一个新帐户,正在等待电子邮件验证。` + }, + no_cookie_found: { + en: `No cookie relating to this application found on request` + }, + update_ok: { + en: `Update successful`, + fr: `Cette mise à jour a réussi.`, + pl: `Uaktualniono.`, + ja: `アップデートに成功しました`, + ko: `업데이트가 성공적으로 진행되었습니다.`, + zh: `更新成功` + }, + account_await_approval: { + en: `This account has been verified but requires administrator approval.`, + fr: `Le compte a été verifié mais il doit être approuvé par l'administrateur. `, + pl: `Konto zostało zweryfikowane i musi zostać zatwierdzone przez administratora.`, + ja: `本アカウント検証完了。アドミニストレーター承認が必要となります`, + ko: `이 계정은 확인되었으나 관리자의 승인이 필요합니다.`, + zh: `此帐户已通过验证,但需要管理员批准。` + }, + password_reset_ok: { + en: `Password has been reset.`, + fr: `Le mot de passe a été réinitialisé.`, + pl: `Ustawiono nowe hasło.`, + ja: `パスワードがリセットされました`, + ko: `비밀번호가 재설정되었습니다.`, + zh: `密码已重设` + }, + auth_failed: { + en: `Authentication failed.`, + de: `Anmeldung gescheitert.` + }, + user_locked: { + en: `User account has been locked due to failed login attempts.`, + de: `Benutzerkonto gesperrt.` + }, + user_blocked: { + en: `User blocked`, + fr: `Cet utilisateur est bloqué.`, + pl: `Konto zostało zablokowane.`, + ja: `ユーザーがブロックされました`, + ko: `사용자 봉쇄`, + zh: `用户被阻止` + }, + user_expired: { + en: `User approval has expired. Please re-register.`, + fr: `Les droits d'accès ont expiré. Veuillez vous réenregistrer.`, + pl: `Prawo dostępu wygasło. Zarejestruj się poownie.`, + ja: `ユーザーの承認は期限切れです。 再登録してください。`, + ko: `사용자 승인이 만료되었습니다. 다시 등록하십시오.`, + zh: `用戶批准已過期。 請重新註冊。` + }, + user_not_verified: { + en: `User not verified or approved`, + fr: `L’utilisateur n’a pas été vérifié ou approuvé.`, + pl: `Konto niezweryfikowane ani zatwierdzone.`, + ja: `ユーザーは確認または承認されていません`, + ko: `사용자 미확인 또는 미승인`, + zh: `用户未经验证或批准` + }, + admin_approved: { + en: `The account has been approved by you. An email has been sent to the account holder.`, + pl: `Konto zostało zatwierdzone. Wysłano wiadomość na zarejestrowany adres e-mail.`, + fr: `Vous avez approuvé ce compte. Un e-mail a été envoyé au propriétaire du compte.`, + ja: `アカウントはあなたによって承認されました。 メールをアカウント所有者に送信しました。`, + ko: `귀하에 의해서 계정이 승인되었습니다. 계정 사용자에게 이메일이 발송되었습니다.`, + zh: `此帐户已被您批准。电子邮件已发送给帐户持有人` + }, + failed_query: { + en: `Failed to query PostGIS table.` + }, + missing_password: { + en: `Missing password`, + fr: `Mot de passe manquant`, + pl: `Nie podano hasła.` + }, + missing_email: { + en: `Missing email`, + fr: `E-mail manquant`, + pl: `Nie podano adresu e-mail.` + } +} \ No newline at end of file diff --git a/mod/workspace/templates/views.js b/mod/workspace/templates/views.js new file mode 100644 index 0000000000..0bdf9de1f0 --- /dev/null +++ b/mod/workspace/templates/views.js @@ -0,0 +1,35 @@ +module.exports = { + default_view: { + en: 'file:/public/views/_default.html' + }, + user_admin_view: { + en: 'file:/public/views/_user.html' + }, + register_view: { + en: 'file:/public/views/register/_register_en.html', + de: 'file:/public/views/register/_register_de.html', + fr: 'file:/public/views/register/_register_fr.html', + ja: 'file:/public/views/register/_register_ja.html', + ko: 'file:/public/views/register/_register_ko.html', + pl: 'file:/public/views/register/_register_pl.html', + zh: 'file:/public/views/register/_register_zh.html' + }, + password_reset_view: { + en: 'file:/public/views/register/_register_en.html', + de: 'file:/public/views/register/_register_de.html', + fr: 'file:/public/views/register/_register_fr.html', + ja: 'file:/public/views/register/_register_ja.html', + ko: 'file:/public/views/register/_register_ko.html', + pl: 'file:/public/views/register/_register_pl.html', + zh: 'file:/public/views/register/_register_zh.html' + }, + login_view: { + en: 'file:/public/views/login/_login_en.html', + de: 'file:/public/views/login/_login_de.html', + fr: 'file:/public/views/login/_login_fr.html', + ja: 'file:/public/views/login/_login_ja.html', + ko: 'file:/public/views/login/_login_ko.html', + pl: 'file:/public/views/login/_login_pl.html', + zh: 'file:/public/views/login/_login_zh.html' + } +} \ No newline at end of file From 65e37a311db3b81610a03f9522ad8cfc30b57b61 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Tue, 24 Oct 2023 18:51:14 +0100 Subject: [PATCH 12/44] format getFrom method --- mod/provider/getFrom.js | 58 ++++++++++++++++++++++++++++++++++ mod/templates/_templates.js | 31 +----------------- mod/user/register.js | 9 +++++- mod/utils/languageTemplates.js | 13 ++++++-- mod/workspace/cache.js | 15 +-------- mod/workspace/getTemplate.js | 12 +------ mod/workspace/httpsAgent.js | 27 ---------------- 7 files changed, 79 insertions(+), 86 deletions(-) create mode 100644 mod/provider/getFrom.js delete mode 100644 mod/workspace/httpsAgent.js diff --git a/mod/provider/getFrom.js b/mod/provider/getFrom.js new file mode 100644 index 0000000000..1f3940a719 --- /dev/null +++ b/mod/provider/getFrom.js @@ -0,0 +1,58 @@ +const cloudfront = require('../provider/cloudfront') + +const file = require('../provider/file') + +const https = require('https') + +const httpsAgent = new https.Agent({ + keepAlive: true, + maxSockets: parseInt(process.env.CUSTOM_AGENT) || 1 +}) + +const mongodb = require('../provider/mongodb') + +module.exports = { + _https: async ref => { + try { + + const response = await fetch(ref, { + agent: process.env.CUSTOM_AGENT && httpsAgent + }) + + if (response.status >= 300) return new Error(`${response.status} ${ref}`) + + if (ref.match(/\.json$/i)) { + return await response.json() + } + + return await response.text() + + } catch (err) { + console.error(err) + return err + } + }, + https: async url => { + + try { + + const response = await fetch(url) + + logger(`${response.status} - ${url}`, 'fetch') + + if (url.match(/\.json$/i)) { + return await response.json() + } + + return await response.text() + + } catch (err) { + console.error(err) + return; + } + + }, + file: ref => file(ref.split(':')[1]), + cloudfront: ref => cloudfront(ref.split(':')[1]), + mongodb: ref => mongodb(ref.split(/:(.*)/s)[1]) +} \ No newline at end of file diff --git a/mod/templates/_templates.js b/mod/templates/_templates.js index 330b08fa5a..8fbc4e5ede 100644 --- a/mod/templates/_templates.js +++ b/mod/templates/_templates.js @@ -6,36 +6,7 @@ const msg_templates = require('./msgs') const merge = require('../utils/merge') -const cloudfront = require('../provider/cloudfront') - -const file = require('../provider/file') - -const logger = require('../utils/logger') - -const getFrom = { - https: async url => { - - try { - - const response = await fetch(url) - - logger(`${response.status} - ${url}`,'fetch') - - if (url.match(/\.json$/i)) { - return await response.json() - } - - return await response.text() - - } catch (err) { - console.error(err) - return; - } - - }, - file: ref => file(ref.split(':')[1]), - cloudfront: ref => cloudfront(ref.split(':')[1]), -} +const getFrom = require('../provider/getFrom') module.exports = async (key, language = 'en', params = {}) => { diff --git a/mod/user/register.js b/mod/user/register.js index cd6d1bc1ef..37edde3bab 100644 --- a/mod/user/register.js +++ b/mod/user/register.js @@ -8,6 +8,8 @@ const mailer = require('../utils/mailer') const templates = require('../templates/_templates') +const languageTemplates = require('../utils/languageTemplates') + module.exports = async (req, res) => { if (!acl) return res.status(500).send('ACL unavailable.') @@ -130,7 +132,12 @@ async function post(req, res) { })) // Return msg. No redirect for password reset. - return res.send(await templates('password_reset_verification', user.language)) + //return res.send(await templates('password_reset_verification', user.language)) + + return res.send(await languageTemplates(req, { + template: 'password_reset_verification', + language: user.language + })) } const language = Intl.Collator.supportedLocalesOf([req.body.language], { localeMatcher: 'lookup' })[0] || 'en'; diff --git a/mod/utils/languageTemplates.js b/mod/utils/languageTemplates.js index 0ea78c2119..96f27abe0b 100644 --- a/mod/utils/languageTemplates.js +++ b/mod/utils/languageTemplates.js @@ -1,11 +1,18 @@ const getTemplate = require('../workspace/getTemplate') +const workspaceCache = require('../workspace/cache') + module.exports = async (req, params) => { - let template = await getTemplate({ - src: req.params.workspace.templates[params.template]?.[params.language] - }) + const workspace = await workspaceCache() + + let template = workspace.templates[params.template]?.[params.language] + console.log(template) + + template = await getTemplate({ + src: workspace.templates[params.template]?.[params.language] + }) template = template.template.replace(/[{]{2}([A-Za-z][A-Za-z0-9]*)[}]{2}/g, matched => { diff --git a/mod/workspace/cache.js b/mod/workspace/cache.js index b620950a8c..0f11978ae3 100644 --- a/mod/workspace/cache.js +++ b/mod/workspace/cache.js @@ -1,17 +1,4 @@ -const file = require('../provider/file') - -const cloudfront = require('../provider/cloudfront') - -const mongodb = require('../provider/mongodb') - -const http = require('./httpsAgent') - -const getFrom = { - 'https': ref => http(ref), - 'file': ref => file(ref.split(/:(.*)/s)[1]), - 'cloudfront': ref => cloudfront(ref.split(/:(.*)/s)[1]), - 'mongodb': ref => mongodb(ref.split(/:(.*)/s)[1]) -} +const getFrom = require('../provider/getFrom') const merge = require('../utils/merge') diff --git a/mod/workspace/getTemplate.js b/mod/workspace/getTemplate.js index fff08e0c14..a061c9d48a 100644 --- a/mod/workspace/getTemplate.js +++ b/mod/workspace/getTemplate.js @@ -1,14 +1,4 @@ -const cloudfront = require('../provider/cloudfront'); - -const file = require('../provider/file'); - -const http = require('./httpsAgent'); - -const getFrom = { - https: (ref) => http(ref), - file: (ref) => file(ref.split(':')[1]), - cloudfront: (ref) => cloudfront(ref.split(':')[1]), -}; +const getFrom = require('../provider/getFrom') const merge = require('../utils/merge') diff --git a/mod/workspace/httpsAgent.js b/mod/workspace/httpsAgent.js deleted file mode 100644 index 6f5ae414e3..0000000000 --- a/mod/workspace/httpsAgent.js +++ /dev/null @@ -1,27 +0,0 @@ -const https = require('https') - -const httpsAgent = new https.Agent({ - keepAlive: true, - maxSockets: parseInt(process.env.CUSTOM_AGENT) || 1 -}) - -module.exports = async ref => { - try { - - const response = await fetch(ref, { - agent: process.env.CUSTOM_AGENT && httpsAgent - }) - - if (response.status >= 300) return new Error(`${response.status} ${ref}`) - - if (ref.match(/\.json$/i)) { - return await response.json() - } - - return await response.text() - - } catch(err) { - console.error(err) - return err - } -} \ No newline at end of file From af0e328bd8d11f71b7ca74c5763ece644cf11cd8 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 26 Oct 2023 14:04:13 +0100 Subject: [PATCH 13/44] remove templates folder; update view endpoint --- mod/provider/cloudfront.js | 1 + mod/provider/getFrom.js | 29 +-- mod/templates/_templates.js | 110 ----------- mod/templates/mails.js | 329 --------------------------------- mod/templates/msgs.js | 116 ------------ mod/templates/views.js | 35 ---- mod/user/admin.js | 12 +- mod/user/cookie.js | 6 +- mod/user/delete.js | 11 +- mod/user/login.js | 88 ++++----- mod/user/register.js | 56 +++--- mod/user/update.js | 17 +- mod/user/verify.js | 23 +-- mod/utils/languageTemplates.js | 68 ++++++- mod/view.js | 88 ++++----- 15 files changed, 205 insertions(+), 784 deletions(-) delete mode 100644 mod/templates/_templates.js delete mode 100644 mod/templates/mails.js delete mode 100644 mod/templates/msgs.js delete mode 100644 mod/templates/views.js diff --git a/mod/provider/cloudfront.js b/mod/provider/cloudfront.js index 9684c14749..35d8d46db3 100644 --- a/mod/provider/cloudfront.js +++ b/mod/provider/cloudfront.js @@ -50,6 +50,7 @@ module.exports = async ref => { } catch(err) { console.error(err) + logger(`err - ${url}`,'cloudfront') return; } diff --git a/mod/provider/getFrom.js b/mod/provider/getFrom.js index 1f3940a719..149b062bb2 100644 --- a/mod/provider/getFrom.js +++ b/mod/provider/getFrom.js @@ -1,37 +1,12 @@ +const logger = require('../utils/logger') + const cloudfront = require('../provider/cloudfront') const file = require('../provider/file') -const https = require('https') - -const httpsAgent = new https.Agent({ - keepAlive: true, - maxSockets: parseInt(process.env.CUSTOM_AGENT) || 1 -}) - const mongodb = require('../provider/mongodb') module.exports = { - _https: async ref => { - try { - - const response = await fetch(ref, { - agent: process.env.CUSTOM_AGENT && httpsAgent - }) - - if (response.status >= 300) return new Error(`${response.status} ${ref}`) - - if (ref.match(/\.json$/i)) { - return await response.json() - } - - return await response.text() - - } catch (err) { - console.error(err) - return err - } - }, https: async url => { try { diff --git a/mod/templates/_templates.js b/mod/templates/_templates.js deleted file mode 100644 index 8fbc4e5ede..0000000000 --- a/mod/templates/_templates.js +++ /dev/null @@ -1,110 +0,0 @@ -const view_templates = require('./views') - -const mail_templates = require('./mails') - -const msg_templates = require('./msgs') - -const merge = require('../utils/merge') - -const getFrom = require('../provider/getFrom') - -module.exports = async (key, language = 'en', params = {}) => { - - if (key === undefined) return; - - // key must be string. - if (typeof key !== 'string') { - - console.warn('Template keys must be of type string.') - return; - } - - // Prevent prototype polluting assignment. - if (/__proto__/.test(key)) return; - - const custom_templates = await new Promise((resolve)=>{ - - if (!process.env.CUSTOM_TEMPLATES) return resolve({}) - - resolve(getFrom[process.env.CUSTOM_TEMPLATES.split(':')[0]](process.env.CUSTOM_TEMPLATES)) - }) - - const templates = merge({}, - view_templates, - mail_templates, - msg_templates, - custom_templates) - - if (!Object.hasOwn(templates, key)) { - - console.warn(`Template key ${key} not found in templates`) - - return key; - } - - let template = templates[key]?.[language] || templates[key]?.en - - if (!template) { - - console.warn(`No language template for key ${found}`) - - return key; - } - - if (typeof template === 'string') { - - // Template string has a valid getFrom method. - if (getFrom[template.split(':')[0]] instanceof Function) { - - // Get template from method. - template = await getFrom[template.split(':')[0]](template) - } - } - - // Return template which is of type string. - if (typeof template === 'string') { - - return template.replace(/\{{2}(.*?)\}{2}/g, - - // Replace matched params in template string - matched => params[matched.replace(/\{{2}|\}{2}/g, '')] || '') - } - - // Template must be of type object at this stage. - if (typeof template !== 'object') { - - console.warn(`Template ${key} must be an object type.`) - return key - } - - // Prevent prototype polluting assignment. - Object.freeze(Object.getPrototypeOf(template)); - - for (key in template) { - - // Prevent prototype polluting assignment. - if (/__proto__/.test(key)) continue; - - // Template key / value is a string with a valid get method. - if (typeof template[key] === 'string' - && Object.hasOwn(template, key) - && Object.hasOwn(getFrom, template[key].split(':')[0])) { - - // Assign template key value from method. - template[key] = await getFrom[template[key].split(':')[0]](template[key]) - } - - // Template key value is still string after assignment - if (typeof template[key] === 'string') { - - // Look for template params to be substituted. - template[key] = template[key].replace(/\$\{{1}(.*?)\}{1}/g, - - // Replace matched params in string values - matched => params[matched.replace(/\$\{{1}|\}{1}/g, '')] || '') - } - - } - - return template -} \ No newline at end of file diff --git a/mod/templates/mails.js b/mod/templates/mails.js deleted file mode 100644 index 4da51f4e51..0000000000 --- a/mod/templates/mails.js +++ /dev/null @@ -1,329 +0,0 @@ -module.exports = { - verify_password_reset: { - en: { - subject: `Please verify your password reset for \${host}`, - text: `A new password has been set for this account. - Please verify that you are the account holder: \${link} - The reset occured from this remote address \${remote_address} - This wasn't you? Please let your manager know.` - }, - fr: { - subject: `Vérifiez votre mot de passe réinitialisé sur \${host}.`, - text: `Le nouveau mot de passe a été défini pour ce compte. - Vérifiez que vous disposez des droits d'accès du compte \${link} - La réinitialisation a été exécutée par \${remote_address} - Vous ne l’avez pas demandé? Veuillez informer votre directeur.` - }, - pl: { - subject: `Zweryfikuj nowe hasło \${host}.`, - text: `Dla tego konta ustawiono nowe hasło. - Potwierdź swoje prawa dostępu do \${link} - Proces zmiany hasła rozpoczęto z tego adresu \${remote_address} - To nie Ty? Zgłoś to osobie odpowiedzialnej.` - }, - ja: { - subject: `リセットするパスワードを検証してください \${host}`, - text: `このアカウントに新規パスワードが設定されました. - アカウントホールダーであることを検証してください \${link} - このリモートアドレスによりリセットがされました \${remote_address} - あなたではなかった場合、マネージャーに連絡をして下さい` - }, - ko: { - subject: `비밀번호 재설정을 확인해주십시오. \${host}`, - text: `이 계정의 새로운 비밀번호가 설정되었습니다. - 계정 소유자임을 확인해주십시오. \${link} - 기기의 고유주소로부터 재설정이 되었습니다. \${remote_address} - 본인이 아니면 담당매니저에게 알려주십시오.` - }, - zh: { - subject: `请验证您的密码重置 \${host}`, - text: `为此帐户设置了新密码 - 请确认您是帐户持有人 \${link} - 账户重置发生于远程地址 \${remote_address} - 如果这不是您本人的操作,请告知您的相关负责人` - } - }, - verify_account: { - en: { - subject: `Please verify your account on \${host}`, - text: `A new account for this email address has been registered with \${host} - Please verify that you are the account holder: \${link} - A site administrator must approve the account before you are able to login. - You will be notified via email once an administrator has approved your account. - The account was registered from this remote address \${remote_address}\n - This wasn't you? Do NOT verify the account and let your manager know.` - }, - de: { - subject: `Bitte verifizieren Sie ihr Benutzerkonto für \${host}`, - text: `A new account for this email address has been registered with \${host} - Please verify that you are the account holder: \${link} - A site administrator must approve the account before you are able to login. - You will be notified via email once an administrator has approved your account. - The account was registered from this remote address \${remote_address}\n - This wasn't you? Do NOT verify the account and let your manager know.` - }, - fr: { - subject: `Vérifiez votre compte sur \${host}`, - text: `Un nouveau compte a été enregistré sur \${host}. - Vérifiez que vous disposez des droits d'accès du compte \${link} - L'adminstrateur doit approuver votre compte avant de vous connecter. - Vous recevrez un e-mail lorsque votre compte sera approuvé. - L'enregistrement a été exécuté par \${remote_address}\n - Vous ne l’avez pas demandé? Veuillez informer votre directeur.` - }, - pl: { - subject: `Zweryfikuj konto \${host}`, - text: `Nowe konto dla tego adresu e-mail zostało zarejestrowane z \${host} - Potwierdź swoje prawa dostępu do \${link} - Administrator musi zatwierdzić konto przed logowaniem. - Otrzymasz powiadomienie na swój adres e-mail. - Proces rejestracji konta rozpoczęto z tego adresu \${remote_address}\n - To nie Ty? Zignoruj ten link i zgłoś to osobie odpowiedzialnej.` - }, - ja: { - subject: `\${host} についてアカウントを検証して下さい`, - text: `このE-メールアドレスの新規アカウントは\${host}に登録されています - アカウントホールダーであることを検証してください \${link} - サイトアドミニストレーター承認後にログインが可能となります - サイトアドミニストレーターによるアカウント承認後通知メールが送信されます - アカウントはこのリモートアドレスより登録されました。 \${remote_address}\n - これがあなたでなかった場合はアカウントの検証は行わずマネージャーに連絡をしてください` - }, - ko: { - subject: `계정 확인바랍니다 \${host}`, - text: `이 이메일주소의 새로운 계정이 등록되었습니다. \${host} - 계정 소유자임을 확인해주십시오. \${protocol}\${link} - 로그인전에 입지 관리자가 계정을 승인해야만 합니다. - 관리자가 계정 승인을 하면 공지 이메일을 받게됩니다. - 기기의 고유주소로부터 계정 등록이 되었습니다. \${remote_address}\n - 본인이 아니면 계정 확인을 하지말고 관리자에게 알려주십시오.` - }, - zh: { - subject: `请验证您的帐户 \${host}`, - text: `已为此电子邮件在\${host}上注册了新账户 - 请确认您是帐户持有人 \${protocol}\${link} - 等待网站管理员批准该帐户,然后才能登录。 - 一旦管理员批准了您的帐户,就会通过电子邮件通知您。 - 该帐户是从该远程地址注册的 \${remote_address}\n - 如果这不是您本人的操作,请不要进行验证;同时请告知您的相关负责人` - } - }, - approved_account: { - en: { - subject: `This account has been approved on \${host}.`, - text: `You are now able to log on to \${protocol}\${host}` - }, - fr: { - text: `Ce compte a été approuvé sur \${host}.`, - subject: `Maintenant vous pouvez vous connecter à \${protocol}\${host}` - }, - pl: { - text: `Konto na \${host} zostało zatwierdzone.`, - subject: `Teraz możesz się zalogować na \${protocol}\${host}` - }, - ja: { - subject: `アカウントは承認されました \${host}`, - text: `これで、\${protocol}\${host}にログオンできます。` - }, - ko: { - subject: `계정이 승인되었습니다. \${host}`, - text: `로그인이 가능합니다. \${protocol}\${host}` - }, - zh: { - subject: `该帐户已得到批准 \${host}`, - text: `您现在已可登录 \${protocol}\${host}` - } - }, - deleted_account: { - en: { - subject: `This \${host} account has been deleted.`, - text: `You will no longer be able to log in to \${protocol}\${host}` - }, - de: { - subject: `Diese Benutzerkonto für \${host} wurde entfernt.`, - text: `Einloggen ist nicht länger möglich \${protocol}\${host}` - }, - fr: { - subject: `Ce compte sur \${host} a été supprimé.`, - text: `Vous ne pouvez plus vous connecter à \${protocol}\${host}` - }, - pl: { - subject: `Konto na \${host} zostało usunięte.`, - text: `Nie możesz się już logować na \${protocol}\${host}` - }, - ja: { - subject: `\${host}のこのアカウントは削除されました`, - text: `\${protocol}\${host}にログインできなくなります削除されました` - }, - ko: { - subject: `계정이 삭제되었습니다. \${host}`, - text: `더 이상 로그인이 불가합니다. \${protocol}\${host}` - }, - zh: { - subject: `此帐户已被删除 \${host}`, - text: `您将不再能登录 \${protocol}\${host}` - } - }, - failed_login: { - en: { - subject: `A failed login attempt was made on \${host}`, - text: `The failed attempt occured from this remote address \${remote_address} - This wasn't you? Please let your manager know.` - }, - fr: { - subject: `Une tentative de connexion a échouée sur \${host}`, - text: `La tentative de connexion échouée a été exécuté par \${remote_address}\n - Vous ne l’avez pas exécuté? Veuillez informer votre directeur.` - }, - pl: { - subject: `Nieudana próba logowania na \${host}`, - text: `Nieudaną próbę logowania rozpoczęto z tego adresu \${remote_address} - To nie Ty? Zgłoś to osobie odpowiedzialnej.` - }, - ja: { - subject: `ログインに失敗しました \${host}にログインしようとしました`, - text: `このリモートアドレスから試されましたが失敗しました \${remote_address} - これがあなたではなかった場合、マネージャーに連絡をして下さい` - }, - ko: { - subject: `잘못된 로그인 시도입니다. \${host}`, - text: `기기의 고유주소로부터 잘못된 로그인 시도가 발생했습니다. \${remote_address} - 본인이 아니면 담당매니저에게 알려주십시오.` - }, - zh: { - subject: `尝试登录失败 \${host}`, - text: `操作失败。该尝试发生于这个远程地址 \${remote_address} - 如果这不是您本人的操作,请告知您的相关负责人` - } - }, - locked_account: { - en: { - subject: `Too many failed login attempts occured on \${host}`, - text: `\${failed_attempts} failed login attempts have been recorded on this account. - This account has now been locked until verified. - Please verify that you are the account holder: \${protocol}\${host}/api/user/verify/\${verificationtoken} - Verifying the account will reset the failed login attempts. - The failed attempt occured from this remote address \${remote_address} - This wasn't you? Please let your manager know.` - }, - fr: { - subject: `Trop d'échecs de tentatives de connexions ont été exécutés sur \${host}`, - text: `\${failed_attempts} échecs de tentatives de connexions ont été exécutés par ce compte. - Il a été verrouillé jusqu’à ce qu’il soit vérifié de nouveau. - Vérifiez que vous disposez des droits d'accès du compte \${protocol}\${host}/api/user/verify/\${verificationtoken} - La vérification du compte réinitialisera des droits d'accès. - La tentative de connexion échouée a été exécuté par \${remote_address}\n - Vous ne l’avez pas exécuté? Veuillez informer votre directeur.` - }, - pl: { - subject: `Zbyt wiele nieudanych prób logowania na \${host}`, - text: `Na tym koncie zarejestrowano \${failed_attempts} \${failed_attempts === 1 ? 'nieudaną próbę' : 'nieudane próby'} - logowania. To konto zostało zablokowane do czasu powtórnej weryfikacji. - Potwierdź swoje prawa dostępu do \${protocol}\${host}/api/user/verify/\${verificationtoken} - Powtórna weryfikacja odświeży dozwoloną liczbę nieudanych prób. - Nieudaną próbę logowania rozpoczęto z tego adresu \${remote_address} - To nie Ty? Zgłoś to osobie odpowiedzialnej.` - }, - ja: { - subject: `\${host}ログインに多数失敗しました`, - text: `このアカウントによるログインが\${failed_attempts}回失敗しました - このアカウントは検証されるまでロックされます - アカウントホールダーであることを検証してください \${protocol}\${host}/api/user/verify/\${verificationtoken} - アカウント検証によりログイン失敗がリセットされます - このリモートアドレスから試されましたが失敗しました \${remote_address} - これがあなたではなかった場合、マネージャーに連絡をして下さい` - }, - ko: { - subject: `다수의 잘못된 로그인 시도가 발생했습니다. \${host}`, - text: `이 계정에 \${failed_attempts} 번의 잘못된 로그인 시도가 발생했습니다. - 이 계정은 확인될때까지 봉쇄되었습니다. - 계정 소유자임을 확인해주십시오. \${protocol}\${host}/api/user/verify/\${verificationtoken} - 계정 확인은 잘못된 로그인 시도를 재설정합니다. - 기기의 고유주소로부터 잘못된 로그인 시도가 발생했습니다. \${remote_address} - 본인이 아니면 담당매니저에게 알려주십시오.` - }, - zh: { - subject: `发生太多失败的登录尝试 \${host}`, - text: `此帐户登录尝试失败已发生\${failed_attempts}次 - 此帐户现已锁定,等待通过验证。 - 请确认您是帐户持有人:\${protocol}\${host}/api/user/verify/\${verificationtoken} - 验证帐户将重置失败的登录尝试。 - 操作失败。该尝试发生于这个远程地址 \${remote_address} - 如果这不是您本人的操作,请告知您的相关负责人` - } - }, - login_incorrect: { - en: { - subject: `A failed login attempt was made on \${host}`, - text: `An incorrect password was entered. - The failed attempt occured from this remote address \${remote_address} - This wasn't you? Please let your manager know.` - }, - fr: { - subject: `Une tentative de connexion a échouée sur \${host}`, - text: `Le mot de passe entré est incorrect. - Vous ne l’avez pas exécuté? Veuillez informer votre directeur.` - }, - pl: { - subject: `Nieudana próba logowania na \${host}`, - text: `Podano błędne hasło. - Nieudaną próbę logowania rozpoczęto z tego adresu \${remote_address} - To nie Ty? Zgłoś to osobie odpowiedzialnej.` - }, - ja: { - subject: `ログインに失敗しました \${host}にログインしようとしました`, - text: `間違ったパスワードが入力されました - このリモートアドレスから試されましたが失敗しました \${remote_address} - これがあなたではなかった場合、マネージャーに連絡をして下さい` - }, - ko: { - subject: `잘못된 로그인 시도입니다. \${host}`, - text: `잘못된 비밀번호입니다. - 기기의 고유주소로부터 잘못된 로그인 시도가 발생했습니다. \${remote_address} - 본인이 아니면 담당매니저에게 알려주십시오.` - }, - zh: { - subject: `尝试登录失败 \${host}`, - text: `密码输入错误 - 操作失败。该尝试发生于这个远程地址 \${remote_address} - 如果这不是您本人的操作,请告知您的相关负责人` - } - }, - admin_email: { - en: { - subject: `A new account has been verified on \${host}`, - text: `Please log into the admin panel \${protocol}\${host}/api/user/admin to approve \${email} - You can also approve the account by following this link: \${protocol}\${host}/api/user/admin?email=\${email}` - }, - de: { - subject: `A neues Benutzerkonto wurde erstellt fuer \${host}`, - text: `Please log into the admin panel \${protocol}\${host}/api/user/admin to approve \${email} - You can also approve the account by following this link: \${protocol}\${host}/api/user/admin?email=\${email}` - }, - fr: { - subject: `Un nouveau compte a été verifié sur \${host}`, - text: `Veuillez vous connecter à votre compte administrateur \${protocol}\${host}/api/user/admin pour approuver \${email} - Vous pouvez également l'approuver en suivant ce lien \${protocol}\${host}/api/user/admin?email=\${email}` - }, - pl: { - subject: `Nowe konto zostało zweryfikowane na \${host}`, - text: `Zaloguj się do panelu administratora \${protocol}\${host}/api/user/admin aby zatwierdzić \${email} - Możesz też zatwierdzić nowego konto za pomocą tego linku \${protocol}\${host}/api/user/admin?email=\${email}` - }, - ja: { - subject: `\${host}についてアカウントを検証されました`, - text: `\${email} を承認するには、管理パネル \${protocol}\${host}/api/user/admin にログインしてください - このリンクからもアカウントを承認することができます \${protocol}\${host}/api/user/admin?email=\${email}` - }, - ko: { - subject: `새로운 계정이 확인되었습니다. \${host}`, - text: `\${email} 을 승인하려면 관리자 패널 \${protocol}\${host}/api/user/admin 에 로그인하세요. - 다음의 링크로 또한 계정 승인을 할 수 있습니다. \${protocol}\${host}/api/user/admin?email=\${email}` - }, - zh: { - subject: `新帐户已通过验证 \${host}`, - text: `请登录管理控制台 \${protocol}\${host}/api/user/admin 批准 \${email} - You can also approve the account by following this link: \${protocol}\${host}/api/user/admin?email=\${email}` - } - } -} \ No newline at end of file diff --git a/mod/templates/msgs.js b/mod/templates/msgs.js deleted file mode 100644 index 6c40c940da..0000000000 --- a/mod/templates/msgs.js +++ /dev/null @@ -1,116 +0,0 @@ -module.exports = { - login_required: { - en: `Login required.` - }, - admin_required: { - en: `Admin priviliges required.` - }, - token_not_found: { - en: `Token not found. The token has probably been resolved already.`, - fr: `Token n’a pas été trouvé. Il a probablement déjà été utilisé.`, - pl: `Token wygasł. Prawdopodobnie został już wykorzystany.`, - ja: `トークンが見つかりません。 トークンはおそらくすでに解決されています。`, - ko: `토근이 발견되지 않았습니다. 이미 해결된 것 같습니다.`, - zh: `未找到相关令牌, 该令牌可能已解析` - }, - no_locales: { - en: `No accessible locales for user account.`, - de: `Keine Locale zugreifbar fuer den User.` - }, - password_reset_verification: { - en: `Password will be reset after email verification.`, - fr: `Le mot de passe sera réinitialisé après la vérification par e-mail.`, - pl: `Hasło zostanie ustawione po weryfikacji konta przez wiadomości e-mail.`, - ja: `E-メール検証完了後にパスワードはリセットされます`, - ko: `이메일 확인 후 비밀번호가 재설정됩니다`, - zh: `电子邮件验证后,密码将被重置。` - }, - new_account_registered: { - en: `A new account has been registered and is awaiting email verification.`, - fr: `Un nouveau compte a été enregistré et il attend la vérification par e-mail.`, - pl: `Nowe konto zostało zarejestrowane i czeka na weryfikację przez wiadomość email.`, - ja: `新規アカウントの登録完了にてE-メール検証待ち`, - ko: `새로운 계정이 등록되었고 이메일 확인을 기다리고 있습니다.`, - zh: `已注册一个新帐户,正在等待电子邮件验证。` - }, - no_cookie_found: { - en: `No cookie relating to this application found on request` - }, - update_ok: { - en: `Update successful`, - fr: `Cette mise à jour a réussi.`, - pl: `Uaktualniono.`, - ja: `アップデートに成功しました`, - ko: `업데이트가 성공적으로 진행되었습니다.`, - zh: `更新成功` - }, - account_await_approval: { - en: `This account has been verified but requires administrator approval.`, - fr: `Le compte a été verifié mais il doit être approuvé par l'administrateur. `, - pl: `Konto zostało zweryfikowane i musi zostać zatwierdzone przez administratora.`, - ja: `本アカウント検証完了。アドミニストレーター承認が必要となります`, - ko: `이 계정은 확인되었으나 관리자의 승인이 필요합니다.`, - zh: `此帐户已通过验证,但需要管理员批准。` - }, - password_reset_ok: { - en: `Password has been reset.`, - fr: `Le mot de passe a été réinitialisé.`, - pl: `Ustawiono nowe hasło.`, - ja: `パスワードがリセットされました`, - ko: `비밀번호가 재설정되었습니다.`, - zh: `密码已重设` - }, - auth_failed: { - en: `Authentication failed.`, - de: `Anmeldung gescheitert.` - }, - user_locked: { - en: `User account has been locked due to failed login attempts.`, - de: `Benutzerkonto gesperrt.` - }, - user_blocked: { - en: `User blocked`, - fr: `Cet utilisateur est bloqué.`, - pl: `Konto zostało zablokowane.`, - ja: `ユーザーがブロックされました`, - ko: `사용자 봉쇄`, - zh: `用户被阻止` - }, - user_expired: { - en: `User approval has expired. Please re-register.`, - fr: `Les droits d'accès ont expiré. Veuillez vous réenregistrer.`, - pl: `Prawo dostępu wygasło. Zarejestruj się poownie.`, - ja: `ユーザーの承認は期限切れです。 再登録してください。`, - ko: `사용자 승인이 만료되었습니다. 다시 등록하십시오.`, - zh: `用戶批准已過期。 請重新註冊。` - }, - user_not_verified: { - en: `User not verified or approved`, - fr: `L’utilisateur n’a pas été vérifié ou approuvé.`, - pl: `Konto niezweryfikowane ani zatwierdzone.`, - ja: `ユーザーは確認または承認されていません`, - ko: `사용자 미확인 또는 미승인`, - zh: `用户未经验证或批准` - }, - admin_approved: { - en: `The account has been approved by you. An email has been sent to the account holder.`, - pl: `Konto zostało zatwierdzone. Wysłano wiadomość na zarejestrowany adres e-mail.`, - fr: `Vous avez approuvé ce compte. Un e-mail a été envoyé au propriétaire du compte.`, - ja: `アカウントはあなたによって承認されました。 メールをアカウント所有者に送信しました。`, - ko: `귀하에 의해서 계정이 승인되었습니다. 계정 사용자에게 이메일이 발송되었습니다.`, - zh: `此帐户已被您批准。电子邮件已发送给帐户持有人` - }, - failed_query: { - en: `Failed to query PostGIS table.` - }, - missing_password: { - en: `Missing password`, - fr: `Mot de passe manquant`, - pl: `Nie podano hasła.` - }, - missing_email: { - en: `Missing email`, - fr: `E-mail manquant`, - pl: `Nie podano adresu e-mail.` - } -} \ No newline at end of file diff --git a/mod/templates/views.js b/mod/templates/views.js deleted file mode 100644 index 0bdf9de1f0..0000000000 --- a/mod/templates/views.js +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = { - default_view: { - en: 'file:/public/views/_default.html' - }, - user_admin_view: { - en: 'file:/public/views/_user.html' - }, - register_view: { - en: 'file:/public/views/register/_register_en.html', - de: 'file:/public/views/register/_register_de.html', - fr: 'file:/public/views/register/_register_fr.html', - ja: 'file:/public/views/register/_register_ja.html', - ko: 'file:/public/views/register/_register_ko.html', - pl: 'file:/public/views/register/_register_pl.html', - zh: 'file:/public/views/register/_register_zh.html' - }, - password_reset_view: { - en: 'file:/public/views/register/_register_en.html', - de: 'file:/public/views/register/_register_de.html', - fr: 'file:/public/views/register/_register_fr.html', - ja: 'file:/public/views/register/_register_ja.html', - ko: 'file:/public/views/register/_register_ko.html', - pl: 'file:/public/views/register/_register_pl.html', - zh: 'file:/public/views/register/_register_zh.html' - }, - login_view: { - en: 'file:/public/views/login/_login_en.html', - de: 'file:/public/views/login/_login_de.html', - fr: 'file:/public/views/login/_login_fr.html', - ja: 'file:/public/views/login/_login_ja.html', - ko: 'file:/public/views/login/_login_ko.html', - pl: 'file:/public/views/login/_login_pl.html', - zh: 'file:/public/views/login/_login_zh.html' - } -} \ No newline at end of file diff --git a/mod/user/admin.js b/mod/user/admin.js index 98f8c15433..4f7d0dbb73 100644 --- a/mod/user/admin.js +++ b/mod/user/admin.js @@ -1,12 +1,10 @@ -const templates = require('../templates/_templates') +const view = require('../view') module.exports = async (req, res) => { - // Get admin view template. - const adminView = await templates('user_admin_view', 'en', { - dir: process.env.DIR, - user: req.params.user.email - }) + req.params.template = 'user_admin_view' + req.params.language = req.params.user.language + req.params.user = req.params.user.email - res.send(adminView) + view(req, res) } \ No newline at end of file diff --git a/mod/user/cookie.js b/mod/user/cookie.js index 0c4b1f806b..97713584df 100644 --- a/mod/user/cookie.js +++ b/mod/user/cookie.js @@ -2,7 +2,7 @@ const login = require('./login') const jwt = require('jsonwebtoken') -const templates = require('../templates/_templates') +const languageTemplates = require('../utils/languageTemplates') module.exports = async (req, res) => { @@ -18,9 +18,9 @@ module.exports = async (req, res) => { if (!cookie) { // Get login view template. - const loginView = await templates('no_cookie_found', req.params.language) + const no_cookie_found = await languageTemplates('no_cookie_found', req.params.language) - return login(req, res, loginView) + return login(req, res, no_cookie_found) } jwt.verify( diff --git a/mod/user/delete.js b/mod/user/delete.js index 1acb37bbeb..0b4cd7ffcb 100644 --- a/mod/user/delete.js +++ b/mod/user/delete.js @@ -2,7 +2,7 @@ const acl = require('./acl')() const mailer = require('../utils/mailer') -const templates = require('../templates/_templates') +const languageTemplates = require('../utils/languageTemplates') module.exports = async (req, res) => { @@ -23,14 +23,13 @@ module.exports = async (req, res) => { const host = `${req.headers.host.includes('localhost') && req.headers.host || process.env.ALIAS || req.headers.host}${process.env.DIR}` // Sent email to inform user that their account has been deleted. - const mail_template = await templates('deleted_account', user.language, { - host, - protocol - }) + const mail_template = await languageTemplates('deleted_account', user.language) // Assign user email to mail_template. Object.assign(mail_template, { - to: user.email + to: user.email, + host, + protocol }) await mailer(mail_template) diff --git a/mod/user/login.js b/mod/user/login.js index 5cdbad6155..d97389d1f6 100644 --- a/mod/user/login.js +++ b/mod/user/login.js @@ -8,7 +8,9 @@ const acl = require('./acl')() const mailer = require('../utils/mailer') -const templates = require('../templates/_templates') +const languageTemplates = require('../utils/languageTemplates') + +const view = require('../view') const { nanoid } = require('nanoid') @@ -22,7 +24,13 @@ module.exports = async (req, res, _message) => { const redirect = req.cookies && req.cookies[`${process.env.TITLE}_redirect`] - if (user instanceof Error && redirect) return view(req, res, user.message) + if (user instanceof Error && redirect) { + + req.params.msg = user.message + + loginView(req, res) + return + } if (user instanceof Error) return res.status(401).send(user.message) @@ -51,7 +59,7 @@ module.exports = async (req, res, _message) => { } // Get message from templates. - const message = await templates(req.params.msg || _message, req.params.language) + const message = await languageTemplates(req.params.msg || _message, req.params.language) if (!message && req.params.user) { @@ -60,26 +68,25 @@ module.exports = async (req, res, _message) => { return; } - view(req, res, message) -} - -async function view(req, res, message) { + req.params.msg = message || ' ' - // The redirect for a successful login. - const redirect = req.url && decodeURIComponent(req.url).replace(/login\=true/, '') + loginView(req, res) +} - let template = await templates('login_view', req.params.language, { - dir: process.env.DIR, - msg: message || ' ' - }) +async function loginView(req, res) { // Clear user token cookie. res.setHeader('Set-Cookie', `${process.env.TITLE}=null;HttpOnly;Max-Age=0;Path=${process.env.DIR || '/'}`) + // The redirect for a successful login. + const redirect = req.url && decodeURIComponent(req.url).replace(/login\=true/, '') + // Set cookie with redirect value. res.setHeader('Set-Cookie', `${process.env.TITLE}_redirect=${redirect};HttpOnly;Max-Age=60000;Path=${process.env.DIR || '/'}`) - res.send(template) + req.params.template = 'login_view' + + view(req, res) } async function post(req, res) { @@ -88,9 +95,9 @@ async function post(req, res) { && /^[A-Za-z0-9.,_-\s]*$/.test(req.headers['x-forwarded-for']) ? req.headers['x-forwarded-for'] : 'invalid' || 'unknown'; - if(!req.body.email) return new Error(await templates('missing_email', req.params.language)) + if(!req.body.email) return new Error(await languageTemplates('missing_email', req.params.language)) - if(!req.body.password) return new Error(await templates('missing_password', req.params.language)) + if(!req.body.password) return new Error(await languageTemplates('missing_password', req.params.language)) const date = new Date() @@ -106,15 +113,15 @@ async function post(req, res) { RETURNING email, roles, language, blocked, approved, approved_by, verified, admin, password ${process.env.APPROVAL_EXPIRY ? ', expires_on;' : ';'}`, [req.body.email]) - if (rows instanceof Error) return new Error(await templates('failed_query', req.params.language)) + if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) // Get user record from first row. const user = rows[0] - if (!user) return new Error(await templates('auth_failed', req.params.language)) + if (!user) return new Error(await languageTemplates('auth_failed', req.params.language)) // Blocked user cannot login. - if (user.blocked) return new Error(await templates('user_blocked', user.language || req.params.language)) + if (user.blocked) return new Error(await languageTemplates('user_blocked', user.language || req.params.language)) // Non admin accounts may expire. if (!user.admin && process.env.APPROVAL_EXPIRY) { @@ -132,10 +139,10 @@ async function post(req, res) { WHERE lower(email) = lower($1);`, [req.body.email]) - if (rows instanceof Error) return new Error(await templates('failed_query', req.params.language)) + if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) } - return new Error(await templates('user_expired', user.language)) + return new Error(await languageTemplates('user_expired', user.language)) } @@ -144,17 +151,16 @@ async function post(req, res) { // Accounts must be verified and approved for login if (!user.verified || !user.approved) { - var mail_template = await templates('failed_login', user.language, { + var mail_template = await languageTemplates('failed_login', user.language) + + await mailer(Object.assign(mail_template, { + to: user.email, host: host, protocol: protocol, remote_address - }) - - await mailer(Object.assign(mail_template, { - to: user.email })) - return new Error(await templates('user_not_verified', user.language)) + return new Error(await languageTemplates('user_not_verified', user.language)) } // Check password from post body against encrypted password from ACL. @@ -175,7 +181,7 @@ async function post(req, res) { WHERE lower(email) = lower($1)`, [req.body.email]) - if (rows instanceof Error) return new Error(await templates('failed_query', req.params.language)) + if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) } @@ -195,7 +201,7 @@ async function post(req, res) { WHERE lower(email) = lower($1) RETURNING failedattempts;`, [req.body.email]) - if (rows instanceof Error) return new Error(await templates('failed_query', req.params.language)) + if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) // Check whether failed login attempts exceeds limit. if (rows[0].failedattempts >= parseInt(process.env.FAILED_ATTEMPTS || 3)) { @@ -211,32 +217,30 @@ async function post(req, res) { verificationtoken = '${verificationtoken}' WHERE lower(email) = lower($1);`, [req.body.email]) - if (rows instanceof Error) return new Error(await templates('failed_query', req.params.language)) + if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) - var mail_template = await templates('locked_account', user.language, { + var mail_template = await languageTemplates('locked_account', user.language) + + await mailer(Object.assign(mail_template, { + to: user.email, host: host, failed_attempts: parseInt(process.env.FAILED_ATTEMPTS) || 3, protocol: protocol, verificationtoken: verificationtoken, remote_address - }) - - await mailer(Object.assign(mail_template, { - to: user.email })) - return new Error(await templates('user_locked', user.language)) + return new Error(await languageTemplates('user_locked', user.language)) } // Login has failed but account is not locked (yet). - var mail_template = await templates('login_incorrect', user.language, { - host: host, - remote_address - }) + var mail_template = await languageTemplates('login_incorrect', user.language) await mailer(Object.assign(mail_template, { - to: user.email + to: user.email, + host: host, + remote_address })) - return new Error(await templates('auth_failed', req.params.language)) + return new Error(await languageTemplates('auth_failed', req.params.language)) } \ No newline at end of file diff --git a/mod/user/register.js b/mod/user/register.js index 37edde3bab..ee3cbe57ec 100644 --- a/mod/user/register.js +++ b/mod/user/register.js @@ -6,10 +6,10 @@ const acl = require('./acl')() const mailer = require('../utils/mailer') -const templates = require('../templates/_templates') - const languageTemplates = require('../utils/languageTemplates') +const view = require('../view') + module.exports = async (req, res) => { if (!acl) return res.status(500).send('ACL unavailable.') @@ -17,21 +17,13 @@ module.exports = async (req, res) => { // Post request to register new user. if (req.body && req.body.register) return post(req, res) - // Get request for registration form view. - view(req, res) -} - -async function view(req, res) { - - // Get password reset or account registration view from templates. - const view = await templates(req.params.reset && 'password_reset_view' || 'register_view', req.params.language, { - dir: process.env.DIR - }) + req.params.template = req.params.reset? 'password_reset_view': 'register_view'; // The login view will set the cookie to null. res.setHeader('Set-Cookie', `${process.env.TITLE}=null;HttpOnly;Max-Age=0;Path=${process.env.DIR || '/'}`) - res.send(view) + // Get request for registration form view. + view(req, res) } async function post(req, res) { @@ -78,7 +70,9 @@ async function post(req, res) { WHERE lower(email) = lower($1);`, [req.body.email]) - if (rows instanceof Error) return res.status(500).send(await templates('failed_query', req.params.language)) + const failed_query = await languageTemplates('failed_query', req.params.language) + + if (rows instanceof Error) return res.status(500).send(failed_query) const user = rows[0] @@ -105,7 +99,7 @@ async function post(req, res) { if (user) { // Blocked user may not reset their password. - if (user.blocked) return res.status(500).send(await templates('user_blocked', user.language || req.params.language)) + if (user.blocked) return res.status(500).send(await languageTemplates('user_blocked', user.language || req.params.language)) // Set new password and verification token. // New passwords will only apply after account verification. @@ -118,26 +112,21 @@ async function post(req, res) { WHERE lower(email) = lower($1);`, [req.body.email]) - if (rows instanceof Error) return res.status(500).send(await templates('failed_query', req.params.language)) + if (rows instanceof Error) return res.status(500).send(await languageTemplates('failed_query', req.params.language)) // Sent mail with verification token to the account email address. - var mail_template = await templates('verify_password_reset', user.language, { + var mail_template = await languageTemplates('verify_password_reset', user.language) + + await mailer(Object.assign(mail_template, { + to: user.email, host: host, link: `${protocol}${host}/api/user/verify/${verificationtoken}`, remote_address - }) - - await mailer(Object.assign(mail_template, { - to: user.email })) - // Return msg. No redirect for password reset. - //return res.send(await templates('password_reset_verification', user.language)) + const password_reset_verification = await languageTemplates('password_reset_verification', user.language) - return res.send(await languageTemplates(req, { - template: 'password_reset_verification', - language: user.language - })) + return res.send(password_reset_verification) } const language = Intl.Collator.supportedLocalesOf([req.body.language], { localeMatcher: 'lookup' })[0] || 'en'; @@ -160,19 +149,18 @@ async function post(req, res) { '${verificationtoken}' AS verificationtoken, array['${date}@${req.ips && req.ips.pop() || req.ip}'] AS access_log;`) - if (rows instanceof Error) return res.status(500).send(await templates('failed_query', req.params.language)) + if (rows instanceof Error) return res.status(500).send(await languageTemplates('failed_query', req.params.language)) // Sent mail with verification token to the account email address. - var mail_template = await templates('verify_account', language, { + var mail_template = await languageTemplates('verify_account', language) + + await mailer(Object.assign(mail_template, { + to: req.body.email, host: host, link: `${protocol}${host}/api/user/verify/${verificationtoken}`, remote_address - }) - - await mailer(Object.assign(mail_template, { - to: req.body.email })) // Return msg. No redirect for password reset. - res.send(await templates('new_account_registered', language)) + res.send(await languageTemplates('new_account_registered', language)) } \ No newline at end of file diff --git a/mod/user/update.js b/mod/user/update.js index d4c4a52095..79bba61a2d 100644 --- a/mod/user/update.js +++ b/mod/user/update.js @@ -2,7 +2,7 @@ const acl = require('./acl')() const mailer = require('../utils/mailer') -const templates = require('../templates/_templates') +const languageTemplates = require('../utils/languageTemplates') module.exports = async (req, res) => { @@ -26,7 +26,7 @@ module.exports = async (req, res) => { if (rows instanceof Error) { // Get error message from templates. - const error_message = await templates('failed_query', req.params.language) + const error_message = await languageTemplates('failed_query', req.params.language) return res.status(500).send(error_message) } @@ -38,18 +38,19 @@ module.exports = async (req, res) => { // Send email to the user account if an account has been approved. if (req.params.field === 'approved' && req.params.value === 'true') { - const mail_template = await templates('approved_account', req.params.user.language, { - host: host, - protocol: protocol - }) + const mail_template = await languageTemplates('approved_account', req.params.user.language) // Assign email to mail template Object.assign(mail_template, { - to: email + to: email, + host: host, + protocol: protocol }) await mailer(mail_template) } - return res.send(await templates('update_ok', req.params.user.language)) + const update_ok = await languageTemplates('update_ok', req.params.user.language) + + return res.send(update_ok) } \ No newline at end of file diff --git a/mod/user/verify.js b/mod/user/verify.js index 2be9994e40..b890178a60 100644 --- a/mod/user/verify.js +++ b/mod/user/verify.js @@ -1,10 +1,8 @@ -const crypto = require('crypto') - const acl = require('./acl')() const mailer = require('../utils/mailer') -const templates = require('../templates/_templates') +const languageTemplates = require('../utils/languageTemplates') const login = require('./login') @@ -22,7 +20,7 @@ module.exports = async (req, res) => { if (rows instanceof Error) { // Get error message from templates. - const error_message = await templates('failed_query', req.params.language) + const error_message = await languageTemplates('failed_query', req.params.language) return res.status(500).send(error_message) } @@ -50,7 +48,7 @@ module.exports = async (req, res) => { if (rows instanceof Error) { // Get error message from templates. - const error_message = await templates('failed_query', req.params.language) + const error_message = await languageTemplates('failed_query', req.params.language) return res.status(500).send(error_message) } @@ -76,7 +74,7 @@ module.exports = async (req, res) => { if (rows instanceof Error) { // Get error message from templates. - const error_message = await templates('failed_query', req.params.language) + const error_message = await languageTemplates('failed_query', req.params.language) return res.status(500).send(error_message) } @@ -91,15 +89,14 @@ module.exports = async (req, res) => { // Get array of mail promises. const mail_promises = rows.map(async row => { - const mail_template = await templates('admin_email', row.language || req.params.language, { - email: user.email, - host: host, - protocol: protocol - }) + const mail_template = await languageTemplates('admin_email', row.language || req.params.language) // Assign email to mail template. Object.assign(mail_template, { - to: row.email + to: row.email, + email: user.email, + host: host, + protocol: protocol }) return mailer(mail_template) @@ -108,7 +105,7 @@ module.exports = async (req, res) => { // Continue after all mail promises have been resolved. Promise .all(mail_promises) - .then(async arr => res.send(await templates('account_await_approval', user.language))) + .then(async arr => res.send(await languageTemplates('account_await_approval', user.language))) .catch(error => console.error(error)) } else { diff --git a/mod/utils/languageTemplates.js b/mod/utils/languageTemplates.js index 96f27abe0b..fc4d6aa669 100644 --- a/mod/utils/languageTemplates.js +++ b/mod/utils/languageTemplates.js @@ -1,24 +1,72 @@ -const getTemplate = require('../workspace/getTemplate') +const getFrom = require('../provider/getFrom') const workspaceCache = require('../workspace/cache') -module.exports = async (req, params) => { +module.exports = async (key, lang = 'en') => { + + if (key === undefined) return; const workspace = await workspaceCache() - let template = workspace.templates[params.template]?.[params.language] + if (!Object.hasOwn(workspace.templates, key)) { + + console.warn(`Template ${key} not found.`) + return; + } + + const allLanguages = workspace.templates[key] + + let template = Object.hasOwn(allLanguages, lang)? allLanguages[lang]: allLanguages.en; console.log(template) - template = await getTemplate({ - src: workspace.templates[params.template]?.[params.language] - }) + if (typeof template === 'string' && Object.hasOwn(getFrom, template.split(':')[0])) { + + // Get template from method. + template = await getFrom[template.split(':')[0]](template) + + } else if (typeof template === 'object' && (Object.hasOwn(template, 'text') || Object.hasOwn(template, 'html'))) { + + if (Object.hasOwn(getFrom, template.text?.split(':')[0])) { + + template.text = await getFrom[template.text.split(':')[0]](template.text) + } + + if (Object.hasOwn(getFrom, template.html?.split(':')[0])) { + + template.html = await getFrom[template.html.split(':')[0]](template.html) + } + + } + + // // Prevent prototype polluting assignment. + // Object.freeze(Object.getPrototypeOf(template)); + + // for (key in template) { + + // // Prevent prototype polluting assignment. + // if (/__proto__/.test(key)) continue; + + // // Template key / value is a string with a valid get method. + // if (typeof template[key] === 'string' + // && Object.hasOwn(template, key) + // && Object.hasOwn(getFrom, template[key].split(':')[0])) { + + // // Assign template key value from method. + // template[key] = await getFrom[template[key].split(':')[0]](template[key]) + // } + + // // Template key value is still string after assignment + // if (typeof template[key] === 'string') { + + // // Look for template params to be substituted. + // template[key] = template[key].replace(/\$\{{1}(.*?)\}{1}/g, - template = template.template.replace(/[{]{2}([A-Za-z][A-Za-z0-9]*)[}]{2}/g, matched => { + // // Replace matched params in string values + // matched => params[matched.replace(/\$\{{1}|\}{1}/g, '')] || '') + // } - // regex matches {{ or }} - return params[matched.replace(/[{]{2}|[}]{2}/g, '')] || ''; - }); + // } return template } diff --git a/mod/view.js b/mod/view.js index f26cea79f8..c65612c34e 100644 --- a/mod/view.js +++ b/mod/view.js @@ -6,66 +6,66 @@ const logger = require('./utils/logger') const languageTemplates = require('./utils/languageTemplates') +const workspaceCache = require('./workspace/cache') + module.exports = async (req, res) => { + const workspace = await workspaceCache() + logger(req.url, 'view-req-url') - const roles = req.params.user?.roles || [] + const params = {} - const locales = Object.values(req.params.workspace.locales) - .filter(locale => !!Roles.check(locale, roles)) - .map(locale => ({ - key: locale.key, - name: locale.name - })) + Object.keys(req.params) + .filter(key => typeof req.params[key] === 'string') + .forEach(key => params[key] = req.params[key]) - if (!locales.length) { + params.template ??= 'default_view' - return login(req, res, 'no_locales') - } + params.dir ??= process.env.DIR + + params.login ??= (process.env.PRIVATE || process.env.PUBLIC) && 'true' + + params.title ??= process.env.TITLE + + if (req.params.user && typeof req.params.user === 'object') { - // Encode stringified user for template. - const user = req.params.user && encodeURI(JSON.stringify({ - email: req.params.user.email, - admin: req.params.user.admin, - roles: req.params.user.roles, - language: req.params.user.language - })); - - const params = { - //...req.params, - language: req.params.user?.language || 'en', - title: process.env.TITLE, - dir: process.env.DIR, - user: user, - login: (process.env.PRIVATE || process.env.PUBLIC) && 'true', + params.language ??= req.params.user.language + + const roles = req.params.user?.roles || [] + + const locales = Object.values(workspace.locales) + .filter(locale => !!Roles.check(locale, roles)) + .map(locale => ({ + key: locale.key, + name: locale.name + })) + + if (!locales.length) { + + return login(req, res, 'no_locales') + } + + // Encode stringified user for template. + params.user ??= encodeURI(JSON.stringify({ + email: req.params.user.email, + admin: req.params.user.admin, + roles: req.params.user.roles, + language: req.params.user.language + })); } // Object.entries(process.env) // .filter(entry => entry[0].match(/^SRC_/)) // .forEach(entry => params[entry[0].replace(/^SRC_/, '')]=entry[1]) - params.template ??= 'default_view' - - // // Template is provided from workspace - // if (req.params.template?.template) { - - // // regex captures characters inside {{ }} - // return res.send(req.params.template?.template.replace(/[{]{2}([A-Za-z][A-Za-z0-9]*)[}]{2}/g, matched => { - - // // regex matches {{ or }} - // return params[matched.replace(/[{]{2}|[}]{2}/g, '')] || ''; - // })); - // } + const template = await languageTemplates(params.template, params.language) - // Get view template. - // const view = await templates( - // 'default_view', - // req.params.language || req.params.user?.language, - // params - // ); + const view = template.replace(/[{]{2}([A-Za-z][A-Za-z0-9]*)[}]{2}/g, matched => { - const view = await languageTemplates(req, params) + // regex matches {{ or }} + return params[matched.replace(/[{]{2}|[}]{2}/g, '')] || ''; + }); res.send(view); } \ No newline at end of file From a4004e86d47251cb6bd07a390986d8e523c31e9a Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 26 Oct 2023 18:21:56 +0100 Subject: [PATCH 14/44] update mailer util --- mod/provider/cloudfront.js | 11 +---- mod/user/delete.js | 19 +++------ mod/user/login.js | 28 ++++++------- mod/user/register.js | 19 ++++----- mod/user/update.js | 11 ++--- mod/user/verify.js | 25 +++++------ mod/utils/languageTemplates.js | 44 ------------------- mod/utils/mailer.js | 77 +++++++++++++++++++++++++--------- 8 files changed, 102 insertions(+), 132 deletions(-) diff --git a/mod/provider/cloudfront.js b/mod/provider/cloudfront.js index 35d8d46db3..d4107b0f60 100644 --- a/mod/provider/cloudfront.js +++ b/mod/provider/cloudfront.js @@ -1,10 +1,3 @@ -const https = require('https') - -const httpsAgent = new https.Agent({ - keepAlive: true, - maxSockets: parseInt(process.env.CUSTOM_AGENT) || 1 -}) - const { readFileSync } = require('fs') const { join } = require('path') @@ -36,9 +29,7 @@ module.exports = async ref => { return signedURL; } - const response = await fetch(signedURL, { - agent: process.env.CUSTOM_AGENT && httpsAgent - }) + const response = await fetch(signedURL) logger(`${response.status} - ${url}`,'cloudfront') diff --git a/mod/user/delete.js b/mod/user/delete.js index 0b4cd7ffcb..734cfd453c 100644 --- a/mod/user/delete.js +++ b/mod/user/delete.js @@ -2,8 +2,6 @@ const acl = require('./acl')() const mailer = require('../utils/mailer') -const languageTemplates = require('../utils/languageTemplates') - module.exports = async (req, res) => { const email = req.params.email.replace(/\s+/g, '') @@ -18,21 +16,14 @@ module.exports = async (req, res) => { const user = rows[0] - const protocol = `${req.headers.host.includes('localhost') && 'http' || 'https'}://` - - const host = `${req.headers.host.includes('localhost') && req.headers.host || process.env.ALIAS || req.headers.host}${process.env.DIR}` - // Sent email to inform user that their account has been deleted. - const mail_template = await languageTemplates('deleted_account', user.language) - - // Assign user email to mail_template. - Object.assign(mail_template, { + await mailer({ + template: 'deleted_account', + language: user.language, to: user.email, - host, - protocol + host: `${req.headers.host.includes('localhost') && req.headers.host || process.env.ALIAS || req.headers.host}${process.env.DIR}`, + protocol: `${req.headers.host.includes('localhost') && 'http' || 'https'}://` }) - - await mailer(mail_template) res.send('User account deleted.') } \ No newline at end of file diff --git a/mod/user/login.js b/mod/user/login.js index d97389d1f6..f4afcc9a13 100644 --- a/mod/user/login.js +++ b/mod/user/login.js @@ -150,15 +150,15 @@ async function post(req, res) { // Accounts must be verified and approved for login if (!user.verified || !user.approved) { - - var mail_template = await languageTemplates('failed_login', user.language) - - await mailer(Object.assign(mail_template, { + + await mailer({ + template: 'failed_login', + language: user.language, to: user.email, host: host, protocol: protocol, remote_address - })) + }) return new Error(await languageTemplates('user_not_verified', user.language)) } @@ -218,29 +218,29 @@ async function post(req, res) { WHERE lower(email) = lower($1);`, [req.body.email]) if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) - - var mail_template = await languageTemplates('locked_account', user.language) - - await mailer(Object.assign(mail_template, { + + await mailer({ + template: 'locked_account', + language: user.language, to: user.email, host: host, failed_attempts: parseInt(process.env.FAILED_ATTEMPTS) || 3, protocol: protocol, verificationtoken: verificationtoken, remote_address - })) + }) return new Error(await languageTemplates('user_locked', user.language)) } // Login has failed but account is not locked (yet). - var mail_template = await languageTemplates('login_incorrect', user.language) - - await mailer(Object.assign(mail_template, { + await mailer({ + template: 'login_incorrect', + language: user.language, to: user.email, host: host, remote_address - })) + }) return new Error(await languageTemplates('auth_failed', req.params.language)) } \ No newline at end of file diff --git a/mod/user/register.js b/mod/user/register.js index ee3cbe57ec..5e6e86595a 100644 --- a/mod/user/register.js +++ b/mod/user/register.js @@ -114,15 +114,15 @@ async function post(req, res) { if (rows instanceof Error) return res.status(500).send(await languageTemplates('failed_query', req.params.language)) - // Sent mail with verification token to the account email address. - var mail_template = await languageTemplates('verify_password_reset', user.language) - - await mailer(Object.assign(mail_template, { + // Sent mail with verification token to the account email address. + await mailer({ + template: 'verify_password_reset', + language: user.language, to: user.email, host: host, link: `${protocol}${host}/api/user/verify/${verificationtoken}`, remote_address - })) + }) const password_reset_verification = await languageTemplates('password_reset_verification', user.language) @@ -151,15 +151,14 @@ async function post(req, res) { if (rows instanceof Error) return res.status(500).send(await languageTemplates('failed_query', req.params.language)) - // Sent mail with verification token to the account email address. - var mail_template = await languageTemplates('verify_account', language) - - await mailer(Object.assign(mail_template, { + await mailer({ + template: 'verify_account', + language, to: req.body.email, host: host, link: `${protocol}${host}/api/user/verify/${verificationtoken}`, remote_address - })) + }) // Return msg. No redirect for password reset. res.send(await languageTemplates('new_account_registered', language)) diff --git a/mod/user/update.js b/mod/user/update.js index 79bba61a2d..fe8f1d183e 100644 --- a/mod/user/update.js +++ b/mod/user/update.js @@ -37,17 +37,14 @@ module.exports = async (req, res) => { // Send email to the user account if an account has been approved. if (req.params.field === 'approved' && req.params.value === 'true') { - - const mail_template = await languageTemplates('approved_account', req.params.user.language) - - // Assign email to mail template - Object.assign(mail_template, { + + await mailer({ + template: 'approved_account', + language: req.params.user.language, to: email, host: host, protocol: protocol }) - - await mailer(mail_template) } const update_ok = await languageTemplates('update_ok', req.params.user.language) diff --git a/mod/user/verify.js b/mod/user/verify.js index b890178a60..6db19d46cc 100644 --- a/mod/user/verify.js +++ b/mod/user/verify.js @@ -82,30 +82,27 @@ module.exports = async (req, res) => { // One or more administrator have been if (rows.length > 0) { - // Create protocol and host for mail templates. - const protocol = `${req.headers.host.includes('localhost') && 'http' || 'https'}://` - const host = `${req.headers.host.includes('localhost') && req.headers.host || process.env.ALIAS || req.headers.host}${process.env.DIR}` - // Get array of mail promises. const mail_promises = rows.map(async row => { - const mail_template = await languageTemplates('admin_email', row.language || req.params.language) - - // Assign email to mail template. - Object.assign(mail_template, { + await mailer({ + template: 'admin_email', + language: row.language, to: row.email, email: user.email, - host: host, - protocol: protocol + host: `${req.headers.host.includes('localhost') && req.headers.host || process.env.ALIAS || req.headers.host}${process.env.DIR}`, + protocol: `${req.headers.host.includes('localhost') && 'http' || 'https'}://` }) - - return mailer(mail_template) }) // Continue after all mail promises have been resolved. Promise - .all(mail_promises) - .then(async arr => res.send(await languageTemplates('account_await_approval', user.language))) + .allSettled(mail_promises) + .then(async arr => { + + console.log(arr) + res.send(await languageTemplates('account_await_approval', user.language)) + }) .catch(error => console.error(error)) } else { diff --git a/mod/utils/languageTemplates.js b/mod/utils/languageTemplates.js index fc4d6aa669..b0fde04dc7 100644 --- a/mod/utils/languageTemplates.js +++ b/mod/utils/languageTemplates.js @@ -18,55 +18,11 @@ module.exports = async (key, lang = 'en') => { let template = Object.hasOwn(allLanguages, lang)? allLanguages[lang]: allLanguages.en; - console.log(template) - if (typeof template === 'string' && Object.hasOwn(getFrom, template.split(':')[0])) { // Get template from method. template = await getFrom[template.split(':')[0]](template) - - } else if (typeof template === 'object' && (Object.hasOwn(template, 'text') || Object.hasOwn(template, 'html'))) { - - if (Object.hasOwn(getFrom, template.text?.split(':')[0])) { - - template.text = await getFrom[template.text.split(':')[0]](template.text) - } - - if (Object.hasOwn(getFrom, template.html?.split(':')[0])) { - - template.html = await getFrom[template.html.split(':')[0]](template.html) - } - } - // // Prevent prototype polluting assignment. - // Object.freeze(Object.getPrototypeOf(template)); - - // for (key in template) { - - // // Prevent prototype polluting assignment. - // if (/__proto__/.test(key)) continue; - - // // Template key / value is a string with a valid get method. - // if (typeof template[key] === 'string' - // && Object.hasOwn(template, key) - // && Object.hasOwn(getFrom, template[key].split(':')[0])) { - - // // Assign template key value from method. - // template[key] = await getFrom[template[key].split(':')[0]](template[key]) - // } - - // // Template key value is still string after assignment - // if (typeof template[key] === 'string') { - - // // Look for template params to be substituted. - // template[key] = template[key].replace(/\$\{{1}(.*?)\}{1}/g, - - // // Replace matched params in string values - // matched => params[matched.replace(/\$\{{1}|\}{1}/g, '')] || '') - // } - - // } - return template } diff --git a/mod/utils/mailer.js b/mod/utils/mailer.js index 8fe306f211..adabdb567f 100644 --- a/mod/utils/mailer.js +++ b/mod/utils/mailer.js @@ -1,36 +1,75 @@ const logger = require('./logger') +const languageTemplates = require('./languageTemplates') + +const getFrom = require('../provider/getFrom') + const mailer = require('nodemailer') -module.exports = async mail => { +let transport - if (!process.env.TRANSPORT && !process.env.TRANSPORT_HOST) return console.error(new Error('Transport not set.')) +module.exports = async params => { + + if (!process.env.TRANSPORT && !process.env.TRANSPORT_HOST) { + console.warn('No transport method set for mail.') + return; + } const email = process.env.TRANSPORT_EMAIL || process.env.TRANSPORT.split(':')[1] - Object.assign(mail, { - from: email, - sender: email, - }) + if (!transport) { + + transport = process.env.TRANSPORT ? + mailer.createTransport(process.env.TRANSPORT) : + mailer.createTransport({ + host: process.env.TRANSPORT_HOST, + name: email.match(/[^@]*$/)[0], + port: process.env.TRANSPORT_PORT || 587, + secure: false, + requireTLS: process.env.TRANSPORT_TLS && true, + auth: { + user: email, + pass: process.env.TRANSPORT_PASSWORD + } + }) + } - mail.text = mail.text && mail.text.replace(/^(?!\s+$)\s+/gm, '') + const template = await languageTemplates(params.template, params.language) - if (!process.env.TRANSPORT_HOST) return mailer.createTransport(process.env.TRANSPORT).sendMail(mail) + if (template.text) { - const transporter = mailer.createTransport( { - host: process.env.TRANSPORT_HOST, - name: email.match(/[^@]*$/)[0], - port: process.env.TRANSPORT_PORT || 587, - secure: false, - requireTLS: process.env.TRANSPORT_TLS && true, - auth: { - user: email, - pass: process.env.TRANSPORT_PASSWORD + if (Object.hasOwn(getFrom, template.text?.split(':')[0])) { + + template.text = await getFrom[template.text.split(':')[0]](template.text) } - }) - const result = await transporter.sendMail(mail).catch(err => console.error(err)) + template.text = replaceStringParams(template.text, params) + + template.text = template.text.replace(/^(?!\s+$)\s+/gm, '') + } + + if (Object.hasOwn(getFrom, template.html?.split(':')[0])) { + + template.html = await getFrom[template.html.split(':')[0]](template.html) + + template.html = replaceStringParams(template.html, params) + } + + template.subject = replaceStringParams(template.subject, params) + + template.to = params.to + template.from = email + template.sender = email + + const result = await transport.sendMail(template).catch(err => console.error(err)) logger(result, 'mailer') +} + +function replaceStringParams(string, params) { + + return string.replace(/\$\{{1}(.*?)\}{1}/g, + // Replace matched params in string values + matched => params[matched.replace(/\$\{{1}|\}{1}/g, '')] || '') } \ No newline at end of file From 0625c27cf77f064541f1e3ac08f71b7e89f769f5 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 26 Oct 2023 19:07:05 +0100 Subject: [PATCH 15/44] param argument for languageTemplates --- mod/user/cookie.js | 5 ++- mod/user/login.js | 70 +++++++++++++++++++++++++++------- mod/user/register.js | 30 ++++++++++++--- mod/user/update.js | 10 ++++- mod/user/verify.js | 22 ++++++++--- mod/utils/languageTemplates.js | 15 +++++--- mod/utils/mailer.js | 2 +- mod/view.js | 2 +- 8 files changed, 119 insertions(+), 37 deletions(-) diff --git a/mod/user/cookie.js b/mod/user/cookie.js index 97713584df..afc0d27cd4 100644 --- a/mod/user/cookie.js +++ b/mod/user/cookie.js @@ -18,7 +18,10 @@ module.exports = async (req, res) => { if (!cookie) { // Get login view template. - const no_cookie_found = await languageTemplates('no_cookie_found', req.params.language) + const no_cookie_found = await languageTemplates({ + template: 'no_cookie_found', + language: req.params.language + }) return login(req, res, no_cookie_found) } diff --git a/mod/user/login.js b/mod/user/login.js index f4afcc9a13..50d626a687 100644 --- a/mod/user/login.js +++ b/mod/user/login.js @@ -59,7 +59,10 @@ module.exports = async (req, res, _message) => { } // Get message from templates. - const message = await languageTemplates(req.params.msg || _message, req.params.language) + const message = await languageTemplates({ + template: req.params.msg || _message, + language: req.params.language + }) if (!message && req.params.user) { @@ -95,9 +98,15 @@ async function post(req, res) { && /^[A-Za-z0-9.,_-\s]*$/.test(req.headers['x-forwarded-for']) ? req.headers['x-forwarded-for'] : 'invalid' || 'unknown'; - if(!req.body.email) return new Error(await languageTemplates('missing_email', req.params.language)) + if (!req.body.email) return new Error(await languageTemplates({ + template: 'missing_email', + language: req.params.language + })) - if(!req.body.password) return new Error(await languageTemplates('missing_password', req.params.language)) + if (!req.body.password) return new Error(await languageTemplates({ + template: 'missing_password', + language: req.params.language + })) const date = new Date() @@ -113,15 +122,24 @@ async function post(req, res) { RETURNING email, roles, language, blocked, approved, approved_by, verified, admin, password ${process.env.APPROVAL_EXPIRY ? ', expires_on;' : ';'}`, [req.body.email]) - if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) + if (rows instanceof Error) return new Error(await languageTemplates({ + template: 'failed_query', + language: req.params.language + })) // Get user record from first row. const user = rows[0] - if (!user) return new Error(await languageTemplates('auth_failed', req.params.language)) + if (!user) return new Error(await languageTemplates({ + template: 'auth_failed', + language: req.params.language + })) // Blocked user cannot login. - if (user.blocked) return new Error(await languageTemplates('user_blocked', user.language || req.params.language)) + if (user.blocked) return new Error(await languageTemplates({ + template: 'user_blocked', + language: user.language || req.params.language + })) // Non admin accounts may expire. if (!user.admin && process.env.APPROVAL_EXPIRY) { @@ -139,10 +157,16 @@ async function post(req, res) { WHERE lower(email) = lower($1);`, [req.body.email]) - if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) + if (rows instanceof Error) return new Error(await languageTemplates({ + template: 'failed_query', + language: req.params.language + })) } - return new Error(await languageTemplates('user_expired', user.language)) + return new Error(await languageTemplates({ + template: 'user_expired', + language: user.language + })) } @@ -160,7 +184,10 @@ async function post(req, res) { remote_address }) - return new Error(await languageTemplates('user_not_verified', user.language)) + return new Error(await languageTemplates({ + template: 'user_not_verified', + language: user.language + })) } // Check password from post body against encrypted password from ACL. @@ -181,7 +208,10 @@ async function post(req, res) { WHERE lower(email) = lower($1)`, [req.body.email]) - if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) + if (rows instanceof Error) return new Error(await languageTemplates({ + template: 'failed_query', + language: req.params.language + })) } @@ -201,7 +231,10 @@ async function post(req, res) { WHERE lower(email) = lower($1) RETURNING failedattempts;`, [req.body.email]) - if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) + if (rows instanceof Error) return new Error(await languageTemplates({ + template: 'failed_query', + language: req.params.language + })) // Check whether failed login attempts exceeds limit. if (rows[0].failedattempts >= parseInt(process.env.FAILED_ATTEMPTS || 3)) { @@ -217,7 +250,10 @@ async function post(req, res) { verificationtoken = '${verificationtoken}' WHERE lower(email) = lower($1);`, [req.body.email]) - if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) + if (rows instanceof Error) return new Error(await languageTemplates({ + template: 'failed_query', + language: req.params.language + })) await mailer({ template: 'locked_account', @@ -230,7 +266,10 @@ async function post(req, res) { remote_address }) - return new Error(await languageTemplates('user_locked', user.language)) + return new Error(await languageTemplates({ + template: 'user_locked', + language: user.language + })) } // Login has failed but account is not locked (yet). @@ -242,5 +281,8 @@ async function post(req, res) { remote_address }) - return new Error(await languageTemplates('auth_failed', req.params.language)) + return new Error(await languageTemplates({ + template: 'auth_failed', + language: req.params.language + })) } \ No newline at end of file diff --git a/mod/user/register.js b/mod/user/register.js index 5e6e86595a..a573d44e9e 100644 --- a/mod/user/register.js +++ b/mod/user/register.js @@ -70,7 +70,10 @@ async function post(req, res) { WHERE lower(email) = lower($1);`, [req.body.email]) - const failed_query = await languageTemplates('failed_query', req.params.language) + const failed_query = await languageTemplates({ + template: 'failed_query', + language: req.params.language + }) if (rows instanceof Error) return res.status(500).send(failed_query) @@ -99,7 +102,10 @@ async function post(req, res) { if (user) { // Blocked user may not reset their password. - if (user.blocked) return res.status(500).send(await languageTemplates('user_blocked', user.language || req.params.language)) + if (user.blocked) return res.status(500).send(await languageTemplates({ + template: 'user_blocked', + language: user.language || req.params.language + })) // Set new password and verification token. // New passwords will only apply after account verification. @@ -112,7 +118,10 @@ async function post(req, res) { WHERE lower(email) = lower($1);`, [req.body.email]) - if (rows instanceof Error) return res.status(500).send(await languageTemplates('failed_query', req.params.language)) + if (rows instanceof Error) return res.status(500).send(await languageTemplates({ + template: 'failed_query', + language: req.params.language + })) // Sent mail with verification token to the account email address. await mailer({ @@ -124,7 +133,10 @@ async function post(req, res) { remote_address }) - const password_reset_verification = await languageTemplates('password_reset_verification', user.language) + const password_reset_verification = await languageTemplates({ + template: 'password_reset_verification', + language: user.language + }) return res.send(password_reset_verification) } @@ -149,7 +161,10 @@ async function post(req, res) { '${verificationtoken}' AS verificationtoken, array['${date}@${req.ips && req.ips.pop() || req.ip}'] AS access_log;`) - if (rows instanceof Error) return res.status(500).send(await languageTemplates('failed_query', req.params.language)) + if (rows instanceof Error) return res.status(500).send(await languageTemplates({ + template: 'failed_query', + language: req.params.language + })) await mailer({ template: 'verify_account', @@ -161,5 +176,8 @@ async function post(req, res) { }) // Return msg. No redirect for password reset. - res.send(await languageTemplates('new_account_registered', language)) + res.send(await languageTemplates({ + template: 'new_account_registered', + language + })) } \ No newline at end of file diff --git a/mod/user/update.js b/mod/user/update.js index fe8f1d183e..a99235067b 100644 --- a/mod/user/update.js +++ b/mod/user/update.js @@ -26,7 +26,10 @@ module.exports = async (req, res) => { if (rows instanceof Error) { // Get error message from templates. - const error_message = await languageTemplates('failed_query', req.params.language) + const error_message = await languageTemplates({ + template: 'failed_query', + language: req.params.language + }) return res.status(500).send(error_message) } @@ -47,7 +50,10 @@ module.exports = async (req, res) => { }) } - const update_ok = await languageTemplates('update_ok', req.params.user.language) + const update_ok = await languageTemplates({ + template: 'update_ok', + language: req.params.user.language + }) return res.send(update_ok) } \ No newline at end of file diff --git a/mod/user/verify.js b/mod/user/verify.js index 6db19d46cc..f2001b2665 100644 --- a/mod/user/verify.js +++ b/mod/user/verify.js @@ -20,7 +20,10 @@ module.exports = async (req, res) => { if (rows instanceof Error) { // Get error message from templates. - const error_message = await languageTemplates('failed_query', req.params.language) + const error_message = await languageTemplates({ + template: 'failed_query', + language: req.params.language + }) return res.status(500).send(error_message) } @@ -48,7 +51,10 @@ module.exports = async (req, res) => { if (rows instanceof Error) { // Get error message from templates. - const error_message = await languageTemplates('failed_query', req.params.language) + const error_message = await languageTemplates({ + template: 'failed_query', + language: req.params.language + }) return res.status(500).send(error_message) } @@ -74,7 +80,10 @@ module.exports = async (req, res) => { if (rows instanceof Error) { // Get error message from templates. - const error_message = await languageTemplates('failed_query', req.params.language) + const error_message = await languageTemplates({ + template: 'failed_query', + language: req.params.language + }) return res.status(500).send(error_message) } @@ -99,9 +108,10 @@ module.exports = async (req, res) => { Promise .allSettled(mail_promises) .then(async arr => { - - console.log(arr) - res.send(await languageTemplates('account_await_approval', user.language)) + res.send(await languageTemplates({ + template: 'account_await_approval', + language: user.language + })) }) .catch(error => console.error(error)) diff --git a/mod/utils/languageTemplates.js b/mod/utils/languageTemplates.js index b0fde04dc7..17fa70a265 100644 --- a/mod/utils/languageTemplates.js +++ b/mod/utils/languageTemplates.js @@ -2,21 +2,24 @@ const getFrom = require('../provider/getFrom') const workspaceCache = require('../workspace/cache') -module.exports = async (key, lang = 'en') => { +module.exports = async (params) => { - if (key === undefined) return; + if (params.template === undefined) return; + + // Set english as default template language. + params.language ??= 'en' const workspace = await workspaceCache() - if (!Object.hasOwn(workspace.templates, key)) { + if (!Object.hasOwn(workspace.templates, params.template)) { - console.warn(`Template ${key} not found.`) + console.warn(`Template ${params.template} not found.`) return; } - const allLanguages = workspace.templates[key] + const allLanguages = workspace.templates[params.template] - let template = Object.hasOwn(allLanguages, lang)? allLanguages[lang]: allLanguages.en; + let template = Object.hasOwn(allLanguages, params.language)? allLanguages[params.language]: allLanguages.en; if (typeof template === 'string' && Object.hasOwn(getFrom, template.split(':')[0])) { diff --git a/mod/utils/mailer.js b/mod/utils/mailer.js index adabdb567f..4528273d74 100644 --- a/mod/utils/mailer.js +++ b/mod/utils/mailer.js @@ -34,7 +34,7 @@ module.exports = async params => { }) } - const template = await languageTemplates(params.template, params.language) + const template = await languageTemplates(params) if (template.text) { diff --git a/mod/view.js b/mod/view.js index c65612c34e..9ddb962bd9 100644 --- a/mod/view.js +++ b/mod/view.js @@ -59,7 +59,7 @@ module.exports = async (req, res) => { // .filter(entry => entry[0].match(/^SRC_/)) // .forEach(entry => params[entry[0].replace(/^SRC_/, '')]=entry[1]) - const template = await languageTemplates(params.template, params.language) + const template = await languageTemplates(params) const view = template.replace(/[{]{2}([A-Za-z][A-Za-z0-9]*)[}]{2}/g, matched => { From 8142d514dd3eff2e3a4b70813e1f8811438c5d01 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 26 Oct 2023 19:28:01 +0100 Subject: [PATCH 16/44] mail template checks --- mod/utils/mailer.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mod/utils/mailer.js b/mod/utils/mailer.js index 4528273d74..f4775a7d7f 100644 --- a/mod/utils/mailer.js +++ b/mod/utils/mailer.js @@ -38,9 +38,14 @@ module.exports = async params => { if (template.text) { + // Prevent mail template from having text and html + delete template.html + if (Object.hasOwn(getFrom, template.text?.split(':')[0])) { template.text = await getFrom[template.text.split(':')[0]](template.text) + + if (!template.text) return; } template.text = replaceStringParams(template.text, params) @@ -52,6 +57,8 @@ module.exports = async params => { template.html = await getFrom[template.html.split(':')[0]](template.html) + if (!template.text) return; + template.html = replaceStringParams(template.html, params) } From 127b84faced6d20d04b819584a9208c363e63df4 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 26 Oct 2023 19:34:13 +0100 Subject: [PATCH 17/44] transport test --- mod/utils/mailer.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mod/utils/mailer.js b/mod/utils/mailer.js index f4775a7d7f..b727c26b87 100644 --- a/mod/utils/mailer.js +++ b/mod/utils/mailer.js @@ -19,9 +19,7 @@ module.exports = async params => { if (!transport) { - transport = process.env.TRANSPORT ? - mailer.createTransport(process.env.TRANSPORT) : - mailer.createTransport({ + transport = mailer.createTransport({ host: process.env.TRANSPORT_HOST, name: email.match(/[^@]*$/)[0], port: process.env.TRANSPORT_PORT || 587, From 1e98a2b41d837bba6971b3ec91917698c22e8468 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 26 Oct 2023 19:39:27 +0100 Subject: [PATCH 18/44] Remove redundant quantifier {1}. --- mod/utils/mailer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mod/utils/mailer.js b/mod/utils/mailer.js index b727c26b87..591ea5bd54 100644 --- a/mod/utils/mailer.js +++ b/mod/utils/mailer.js @@ -73,8 +73,8 @@ module.exports = async params => { function replaceStringParams(string, params) { - return string.replace(/\$\{{1}(.*?)\}{1}/g, + return string.replace(/\$\{(.*?)\}/g, // Replace matched params in string values - matched => params[matched.replace(/\$\{{1}|\}{1}/g, '')] || '') + matched => params[matched.replace(/\$\{|\}/g, '')] || '') } \ No newline at end of file From ab333648b3fb408639821d8bc4f39616567937a0 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 26 Oct 2023 19:43:42 +0100 Subject: [PATCH 19/44] validate getFrom method call --- mod/workspace/getTemplate.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mod/workspace/getTemplate.js b/mod/workspace/getTemplate.js index a061c9d48a..5e998b9581 100644 --- a/mod/workspace/getTemplate.js +++ b/mod/workspace/getTemplate.js @@ -26,7 +26,8 @@ module.exports = async (template) => { return template } - const response = await getFrom[template.src.split(':')[0]](template.src) + const response = Object.hasOwn(getFrom, template.src.split(':')[0]) + && await getFrom[template.src.split(':')[0]](template.src) if (response instanceof Error) { From a6c431f30fe2270b5b1e686102c7d485d6cd5953 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 26 Oct 2023 19:52:34 +0100 Subject: [PATCH 20/44] validate dynamic method call --- mod/workspace/getTemplate.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mod/workspace/getTemplate.js b/mod/workspace/getTemplate.js index 5e998b9581..55d103e6b4 100644 --- a/mod/workspace/getTemplate.js +++ b/mod/workspace/getTemplate.js @@ -26,8 +26,12 @@ module.exports = async (template) => { return template } - const response = Object.hasOwn(getFrom, template.src.split(':')[0]) - && await getFrom[template.src.split(':')[0]](template.src) + let reponse; + + if (Object.hasOwn(getFrom, template.src.split(':')[0])) { + + response = await getFrom[template.src.split(':')[0]](template.src) + } if (response instanceof Error) { From 6a835561127a19c26bd4710960a7b2a4a5ff2805 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 26 Oct 2023 20:00:15 +0100 Subject: [PATCH 21/44] dynamic method validation --- mod/workspace/getTemplate.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mod/workspace/getTemplate.js b/mod/workspace/getTemplate.js index 55d103e6b4..8a53cb7197 100644 --- a/mod/workspace/getTemplate.js +++ b/mod/workspace/getTemplate.js @@ -26,13 +26,14 @@ module.exports = async (template) => { return template } - let reponse; - - if (Object.hasOwn(getFrom, template.src.split(':')[0])) { - - response = await getFrom[template.src.split(':')[0]](template.src) + if (typeof getFrom[template.src.split(':')[0]] !== 'function') { + + console.warn(`getFrom[${template.src.split(':')[0]}] is not a method.`); + return template } + const response = await getFrom[template.src.split(':')[0]]?.(template.src) + if (response instanceof Error) { template.err = response From 8c0387c815281e2ebe1735311ac0e07ee4ef166b Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 26 Oct 2023 20:10:20 +0100 Subject: [PATCH 22/44] get workspace from module in getLayer --- mod/workspace/getLayer.js | 4 +++- mod/workspace/getTemplate.js | 8 +------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/mod/workspace/getLayer.js b/mod/workspace/getLayer.js index 7d7a593040..7617f4eaae 100644 --- a/mod/workspace/getLayer.js +++ b/mod/workspace/getLayer.js @@ -4,9 +4,11 @@ const merge = require('../utils/merge') const getTemplate = require('./getTemplate') +const workspaceCache = require('./cache') + module.exports = async (req) => { - const workspace = req.params.workspace + const workspace = await workspaceCache() if (!Object.hasOwn(workspace.locales, req.params.locale)) { return new Error('Unable to validate locale param.') //400 diff --git a/mod/workspace/getTemplate.js b/mod/workspace/getTemplate.js index 8a53cb7197..45f003839a 100644 --- a/mod/workspace/getTemplate.js +++ b/mod/workspace/getTemplate.js @@ -26,13 +26,7 @@ module.exports = async (template) => { return template } - if (typeof getFrom[template.src.split(':')[0]] !== 'function') { - - console.warn(`getFrom[${template.src.split(':')[0]}] is not a method.`); - return template - } - - const response = await getFrom[template.src.split(':')[0]]?.(template.src) + const response = await getFrom[template.src.split(':')[0]](template.src) if (response instanceof Error) { From 6f7ab5a6139cc71aecf5a1602a649b5ef6faaa27 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 13:11:46 +0100 Subject: [PATCH 23/44] req.params.workspace refactor --- api/api.js | 35 ++------------------------------- mod/layer/_layer.js | 16 +++++++++------ mod/layer/mvt.js | 20 +++++++++++-------- mod/location/_location.js | 8 ++++++-- mod/location/delete.js | 8 ++++++-- mod/location/get.js | 8 ++++++-- mod/location/new.js | 8 ++++++-- mod/location/update.js | 8 ++++++-- mod/query.js | 39 ++++++++++++++++++++++++++++++++----- mod/workspace/_workspace.js | 26 +++++++++++++------------ 10 files changed, 102 insertions(+), 74 deletions(-) diff --git a/api/api.js b/api/api.js index effd19d03a..673069a35e 100644 --- a/api/api.js +++ b/api/api.js @@ -8,10 +8,6 @@ const auth = require('../mod/user/auth') const saml = process.env.SAML_ENTITY_ID && require('../mod/user/saml') -const workspaceCache = require('../mod/workspace/cache') - -const getTemplate = require('../mod/workspace/getTemplate') - const routes = { layer: require('../mod/layer/_layer'), location: require('../mod/location/_location'), @@ -111,7 +107,8 @@ module.exports = async (req, res) => { // Language param will default to english [en] is not explicitly set. req.params.language = req.params.language || 'en' - req.params.template = req.params._template || req.params.template + // Assign from _template if provided as path param. + req.params.template ??= req.params._template // Decode string params. Object.entries(req.params) @@ -184,34 +181,6 @@ module.exports = async (req, res) => { return login(req, res) } - // Retrieve workspace and assign to request params. - const workspace = await workspaceCache() - - if (workspace instanceof Error) { - return res.status(500).send(workspace.message) - } - - req.params.workspace = workspace - - // Retrieve query or view template from workspace - if (req.params.template) { - - if (!Object.hasOwn(workspace.templates, req.params.template)) { - - return res.status(404).send('Template not found.') - } - - const template = await getTemplate(workspace.templates[req.params.template]) - - if (template.err) return res.status(500).send(template.err.message) - - if (!user && (template.login || template.admin)) return login(req, res, 'login_required') - - if (user && (!user.admin && template.admin)) return login(req, res, 'admin_required') - - req.params.template = template - } - // Layer route if (req.url.match(/(?<=\/api\/layer)/)) { return routes.layer(req, res) diff --git a/mod/layer/_layer.js b/mod/layer/_layer.js index 9ddc1cb156..788d7cb007 100644 --- a/mod/layer/_layer.js +++ b/mod/layer/_layer.js @@ -2,8 +2,12 @@ const formats = { mvt: require('./mvt') } +const workspaceCache = require('../workspace/cache') + module.exports = async (req, res) => { + const workspace = workspaceCache() + if (!req.params.layer) { return res.send(`Failed to evaluate 'layer' param.

Layer API`) @@ -18,11 +22,11 @@ module.exports = async (req, res) => { if (req.params.locale) { // The locale key must be an own property of the workspace.locales, and must be an object. - if (Object.hasOwn(req.params.workspace.locales, req.params.locale) - && typeof req.params.workspace.locales[req.params.locale] === 'object') { + if (Object.hasOwn(workspace.locales, req.params.locale) + && typeof workspace.locales[req.params.locale] === 'object') { // Assign layer from locale in workspace. - req.params.layer = req.params.workspace.locales[req.params.locale].layers[req.params.layer] + req.params.layer = workspace.locales[req.params.locale].layers[req.params.layer] } else { @@ -31,11 +35,11 @@ module.exports = async (req, res) => { } // A layer must be specified in the templates without a locale specifier, and must be an object - } else if (Object.hasOwn(req.params.workspace.templates, req.params.layer) - && typeof req.params.workspace.templates[req.params.layer] === 'object') { + } else if (Object.hasOwn(workspace.templates, req.params.layer) + && typeof workspace.templates[req.params.layer] === 'object') { // Assign layer object from templates. - req.params.layer = req.params.workspace.templates[req.params.layer] + req.params.layer = workspace.templates[req.params.layer] } else { diff --git a/mod/layer/mvt.js b/mod/layer/mvt.js index 25b1e8e576..a47c3bab8b 100644 --- a/mod/layer/mvt.js +++ b/mod/layer/mvt.js @@ -8,8 +8,12 @@ const Roles = require('../utils/roles.js') const logger = require('../utils/logger') +const workspaceCache = require('../workspace/cache') + module.exports = async (req, res) => { + const workspace = workspaceCache() + // Check the layer.roles{} against the user.roles[] const layer = Roles.check(req.params.layer, req.params.user?.roles) @@ -76,12 +80,12 @@ module.exports = async (req, res) => { if (Array.isArray(theme.fields)) { - return theme.fields.map(field => `${req.params.workspace.templates[field]?.template || field} AS ${field}`).join(', ') + return theme.fields.map(field => `${workspace.templates[field]?.template || field} AS ${field}`).join(', ') } if (!theme.field) return; - return `${req.params.workspace.templates[theme.field]?.template || theme.field} AS ${theme.field}` + return `${workspace.templates[theme.field]?.template || theme.field} AS ${theme.field}` } const geoms = layer.geoms && Object.keys(layer.geoms) @@ -149,18 +153,18 @@ module.exports = async (req, res) => { if ((!filter || filter === '') && layer.mvt_cache) { // Validate dynamic method call. - if (!Object.hasOwn(dbs, layer.dbs || req.params.workspace.dbs) || typeof dbs[layer.dbs || req.params.workspace.dbs] !== 'function') return; + if (!Object.hasOwn(dbs, layer.dbs || workspace.dbs) || typeof dbs[layer.dbs || workspace.dbs] !== 'function') return; - var rows = await dbs[layer.dbs || req.params.workspace.dbs](`SELECT mvt FROM ${layer.mvt_cache} WHERE z = ${z} AND x = ${x} AND y = ${y}`) + var rows = await dbs[layer.dbs || workspace.dbs](`SELECT mvt FROM ${layer.mvt_cache} WHERE z = ${z} AND x = ${x} AND y = ${y}`) if (rows instanceof Error) console.log('failed to query mvt cache') if(!rows.length) { // Validate dynamic method call. - if (typeof dbs[layer.dbs || req.params.workspace.dbs] !== 'function') return; + if (typeof dbs[layer.dbs || workspace.dbs] !== 'function') return; - rows = await dbs[layer.dbs || req.params.workspace.dbs](` + rows = await dbs[layer.dbs || workspace.dbs](` WITH n AS ( INSERT INTO ${layer.mvt_cache} ${tile} ON CONFLICT (z, x, y) DO NOTHING RETURNING mvt @@ -175,9 +179,9 @@ module.exports = async (req, res) => { } // Validate dynamic method call. - if (typeof dbs[layer.dbs || req.params.workspace.dbs] !== 'function') return; + if (typeof dbs[layer.dbs || workspace.dbs] !== 'function') return; - var rows = await dbs[layer.dbs || req.params.workspace.dbs](tile, SQLparams) + var rows = await dbs[layer.dbs || workspace.dbs](tile, SQLparams) if (rows instanceof Error) return res.status(500).send('Failed to query PostGIS table.') diff --git a/mod/location/_location.js b/mod/location/_location.js index 948ba10264..0094e14417 100644 --- a/mod/location/_location.js +++ b/mod/location/_location.js @@ -5,8 +5,12 @@ const methods = { delete: require('./delete'), } +const workspaceCache = require('../workspace/cache') + module.exports = async (req, res) => { + const workspace = workspaceCache() + if (!Object.hasOwn(methods, req.params.method)) { return res.send(`Failed to evaluate 'method' param.

Location API`) @@ -16,9 +20,9 @@ module.exports = async (req, res) => { if (typeof method !== 'function') return; - const locale = req.params.locale && req.params.workspace.locales[req.params.locale] + const locale = req.params.locale && workspace.locales[req.params.locale] - const layer = locale && locale.layers[req.params.layer] || req.params.workspace.templates[req.params.layer] + const layer = locale && locale.layers[req.params.layer] || workspace.templates[req.params.layer] if (!layer) return res.status(400).send('Layer not found.') diff --git a/mod/location/delete.js b/mod/location/delete.js index 7734680beb..987280854f 100644 --- a/mod/location/delete.js +++ b/mod/location/delete.js @@ -1,13 +1,17 @@ const dbs = require('../utils/dbs')() +const workspaceCache = require('../workspace/cache') + module.exports = async (req, res) => { + const workspace = workspaceCache() + const layer = req.params.layer // Validate dynamic method call. - if (typeof dbs[layer.dbs || req.params.workspace.dbs] !== 'function') return; + if (typeof dbs[layer.dbs || workspace.dbs] !== 'function') return; - var rows = await dbs[layer.dbs || req.params.workspace.dbs](` + var rows = await dbs[layer.dbs || workspace.dbs](` DELETE FROM ${req.params.table} WHERE ${layer.qID} = $1;`, [req.params.id]) if (rows instanceof Error) return res.status(500).send('PostgreSQL query error - please check backend logs.') diff --git a/mod/location/get.js b/mod/location/get.js index 17326a36e9..bfbb58e3b9 100644 --- a/mod/location/get.js +++ b/mod/location/get.js @@ -4,8 +4,12 @@ const sqlFilter = require('../utils/sqlFilter.js') const Roles = require('../utils/roles.js') +const workspaceCache = require('../workspace/cache') + module.exports = async (req, res) => { + const workspace = workspaceCache() + // Check the layer.roles{} against the user.roles[] const layer = Roles.check(req.params.layer, req.params.user?.roles) @@ -38,9 +42,9 @@ module.exports = async (req, res) => { ${roleFilter}` // Validate dynamic method call. - if (!Object.hasOwn(dbs, layer.dbs || req.params.workspace.dbs) || typeof dbs[layer.dbs || req.params.workspace.dbs] !== 'function') return; + if (!Object.hasOwn(dbs, layer.dbs || workspace.dbs) || typeof dbs[layer.dbs || workspace.dbs] !== 'function') return; - const rows = await dbs[layer.dbs || req.params.workspace.dbs](q, SQLparams) + const rows = await dbs[layer.dbs || workspace.dbs](q, SQLparams) if (rows instanceof Error) return res.status(500).send('Failed to query PostGIS table.') diff --git a/mod/location/new.js b/mod/location/new.js index ef78fc866d..56976eec9d 100644 --- a/mod/location/new.js +++ b/mod/location/new.js @@ -1,7 +1,11 @@ const dbs = require('../utils/dbs')() +const workspaceCache = require('../workspace/cache') + module.exports = async (req, res) => { + const workspace = workspaceCache() + const layer = req.params.layer // vals for variable substitution in query @@ -39,9 +43,9 @@ module.exports = async (req, res) => { RETURNING ${layer.qID} AS id;` // Validate dynamic method call. - if (!Object.hasOwn(dbs, layer.dbs || req.params.workspace.dbs) || typeof dbs[layer.dbs || req.params.workspace.dbs] !== 'function') return; + if (!Object.hasOwn(dbs, layer.dbs || workspace.dbs) || typeof dbs[layer.dbs || workspace.dbs] !== 'function') return; - var rows = await dbs[layer.dbs || req.params.workspace.dbs](q, vals) + var rows = await dbs[layer.dbs || workspace.dbs](q, vals) if (rows instanceof Error) return res.status(500).send('Failed to query PostGIS table.') diff --git a/mod/location/update.js b/mod/location/update.js index 5c6c83a083..48dd225750 100644 --- a/mod/location/update.js +++ b/mod/location/update.js @@ -1,7 +1,11 @@ const dbs = require('../utils/dbs')() +const workspaceCache = require('../workspace/cache') + module.exports = async (req, res) => { + const workspace = workspaceCache() + const layer = req.params.layer const fields = Object.entries(req.body) @@ -45,11 +49,11 @@ module.exports = async (req, res) => { }) // Validate dynamic method call. - if (!Object.hasOwn(dbs, layer.dbs || req.params.workspace.dbs) || typeof dbs[layer.dbs || req.params.workspace.dbs] !== 'function') return; + if (!Object.hasOwn(dbs, layer.dbs || workspace.dbs) || typeof dbs[layer.dbs || workspace.dbs] !== 'function') return; var q = `UPDATE ${req.params.table} SET ${fields.join()} WHERE ${layer.qID} = $1;` - var rows = await dbs[layer.dbs || req.params.workspace.dbs](q, [req.params.id]) + var rows = await dbs[layer.dbs || workspace.dbs](q, [req.params.id]) if (rows instanceof Error) return res.status(500).send('PostgreSQL query error - please check backend logs.') diff --git a/mod/query.js b/mod/query.js index 14e5ba6047..a4ab6f8a3a 100644 --- a/mod/query.js +++ b/mod/query.js @@ -6,11 +6,36 @@ const Roles = require('./utils/roles.js') const logger = require('./utils/logger'); +const login = require('./user/login') + +const workspaceCache = require('./workspace/cache') + +const getTemplate = require('./workspace/getTemplate') + const getLayer = require('./workspace/getLayer'); module.exports = async (req, res) => { - const template = req.params.template + const workspace = await workspaceCache() + + if (!Object.hasOwn(workspace.templates, req.params.template)) { + + return res.status(404).send('Template not found.') + } + + const template = await getTemplate(workspace.templates[req.params.template]) + + if (template.err) return res.status(500).send(template.err.message) + + if (!req.params.user && (template.login || template.admin)) { + login(req, res, 'login_required') + return + } + + if (req.params.user && (!req.params.user.admin && template.admin)) { + login(req, res, 'admin_required') + return + } // Array of params for parameterized queries with node-pg. const SQLparams = [] @@ -22,7 +47,7 @@ module.exports = async (req, res) => { if (req.params.layer) { // Get locale for layer. - const locale = req.params.workspace.locales[req.params.locale] + const locale = workspace.locales[req.params.locale] // A layer must be found if the layer param is set. if (!locale) return res.status(400).send('Locale not found.') @@ -96,7 +121,11 @@ module.exports = async (req, res) => { // Render the query string q from tbe template and request params. try { - template.template = template.render && template.render(req.params) || template.template + if (typeof template.render === 'function') { + + req.params.workspace = workspace + template.template = template.render(req.params) + } if (!template.template) { @@ -160,7 +189,7 @@ module.exports = async (req, res) => { } // The dbs param or workspace dbs will be used as fallback if the dbs is not implicit in the template object. - const dbs_connection = String(template.dbs || req.params.dbs || req.params.workspace.dbs); + const dbs_connection = String(template.dbs || req.params.dbs || workspace.dbs); // Validate that the dbs_connection string exists as a stored connection method in dbs_connections. if (!Object.hasOwn(dbs_connections, dbs_connection)) { @@ -202,7 +231,7 @@ module.exports = async (req, res) => { return res.status(202).send('No rows returned from table.') } - if (req.params.reduced || req.params.template?.reduce) { + if (req.params.reduce || template?.reduce) { // Reduce row values to an values array. return res.send(rows.map(row=>Object.values(row))) diff --git a/mod/workspace/_workspace.js b/mod/workspace/_workspace.js index fa26af4dd4..89bac35d7c 100644 --- a/mod/workspace/_workspace.js +++ b/mod/workspace/_workspace.js @@ -4,15 +4,19 @@ const Roles = require('../utils/roles.js') const _getLayer = require('./getLayer') +const workspaceCache = require('./cache') + +let workspace; + module.exports = async (req, res) => { + workspace = await workspaceCache() + const keys = { - defaults: () => res.send(defaults), layer: getLayer, locale: getLocale, locales: getLocales, roles: getRoles, - timestamp: () => res.send(req.params.workspace.timestamp.toString()), } // The keys object must own a user provided lookup key @@ -28,8 +32,6 @@ module.exports = async (req, res) => { async function getLayer(req, res) { - const workspace = req.params.workspace - if (!Object.hasOwn(workspace.locales, req.params.locale)) { return res.status(400).send(`Unable to validate locale param.`) } @@ -59,7 +61,7 @@ function getLocales(req, res) { const roles = req.params.user?.roles || [] - const locales = Object.values(req.params.workspace.locales) + const locales = Object.values(workspace.locales) .filter(locale => !!Roles.check(locale, roles)) .map(locale => ({ key: locale.key, @@ -71,19 +73,19 @@ function getLocales(req, res) { function getLocale(req, res) { - if (req.params.locale && !Object.hasOwn(req.params.workspace.locales, req.params.locale)) { + if (req.params.locale && !Object.hasOwn(workspace.locales, req.params.locale)) { return res.status(400).send(`Unable to validate locale param.`) } let locale = {}; - if (Object.hasOwn(req.params.workspace.locales, req.params.locale)) { + if (Object.hasOwn(workspace.locales, req.params.locale)) { - locale = clone(req.params.workspace.locales?.[req.params.locale]) + locale = clone(workspace.locales?.[req.params.locale]) - } else if (typeof req.params.workspace.locale === 'object') { + } else if (typeof workspace.locale === 'object') { - locale = clone(req.params.workspace.locale) + locale = clone(workspace.locale) } const roles = req.params.user?.roles || [] @@ -102,9 +104,9 @@ function getLocale(req, res) { function getRoles(req, res) { - if (!req.params.workspace.locales) return res.send({}) + if (!workspace.locales) return res.send({}) - let roles = Roles.get(req.params.workspace) + let roles = Roles.get(workspace) res.send(roles) } \ No newline at end of file From b9aadf3ad89232a2bcd040b70dfa3204f57282f1 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 14:46:07 +0100 Subject: [PATCH 24/44] location workspaces; login routes; --- api/api.js | 12 +++++------- mod/layer/_layer.js | 2 +- mod/layer/mvt.js | 2 +- mod/location/_location.js | 2 +- mod/location/delete.js | 2 +- mod/location/get.js | 2 +- mod/location/new.js | 2 +- mod/location/update.js | 2 +- mod/query.js | 8 ++++++-- mod/user/_user.js | 13 ++++++++++--- mod/user/cookie.js | 9 ++------- mod/user/login.js | 27 +++++---------------------- mod/user/verify.js | 7 +++++-- mod/utils/languageTemplates.js | 2 +- mod/view.js | 10 ++++++++-- 15 files changed, 49 insertions(+), 53 deletions(-) diff --git a/api/api.js b/api/api.js index 673069a35e..68f95144a0 100644 --- a/api/api.js +++ b/api/api.js @@ -105,7 +105,7 @@ module.exports = async (req, res) => { } // Language param will default to english [en] is not explicitly set. - req.params.language = req.params.language || 'en' + req.params.language ??= 'en' // Assign from _template if provided as path param. req.params.template ??= req.params._template @@ -147,8 +147,10 @@ module.exports = async (req, res) => { // Remove cookie. res.setHeader('Set-Cookie', `${process.env.TITLE}=null;HttpOnly;Max-Age=0;Path=${process.env.DIR || '/'};SameSite=Strict${!req.headers.host.includes('localhost') && ';Secure' || ''}`) + req.params.msg = user.msg + // Return login view with error message. - return login(req, res, user.msg) + return login(req, res) } // Set user as request parameter. @@ -163,11 +165,7 @@ module.exports = async (req, res) => { if (req.url.match(/(?<=\/api\/user)/)) { // A msg will be returned if the user does not met the required priviliges. - const msg = routes.user(req, res) - - // Return the login view with the msg. - msg && login(req, res, msg) - return + return routes.user(req, res) } // The login view will be returned for all PRIVATE requests without a valid user. diff --git a/mod/layer/_layer.js b/mod/layer/_layer.js index 788d7cb007..ef44d52420 100644 --- a/mod/layer/_layer.js +++ b/mod/layer/_layer.js @@ -6,7 +6,7 @@ const workspaceCache = require('../workspace/cache') module.exports = async (req, res) => { - const workspace = workspaceCache() + const workspace = await workspaceCache() if (!req.params.layer) { return res.send(`Failed to evaluate 'layer' param.

diff --git a/mod/layer/mvt.js b/mod/layer/mvt.js index a47c3bab8b..5fb265d0f8 100644 --- a/mod/layer/mvt.js +++ b/mod/layer/mvt.js @@ -12,7 +12,7 @@ const workspaceCache = require('../workspace/cache') module.exports = async (req, res) => { - const workspace = workspaceCache() + const workspace = await workspaceCache() // Check the layer.roles{} against the user.roles[] const layer = Roles.check(req.params.layer, req.params.user?.roles) diff --git a/mod/location/_location.js b/mod/location/_location.js index 0094e14417..7bb1f2036b 100644 --- a/mod/location/_location.js +++ b/mod/location/_location.js @@ -9,7 +9,7 @@ const workspaceCache = require('../workspace/cache') module.exports = async (req, res) => { - const workspace = workspaceCache() + const workspace = await workspaceCache() if (!Object.hasOwn(methods, req.params.method)) { return res.send(`Failed to evaluate 'method' param.

diff --git a/mod/location/delete.js b/mod/location/delete.js index 987280854f..7bca52cb34 100644 --- a/mod/location/delete.js +++ b/mod/location/delete.js @@ -4,7 +4,7 @@ const workspaceCache = require('../workspace/cache') module.exports = async (req, res) => { - const workspace = workspaceCache() + const workspace = await workspaceCache() const layer = req.params.layer diff --git a/mod/location/get.js b/mod/location/get.js index bfbb58e3b9..fce4aa17cd 100644 --- a/mod/location/get.js +++ b/mod/location/get.js @@ -8,7 +8,7 @@ const workspaceCache = require('../workspace/cache') module.exports = async (req, res) => { - const workspace = workspaceCache() + const workspace = await workspaceCache() // Check the layer.roles{} against the user.roles[] const layer = Roles.check(req.params.layer, req.params.user?.roles) diff --git a/mod/location/new.js b/mod/location/new.js index 56976eec9d..3064e29e63 100644 --- a/mod/location/new.js +++ b/mod/location/new.js @@ -4,7 +4,7 @@ const workspaceCache = require('../workspace/cache') module.exports = async (req, res) => { - const workspace = workspaceCache() + const workspace = await workspaceCache() const layer = req.params.layer diff --git a/mod/location/update.js b/mod/location/update.js index 48dd225750..edde91083b 100644 --- a/mod/location/update.js +++ b/mod/location/update.js @@ -4,7 +4,7 @@ const workspaceCache = require('../workspace/cache') module.exports = async (req, res) => { - const workspace = workspaceCache() + const workspace = await workspaceCache() const layer = req.params.layer diff --git a/mod/query.js b/mod/query.js index a4ab6f8a3a..67484efed0 100644 --- a/mod/query.js +++ b/mod/query.js @@ -28,12 +28,16 @@ module.exports = async (req, res) => { if (template.err) return res.status(500).send(template.err.message) if (!req.params.user && (template.login || template.admin)) { - login(req, res, 'login_required') + + req.params.msg = 'login_required' + login(req, res) return } if (req.params.user && (!req.params.user.admin && template.admin)) { - login(req, res, 'admin_required') + + req.params.msg = 'admin_required' + login(req, res) return } diff --git a/mod/user/_user.js b/mod/user/_user.js index 68098915f3..a01126f016 100644 --- a/mod/user/_user.js +++ b/mod/user/_user.js @@ -54,10 +54,17 @@ module.exports = (req, res) => { User API`) } - if (!req.params.user && (method.login || method.admin)) return 'login_required' + if (!req.params.user && (method.login || method.admin)) { - if (req.params.user && (!req.params.user.admin && method.admin)) return 'admin_required' + req.params.msg = 'login_required' + return + } + + if (req.params.user && (!req.params.user.admin && method.admin)) { + + req.params.msg = 'admin_required' + return + } method.handler(req, res) - } \ No newline at end of file diff --git a/mod/user/cookie.js b/mod/user/cookie.js index afc0d27cd4..388c0001c5 100644 --- a/mod/user/cookie.js +++ b/mod/user/cookie.js @@ -16,14 +16,9 @@ module.exports = async (req, res) => { if (!cookie && req.params.renew) return res.status(401).send('Failed to renew cookie') if (!cookie) { - - // Get login view template. - const no_cookie_found = await languageTemplates({ - template: 'no_cookie_found', - language: req.params.language - }) + req.params.msg = 'no_cookie_found' - return login(req, res, no_cookie_found) + return login(req, res) } jwt.verify( diff --git a/mod/user/login.js b/mod/user/login.js index 50d626a687..efb30736e9 100644 --- a/mod/user/login.js +++ b/mod/user/login.js @@ -14,7 +14,7 @@ const view = require('../view') const { nanoid } = require('nanoid') -module.exports = async (req, res, _message) => { +module.exports = async (req, res) => { if (!acl) return res.status(500).send('ACL unavailable.') @@ -58,21 +58,13 @@ module.exports = async (req, res, _message) => { } - // Get message from templates. - const message = await languageTemplates({ - template: req.params.msg || _message, - language: req.params.language - }) - - if (!message && req.params.user) { + if (!req.params.msg && req.params.user) { res.setHeader('location', `${process.env.DIR}`) res.status(302).send() return; } - req.params.msg = message || ' ' - loginView(req, res) } @@ -130,10 +122,7 @@ async function post(req, res) { // Get user record from first row. const user = rows[0] - if (!user) return new Error(await languageTemplates({ - template: 'auth_failed', - language: req.params.language - })) + if (!user) return new Error('auth_failed') // Blocked user cannot login. if (user.blocked) return new Error(await languageTemplates({ @@ -184,10 +173,7 @@ async function post(req, res) { remote_address }) - return new Error(await languageTemplates({ - template: 'user_not_verified', - language: user.language - })) + return new Error('user_not_verified') } // Check password from post body against encrypted password from ACL. @@ -281,8 +267,5 @@ async function post(req, res) { remote_address }) - return new Error(await languageTemplates({ - template: 'auth_failed', - language: req.params.language - })) + return new Error('auth_failed') } \ No newline at end of file diff --git a/mod/user/verify.js b/mod/user/verify.js index f2001b2665..019da38e1d 100644 --- a/mod/user/verify.js +++ b/mod/user/verify.js @@ -32,9 +32,12 @@ module.exports = async (req, res) => { if (!user) { - res.setHeader('location', `${process.env.DIR}?msg=token_not_found`) + const token_not_found = await languageTemplates({ + template: 'token_not_found', + language: req.params.language + }) - return res.status(302).send() + return res.status(302).send(token_not_found) } // Update user account in ACL with the approval token and remove verification token. diff --git a/mod/utils/languageTemplates.js b/mod/utils/languageTemplates.js index 17fa70a265..4fc7751f5e 100644 --- a/mod/utils/languageTemplates.js +++ b/mod/utils/languageTemplates.js @@ -14,7 +14,7 @@ module.exports = async (params) => { if (!Object.hasOwn(workspace.templates, params.template)) { console.warn(`Template ${params.template} not found.`) - return; + return params.template; } const allLanguages = workspace.templates[params.template] diff --git a/mod/view.js b/mod/view.js index 9ddb962bd9..e89df4e236 100644 --- a/mod/view.js +++ b/mod/view.js @@ -28,6 +28,11 @@ module.exports = async (req, res) => { params.title ??= process.env.TITLE + params.msg = req.params.msg && await languageTemplates({ + template: req.params.msg, + language: req.params.language + }) + if (req.params.user && typeof req.params.user === 'object') { params.language ??= req.params.user.language @@ -42,8 +47,9 @@ module.exports = async (req, res) => { })) if (!locales.length) { - - return login(req, res, 'no_locales') + + req.params.msg = 'no_locales' + return login(req, res) } // Encode stringified user for template. From bc8b312bcc894f84a80bb893fbc19227ca86f79a Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 16:34:42 +0100 Subject: [PATCH 25/44] polynomial regular expression --- mod/workspace/getTemplate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/workspace/getTemplate.js b/mod/workspace/getTemplate.js index 45f003839a..ecdab89a8e 100644 --- a/mod/workspace/getTemplate.js +++ b/mod/workspace/getTemplate.js @@ -16,7 +16,7 @@ module.exports = async (template) => { // Substitute parameter in src string. template.src = template.src.replace(/\$\{(.*?)\}/g, - (matched) => process.env[`SRC_${matched.replace(/\$|\{|\}/g, '')}`] || matched); + (matched) => process.env[`SRC_${matched.replace(/^\$|\{|\}$/g, '')}`]); if (!Object.hasOwn(getFrom, template.src.split(':')[0])) { From 0d246d8725781abb35562c195a3729f7e50a55c5 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 16:39:43 +0100 Subject: [PATCH 26/44] var rows replacement --- mod/layer/mvt.js | 4 ++-- mod/location/delete.js | 2 +- mod/location/new.js | 2 +- mod/location/update.js | 2 +- mod/user/add.js | 4 ++-- mod/user/auth.js | 4 ++-- mod/user/delete.js | 2 +- mod/user/key.js | 4 ++-- mod/user/login.js | 10 +++++----- mod/user/register.js | 6 +++--- mod/user/saml.js | 2 +- mod/user/update.js | 2 +- mod/user/verify.js | 6 +++--- 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/mod/layer/mvt.js b/mod/layer/mvt.js index 5fb265d0f8..4efd33721c 100644 --- a/mod/layer/mvt.js +++ b/mod/layer/mvt.js @@ -155,7 +155,7 @@ module.exports = async (req, res) => { // Validate dynamic method call. if (!Object.hasOwn(dbs, layer.dbs || workspace.dbs) || typeof dbs[layer.dbs || workspace.dbs] !== 'function') return; - var rows = await dbs[layer.dbs || workspace.dbs](`SELECT mvt FROM ${layer.mvt_cache} WHERE z = ${z} AND x = ${x} AND y = ${y}`) + let rows = await dbs[layer.dbs || workspace.dbs](`SELECT mvt FROM ${layer.mvt_cache} WHERE z = ${z} AND x = ${x} AND y = ${y}`) if (rows instanceof Error) console.log('failed to query mvt cache') @@ -181,7 +181,7 @@ module.exports = async (req, res) => { // Validate dynamic method call. if (typeof dbs[layer.dbs || workspace.dbs] !== 'function') return; - var rows = await dbs[layer.dbs || workspace.dbs](tile, SQLparams) + let rows = await dbs[layer.dbs || workspace.dbs](tile, SQLparams) if (rows instanceof Error) return res.status(500).send('Failed to query PostGIS table.') diff --git a/mod/location/delete.js b/mod/location/delete.js index 7bca52cb34..5d1de3f317 100644 --- a/mod/location/delete.js +++ b/mod/location/delete.js @@ -11,7 +11,7 @@ module.exports = async (req, res) => { // Validate dynamic method call. if (typeof dbs[layer.dbs || workspace.dbs] !== 'function') return; - var rows = await dbs[layer.dbs || workspace.dbs](` + let rows = await dbs[layer.dbs || workspace.dbs](` DELETE FROM ${req.params.table} WHERE ${layer.qID} = $1;`, [req.params.id]) if (rows instanceof Error) return res.status(500).send('PostgreSQL query error - please check backend logs.') diff --git a/mod/location/new.js b/mod/location/new.js index 3064e29e63..ed371ce35d 100644 --- a/mod/location/new.js +++ b/mod/location/new.js @@ -45,7 +45,7 @@ module.exports = async (req, res) => { // Validate dynamic method call. if (!Object.hasOwn(dbs, layer.dbs || workspace.dbs) || typeof dbs[layer.dbs || workspace.dbs] !== 'function') return; - var rows = await dbs[layer.dbs || workspace.dbs](q, vals) + let rows = await dbs[layer.dbs || workspace.dbs](q, vals) if (rows instanceof Error) return res.status(500).send('Failed to query PostGIS table.') diff --git a/mod/location/update.js b/mod/location/update.js index edde91083b..e0e940a0a0 100644 --- a/mod/location/update.js +++ b/mod/location/update.js @@ -53,7 +53,7 @@ module.exports = async (req, res) => { var q = `UPDATE ${req.params.table} SET ${fields.join()} WHERE ${layer.qID} = $1;` - var rows = await dbs[layer.dbs || workspace.dbs](q, [req.params.id]) + let rows = await dbs[layer.dbs || workspace.dbs](q, [req.params.id]) if (rows instanceof Error) return res.status(500).send('PostgreSQL query error - please check backend logs.') diff --git a/mod/user/add.js b/mod/user/add.js index d2e63c35e7..236bedbe33 100644 --- a/mod/user/add.js +++ b/mod/user/add.js @@ -12,7 +12,7 @@ module.exports = async (req, res) => { const email = req.params.email.replace(/\s+/g, '') // Delete exsiting user account with same email in ACL. - var rows = await acl(` + let rows = await acl(` SELECT email FROM acl_schema.acl_table WHERE lower(email) = lower($1);`, [email]) @@ -25,7 +25,7 @@ module.exports = async (req, res) => { } // Create new user account - var rows = await acl(` + rows = await acl(` INSERT INTO acl_schema.acl_table ( email, verified, diff --git a/mod/user/auth.js b/mod/user/auth.js index 9c8a2f50eb..81bdc096e2 100644 --- a/mod/user/auth.js +++ b/mod/user/auth.js @@ -21,7 +21,7 @@ module.exports = async (req, res) => { if (process.env.NANO_SESSION) { - var rows = await acl(` + let rows = await acl(` SELECT session FROM acl_schema.acl_table WHERE lower(email) = lower($1);`, @@ -39,7 +39,7 @@ module.exports = async (req, res) => { if (user.api) { // Retrieve the original api key for the user from ACL. - var rows = await acl(` + let rows = await acl(` SELECT api, blocked FROM acl_schema.acl_table WHERE lower(email) = lower($1);`, [user.email]) diff --git a/mod/user/delete.js b/mod/user/delete.js index 734cfd453c..2ebd25a63b 100644 --- a/mod/user/delete.js +++ b/mod/user/delete.js @@ -7,7 +7,7 @@ module.exports = async (req, res) => { const email = req.params.email.replace(/\s+/g, '') // Delete user account in ACL. - var rows = await acl(` + let rows = await acl(` DELETE FROM acl_schema.acl_table WHERE lower(email) = lower($1) RETURNING *;`, [email]) diff --git a/mod/user/key.js b/mod/user/key.js index 105c4e9636..bbd0403d59 100644 --- a/mod/user/key.js +++ b/mod/user/key.js @@ -5,7 +5,7 @@ const jwt = require('jsonwebtoken') module.exports = async (req, res) => { // Get user from ACL. - var rows = await acl(` + let rows = await acl(` SELECT * FROM acl_schema.acl_table WHERE lower(email) = lower($1);`, [req.params.user.email]) @@ -25,7 +25,7 @@ module.exports = async (req, res) => { const key = jwt.sign(api_user, process.env.SECRET) // Store api_token in ACL. - var rows = await acl(` + rows = await acl(` UPDATE acl_schema.acl_table SET api = '${key}' WHERE lower(email) = lower($1);`, [user.email]) diff --git a/mod/user/login.js b/mod/user/login.js index efb30736e9..f60f1350e9 100644 --- a/mod/user/login.js +++ b/mod/user/login.js @@ -107,7 +107,7 @@ async function post(req, res) { const host = `${req.headers.host.includes('localhost') && req.headers.host || process.env.ALIAS || req.headers.host}${process.env.DIR}` // Update access_log and return user record matched by email. - var rows = await acl(` + let rows = await acl(` UPDATE acl_schema.acl_table SET access_log = array_append(access_log, '${date.toISOString().replace(/\..*/,'')}@${remote_address}') WHERE lower(email) = lower($1) @@ -140,7 +140,7 @@ async function post(req, res) { if (user.approved) { // Remove approval of expired user account. - var rows = await acl(` + rows = await acl(` UPDATE acl_schema.acl_table SET approved = false WHERE lower(email) = lower($1);`, @@ -188,7 +188,7 @@ async function post(req, res) { user.session = nano_session - var rows = await acl(` + rows = await acl(` UPDATE acl_schema.acl_table SET session = '${nano_session}' WHERE lower(email) = lower($1)`, @@ -211,7 +211,7 @@ async function post(req, res) { // Password from login form does NOT match encrypted password in ACL! // Increase failed login attempts counter by 1. - var rows = await acl(` + rows = await acl(` UPDATE acl_schema.acl_table SET failedattempts = failedattempts + 1 WHERE lower(email) = lower($1) @@ -229,7 +229,7 @@ async function post(req, res) { const verificationtoken = crypto.randomBytes(20).toString('hex') // Store verificationtoken and remove verification status. - var rows = await acl(` + rows = await acl(` UPDATE acl_schema.acl_table SET verified = false, diff --git a/mod/user/register.js b/mod/user/register.js index a573d44e9e..3d170e4106 100644 --- a/mod/user/register.js +++ b/mod/user/register.js @@ -64,7 +64,7 @@ async function post(req, res) { if (!passwordRgx.test(req.body.password)) return res.status(403).send('Invalid password provided') // Attempt to retrieve ACL record with matching email field. - var rows = await acl(` + let rows = await acl(` SELECT email, password, language, blocked FROM acl_schema.acl_table WHERE lower(email) = lower($1);`, @@ -109,7 +109,7 @@ async function post(req, res) { // Set new password and verification token. // New passwords will only apply after account verification. - var rows = await acl(` + rows = await acl(` UPDATE acl_schema.acl_table SET ${process.env.APPROVAL_EXPIRY && user.expires_on ? `expires_on = ${parseInt((new Date().getTime() + process.env.APPROVAL_EXPIRY * 1000 * 60 * 60 * 24)/1000)},` : ''} password_reset = '${password}', @@ -144,7 +144,7 @@ async function post(req, res) { const language = Intl.Collator.supportedLocalesOf([req.body.language], { localeMatcher: 'lookup' })[0] || 'en'; // Create new user account - var rows = await acl(` + rows = await acl(` INSERT INTO acl_schema.acl_table ( email, password, diff --git a/mod/user/saml.js b/mod/user/saml.js index a8b738529b..162d87d876 100644 --- a/mod/user/saml.js +++ b/mod/user/saml.js @@ -150,7 +150,7 @@ async function acl_lookup(email) { const date = new Date() // Update access_log and return user record matched by email. - var rows = await acl(` + let rows = await acl(` UPDATE acl_schema.acl_table SET access_log = array_append(access_log, '${date.toISOString().replace(/\..*/,'')}') WHERE lower(email) = lower($1) diff --git a/mod/user/update.js b/mod/user/update.js index a99235067b..a5b969d5d5 100644 --- a/mod/user/update.js +++ b/mod/user/update.js @@ -14,7 +14,7 @@ module.exports = async (req, res) => { } // Get user to update from ACL. - var rows = await acl(` + let rows = await acl(` UPDATE acl_schema.acl_table SET ${req.params.field} = $2 diff --git a/mod/user/verify.js b/mod/user/verify.js index 019da38e1d..f644743b19 100644 --- a/mod/user/verify.js +++ b/mod/user/verify.js @@ -11,7 +11,7 @@ module.exports = async (req, res) => { if (!acl) return res.status(500).send('ACL unavailable.') // Find user record with matching verificationtoken. - var rows = await acl(` + let rows = await acl(` SELECT email, language, approved, password_reset FROM acl_schema.acl_table WHERE verificationtoken = $1;`, @@ -41,7 +41,7 @@ module.exports = async (req, res) => { } // Update user account in ACL with the approval token and remove verification token. - var rows = await acl(` + rows = await acl(` UPDATE acl_schema.acl_table SET failedattempts = 0, ${user.password_reset ? @@ -75,7 +75,7 @@ module.exports = async (req, res) => { } // Get all admin accounts from the ACL. - var rows = await acl(` + rows = await acl(` SELECT email, language FROM acl_schema.acl_table WHERE admin = true;`) From 799ef06f9de8f9d26c8855ef7666ead6cb963ee7 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 16:46:49 +0100 Subject: [PATCH 27/44] group regex expression --- mod/workspace/getTemplate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/workspace/getTemplate.js b/mod/workspace/getTemplate.js index ecdab89a8e..8df5ebd971 100644 --- a/mod/workspace/getTemplate.js +++ b/mod/workspace/getTemplate.js @@ -16,7 +16,7 @@ module.exports = async (template) => { // Substitute parameter in src string. template.src = template.src.replace(/\$\{(.*?)\}/g, - (matched) => process.env[`SRC_${matched.replace(/^\$|\{|\}$/g, '')}`]); + (matched) => process.env[`SRC_${matched.replace(/(^\${)|(}$)/g, '')}`]); if (!Object.hasOwn(getFrom, template.src.split(':')[0])) { From f0fafe74bad8e591871f23b385fa5b4e4ceb70d0 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 16:48:19 +0100 Subject: [PATCH 28/44] use optional chain expression --- mod/location/_location.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/location/_location.js b/mod/location/_location.js index 7bb1f2036b..e7203b18f9 100644 --- a/mod/location/_location.js +++ b/mod/location/_location.js @@ -22,7 +22,7 @@ module.exports = async (req, res) => { const locale = req.params.locale && workspace.locales[req.params.locale] - const layer = locale && locale.layers[req.params.layer] || workspace.templates[req.params.layer] + const layer = locale?.layers[req.params.layer] || workspace.templates[req.params.layer] if (!layer) return res.status(400).send('Layer not found.') From cafa2658083c275e23dea3f95e8eb8aefc43e218 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 16:54:04 +0100 Subject: [PATCH 29/44] regex capture groups --- mod/view.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mod/view.js b/mod/view.js index e89df4e236..c4ab3ce9f8 100644 --- a/mod/view.js +++ b/mod/view.js @@ -67,11 +67,11 @@ module.exports = async (req, res) => { const template = await languageTemplates(params) - const view = template.replace(/[{]{2}([A-Za-z][A-Za-z0-9]*)[}]{2}/g, matched => { + const view = template.replace(/{{2}([A-Za-z][A-Za-z0-9]*)}{2}/g, matched => { - // regex matches {{ or }} - return params[matched.replace(/[{]{2}|[}]{2}/g, '')] || ''; - }); + // regex matches {{ or }} + return params[matched.replace(/(^{{)|(}}$)/g, '')] || '' + }); res.send(view); } \ No newline at end of file From 69c53632754f496bed68a5e280f157eabe67ab63 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 17:43:09 +0100 Subject: [PATCH 30/44] remove layer folder; move mvt mod; update getLayer --- api/api.js | 4 +-- mod/layer/_layer.js | 50 ------------------------------------- mod/{layer => }/mvt.js | 24 ++++++++++-------- mod/query.js | 10 +++----- mod/workspace/_workspace.js | 24 +++++++++--------- mod/workspace/getLayer.js | 20 ++++++--------- 6 files changed, 38 insertions(+), 94 deletions(-) delete mode 100644 mod/layer/_layer.js rename mod/{layer => }/mvt.js (90%) diff --git a/api/api.js b/api/api.js index 68f95144a0..4b55fda9b2 100644 --- a/api/api.js +++ b/api/api.js @@ -9,7 +9,7 @@ const auth = require('../mod/user/auth') const saml = process.env.SAML_ENTITY_ID && require('../mod/user/saml') const routes = { - layer: require('../mod/layer/_layer'), + mvt: require('../mod/mvt'), location: require('../mod/location/_location'), provider: require('../mod/provider/_provider'), query: require('../mod/query'), @@ -181,7 +181,7 @@ module.exports = async (req, res) => { // Layer route if (req.url.match(/(?<=\/api\/layer)/)) { - return routes.layer(req, res) + return routes.mvt(req, res) } // Location route diff --git a/mod/layer/_layer.js b/mod/layer/_layer.js deleted file mode 100644 index ef44d52420..0000000000 --- a/mod/layer/_layer.js +++ /dev/null @@ -1,50 +0,0 @@ -const formats = { - mvt: require('./mvt') -} - -const workspaceCache = require('../workspace/cache') - -module.exports = async (req, res) => { - - const workspace = await workspaceCache() - - if (!req.params.layer) { - return res.send(`Failed to evaluate 'layer' param.

- Layer API`) - } - - // The format key must be an own property of the formats object. - if (!Object.hasOwn(formats, req.params.format)) { - return res.send(`Failed to evaluate 'format' param.

- Layer API`) - } - - if (req.params.locale) { - - // The locale key must be an own property of the workspace.locales, and must be an object. - if (Object.hasOwn(workspace.locales, req.params.locale) - && typeof workspace.locales[req.params.locale] === 'object') { - - // Assign layer from locale in workspace. - req.params.layer = workspace.locales[req.params.locale].layers[req.params.layer] - - } else { - - // Terminate request if locale is defined but not valid. - return res.send(`Failed to evaluate locale param.`) - } - - // A layer must be specified in the templates without a locale specifier, and must be an object - } else if (Object.hasOwn(workspace.templates, req.params.layer) - && typeof workspace.templates[req.params.layer] === 'object') { - - // Assign layer object from templates. - req.params.layer = workspace.templates[req.params.layer] - - } else { - - return res.send(`Failed to evaluate layer param.`) - } - - return formats[req.params.format](req, res) -} \ No newline at end of file diff --git a/mod/layer/mvt.js b/mod/mvt.js similarity index 90% rename from mod/layer/mvt.js rename to mod/mvt.js index 4efd33721c..19c6544a22 100644 --- a/mod/layer/mvt.js +++ b/mod/mvt.js @@ -1,25 +1,29 @@ -const dbs = require('../utils/dbs')() +const dbs = require('./utils/dbs')() -const sqlFilter = require('../utils/sqlFilter') +const sqlFilter = require('./utils/sqlFilter') -const validateRequestParams = require('../utils/validateRequestParams') +const validateRequestParams = require('./utils/validateRequestParams') -const Roles = require('../utils/roles.js') +const Roles = require('./utils/roles.js') -const logger = require('../utils/logger') +const logger = require('./utils/logger') -const workspaceCache = require('../workspace/cache') +const workspaceCache = require('./workspace/cache') + +const getLayer = require('./workspace/getLayer') module.exports = async (req, res) => { const workspace = await workspaceCache() - // Check the layer.roles{} against the user.roles[] - const layer = Roles.check(req.params.layer, req.params.user?.roles) + const layer = await getLayer(req.params) - // The layer object did not pass the Roles.check() if (!layer) { - return res.status(403).send('Access prohibited.') + return res.status(403).send('Layer not found.') + } + + if (!Roles.check(layer, req.params.user?.roles)) { + return res.status(403).send('Role access denied for layer.') } // Validate URL parameter diff --git a/mod/query.js b/mod/query.js index 67484efed0..84a6043a4a 100644 --- a/mod/query.js +++ b/mod/query.js @@ -61,14 +61,10 @@ module.exports = async (req, res) => { return res.status(400).send('Layer not found.') } - let layer = await getLayer(req) + const layer = await getLayer(req.params) - // Get layer from locale. - layer = Roles.check(layer, req.params.user?.roles) - - if (!layer) { - - return res.status(403).send('Access prohibited.') + if (!Roles.check(layer, req.params.user?.roles)) { + return res.status(403).send('Role access denied for layer.') } // Set layer dbs as fallback param if not defined. diff --git a/mod/workspace/_workspace.js b/mod/workspace/_workspace.js index 89bac35d7c..917ecf8596 100644 --- a/mod/workspace/_workspace.js +++ b/mod/workspace/_workspace.js @@ -2,7 +2,7 @@ const clone = require('../utils/clone.js') const Roles = require('../utils/roles.js') -const _getLayer = require('./getLayer') +const getLayer = require('./getLayer') const workspaceCache = require('./cache') @@ -13,10 +13,10 @@ module.exports = async (req, res) => { workspace = await workspaceCache() const keys = { - layer: getLayer, - locale: getLocale, - locales: getLocales, - roles: getRoles, + layer, + locale, + locales, + roles, } // The keys object must own a user provided lookup key @@ -30,7 +30,7 @@ module.exports = async (req, res) => { return keys[req.params.key](req, res) } -async function getLayer(req, res) { +async function layer(req, res) { if (!Object.hasOwn(workspace.locales, req.params.locale)) { return res.status(400).send(`Unable to validate locale param.`) @@ -41,23 +41,23 @@ async function getLayer(req, res) { const roles = req.params.user?.roles || [] if (!Roles.check(locale, roles)) { - return res.status(403).send('Role access denied.') + return res.status(403).send('Role access denied for locale.') } if (!Object.hasOwn(locale.layers, req.params.layer)) { return res.status(400).send(`Unable to validate layer param.`) } - const layer = await _getLayer(req) + const layer = await getLayer(req.params) if (!Roles.check(layer, roles)) { - return res.status(403).send('Role access denied.') + return res.status(403).send('Role access denied for layer.') } res.json(layer) } -function getLocales(req, res) { +function locales(req, res) { const roles = req.params.user?.roles || [] @@ -71,7 +71,7 @@ function getLocales(req, res) { res.send(locales) } -function getLocale(req, res) { +function locale(req, res) { if (req.params.locale && !Object.hasOwn(workspace.locales, req.params.locale)) { return res.status(400).send(`Unable to validate locale param.`) @@ -102,7 +102,7 @@ function getLocale(req, res) { res.json(locale) } -function getRoles(req, res) { +function roles(req, res) { if (!workspace.locales) return res.send({}) diff --git a/mod/workspace/getLayer.js b/mod/workspace/getLayer.js index 7617f4eaae..575378c3f2 100644 --- a/mod/workspace/getLayer.js +++ b/mod/workspace/getLayer.js @@ -6,30 +6,30 @@ const getTemplate = require('./getTemplate') const workspaceCache = require('./cache') -module.exports = async (req) => { +module.exports = async (params) => { const workspace = await workspaceCache() - if (!Object.hasOwn(workspace.locales, req.params.locale)) { + if (!Object.hasOwn(workspace.locales, params.locale)) { return new Error('Unable to validate locale param.') //400 } - const locale = workspace.locales[req.params.locale] + const locale = workspace.locales[params.locale] - const roles = req.params.user?.roles || [] + const roles = params.user?.roles || [] if (!Roles.check(locale, roles)) { return new Error('Role access denied.') //403 } - if (!Object.hasOwn(locale.layers, req.params.layer)) { + if (!Object.hasOwn(locale.layers, params.layer)) { return new Error('Unable to validate layer param.') //400 } - const layer = locale.layers[req.params.layer] + const layer = locale.layers[params.layer] // Assign key value as key on layer object. - layer.key ??= req.params.layer + layer.key ??= params.layer if (Object.hasOwn(workspace.templates, layer.template || layer.key)) { @@ -53,11 +53,5 @@ module.exports = async (req) => { // Assign layer key as name with no existing name on layer object. layer.name ??= layer.key - //const layer = clone(locale.layers[req.params.layer]) - - // if (!Roles.check(layer, roles)) { - // return res.status(403).send('Role access denied.') - // } - return layer } \ No newline at end of file From c29efb1bbe86871e6c5ab2e9d8890a08159b4261 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 17:50:12 +0100 Subject: [PATCH 31/44] mvt complexity --- mod/mvt.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/mod/mvt.js b/mod/mvt.js index 19c6544a22..a7711e8bb1 100644 --- a/mod/mvt.js +++ b/mod/mvt.js @@ -94,11 +94,11 @@ module.exports = async (req, res) => { const geoms = layer.geoms && Object.keys(layer.geoms) - var geom = geoms && layer.geoms[z] || layer.geom + let geom = geoms && layer.geoms[z] || layer.geom - var geom = geoms && z < parseInt(geoms[0]) && Object.values(layer.geoms)[0] || geom + geom = geoms && z < parseInt(geoms[0]) && Object.values(layer.geoms)[0] || geom - var geom = geoms && z > parseInt(geoms[geoms.length -1]) && Object.values(layer.geoms)[geoms.length -1] || geom + geom = geoms && z > parseInt(geoms[geoms.length -1]) && Object.values(layer.geoms)[geoms.length -1] || geom if (!geom) { return res.status(204).send(null) @@ -172,14 +172,13 @@ module.exports = async (req, res) => { WITH n AS ( INSERT INTO ${layer.mvt_cache} ${tile} ON CONFLICT (z, x, y) DO NOTHING RETURNING mvt - ) SELECT mvt FROM n; - `) + ) SELECT mvt FROM n;`) if (rows instanceof Error) console.log('failed to cache mvt') } - if (rows.length === 1) return res.send(rows[0].mvt) // If found return the cached MVT to client. - + // If found return the cached MVT to client. + if (rows.length === 1) return res.send(rows[0].mvt) } // Validate dynamic method call. @@ -191,8 +190,5 @@ module.exports = async (req, res) => { logger(`Get tile ${z}/${x}/${y}`, 'mvt') - // Return MVT to client. - // res.setHeader('Cache-Control', 's-maxage=1, stale-while-revalidate') res.send(rows[0].mvt) - } \ No newline at end of file From a7be88c0e9cddef39bc0cf1fc47f5687f2feb9ef Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 18:01:20 +0100 Subject: [PATCH 32/44] mvt template refactor --- mod/mvt.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mod/mvt.js b/mod/mvt.js index a7711e8bb1..85e3c664d6 100644 --- a/mod/mvt.js +++ b/mod/mvt.js @@ -45,17 +45,17 @@ module.exports = async (req, res) => { m = 20037508.34, r = (m * 2) / (Math.pow(2, z)) - const roles = Roles.filter(layer, req.params.user && req.params.user.roles) + const roleFilter = Roles.filter(layer, req.params.user?.roles) const SQLparams = [] - const filter = - `${req.params.filter && ` AND ${sqlFilter(JSON.parse(req.params.filter), SQLparams)}` || ''}` - +`${roles && Object.values(roles).some(r => !!r) - ? `AND ${sqlFilter(Object.values(roles).filter(r => !!r), SQLparams)}` - : ''}` + const filter = [ + req.params.filter? + ` AND ${sqlFilter(JSON.parse(req.params.filter), SQLparams)}`: '', + roleFilter && Object.values(roleFilter).some(r => !!r)? + ` AND ${sqlFilter(Object.values(roles).filter(r => !!r), SQLparams)}`: ''].join('') - // Construct array of fields queried + // Construct array of fields queried let mvt_fields = Object.values(layer.style?.themes || {}) .map(theme => getField(theme)) .filter(field => typeof field !== 'undefined') From fd19449c1b981781adc44e129e460c4560f18a85 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 18:33:06 +0100 Subject: [PATCH 33/44] Remove wiki link in 400 message --- mod/workspace/_workspace.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mod/workspace/_workspace.js b/mod/workspace/_workspace.js index 917ecf8596..32277073af 100644 --- a/mod/workspace/_workspace.js +++ b/mod/workspace/_workspace.js @@ -22,9 +22,7 @@ module.exports = async (req, res) => { // The keys object must own a user provided lookup key if (!Object.hasOwn(keys, req.params.key)) { - return res.send(` - Failed to evaluate 'key' param.

- Workspace API`) + return res.status(400).send(`Failed to evaluate '${req.params.key}' param.`) } return keys[req.params.key](req, res) From 3eafd595a724389a1f17a4d35867486e750bee9a Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 27 Oct 2023 18:57:10 +0100 Subject: [PATCH 34/44] query templates --- mod/workspace/cache.js | 56 +++--------------------------- mod/workspace/templates/queries.js | 51 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 51 deletions(-) create mode 100644 mod/workspace/templates/queries.js diff --git a/mod/workspace/cache.js b/mod/workspace/cache.js index 0f11978ae3..0b5ac3e929 100644 --- a/mod/workspace/cache.js +++ b/mod/workspace/cache.js @@ -38,6 +38,8 @@ const mail_templates = require('./templates/mails') const msg_templates = require('./templates/msgs') +const query_templates = require('./templates/queries') + async function cache() { // Get workspace from source. @@ -56,58 +58,10 @@ async function cache() { ...view_templates, ...mail_templates, ...msg_templates, - ...custom_templates, + ...query_templates, - // Query templates: - gaz_query: { - template: require('./templates/gaz_query'), - }, - get_last_location: { - template: require('./templates/get_last_location'), - }, - distinct_values: { - template: require('./templates/distinct_values'), - }, - field_stats: { - template: require('./templates/field_stats'), - }, - field_min: { - template: require('./templates/field_min'), - }, - field_max: { - template: require('./templates/field_max'), - }, - get_nnearest: { - render: require('./templates/get_nnearest'), - }, - geojson: { - render: require('./templates/geojson'), - }, - cluster: { - render: require('./templates/cluster'), - reduce: true - }, - cluster_hex: { - render: require('./templates/cluster_hex'), - reduce: true - }, - wkt: { - render: require('./templates/wkt'), - reduce: true - }, - infotip: { - render: require('./templates/infotip'), - }, - layer_extent: { - template: require('./templates/layer_extent'), - }, - mvt_cache: { - admin: true, - render: require('./templates/mvt_cache'), - }, - mvt_cache_delete_intersects: { - template: require('./templates/mvt_cache_delete_intersects'), - }, + // Can override default templates. + ...custom_templates, // Default templates can be overridden by assigning a template with the same name. ...workspace.templates diff --git a/mod/workspace/templates/queries.js b/mod/workspace/templates/queries.js new file mode 100644 index 0000000000..6f515a50fb --- /dev/null +++ b/mod/workspace/templates/queries.js @@ -0,0 +1,51 @@ +module.exports = { + gaz_query: { + template: require('./gaz_query'), + }, + get_last_location: { + template: require('./get_last_location'), + }, + distinct_values: { + template: require('./distinct_values'), + }, + field_stats: { + template: require('./field_stats'), + }, + field_min: { + template: require('./field_min'), + }, + field_max: { + template: require('./field_max'), + }, + get_nnearest: { + render: require('./get_nnearest'), + }, + geojson: { + render: require('./geojson'), + }, + cluster: { + render: require('./cluster'), + reduce: true + }, + cluster_hex: { + render: require('./cluster_hex'), + reduce: true + }, + wkt: { + render: require('./wkt'), + reduce: true + }, + infotip: { + render: require('./infotip'), + }, + layer_extent: { + template: require('./layer_extent'), + }, + mvt_cache: { + admin: true, + render: require('./mvt_cache'), + }, + mvt_cache_delete_intersects: { + template: require('./mvt_cache_delete_intersects'), + } +} \ No newline at end of file From 9b2e65d7aa28c6aae18186f07657a912f0f85f14 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Tue, 31 Oct 2023 18:47:47 +0000 Subject: [PATCH 35/44] workspace cache --- mod/provider/cloudfront.js | 2 -- mod/workspace/cache.js | 42 ++++++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/mod/provider/cloudfront.js b/mod/provider/cloudfront.js index d4107b0f60..1ccbe14b82 100644 --- a/mod/provider/cloudfront.js +++ b/mod/provider/cloudfront.js @@ -41,8 +41,6 @@ module.exports = async ref => { } catch(err) { console.error(err) - logger(`err - ${url}`,'cloudfront') - return; } } \ No newline at end of file diff --git a/mod/workspace/cache.js b/mod/workspace/cache.js index 0b5ac3e929..9757e76139 100644 --- a/mod/workspace/cache.js +++ b/mod/workspace/cache.js @@ -4,32 +4,31 @@ const merge = require('../utils/merge') process.env.WORKSPACE_AGE ??= 3600000 -let workspace = null +let cache = null + +let timestamp = Infinity const logger = require('../utils/logger') -module.exports = async () => { +module.exports = () => { - const timestamp = Date.now() + // cache is null on first request for workspace. + // cacheWorkspace is async and must be awaited. + if (!cache) return cacheWorkspace() - // Cache workspace if empty. - if (!workspace) { - await cache() - logger(`Workspace empty; Time to cache: ${Date.now() - timestamp}`, 'workspace') - } + // cacheWorkspace will set the current timestamp + // and cache workspace outside export closure prior to returning workspace. + if ((Date.now() - timestamp) > +process.env.WORKSPACE_AGE) { - // Logically assign timestamp. - workspace.timestamp ??= timestamp + // current time minus cached timestamp exceeds WORKSPACE_AGE + delete cache - // Cache workspace if expired. - if ((timestamp - workspace.timestamp) > +process.env.WORKSPACE_AGE) { + logger(`Workspace cache expired;`, 'workspace') - await cache() - logger(`Workspace cache expired; Time to cache: ${Date.now() - timestamp}`, 'workspace') - workspace.timestamp = timestamp + return cacheWorkspace() } - return workspace + return cache } const view_templates = require('./templates/views') @@ -40,14 +39,16 @@ const msg_templates = require('./templates/msgs') const query_templates = require('./templates/queries') -async function cache() { +async function cacheWorkspace() { // Get workspace from source. workspace = process.env.WORKSPACE ? await getFrom[process.env.WORKSPACE.split(':')[0]](process.env.WORKSPACE) : {} // Return error if source failed. - if (workspace instanceof Error) return workspace + if (workspace instanceof Error) { + return {}; + } const custom_templates = process.env.CUSTOM_TEMPLATES && await getFrom[process.env.CUSTOM_TEMPLATES.split(':')[0]](process.env.CUSTOM_TEMPLATES) @@ -113,4 +114,9 @@ async function cache() { matched => process.env[`SRC_${matched.replace(/\$|\{|\}/g, '')}`] || matched) ) + timestamp = Date.now() + + cache = workspace + + return workspace } \ No newline at end of file From 11e39510878c50dc88744b3ac2b147f28c61e85f Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Wed, 1 Nov 2023 09:36:22 +0000 Subject: [PATCH 36/44] remove workspace cache code smells --- mod/workspace/cache.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mod/workspace/cache.js b/mod/workspace/cache.js index 9757e76139..c2befe7e16 100644 --- a/mod/workspace/cache.js +++ b/mod/workspace/cache.js @@ -21,7 +21,7 @@ module.exports = () => { if ((Date.now() - timestamp) > +process.env.WORKSPACE_AGE) { // current time minus cached timestamp exceeds WORKSPACE_AGE - delete cache + cache = null logger(`Workspace cache expired;`, 'workspace') @@ -42,7 +42,7 @@ const query_templates = require('./templates/queries') async function cacheWorkspace() { // Get workspace from source. - workspace = process.env.WORKSPACE ? + const workspace = process.env.WORKSPACE ? await getFrom[process.env.WORKSPACE.split(':')[0]](process.env.WORKSPACE) : {} // Return error if source failed. From d894f185e948725d6da76202dca5b0c61697fc25 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 2 Nov 2023 19:35:18 +0000 Subject: [PATCH 37/44] getLocale; role checks; remove clone; logout msg; xhr resolution; --- api/api.js | 2 +- lib/utils/xhr.mjs | 39 +++--- mod/location/_location.js | 33 ++--- mod/location/get.js | 4 +- mod/mvt.js | 2 +- mod/query.js | 15 +-- mod/user/_user.js | 3 +- mod/utils/clone.js | 24 ---- mod/view.js | 27 ---- mod/workspace/_workspace.js | 26 ++-- mod/workspace/cache.js | 23 +--- mod/workspace/getLayer.js | 21 ++- mod/workspace/getLocale.js | 33 +++++ mod/workspace/templates/msgs.js | 231 ++++++++++++++++---------------- public/views/_default.js | 17 ++- 15 files changed, 242 insertions(+), 258 deletions(-) delete mode 100644 mod/utils/clone.js create mode 100644 mod/workspace/getLocale.js diff --git a/api/api.js b/api/api.js index 4b55fda9b2..5787ffbc63 100644 --- a/api/api.js +++ b/api/api.js @@ -130,7 +130,7 @@ module.exports = async (req, res) => { res.setHeader('Set-Cookie', `${process.env.TITLE}=null;HttpOnly;Max-Age=0;Path=${process.env.DIR || '/'}`) // Remove logout parameter. - res.setHeader('location', `${process.env.DIR || '/'}`) + res.setHeader('location', (process.env.DIR || '/') + (req.params.msg && `?msg=${req.params.msg}`||'')) return res.status(302).send() } diff --git a/lib/utils/xhr.mjs b/lib/utils/xhr.mjs index 1a59cd6011..7a66eb0d75 100644 --- a/lib/utils/xhr.mjs +++ b/lib/utils/xhr.mjs @@ -1,28 +1,42 @@ const requestMap = new Map() -export default params => new Promise((resolve, reject) => { +export default params => new Promise(resolve => { - if (!params) return reject() + // Return if params are falsy. + if (!params) { + console.error(`xhr params are falsy.`) + return; + } + // Set params as object with url from string. params = typeof params === 'string' ? { url: params } : params + // A request url must be provided. + if (!params.url) { + console.error(`no xhr request url has been provided.`) + return; + }; + // Check whether a request with the same params has already been resolved. if (params.cache && requestMap.has(params)) return resolve(requestMap.get(params)) + // Assign 'GET' as default method. + params.method ??= 'GET' + const xhr = new XMLHttpRequest() - xhr.open(params.method || 'GET', params.url) + xhr.open(params.method, params.url) // Use requestHeader: null to prevent assignment of requestHeader. if (params.requestHeader !== null) { + // Butter (spread) over requestHeader. const requestHeader = { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + ...params.requestHeader } - - Object.assign(requestHeader, params.requestHeader || {}) - - Object.entries(requestHeader).forEach(entry=>xhr.setRequestHeader(...entry)) + + Object.entries(requestHeader).forEach(entry => xhr.setRequestHeader(...entry)) } xhr.responseType = params.responseType || 'json' @@ -30,7 +44,7 @@ export default params => new Promise((resolve, reject) => { xhr.onload = e => { if (e.target.status >= 400) { - reject(new Error(e.target.status)) + resolve(new Error(e.target.status)) return; } @@ -38,14 +52,7 @@ export default params => new Promise((resolve, reject) => { params.cache && requestMap.set(params, e.target.response) resolve(params.resolveTarget ? e.target : e.target.response) - } - // xhr.onerror = err => { - // console.error(err) - // reject(err); - // }; - xhr.send(params.body) - }) \ No newline at end of file diff --git a/mod/location/_location.js b/mod/location/_location.js index e7203b18f9..6c9c4a0310 100644 --- a/mod/location/_location.js +++ b/mod/location/_location.js @@ -5,34 +5,27 @@ const methods = { delete: require('./delete'), } -const workspaceCache = require('../workspace/cache') +const Roles = require('../utils/roles') -module.exports = async (req, res) => { +const getLayer = require('../workspace/getLayer') - const workspace = await workspaceCache() +module.exports = async (req, res) => { if (!Object.hasOwn(methods, req.params.method)) { - return res.send(`Failed to evaluate 'method' param.

- Location API`) + return res.send(`Failed to evaluate 'method' param.`) } - const method = methods[req.params.method] - - if (typeof method !== 'function') return; - - const locale = req.params.locale && workspace.locales[req.params.locale] - - const layer = locale?.layers[req.params.layer] || workspace.templates[req.params.layer] + const layer = await getLayer(req.params) - if (!layer) return res.status(400).send('Layer not found.') - - req.params.layer = layer + if (layer instanceof Error) { + return res.status(400).send('Failed to access layer.') + } - if (!req.params.layer) { - return res.status(400).send(`Failed to evaluate 'layer' param.

- Location API`) + if (!Roles.check(layer, req.params.user?.roles)) { + return res.status(403).send('Role access denied for layer.') } - return method(req, res) - + req.params.layer = layer + + return methods[req.params.method](req, res) } diff --git a/mod/location/get.js b/mod/location/get.js index fce4aa17cd..3d406c519e 100644 --- a/mod/location/get.js +++ b/mod/location/get.js @@ -1,8 +1,8 @@ const dbs = require('../utils/dbs')() -const sqlFilter = require('../utils/sqlFilter.js') +const sqlFilter = require('../utils/sqlFilter') -const Roles = require('../utils/roles.js') +const Roles = require('../utils/roles') const workspaceCache = require('../workspace/cache') diff --git a/mod/mvt.js b/mod/mvt.js index 85e3c664d6..f30897f1aa 100644 --- a/mod/mvt.js +++ b/mod/mvt.js @@ -4,7 +4,7 @@ const sqlFilter = require('./utils/sqlFilter') const validateRequestParams = require('./utils/validateRequestParams') -const Roles = require('./utils/roles.js') +const Roles = require('./utils/roles') const logger = require('./utils/logger') diff --git a/mod/query.js b/mod/query.js index 84a6043a4a..568e7f7fc2 100644 --- a/mod/query.js +++ b/mod/query.js @@ -2,7 +2,7 @@ const dbs_connections = require('./utils/dbs')() const sqlFilter = require('./utils/sqlFilter') -const Roles = require('./utils/roles.js') +const Roles = require('./utils/roles') const logger = require('./utils/logger'); @@ -50,19 +50,12 @@ module.exports = async (req, res) => { // Assign role filter and viewport params from layer object. if (req.params.layer) { - // Get locale for layer. - const locale = workspace.locales[req.params.locale] - - // A layer must be found if the layer param is set. - if (!locale) return res.status(400).send('Locale not found.') - - if (!Object.hasOwn(locale.layers, req.params.layer)) { + const layer = await getLayer(req.params) - return res.status(400).send('Layer not found.') + if (layer instanceof Error) { + return res.status(400).send('Failed to access layer.') } - const layer = await getLayer(req.params) - if (!Roles.check(layer, req.params.user?.roles)) { return res.status(403).send('Role access denied for layer.') } diff --git a/mod/user/_user.js b/mod/user/_user.js index a01126f016..07db2d0afb 100644 --- a/mod/user/_user.js +++ b/mod/user/_user.js @@ -50,8 +50,7 @@ module.exports = (req, res) => { const method = methods[req.params.method] if (!method) { - return res.send(`Failed to evaluate 'method' param.

- User API`) + return res.send(`Failed to evaluate 'method' param.`) } if (!req.params.user && (method.login || method.admin)) { diff --git a/mod/utils/clone.js b/mod/utils/clone.js deleted file mode 100644 index f3024b3bc9..0000000000 --- a/mod/utils/clone.js +++ /dev/null @@ -1,24 +0,0 @@ -module.exports = function clone(target, map = new WeakMap()) { - - // Return the value if target is not an object or null - if (target === null - || typeof target !== 'object' - || typeof target === 'function') { - return target - } - - // Check whether target is an array. - let cloneTarget = Array.isArray(target) ? [] : {}; - - // Use WeakMap to prevent circular references. - if (map.get(target)) { - return map.get(target); - } - map.set(target, cloneTarget); - - for (const key in target) { - cloneTarget[key] = clone(target[key], map); - } - - return cloneTarget; -}; \ No newline at end of file diff --git a/mod/view.js b/mod/view.js index c4ab3ce9f8..bd7a9797e4 100644 --- a/mod/view.js +++ b/mod/view.js @@ -1,17 +1,9 @@ -const Roles = require('./utils/roles.js') - -const login = require('./user/login') - const logger = require('./utils/logger') const languageTemplates = require('./utils/languageTemplates') -const workspaceCache = require('./workspace/cache') - module.exports = async (req, res) => { - const workspace = await workspaceCache() - logger(req.url, 'view-req-url') const params = {} @@ -37,21 +29,6 @@ module.exports = async (req, res) => { params.language ??= req.params.user.language - const roles = req.params.user?.roles || [] - - const locales = Object.values(workspace.locales) - .filter(locale => !!Roles.check(locale, roles)) - .map(locale => ({ - key: locale.key, - name: locale.name - })) - - if (!locales.length) { - - req.params.msg = 'no_locales' - return login(req, res) - } - // Encode stringified user for template. params.user ??= encodeURI(JSON.stringify({ email: req.params.user.email, @@ -61,10 +38,6 @@ module.exports = async (req, res) => { })); } - // Object.entries(process.env) - // .filter(entry => entry[0].match(/^SRC_/)) - // .forEach(entry => params[entry[0].replace(/^SRC_/, '')]=entry[1]) - const template = await languageTemplates(params) const view = template.replace(/{{2}([A-Za-z][A-Za-z0-9]*)}{2}/g, matched => { diff --git a/mod/workspace/_workspace.js b/mod/workspace/_workspace.js index 32277073af..c93e4e1992 100644 --- a/mod/workspace/_workspace.js +++ b/mod/workspace/_workspace.js @@ -1,10 +1,10 @@ -const clone = require('../utils/clone.js') +const Roles = require('../utils/roles') -const Roles = require('../utils/roles.js') +const workspaceCache = require('./cache') -const getLayer = require('./getLayer') +const getLocale = require('./getLocale') -const workspaceCache = require('./cache') +const getLayer = require('./getLayer') let workspace; @@ -69,21 +69,25 @@ function locales(req, res) { res.send(locales) } -function locale(req, res) { +async function locale(req, res) { if (req.params.locale && !Object.hasOwn(workspace.locales, req.params.locale)) { return res.status(400).send(`Unable to validate locale param.`) } - let locale = {}; + let locale; if (Object.hasOwn(workspace.locales, req.params.locale)) { - locale = clone(workspace.locales?.[req.params.locale]) + locale = await getLocale(req.params) } else if (typeof workspace.locale === 'object') { - locale = clone(workspace.locale) + locale = workspace.locale + } + + if (locale instanceof Error) { + return res.status(400).send('Failed to access locale.') } const roles = req.params.user?.roles || [] @@ -92,6 +96,12 @@ function locale(req, res) { return res.status(403).send('Role access denied.') } + // Subtitutes ${*} in locale with process.env.SRC_* values. + locale = JSON.parse( + JSON.stringify(locale).replace(/\$\{(.*?)\}/g, + matched => process.env[`SRC_${matched.replace(/\$|\{|\}/g, '')}`] || matched) + ) + // Check layer access. locale.layers = locale.layers && Object.entries(locale.layers) .filter(layer => !!Roles.check(layer[1], roles)) diff --git a/mod/workspace/cache.js b/mod/workspace/cache.js index c2befe7e16..e2dd8f98a2 100644 --- a/mod/workspace/cache.js +++ b/mod/workspace/cache.js @@ -82,25 +82,14 @@ async function cacheWorkspace() { // Get locale object from key. const locale = workspace.locales[locale_key] - // A default locale has been defined in the workspace. - if (typeof workspace.locale === 'object') { - - // Merge the workspace template into workspace. - merge(locale, workspace.locale) - } - - // A template exists for the locale key. - if (Object.hasOwn(workspace.templates, locale_key) && typeof workspace.templates[locale_key] === 'object') { - - // Merge the workspace template into workspace. - merge(locale, workspace.templates[locale_key]) - } + // Merge the workspace template into workspace. + merge(locale, workspace.locale) // Assign key value as key on locale object. locale.key = locale_key // Assign locale key as name with no existing name on locale object. - locale.name = locale.name || locale_key + locale.name ??= locale_key }) if (workspace.plugins) { @@ -108,12 +97,6 @@ async function cacheWorkspace() { console.warn(`Default plugins should be defined in the default workspace.locale{}`) } - // Substitute all SRC_* variables in locales. - workspace.locales = JSON.parse( - JSON.stringify(workspace.locales).replace(/\$\{(.*?)\}/g, - matched => process.env[`SRC_${matched.replace(/\$|\{|\}/g, '')}`] || matched) - ) - timestamp = Date.now() cache = workspace diff --git a/mod/workspace/getLayer.js b/mod/workspace/getLayer.js index 575378c3f2..8aa9675d96 100644 --- a/mod/workspace/getLayer.js +++ b/mod/workspace/getLayer.js @@ -1,29 +1,36 @@ -const Roles = require('../utils/roles.js') +const Roles = require('../utils/roles') const merge = require('../utils/merge') -const getTemplate = require('./getTemplate') - const workspaceCache = require('./cache') +const getLocale = require('./getLocale') + +const getTemplate = require('./getTemplate') + module.exports = async (params) => { const workspace = await workspaceCache() if (!Object.hasOwn(workspace.locales, params.locale)) { - return new Error('Unable to validate locale param.') //400 + return new Error('Unable to validate locale param.') } - const locale = workspace.locales[params.locale] + const locale = await getLocale(params) + + if (locale instanceof Error) { + + return locale + } const roles = params.user?.roles || [] if (!Roles.check(locale, roles)) { - return new Error('Role access denied.') //403 + return new Error('Role access denied.') } if (!Object.hasOwn(locale.layers, params.layer)) { - return new Error('Unable to validate layer param.') //400 + return new Error('Unable to validate layer param.') } const layer = locale.layers[params.layer] diff --git a/mod/workspace/getLocale.js b/mod/workspace/getLocale.js new file mode 100644 index 0000000000..0ef979cc06 --- /dev/null +++ b/mod/workspace/getLocale.js @@ -0,0 +1,33 @@ +const Roles = require('../utils/roles') + +const merge = require('../utils/merge') + +const workspaceCache = require('./cache') + +const getTemplate = require('./getTemplate') + +module.exports = async (params) => { + + const workspace = await workspaceCache() + + if (!Object.hasOwn(workspace.locales, params.locale)) { + return new Error('Unable to validate locale param.') + } + + const locale = workspace.locales[params.locale] + + const roles = params.user?.roles || [] + + if (!Roles.check(locale, roles)) { + return new Error('Role access denied.') + } + + // A template exists for the locale key. + if (Object.hasOwn(workspace.templates, params.locale)) { + + // Merge the workspace template into workspace. + merge(locale, await getTemplate(workspace.templates[params.locale])) + } + + return locale +} \ No newline at end of file diff --git a/mod/workspace/templates/msgs.js b/mod/workspace/templates/msgs.js index 6c40c940da..fd48aaac95 100644 --- a/mod/workspace/templates/msgs.js +++ b/mod/workspace/templates/msgs.js @@ -1,116 +1,119 @@ module.exports = { - login_required: { - en: `Login required.` - }, - admin_required: { - en: `Admin priviliges required.` - }, - token_not_found: { - en: `Token not found. The token has probably been resolved already.`, - fr: `Token n’a pas été trouvé. Il a probablement déjà été utilisé.`, - pl: `Token wygasł. Prawdopodobnie został już wykorzystany.`, - ja: `トークンが見つかりません。 トークンはおそらくすでに解決されています。`, - ko: `토근이 발견되지 않았습니다. 이미 해결된 것 같습니다.`, - zh: `未找到相关令牌, 该令牌可能已解析` - }, - no_locales: { - en: `No accessible locales for user account.`, - de: `Keine Locale zugreifbar fuer den User.` - }, - password_reset_verification: { - en: `Password will be reset after email verification.`, - fr: `Le mot de passe sera réinitialisé après la vérification par e-mail.`, - pl: `Hasło zostanie ustawione po weryfikacji konta przez wiadomości e-mail.`, - ja: `E-メール検証完了後にパスワードはリセットされます`, - ko: `이메일 확인 후 비밀번호가 재설정됩니다`, - zh: `电子邮件验证后,密码将被重置。` - }, - new_account_registered: { - en: `A new account has been registered and is awaiting email verification.`, - fr: `Un nouveau compte a été enregistré et il attend la vérification par e-mail.`, - pl: `Nowe konto zostało zarejestrowane i czeka na weryfikację przez wiadomość email.`, - ja: `新規アカウントの登録完了にてE-メール検証待ち`, - ko: `새로운 계정이 등록되었고 이메일 확인을 기다리고 있습니다.`, - zh: `已注册一个新帐户,正在等待电子邮件验证。` - }, - no_cookie_found: { - en: `No cookie relating to this application found on request` - }, - update_ok: { - en: `Update successful`, - fr: `Cette mise à jour a réussi.`, - pl: `Uaktualniono.`, - ja: `アップデートに成功しました`, - ko: `업데이트가 성공적으로 진행되었습니다.`, - zh: `更新成功` - }, - account_await_approval: { - en: `This account has been verified but requires administrator approval.`, - fr: `Le compte a été verifié mais il doit être approuvé par l'administrateur. `, - pl: `Konto zostało zweryfikowane i musi zostać zatwierdzone przez administratora.`, - ja: `本アカウント検証完了。アドミニストレーター承認が必要となります`, - ko: `이 계정은 확인되었으나 관리자의 승인이 필요합니다.`, - zh: `此帐户已通过验证,但需要管理员批准。` - }, - password_reset_ok: { - en: `Password has been reset.`, - fr: `Le mot de passe a été réinitialisé.`, - pl: `Ustawiono nowe hasło.`, - ja: `パスワードがリセットされました`, - ko: `비밀번호가 재설정되었습니다.`, - zh: `密码已重设` - }, - auth_failed: { - en: `Authentication failed.`, - de: `Anmeldung gescheitert.` - }, - user_locked: { - en: `User account has been locked due to failed login attempts.`, - de: `Benutzerkonto gesperrt.` - }, - user_blocked: { - en: `User blocked`, - fr: `Cet utilisateur est bloqué.`, - pl: `Konto zostało zablokowane.`, - ja: `ユーザーがブロックされました`, - ko: `사용자 봉쇄`, - zh: `用户被阻止` - }, - user_expired: { - en: `User approval has expired. Please re-register.`, - fr: `Les droits d'accès ont expiré. Veuillez vous réenregistrer.`, - pl: `Prawo dostępu wygasło. Zarejestruj się poownie.`, - ja: `ユーザーの承認は期限切れです。 再登録してください。`, - ko: `사용자 승인이 만료되었습니다. 다시 등록하십시오.`, - zh: `用戶批准已過期。 請重新註冊。` - }, - user_not_verified: { - en: `User not verified or approved`, - fr: `L’utilisateur n’a pas été vérifié ou approuvé.`, - pl: `Konto niezweryfikowane ani zatwierdzone.`, - ja: `ユーザーは確認または承認されていません`, - ko: `사용자 미확인 또는 미승인`, - zh: `用户未经验证或批准` - }, - admin_approved: { - en: `The account has been approved by you. An email has been sent to the account holder.`, - pl: `Konto zostało zatwierdzone. Wysłano wiadomość na zarejestrowany adres e-mail.`, - fr: `Vous avez approuvé ce compte. Un e-mail a été envoyé au propriétaire du compte.`, - ja: `アカウントはあなたによって承認されました。 メールをアカウント所有者に送信しました。`, - ko: `귀하에 의해서 계정이 승인되었습니다. 계정 사용자에게 이메일이 발송되었습니다.`, - zh: `此帐户已被您批准。电子邮件已发送给帐户持有人` - }, - failed_query: { - en: `Failed to query PostGIS table.` - }, - missing_password: { - en: `Missing password`, - fr: `Mot de passe manquant`, - pl: `Nie podano hasła.` - }, - missing_email: { - en: `Missing email`, - fr: `E-mail manquant`, - pl: `Nie podano adresu e-mail.` - } + login_required: { + en: `Login required.` + }, + admin_required: { + en: `Admin priviliges required.` + }, + token_not_found: { + en: `Token not found. The token has probably been resolved already.`, + fr: `Token n’a pas été trouvé. Il a probablement déjà été utilisé.`, + pl: `Token wygasł. Prawdopodobnie został już wykorzystany.`, + ja: `トークンが見つかりません。 トークンはおそらくすでに解決されています。`, + ko: `토근이 발견되지 않았습니다. 이미 해결된 것 같습니다.`, + zh: `未找到相关令牌, 该令牌可能已解析` + }, + no_locale: { + en: `Locale not accessible.` + }, + no_locales: { + en: `No accessible locales for user account.`, + de: `Keine Locale zugreifbar fuer den User.` + }, + password_reset_verification: { + en: `Password will be reset after email verification.`, + fr: `Le mot de passe sera réinitialisé après la vérification par e-mail.`, + pl: `Hasło zostanie ustawione po weryfikacji konta przez wiadomości e-mail.`, + ja: `E-メール検証完了後にパスワードはリセットされます`, + ko: `이메일 확인 후 비밀번호가 재설정됩니다`, + zh: `电子邮件验证后,密码将被重置。` + }, + new_account_registered: { + en: `A new account has been registered and is awaiting email verification.`, + fr: `Un nouveau compte a été enregistré et il attend la vérification par e-mail.`, + pl: `Nowe konto zostało zarejestrowane i czeka na weryfikację przez wiadomość email.`, + ja: `新規アカウントの登録完了にてE-メール検証待ち`, + ko: `새로운 계정이 등록되었고 이메일 확인을 기다리고 있습니다.`, + zh: `已注册一个新帐户,正在等待电子邮件验证。` + }, + no_cookie_found: { + en: `No cookie relating to this application found on request` + }, + update_ok: { + en: `Update successful`, + fr: `Cette mise à jour a réussi.`, + pl: `Uaktualniono.`, + ja: `アップデートに成功しました`, + ko: `업데이트가 성공적으로 진행되었습니다.`, + zh: `更新成功` + }, + account_await_approval: { + en: `This account has been verified but requires administrator approval.`, + fr: `Le compte a été verifié mais il doit être approuvé par l'administrateur. `, + pl: `Konto zostało zweryfikowane i musi zostać zatwierdzone przez administratora.`, + ja: `本アカウント検証完了。アドミニストレーター承認が必要となります`, + ko: `이 계정은 확인되었으나 관리자의 승인이 필요합니다.`, + zh: `此帐户已通过验证,但需要管理员批准。` + }, + password_reset_ok: { + en: `Password has been reset.`, + fr: `Le mot de passe a été réinitialisé.`, + pl: `Ustawiono nowe hasło.`, + ja: `パスワードがリセットされました`, + ko: `비밀번호가 재설정되었습니다.`, + zh: `密码已重设` + }, + auth_failed: { + en: `Authentication failed.`, + de: `Anmeldung gescheitert.` + }, + user_locked: { + en: `User account has been locked due to failed login attempts.`, + de: `Benutzerkonto gesperrt.` + }, + user_blocked: { + en: `User blocked`, + fr: `Cet utilisateur est bloqué.`, + pl: `Konto zostało zablokowane.`, + ja: `ユーザーがブロックされました`, + ko: `사용자 봉쇄`, + zh: `用户被阻止` + }, + user_expired: { + en: `User approval has expired. Please re-register.`, + fr: `Les droits d'accès ont expiré. Veuillez vous réenregistrer.`, + pl: `Prawo dostępu wygasło. Zarejestruj się poownie.`, + ja: `ユーザーの承認は期限切れです。 再登録してください。`, + ko: `사용자 승인이 만료되었습니다. 다시 등록하십시오.`, + zh: `用戶批准已過期。 請重新註冊。` + }, + user_not_verified: { + en: `User not verified or approved`, + fr: `L’utilisateur n’a pas été vérifié ou approuvé.`, + pl: `Konto niezweryfikowane ani zatwierdzone.`, + ja: `ユーザーは確認または承認されていません`, + ko: `사용자 미확인 또는 미승인`, + zh: `用户未经验证或批准` + }, + admin_approved: { + en: `The account has been approved by you. An email has been sent to the account holder.`, + pl: `Konto zostało zatwierdzone. Wysłano wiadomość na zarejestrowany adres e-mail.`, + fr: `Vous avez approuvé ce compte. Un e-mail a été envoyé au propriétaire du compte.`, + ja: `アカウントはあなたによって承認されました。 メールをアカウント所有者に送信しました。`, + ko: `귀하에 의해서 계정이 승인되었습니다. 계정 사용자에게 이메일이 발송되었습니다.`, + zh: `此帐户已被您批准。电子邮件已发送给帐户持有人` + }, + failed_query: { + en: `Failed to query PostGIS table.` + }, + missing_password: { + en: `Missing password`, + fr: `Mot de passe manquant`, + pl: `Nie podano hasła.` + }, + missing_email: { + en: `Missing email`, + fr: `E-mail manquant`, + pl: `Nie podano adresu e-mail.` + } } \ No newline at end of file diff --git a/public/views/_default.js b/public/views/_default.js index 33a7149ae0..133bf3b97e 100644 --- a/public/views/_default.js +++ b/public/views/_default.js @@ -188,14 +188,21 @@ window.onload = async () => { // Get list of accessible locales from Workspace API. const locales = await mapp.utils.xhr(`${host}/api/workspace/locales`); - if (!locales.length) return alert('No accessible locales'); + if (!locales.length) { + + location.href = '?logout=true&msg=no_locales' + return; + } // Get locale with list of layers from Workspace API. const locale = await mapp.utils.xhr( - `${host}/api/workspace/locale?locale=${ - document.head.dataset.locale || mapp.hooks.current.locale || locales[0].key - }` - ); + `${host}/api/workspace/locale?locale=${mapp.hooks.current.locale || locales[0].key}`); + + if(locale instanceof Error){ + + location.href = '?logout=true&msg=no_locale' + return; + } // Add locale dropdown to layers panel if multiple locales are accessible. if (locales.length > 1) { From 3cbe8666e28564c720b2940bd74b705a74e2a86c Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 2 Nov 2023 19:45:01 +0000 Subject: [PATCH 38/44] substitution regex --- mod/provider/cloudfront.js | 5 +++-- mod/provider/file.js | 5 +++-- mod/workspace/_workspace.js | 4 ++-- mod/workspace/getTemplate.js | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/mod/provider/cloudfront.js b/mod/provider/cloudfront.js index 1ccbe14b82..59e39ad447 100644 --- a/mod/provider/cloudfront.js +++ b/mod/provider/cloudfront.js @@ -10,8 +10,9 @@ module.exports = async ref => { try { - const url = (ref.params?.url || ref).replace(/\{{1}(.+?)\}{1}/g, - matched => process.env[`SRC_${matched.replace(/\{{1}|\}{1}/g, '')}`] || matched) + // Subtitutes {*} with process.env.SRC_* key values. + const url = (ref.params?.url || ref).replace(/{(.*?)}/g, + matched => process.env[`SRC_${matched.replace(/(^{)|(}$)/g, '')}`]) const date = new Date(Date.now()) date.setDate(date.getDate() + 1); diff --git a/mod/provider/file.js b/mod/provider/file.js index 3cdf07a4a7..170a2882f0 100644 --- a/mod/provider/file.js +++ b/mod/provider/file.js @@ -5,8 +5,9 @@ const { join } = require('path') module.exports = async ref => { try { - const path = (ref.params?.url || ref).replace(/\{{1}(.+?)\}{1}/g, - matched => process.env[`SRC_${matched.replace(/\{{1}|\}{1}/g, '')}`] || matched) + // Subtitutes {*} with process.env.SRC_* key values. + const path = (ref.params?.url || ref).replace(/{(.*?)}/g, + matched => process.env[`SRC_${matched.replace(/(^{)|(}$)/g, '')}`]) const file = readFileSync(join(__dirname, `../../${path}`)) diff --git a/mod/workspace/_workspace.js b/mod/workspace/_workspace.js index c93e4e1992..bb5add288e 100644 --- a/mod/workspace/_workspace.js +++ b/mod/workspace/_workspace.js @@ -96,10 +96,10 @@ async function locale(req, res) { return res.status(403).send('Role access denied.') } - // Subtitutes ${*} in locale with process.env.SRC_* values. + // Subtitutes ${*} with process.env.SRC_* key values. locale = JSON.parse( JSON.stringify(locale).replace(/\$\{(.*?)\}/g, - matched => process.env[`SRC_${matched.replace(/\$|\{|\}/g, '')}`] || matched) + matched => process.env[`SRC_${matched.replace(/(^\${)|(}$)/g, '')}`]) ) // Check layer access. diff --git a/mod/workspace/getTemplate.js b/mod/workspace/getTemplate.js index 8df5ebd971..5b7fcd2af2 100644 --- a/mod/workspace/getTemplate.js +++ b/mod/workspace/getTemplate.js @@ -14,9 +14,9 @@ module.exports = async (template) => { return template } - // Substitute parameter in src string. + // Subtitutes ${*} with process.env.SRC_* key values. template.src = template.src.replace(/\$\{(.*?)\}/g, - (matched) => process.env[`SRC_${matched.replace(/(^\${)|(}$)/g, '')}`]); + matched => process.env[`SRC_${matched.replace(/(^\${)|(}$)/g, '')}`]); if (!Object.hasOwn(getFrom, template.src.split(':')[0])) { From ad18437974695dbe12f05f469d8715e0abbac11b Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 2 Nov 2023 19:48:49 +0000 Subject: [PATCH 39/44] prevent linear backtracking --- mod/provider/cloudfront.js | 2 +- mod/provider/file.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mod/provider/cloudfront.js b/mod/provider/cloudfront.js index 59e39ad447..0f94e19268 100644 --- a/mod/provider/cloudfront.js +++ b/mod/provider/cloudfront.js @@ -11,7 +11,7 @@ module.exports = async ref => { try { // Subtitutes {*} with process.env.SRC_* key values. - const url = (ref.params?.url || ref).replace(/{(.*?)}/g, + const url = (ref.params?.url || ref).replace(/{{1}(.*?)}{1}/g, matched => process.env[`SRC_${matched.replace(/(^{)|(}$)/g, '')}`]) const date = new Date(Date.now()) diff --git a/mod/provider/file.js b/mod/provider/file.js index 170a2882f0..21aa57330e 100644 --- a/mod/provider/file.js +++ b/mod/provider/file.js @@ -6,7 +6,7 @@ module.exports = async ref => { try { // Subtitutes {*} with process.env.SRC_* key values. - const path = (ref.params?.url || ref).replace(/{(.*?)}/g, + const path = (ref.params?.url || ref).replace(/{{1}(.*?)}{1}/g, matched => process.env[`SRC_${matched.replace(/(^{)|(}$)/g, '')}`]) const file = readFileSync(join(__dirname, `../../${path}`)) From 56c1315ee4faa6f7dde52d2170393c810724caf7 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 2 Nov 2023 19:50:44 +0000 Subject: [PATCH 40/44] regex backtracking --- mod/provider/cloudfront.js | 2 +- mod/provider/file.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mod/provider/cloudfront.js b/mod/provider/cloudfront.js index 0f94e19268..59e39ad447 100644 --- a/mod/provider/cloudfront.js +++ b/mod/provider/cloudfront.js @@ -11,7 +11,7 @@ module.exports = async ref => { try { // Subtitutes {*} with process.env.SRC_* key values. - const url = (ref.params?.url || ref).replace(/{{1}(.*?)}{1}/g, + const url = (ref.params?.url || ref).replace(/{(.*?)}/g, matched => process.env[`SRC_${matched.replace(/(^{)|(}$)/g, '')}`]) const date = new Date(Date.now()) diff --git a/mod/provider/file.js b/mod/provider/file.js index 21aa57330e..170a2882f0 100644 --- a/mod/provider/file.js +++ b/mod/provider/file.js @@ -6,7 +6,7 @@ module.exports = async ref => { try { // Subtitutes {*} with process.env.SRC_* key values. - const path = (ref.params?.url || ref).replace(/{{1}(.*?)}{1}/g, + const path = (ref.params?.url || ref).replace(/{(.*?)}/g, matched => process.env[`SRC_${matched.replace(/(^{)|(}$)/g, '')}`]) const file = readFileSync(join(__dirname, `../../${path}`)) From 05ec21febd7e4dc8064dd23c8e30f05877c4bdb1 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 2 Nov 2023 19:52:07 +0000 Subject: [PATCH 41/44] regex backtracking check --- mod/provider/cloudfront.js | 2 +- mod/provider/file.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mod/provider/cloudfront.js b/mod/provider/cloudfront.js index 59e39ad447..bc45241d5f 100644 --- a/mod/provider/cloudfront.js +++ b/mod/provider/cloudfront.js @@ -11,7 +11,7 @@ module.exports = async ref => { try { // Subtitutes {*} with process.env.SRC_* key values. - const url = (ref.params?.url || ref).replace(/{(.*?)}/g, + const url = (ref.params?.url || ref).replace(/[{](.*?)[}]/g, matched => process.env[`SRC_${matched.replace(/(^{)|(}$)/g, '')}`]) const date = new Date(Date.now()) diff --git a/mod/provider/file.js b/mod/provider/file.js index 170a2882f0..424f6c0c67 100644 --- a/mod/provider/file.js +++ b/mod/provider/file.js @@ -6,7 +6,7 @@ module.exports = async ref => { try { // Subtitutes {*} with process.env.SRC_* key values. - const path = (ref.params?.url || ref).replace(/{(.*?)}/g, + const path = (ref.params?.url || ref).replace(/[{](.*?)[}]/g, matched => process.env[`SRC_${matched.replace(/(^{)|(}$)/g, '')}`]) const file = readFileSync(join(__dirname, `../../${path}`)) From d3d551e45f877ea89182a4c6891e7eabaceed38c Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 2 Nov 2023 20:03:11 +0000 Subject: [PATCH 42/44] prevent regex backtracking --- mod/provider/cloudfront.js | 2 +- mod/provider/file.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mod/provider/cloudfront.js b/mod/provider/cloudfront.js index bc45241d5f..17a9e34c0d 100644 --- a/mod/provider/cloudfront.js +++ b/mod/provider/cloudfront.js @@ -11,7 +11,7 @@ module.exports = async ref => { try { // Subtitutes {*} with process.env.SRC_* key values. - const url = (ref.params?.url || ref).replace(/[{](.*?)[}]/g, + const url = (ref.params?.url || ref).replace(/{(?!{)(.*?)}/g, matched => process.env[`SRC_${matched.replace(/(^{)|(}$)/g, '')}`]) const date = new Date(Date.now()) diff --git a/mod/provider/file.js b/mod/provider/file.js index 424f6c0c67..7e9794935a 100644 --- a/mod/provider/file.js +++ b/mod/provider/file.js @@ -6,7 +6,7 @@ module.exports = async ref => { try { // Subtitutes {*} with process.env.SRC_* key values. - const path = (ref.params?.url || ref).replace(/[{](.*?)[}]/g, + const path = (ref.params?.url || ref).replace(/{(?!{)(.*?)}/g, matched => process.env[`SRC_${matched.replace(/(^{)|(}$)/g, '')}`]) const file = readFileSync(join(__dirname, `../../${path}`)) From f1e3146cca129239f3200591ca3f46e576341df5 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 3 Nov 2023 09:30:57 +0000 Subject: [PATCH 43/44] move workspace cache logger --- mod/workspace/cache.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mod/workspace/cache.js b/mod/workspace/cache.js index e2dd8f98a2..15d20a389a 100644 --- a/mod/workspace/cache.js +++ b/mod/workspace/cache.js @@ -23,8 +23,6 @@ module.exports = () => { // current time minus cached timestamp exceeds WORKSPACE_AGE cache = null - logger(`Workspace cache expired;`, 'workspace') - return cacheWorkspace() } @@ -97,6 +95,8 @@ async function cacheWorkspace() { console.warn(`Default plugins should be defined in the default workspace.locale{}`) } + logger(`Workspace cached;`, 'workspace') + timestamp = Date.now() cache = workspace From 85cb4af76ed4ae63c8bd826c821df67d5da53c93 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Fri, 3 Nov 2023 09:40:26 +0000 Subject: [PATCH 44/44] remove clone test --- tests/mod/utils/clone.test.js | 42 ----------------------------------- 1 file changed, 42 deletions(-) delete mode 100644 tests/mod/utils/clone.test.js diff --git a/tests/mod/utils/clone.test.js b/tests/mod/utils/clone.test.js deleted file mode 100644 index 4121d21d1d..0000000000 --- a/tests/mod/utils/clone.test.js +++ /dev/null @@ -1,42 +0,0 @@ -const clone = require('../../../mod/utils/clone'); - -describe('clone', () => { - test('should return the cloned object', () => { - const obj = { - name: 'John', - age: 30, - address: { - street: '123 Main St', - city: 'New York', - }, - hobbies: ['reading', 'painting'], - }; - - const clonedObj = clone(obj); - - expect(clonedObj).toEqual(obj); - expect(clonedObj).not.toBe(obj); - }); - - test('should handle circular references', () => { - const obj = { prop: null }; - obj.prop = obj; - - const clonedObj = clone(obj); - - expect(clonedObj).toEqual(obj); - expect(clonedObj.prop).toBe(clonedObj); - }); - - test('should return the same value if not an object or null', () => { - const str = 'hello'; - const num = 42; - const bool = true; - const fn = () => {}; - - expect(clone(str)).toBe(str); - expect(clone(num)).toBe(num); - expect(clone(bool)).toBe(bool); - expect(clone(fn)).toBe(fn); - }); -});