@frangoteam/fuxa-min 1.2.6 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/api/apikeys/index.js +107 -0
- package/api/apikeys/verify-api-or-token.js +47 -0
- package/api/index.js +37 -12
- package/api/jwt-helper.js +46 -0
- package/api/resources/index.js +162 -11
- package/api/scheduler/index.js +184 -0
- package/dist/3rdpartylicenses.txt +2 -378
- package/dist/assets/i18n/de.json +1 -1
- package/dist/assets/i18n/en.json +197 -18
- package/dist/assets/i18n/fr.json +836 -349
- package/dist/assets/i18n/ja.json +1751 -0
- package/dist/assets/i18n/ru.json +1 -1
- package/dist/assets/i18n/sv.json +6 -1
- package/dist/assets/i18n/zh-cn.json +525 -68
- package/dist/assets/i18n/zh-tw.json +1718 -0
- package/dist/assets/images/nodered-icon.svg +19 -0
- package/dist/assets/lib/svgeditor/extensions/ext-bundle.min.js +1 -1
- package/dist/assets/lib/svgeditor/fuxa-editor.min.js +2 -2
- package/dist/index.html +2 -2
- package/dist/main.a17204bdf90c1b7a.js +329 -0
- package/dist/{polyfills.8df7953530aa56e1.js → polyfills.c8e7db9850a3ad8b.js} +1 -1
- package/dist/{runtime.9136a61a9b98f987.js → runtime.8ef63094e52a66ba.js} +1 -1
- package/dist/styles.7afa4817cbd46a9c.css +1 -0
- package/docs/openapi.yaml +1045 -0
- package/integrations/node-red/index.js +219 -0
- package/integrations/node-red/node-red-contrib-fuxa/index.js +1 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-ack-alarm.html +49 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-ack-alarm.js +27 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-emit-event.html +31 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-emit-event.js +17 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-enable-device.html +49 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-enable-device.js +25 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-execute-script.html +50 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-execute-script.js +49 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-alarms.html +27 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-alarms.js +19 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.html +100 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.js +25 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-device-property.html +48 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-device-property.js +25 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-device.html +49 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-device.js +25 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-historical-tags.html +152 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-historical-tags.js +76 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-history-alarms.html +88 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-history-alarms.js +28 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.html +53 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.js +78 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.html +45 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.js +28 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-id.html +45 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-id.js +23 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.html +44 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.js +25 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-open-card.html +44 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-open-card.js +23 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-send-message.html +43 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-send-message.js +24 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-device-property.html +53 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-device-property.js +27 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.html +61 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.js +39 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.html +43 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.js +23 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-view.html +44 -0
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-view.js +23 -0
- package/integrations/node-red/node-red-contrib-fuxa/package.json +41 -0
- package/main.js +146 -42
- package/package.json +13 -7
- package/runtime/apikeys/apiKeysStorage.js +125 -0
- package/runtime/apikeys/index.js +123 -0
- package/runtime/devices/device-utils.js +23 -3
- package/runtime/devices/device.js +23 -5
- package/runtime/devices/fuxaserver/index.js +22 -2
- package/runtime/devices/melsec/index.js +362 -0
- package/runtime/devices/modbus/datatypes.js +35 -5
- package/runtime/devices/modbus/index.js +76 -41
- package/runtime/devices/opcua/index.js +19 -6
- package/runtime/devices/webcam/index.js +292 -0
- package/runtime/events.js +3 -0
- package/runtime/index.js +26 -5
- package/runtime/jobs/cleaner.js +34 -1
- package/runtime/notificator/index.js +46 -18
- package/runtime/plugins/index.js +7 -0
- package/runtime/scheduler/scheduler-service.js +1608 -0
- package/runtime/scheduler/scheduler-storage.js +184 -0
- package/runtime/scripts/index.js +9 -7
- package/runtime/storage/influxdb/index.js +1 -1
- package/runtime/utils.js +23 -0
- package/settings.default.js +20 -2
- package/dist/main.744ebf9b135efd34.js +0 -329
- package/dist/styles.466e813c4ba26c88.css +0 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ FUXA is a web-based Process Visualization (SCADA/HMI/Dashboard) software. With F
|
|
|
9
9
|

|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
|
-
- Devices connectivity with Modbus RTU/TCP, Siemens S7 Protocol, OPC-UA, BACnet IP, MQTT, WebAPI, Ethernet/IP (Allen Bradley), ODBC
|
|
12
|
+
- Devices connectivity with Modbus RTU/TCP, Siemens S7 Protocol, OPC-UA, BACnet IP, MQTT, WebAPI, Ethernet/IP (Allen Bradley), ADSclient, Gpio (Raspberry), WebCam, MELSEC, ODBC, Redis
|
|
13
13
|
- SCADA/HMI Web-Editor - Engineering and Design completely web-based
|
|
14
14
|
- Cross-Platform Full-Stack - Backend with NodeJs and Frontend with Web technologies (HTML5, CSS, Javascript, Angular, SVG)
|
|
15
15
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 'api/apikeys': ApiKeys API to GET/POST/DELETE apikeys
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
var express = require("express");
|
|
6
|
+
const authJwt = require('../jwt-helper');
|
|
7
|
+
var runtime;
|
|
8
|
+
var secureFnc;
|
|
9
|
+
var checkGroupsFnc;
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
init: function (_runtime, _secureFnc, _checkGroupsFnc) {
|
|
13
|
+
runtime = _runtime;
|
|
14
|
+
secureFnc = _secureFnc;
|
|
15
|
+
checkGroupsFnc = _checkGroupsFnc;
|
|
16
|
+
},
|
|
17
|
+
app: function() {
|
|
18
|
+
var apiKeysApp = express();
|
|
19
|
+
apiKeysApp.use(function(req,res,next) {
|
|
20
|
+
if (!runtime.project) {
|
|
21
|
+
res.status(404).end();
|
|
22
|
+
} else {
|
|
23
|
+
next();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* GET ApiKeys
|
|
29
|
+
* Take from ApiKeys storage and reply
|
|
30
|
+
*/
|
|
31
|
+
apiKeysApp.get("/api/apikeys", secureFnc, function(req, res) {
|
|
32
|
+
const permission = checkGroupsFnc(req);
|
|
33
|
+
if (res.statusCode === 403) {
|
|
34
|
+
runtime.logger.error("api get apikeys: Tocken Expired");
|
|
35
|
+
} else if (!authJwt.haveAdminPermission(permission)) {
|
|
36
|
+
res.status(401).json({error:"unauthorized_error", message: "Unauthorized!"});
|
|
37
|
+
runtime.logger.error("api get apikeys: Unauthorized!");
|
|
38
|
+
} else {
|
|
39
|
+
runtime.apiKeys.getApiKeys(req.query).then(result => {
|
|
40
|
+
if (result) {
|
|
41
|
+
res.json(result);
|
|
42
|
+
} else {
|
|
43
|
+
res.end();
|
|
44
|
+
}
|
|
45
|
+
}).catch(function(err) {
|
|
46
|
+
if (err.code) {
|
|
47
|
+
res.status(400).json({error:err.code, message: err.message});
|
|
48
|
+
} else {
|
|
49
|
+
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
|
50
|
+
}
|
|
51
|
+
runtime.logger.error("api get apikeys: " + err.message);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* POST ApiKeys
|
|
58
|
+
* Set apikeys storage
|
|
59
|
+
*/
|
|
60
|
+
apiKeysApp.post("/api/apikeys", secureFnc, function(req, res, next) {
|
|
61
|
+
const permission = checkGroupsFnc(req);
|
|
62
|
+
if (res.statusCode === 403) {
|
|
63
|
+
runtime.logger.error("api post apikeys: Tocken Expired");
|
|
64
|
+
} else if (!authJwt.haveAdminPermission(permission)) {
|
|
65
|
+
res.status(401).json({error:"unauthorized_error", message: "Unauthorized!"});
|
|
66
|
+
runtime.logger.error("api post apikeys: Unauthorized");
|
|
67
|
+
} else {
|
|
68
|
+
runtime.apiKeys.setApiKeys(req.body.params).then(function(data) {
|
|
69
|
+
res.end();
|
|
70
|
+
}).catch(function(err) {
|
|
71
|
+
if (err.code) {
|
|
72
|
+
res.status(400).json({error:err.code, message: err.message});
|
|
73
|
+
} else {
|
|
74
|
+
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
|
75
|
+
}
|
|
76
|
+
runtime.logger.error("api post apikeys: " + err.message);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* DELETE Roles
|
|
83
|
+
* Delete apikeys from storage
|
|
84
|
+
*/
|
|
85
|
+
apiKeysApp.delete("/api/apikeys", secureFnc, function(req, res, next) {
|
|
86
|
+
const permission = checkGroupsFnc(req);
|
|
87
|
+
if (res.statusCode === 403) {
|
|
88
|
+
runtime.logger.error("api delete apikeys: Tocken Expired");
|
|
89
|
+
} else if (!authJwt.haveAdminPermission(permission)) {
|
|
90
|
+
res.status(401).json({error:"unauthorized_error", message: "Unauthorized!"});
|
|
91
|
+
runtime.logger.error("api delete apikeys: Unauthorized");
|
|
92
|
+
} else {
|
|
93
|
+
runtime.apiKeys.removeApiKeys(JSON.parse(req.query.apikeys)).then(function(data) {
|
|
94
|
+
res.end();
|
|
95
|
+
}).catch(function(err) {
|
|
96
|
+
if (err.code) {
|
|
97
|
+
res.status(400).json({error:err.code, message: err.message});
|
|
98
|
+
} else {
|
|
99
|
+
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
|
100
|
+
}
|
|
101
|
+
runtime.logger.error("api delete apikeys: " + err.message);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return apiKeysApp;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const authJwt = require('../jwt-helper');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Middleware that accepts either JWT (x-access-token) or API key (x-api-key).
|
|
7
|
+
* It is active only when security is enabled.
|
|
8
|
+
*/
|
|
9
|
+
module.exports = function verifyApiOrToken(runtime) {
|
|
10
|
+
return async function (req, res, next) {
|
|
11
|
+
if (!runtime?.settings?.secureEnabled) {
|
|
12
|
+
return next();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const apiKey = req.headers['x-api-key'];
|
|
16
|
+
if (apiKey) {
|
|
17
|
+
try {
|
|
18
|
+
const stored = await runtime.apiKeys.getApiKeys();
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const validKey = stored.find(k => {
|
|
21
|
+
if (!k || k.key !== apiKey || k.enabled === false) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (!k.expires) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const expiresAt = new Date(k.expires).getTime();
|
|
28
|
+
return !isNaN(expiresAt) && expiresAt > now;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (validKey) {
|
|
32
|
+
req.apiKey = validKey;
|
|
33
|
+
// Grant admin group to match existing authorization checks.
|
|
34
|
+
req.userId = validKey.id || `apikey:${apiKey}`;
|
|
35
|
+
req.userGroups = authJwt.adminGroups[0];
|
|
36
|
+
return next();
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
runtime.logger.error(`api-key validation failed: ${err}`);
|
|
40
|
+
return res.status(500).json({ error: 'unexpected_error', message: 'ApiKey validation failed' });
|
|
41
|
+
}
|
|
42
|
+
return res.status(401).json({ error: 'unauthorized_error', message: 'Invalid API Key' });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return authJwt.verifyToken(req, res, next);
|
|
46
|
+
};
|
|
47
|
+
};
|
package/api/index.js
CHANGED
|
@@ -12,15 +12,20 @@ const rateLimit = require("express-rate-limit");
|
|
|
12
12
|
var prjApi = require('./projects');
|
|
13
13
|
var authApi = require('./auth');
|
|
14
14
|
var usersApi = require('./users');
|
|
15
|
+
var apiKeysApi = require('./apikeys');
|
|
15
16
|
var alarmsApi = require('./alarms');
|
|
16
17
|
var pluginsApi = require('./plugins');
|
|
17
18
|
var diagnoseApi = require('./diagnose');
|
|
18
19
|
var scriptsApi = require('./scripts');
|
|
19
20
|
var resourcesApi = require('./resources');
|
|
20
21
|
var daqApi = require('./daq');
|
|
22
|
+
var schedulerApi = require('./scheduler');
|
|
21
23
|
var commandApi = require('./command');
|
|
22
24
|
const reports = require('../dist/reports.service');
|
|
23
25
|
const reportsApi = new reports.ReportsApiService();
|
|
26
|
+
const verifyApiOrToken = require('./apikeys/verify-api-or-token');
|
|
27
|
+
|
|
28
|
+
const version = '1.0.0';
|
|
24
29
|
|
|
25
30
|
var apiApp;
|
|
26
31
|
var server;
|
|
@@ -29,6 +34,7 @@ var runtime;
|
|
|
29
34
|
function init(_server, _runtime) {
|
|
30
35
|
server = _server;
|
|
31
36
|
runtime = _runtime;
|
|
37
|
+
|
|
32
38
|
return new Promise(function (resolve, reject) {
|
|
33
39
|
if (runtime.settings.disableServer !== false) {
|
|
34
40
|
apiApp = express();
|
|
@@ -39,28 +45,33 @@ function init(_server, _runtime) {
|
|
|
39
45
|
apiApp.use(bodyParser.json({limit:maxApiRequestSize}));
|
|
40
46
|
apiApp.use(bodyParser.urlencoded({limit:maxApiRequestSize, extended: true}));
|
|
41
47
|
authJwt.init(runtime.settings.secureEnabled, runtime.settings.secretCode, runtime.settings.tokenExpiresIn);
|
|
42
|
-
|
|
48
|
+
const authMiddleware = verifyApiOrToken(runtime);
|
|
49
|
+
prjApi.init(runtime, authMiddleware, verifyGroups);
|
|
43
50
|
apiApp.use(prjApi.app());
|
|
44
|
-
usersApi.init(runtime,
|
|
51
|
+
usersApi.init(runtime, authMiddleware, verifyGroups);
|
|
45
52
|
apiApp.use(usersApi.app());
|
|
46
|
-
alarmsApi.init(runtime,
|
|
53
|
+
alarmsApi.init(runtime, authMiddleware, verifyGroups);
|
|
47
54
|
apiApp.use(alarmsApi.app());
|
|
48
55
|
authApi.init(runtime, authJwt.secretCode, authJwt.tokenExpiresIn);
|
|
49
56
|
apiApp.use(authApi.app());
|
|
50
|
-
pluginsApi.init(runtime,
|
|
57
|
+
pluginsApi.init(runtime, authMiddleware, verifyGroups);
|
|
51
58
|
apiApp.use(pluginsApi.app());
|
|
52
|
-
diagnoseApi.init(runtime,
|
|
59
|
+
diagnoseApi.init(runtime, authMiddleware, verifyGroups);
|
|
53
60
|
apiApp.use(diagnoseApi.app());
|
|
54
|
-
daqApi.init(runtime,
|
|
61
|
+
daqApi.init(runtime, authMiddleware, verifyGroups);
|
|
55
62
|
apiApp.use(daqApi.app());
|
|
56
|
-
|
|
63
|
+
schedulerApi.init(runtime, authMiddleware, verifyGroups);
|
|
64
|
+
apiApp.use(schedulerApi.app());
|
|
65
|
+
scriptsApi.init(runtime, authMiddleware, verifyGroups);
|
|
57
66
|
apiApp.use(scriptsApi.app());
|
|
58
|
-
resourcesApi.init(runtime,
|
|
67
|
+
resourcesApi.init(runtime, authMiddleware, verifyGroups);
|
|
59
68
|
apiApp.use(resourcesApi.app());
|
|
60
|
-
commandApi.init(runtime,
|
|
69
|
+
commandApi.init(runtime, authMiddleware, verifyGroups);
|
|
61
70
|
apiApp.use(commandApi.app());
|
|
62
|
-
reportsApi.init(runtime,
|
|
71
|
+
reportsApi.init(runtime, authMiddleware, verifyGroups);
|
|
63
72
|
apiApp.use(reportsApi.app());
|
|
73
|
+
apiKeysApi.init(runtime, authMiddleware, verifyGroups);
|
|
74
|
+
apiApp.use(apiKeysApi.app());
|
|
64
75
|
|
|
65
76
|
const limiter = rateLimit({
|
|
66
77
|
windowMs: 5 * 60 * 1000, // 5 minutes
|
|
@@ -79,6 +90,13 @@ function init(_server, _runtime) {
|
|
|
79
90
|
next(err);
|
|
80
91
|
});
|
|
81
92
|
|
|
93
|
+
/**
|
|
94
|
+
* GET Server setting data
|
|
95
|
+
*/
|
|
96
|
+
apiApp.get('/api/version', function (req, res) {
|
|
97
|
+
res.json(version);
|
|
98
|
+
});
|
|
99
|
+
|
|
82
100
|
/**
|
|
83
101
|
* GET Server setting data
|
|
84
102
|
*/
|
|
@@ -101,7 +119,7 @@ function init(_server, _runtime) {
|
|
|
101
119
|
/**
|
|
102
120
|
* POST Server user settings
|
|
103
121
|
*/
|
|
104
|
-
apiApp.post("/api/settings",
|
|
122
|
+
apiApp.post("/api/settings", authMiddleware, function(req, res, next) {
|
|
105
123
|
const permission = verifyGroups(req);
|
|
106
124
|
if (res.statusCode === 403) {
|
|
107
125
|
runtime.logger.error("api post settings: Tocken Expired");
|
|
@@ -128,7 +146,7 @@ function init(_server, _runtime) {
|
|
|
128
146
|
/**
|
|
129
147
|
* GET Heartbeat to check token
|
|
130
148
|
*/
|
|
131
|
-
apiApp.post('/api/heartbeat',
|
|
149
|
+
apiApp.post('/api/heartbeat', authMiddleware, function (req, res) {
|
|
132
150
|
if (!runtime.settings.secureEnabled) {
|
|
133
151
|
res.end();
|
|
134
152
|
} else if (res.statusCode === 403) {
|
|
@@ -152,6 +170,7 @@ function init(_server, _runtime) {
|
|
|
152
170
|
res.end();
|
|
153
171
|
}
|
|
154
172
|
});
|
|
173
|
+
|
|
155
174
|
runtime.logger.info('api: init successful!', true);
|
|
156
175
|
} else {
|
|
157
176
|
}
|
|
@@ -179,10 +198,16 @@ function mergeUserSettings(settings) {
|
|
|
179
198
|
if (settings.alarms) {
|
|
180
199
|
runtime.settings.alarms = settings.alarms;
|
|
181
200
|
}
|
|
201
|
+
if (settings.logs) {
|
|
202
|
+
runtime.settings.logs = settings.logs;
|
|
203
|
+
}
|
|
182
204
|
}
|
|
183
205
|
|
|
184
206
|
function verifyGroups(req) {
|
|
185
207
|
if (runtime.settings && runtime.settings.secureEnabled) {
|
|
208
|
+
if (req.apiKey) {
|
|
209
|
+
return authJwt.adminGroups[0];
|
|
210
|
+
}
|
|
186
211
|
if (req.tokenExpired) {
|
|
187
212
|
return (runtime.settings.userRole) ? null : 0;
|
|
188
213
|
}
|
package/api/jwt-helper.js
CHANGED
|
@@ -76,6 +76,51 @@ function verifyToken (req, res, next) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function requireAuth (req, res, next) {
|
|
80
|
+
// Allow requests from FUXA interface (iframe embedding)
|
|
81
|
+
// Check for common FUXA referer patterns
|
|
82
|
+
const referer = req.headers.referer;
|
|
83
|
+
if (referer) {
|
|
84
|
+
// Allow if referer is from the same host (to support IP access without specific paths)
|
|
85
|
+
const requestHost = req.headers.host;
|
|
86
|
+
if (referer.startsWith(`http://${requestHost}`) || referer.startsWith(`https://${requestHost}`)) {
|
|
87
|
+
return next();
|
|
88
|
+
}
|
|
89
|
+
// Allow if referer contains common FUXA paths or is from the same server
|
|
90
|
+
const fuxaPatterns = [
|
|
91
|
+
'/fuxa', '/editor', '/viewer', '/lab', '/home',
|
|
92
|
+
'localhost:', '127.0.0.1:', '0.0.0.0:'
|
|
93
|
+
];
|
|
94
|
+
const hasFuxaReferer = fuxaPatterns.some(pattern => referer.includes(pattern));
|
|
95
|
+
if (hasFuxaReferer) {
|
|
96
|
+
return next();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// For direct access, require authentication
|
|
101
|
+
let token = req.headers['x-access-token'];
|
|
102
|
+
|
|
103
|
+
if (!token) {
|
|
104
|
+
return res.status(401).json({ error: "unauthorized_error", message: "Authentication required!" });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
jwt.verify(token, secretCode, (err, decoded) => {
|
|
108
|
+
if (err) {
|
|
109
|
+
return res.status(401).json({ error: "unauthorized_error", message: "Invalid token!" });
|
|
110
|
+
} else {
|
|
111
|
+
req.userId = decoded.id;
|
|
112
|
+
req.userGroups = decoded.groups;
|
|
113
|
+
if (req.headers['x-auth-user']) {
|
|
114
|
+
let user = JSON.parse(req.headers['x-auth-user']);
|
|
115
|
+
if (user && user.groups != req.userGroups) {
|
|
116
|
+
return res.status(403).json({ error: "unauthorized_error", message: "User Profile Corrupted!" });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
next();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
79
124
|
function getNewToken(headers) {
|
|
80
125
|
const authUser = (headers['x-auth-user']) ? JSON.parse(headers['x-auth-user']) : null;
|
|
81
126
|
if (authUser) {
|
|
@@ -119,6 +164,7 @@ module.exports = {
|
|
|
119
164
|
init: init,
|
|
120
165
|
verify: verify,
|
|
121
166
|
verifyToken: verifyToken,
|
|
167
|
+
requireAuth: requireAuth,
|
|
122
168
|
getNewToken: getNewToken,
|
|
123
169
|
getGuestToken: getGuestToken,
|
|
124
170
|
get secretCode() { return secretCode },
|
package/api/resources/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const path = require('path');
|
|
|
7
7
|
var express = require("express");
|
|
8
8
|
const authJwt = require('../jwt-helper');
|
|
9
9
|
const Report = require('../../runtime/jobs/report');
|
|
10
|
+
const fontkit = require('fontkit');
|
|
10
11
|
const os = require('os');
|
|
11
12
|
|
|
12
13
|
var runtime;
|
|
@@ -41,16 +42,16 @@ module.exports = {
|
|
|
41
42
|
runtime.logger.error("api get resources/images: Unauthorized!");
|
|
42
43
|
} else {
|
|
43
44
|
try {
|
|
44
|
-
var result = {...req.query, ...{ groups: [] }};
|
|
45
|
+
var result = { ...req.query, ...{ groups: [] } };
|
|
45
46
|
var resourcesDirs = getDirectories(runtime.settings.imagesFileDir);
|
|
46
47
|
for (var i = 0; i < resourcesDirs.length; i++) {
|
|
47
48
|
var group = { name: resourcesDirs[i], items: [] };
|
|
48
49
|
var dirPath = path.resolve(runtime.settings.imagesFileDir, resourcesDirs[i]);
|
|
49
|
-
var wwwSubDir =
|
|
50
|
-
var files =
|
|
50
|
+
var wwwSubDir = path.join('_images', resourcesDirs[i]);
|
|
51
|
+
var files = getFiles(dirPath, ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.mp4', '.webm', '.ogg', '.ogv']);
|
|
51
52
|
for (var x = 0; x < files.length; x++) {
|
|
52
53
|
var filename = files[x].replace(/\.[^\/.]+$/, '');
|
|
53
|
-
group.items.push({ path:
|
|
54
|
+
group.items.push({ path: path.join(wwwSubDir, files[x]).split(path.sep).join(path.posix.sep), name: filename });
|
|
54
55
|
}
|
|
55
56
|
result.groups.push(group);
|
|
56
57
|
}
|
|
@@ -66,6 +67,74 @@ module.exports = {
|
|
|
66
67
|
}
|
|
67
68
|
});
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* GET Server resources folder content
|
|
72
|
+
*/
|
|
73
|
+
resourcesApp.get('/api/resources/resources', secureFnc, function (req, res) {
|
|
74
|
+
try {
|
|
75
|
+
const resourcesFilter = { fonts: ['ttf'] };
|
|
76
|
+
const wwwSubDir = '_resources';
|
|
77
|
+
const result = { ...req.query, ...{ groups: [] } };
|
|
78
|
+
const group = { name: wwwSubDir, items: [] };
|
|
79
|
+
var files = getFiles(runtime.settings.resourcesFileDir, ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.pdf', '.ttf', '.mp4', '.webm', '.ogg', '.ogv']);
|
|
80
|
+
for (var x = 0; x < files.length; x++) {
|
|
81
|
+
const fileName = files[x];
|
|
82
|
+
const filePath = path.join(wwwSubDir, files[x]).split(path.sep).join(path.posix.sep);
|
|
83
|
+
var fileLabel;
|
|
84
|
+
if (resourcesFilter.fonts.some(suffix => fileName.endsWith(suffix))) {
|
|
85
|
+
const font = fontkit.openSync(filePath);
|
|
86
|
+
fileLabel = font.fullName;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
group.items.push({
|
|
90
|
+
path: filePath,
|
|
91
|
+
name: fileName,
|
|
92
|
+
label: fileLabel
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
result.groups.push(group);
|
|
96
|
+
res.json(result);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
if (err.code) {
|
|
99
|
+
res.status(400).json({ error: err.code, message: err.message });
|
|
100
|
+
} else {
|
|
101
|
+
res.status(400).json({ error: "unexpected_error", message: err.toString() });
|
|
102
|
+
}
|
|
103
|
+
runtime.logger.error("api get resources/resources: " + err.message);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* POST remove resource file
|
|
109
|
+
*/
|
|
110
|
+
resourcesApp.post('/api/resources/remove', secureFnc, function (req, res) {
|
|
111
|
+
const permission = checkGroupsFnc(req);
|
|
112
|
+
if (res.statusCode === 403) {
|
|
113
|
+
runtime.logger.error("api post device: Tocken Expired");
|
|
114
|
+
} else if (!authJwt.haveAdminPermission(permission)) {
|
|
115
|
+
res.status(401).json({ error: "unauthorized_error", message: "Unauthorized!" });
|
|
116
|
+
runtime.logger.error("api post remove resource: Unauthorized");
|
|
117
|
+
} else {
|
|
118
|
+
try {
|
|
119
|
+
let fileName = req.body.file.replace(new RegExp('../', 'g'), '');
|
|
120
|
+
const filePath = path.join(runtime.settings.resourcesFileDir, fileName);
|
|
121
|
+
if (fs.existsSync(filePath)) {
|
|
122
|
+
fs.unlinkSync(filePath);
|
|
123
|
+
}
|
|
124
|
+
runtime.logger.info(`resources '${filePath}' deleted!`, true);
|
|
125
|
+
res.end();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (err && err.code) {
|
|
128
|
+
res.status(400).json({ error: err.code, message: err.message });
|
|
129
|
+
runtime.logger.error("api remove resource: " + err.message);
|
|
130
|
+
} else {
|
|
131
|
+
res.status(400).json({ error: "unexpected_error", message: err });
|
|
132
|
+
runtime.logger.error("api remove resource: " + err);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
69
138
|
/**
|
|
70
139
|
* GET svg/canvas rendered and converted to image
|
|
71
140
|
*/
|
|
@@ -102,8 +171,90 @@ module.exports = {
|
|
|
102
171
|
});
|
|
103
172
|
|
|
104
173
|
/**
|
|
105
|
-
* GET
|
|
174
|
+
* GET Templates
|
|
175
|
+
* Take from resources storage and reply
|
|
106
176
|
*/
|
|
177
|
+
resourcesApp.get("/api/resources/templates", secureFnc, function (req, res) {
|
|
178
|
+
const permission = checkGroupsFnc(req);
|
|
179
|
+
if (res.statusCode === 403) {
|
|
180
|
+
runtime.logger.error("api get templates: Tocken Expired");
|
|
181
|
+
} else if (!authJwt.haveAdminPermission(permission)) {
|
|
182
|
+
res.status(401).json({ error: "unauthorized_error", message: "Unauthorized!" });
|
|
183
|
+
runtime.logger.error("api get templates: Unauthorized!");
|
|
184
|
+
} else {
|
|
185
|
+
runtime.resourcesMgr.getTemplates(req.query).then(result => {
|
|
186
|
+
// res.header("Access-Control-Allow-Origin", "*");
|
|
187
|
+
// res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
188
|
+
if (result) {
|
|
189
|
+
result.forEach(template => {
|
|
190
|
+
template.content = JSON.parse(template.content);
|
|
191
|
+
});
|
|
192
|
+
res.json(result);
|
|
193
|
+
} else {
|
|
194
|
+
res.end();
|
|
195
|
+
}
|
|
196
|
+
}).catch(function (err) {
|
|
197
|
+
if (err.code) {
|
|
198
|
+
res.status(400).json({ error: err.code, message: err.message });
|
|
199
|
+
} else {
|
|
200
|
+
res.status(400).json({ error: "unexpected_error", message: err.toString() });
|
|
201
|
+
}
|
|
202
|
+
runtime.logger.error("api get templates: " + err.message);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* POST template
|
|
209
|
+
*/
|
|
210
|
+
resourcesApp.post('/api/resources/template', secureFnc, function (req, res) {
|
|
211
|
+
const permission = checkGroupsFnc(req);
|
|
212
|
+
if (res.statusCode === 403) {
|
|
213
|
+
runtime.logger.error("api post device: Tocken Expired");
|
|
214
|
+
} else if (!authJwt.haveAdminPermission(permission)) {
|
|
215
|
+
res.status(401).json({ error: "unauthorized_error", message: "Unauthorized!" });
|
|
216
|
+
runtime.logger.error("api post template: Unauthorized");
|
|
217
|
+
} else {
|
|
218
|
+
runtime.resourcesMgr.setTemplate(req.body.template).then((data) => {
|
|
219
|
+
res.end();
|
|
220
|
+
}).catch(function (err) {
|
|
221
|
+
if (err.code) {
|
|
222
|
+
res.status(400).json({ error: err.code, message: err.message });
|
|
223
|
+
} else {
|
|
224
|
+
res.status(400).json({ error: "unexpected_error", message: err.toString() });
|
|
225
|
+
}
|
|
226
|
+
runtime.logger.error("api post template: " + err.message);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* DELETE template
|
|
233
|
+
*/
|
|
234
|
+
resourcesApp.delete("/api/resources/templates", secureFnc, function (req, res, next) {
|
|
235
|
+
const permission = checkGroupsFnc(req);
|
|
236
|
+
if (res.statusCode === 403) {
|
|
237
|
+
runtime.logger.error("api delete templates: Tocken Expired");
|
|
238
|
+
} else if (!authJwt.haveAdminPermission(permission)) {
|
|
239
|
+
res.status(401).json({ error: "unauthorized_error", message: "Unauthorized!" });
|
|
240
|
+
runtime.logger.error("api delete templates: Unauthorized");
|
|
241
|
+
} else {
|
|
242
|
+
runtime.resourcesMgr.removeTemplates(req.query.templates).then((data) => {
|
|
243
|
+
res.end();
|
|
244
|
+
}).catch(function (err) {
|
|
245
|
+
if (err.code) {
|
|
246
|
+
res.status(400).json({ error: err.code, message: err.message });
|
|
247
|
+
} else {
|
|
248
|
+
res.status(400).json({ error: "unexpected_error", message: err.toString() });
|
|
249
|
+
}
|
|
250
|
+
runtime.logger.error("api delete templates: " + err.message);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* GET Server widgets folder content
|
|
257
|
+
*/
|
|
107
258
|
resourcesApp.get('/api/resources/widgets', secureFnc, function (req, res) {
|
|
108
259
|
const permission = checkGroupsFnc(req);
|
|
109
260
|
if (res.statusCode === 403) {
|
|
@@ -113,16 +264,16 @@ module.exports = {
|
|
|
113
264
|
runtime.logger.error("api get resources/widgets: Unauthorized!");
|
|
114
265
|
} else {
|
|
115
266
|
try {
|
|
116
|
-
var result = {...req.query, ...{ groups: [] }};
|
|
267
|
+
var result = { ...req.query, ...{ groups: [] } };
|
|
117
268
|
var resourcesDirs = getDirectories(runtime.settings.widgetsFileDir);
|
|
118
269
|
for (var i = 0; i < resourcesDirs.length; i++) {
|
|
119
270
|
var group = { name: resourcesDirs[i], items: [] };
|
|
120
271
|
var dirPath = path.resolve(runtime.settings.widgetsFileDir, resourcesDirs[i]);
|
|
121
|
-
var wwwSubDir =
|
|
122
|
-
var files =
|
|
272
|
+
var wwwSubDir = path.join('_widgets', resourcesDirs[i]);
|
|
273
|
+
var files = getFiles(dirPath, ['.svg']);
|
|
123
274
|
for (var x = 0; x < files.length; x++) {
|
|
124
275
|
var filename = files[x];
|
|
125
|
-
group.items.push({ path:
|
|
276
|
+
group.items.push({ path: path.join(wwwSubDir, files[x]).split(path.sep).join(path.posix.sep), name: filename });
|
|
126
277
|
}
|
|
127
278
|
result.groups.push(group);
|
|
128
279
|
}
|
|
@@ -191,14 +342,14 @@ module.exports = {
|
|
|
191
342
|
}
|
|
192
343
|
}
|
|
193
344
|
|
|
194
|
-
function getDirectories
|
|
345
|
+
function getDirectories(pathDir) {
|
|
195
346
|
const directoriesInDIrectory = fs.readdirSync(pathDir, { withFileTypes: true })
|
|
196
347
|
.filter((item) => item.isDirectory())
|
|
197
348
|
.map((item) => item.name);
|
|
198
349
|
return directoriesInDIrectory;
|
|
199
350
|
}
|
|
200
351
|
|
|
201
|
-
function getFiles
|
|
352
|
+
function getFiles(pathDir, extensions) {
|
|
202
353
|
const filesInDIrectory = fs.readdirSync(pathDir)
|
|
203
354
|
.filter((item) => extensions.indexOf(path.extname(item).toLowerCase()) !== -1);
|
|
204
355
|
return filesInDIrectory;
|