@vitormnm/node-red-simple-opcua 1.6.3 → 1.7.0
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 +89 -136
- package/client/lib/opcua-client-browser.js +238 -10
- package/client/lib/opcua-client-method-service.js +1 -1
- package/client/lib/opcua-client-subscription-service.js +0 -2
- package/client/opcua-client-config.html +118 -1
- package/client/opcua-client-config.js +74 -8
- package/client/opcua-client-help.html +6 -0
- package/client/opcua-client-utils.js +34 -10
- package/client/opcua-client.html +7 -0
- package/client/opcua-client.js +97 -1
- package/examples/flows_simple_opc.json +1 -1
- package/package.json +1 -1
- package/server/lib/opcua-address-space-alarm.js +11 -5
- package/server/lib/opcua-address-space-builder.js +65 -15
- package/server/lib/opcua-config.js +81 -23
- package/server/lib/opcua-server-events-child.js +1 -1
- package/server/lib/opcua-server-runtime-child.js +284 -19
- package/server/lib/opcua-server-runtime.js +49 -5
- package/server/lib/opcua-server-status-child.js +14 -14
- package/server/nodered/simple_opcua/server/certificates/mutex +0 -0
- package/server/nodered/simple_opcua/server/certificates/own/certs/server_selfsigned_cert_2048.pem +25 -0
- package/server/nodered/simple_opcua/server/certificates/own/certs/server_selfsigned_cert_2048.pem.mutex +0 -0
- package/server/nodered/simple_opcua/server/certificates/own/openssl.cnf +72 -0
- package/server/nodered/simple_opcua/server/certificates/own/private/private_key.pem +28 -0
- package/server/nodered/simple_opcua/server/certificates/trusted/certs/NodeOPCUA-Client@tuf[c5a9e20a8b680cdff76aaf0165bb3c9318da37a5].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/mutex +0 -0
- package/server/nodered/simple_opcua/server/myServer1/own/certs/server_selfsigned_cert_2048.pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/own/certs/server_selfsigned_cert_2048.pem.mutex +0 -0
- package/server/nodered/simple_opcua/server/myServer1/own/openssl.cnf +72 -0
- package/server/nodered/simple_opcua/server/myServer1/own/private/private_key.pem +28 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[91e520c64ff891c67168f08a46dd194071e15dae].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[98ae95da627cea4c500753c319161a3554ee38d7].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[aef8d7a1cfba13d84189a0bcf1694208fc51a7f9].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[c5a9e20a8b680cdff76aaf0165bb3c9318da37a5].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[ebdf9acf1d02e347917a14108d3144799c638ea3].pem +25 -0
- package/server/opcua-server-io.html +76 -0
- package/server/opcua-server-io.js +130 -27
- package/server/opcua-server.css +52 -0
- package/server/opcua-server.html +166 -44
- package/server/opcua-server.js +115 -5
- package/server/view/opcua-server.css +89 -6
- package/server/view/opcua-server.js +523 -42
|
@@ -257,18 +257,33 @@ class OpcUaServerProcess {
|
|
|
257
257
|
|
|
258
258
|
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
let hasFailures = false;
|
|
261
|
+
if (readArrayResults) {
|
|
262
|
+
const failed = readArrayResults.filter(item => item && item.status !== "Good");
|
|
263
|
+
if (failed.length) {
|
|
264
|
+
hasFailures = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (result.directError) {
|
|
268
|
+
hasFailures = true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!hasFailures) {
|
|
272
|
+
process.send({
|
|
273
|
+
type: "send",
|
|
274
|
+
data: msg,
|
|
275
|
+
nodeId: nodeId
|
|
276
|
+
});
|
|
277
|
+
}
|
|
265
278
|
|
|
266
279
|
process.send({
|
|
267
280
|
type: "status",
|
|
268
281
|
data: {
|
|
269
|
-
fill: "green",
|
|
282
|
+
fill: hasFailures ? (result.directError ? "red" : "yellow") : "green",
|
|
270
283
|
shape: "dot",
|
|
271
|
-
text:
|
|
284
|
+
text: hasFailures
|
|
285
|
+
? (result.directError ? "read failed" : "partial read failed")
|
|
286
|
+
: (result.identifiers.length > 1 ? "read " + result.identifiers.length + " tags" : "read " + result.identifiers[0])
|
|
272
287
|
},
|
|
273
288
|
nodeId: nodeId
|
|
274
289
|
});
|
|
@@ -308,6 +323,7 @@ class OpcUaServerProcess {
|
|
|
308
323
|
type: "error",
|
|
309
324
|
data: { fill: "red", shape: "ring", text: "failed read" },
|
|
310
325
|
error: error.message,
|
|
326
|
+
originalMsg: msg,
|
|
311
327
|
nodeId: nodeId
|
|
312
328
|
});
|
|
313
329
|
}
|
|
@@ -362,6 +378,7 @@ class OpcUaServerProcess {
|
|
|
362
378
|
type: "error",
|
|
363
379
|
data: { fill: "red", shape: "ring", text: "failed write" },
|
|
364
380
|
error: error.message,
|
|
381
|
+
originalMsg: msg,
|
|
365
382
|
nodeId: nodeId
|
|
366
383
|
});
|
|
367
384
|
}
|
|
@@ -524,21 +541,33 @@ class OpcUaServerProcess {
|
|
|
524
541
|
msg.topic = writtenPaths[0];
|
|
525
542
|
}
|
|
526
543
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
544
|
+
let hasFailures = false;
|
|
545
|
+
if (Array.isArray(msg.payload) && msg.payload.length && msg.payload[0] && typeof msg.payload[0].status === "string") {
|
|
546
|
+
const failed = msg.payload.filter(item => item.status !== "Good");
|
|
547
|
+
if (failed.length) {
|
|
548
|
+
hasFailures = true;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (directError) {
|
|
552
|
+
hasFailures = true;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (!hasFailures) {
|
|
556
|
+
process.send({
|
|
557
|
+
type: "send",
|
|
558
|
+
data: msg,
|
|
559
|
+
nodeId
|
|
560
|
+
});
|
|
561
|
+
}
|
|
532
562
|
|
|
533
563
|
process.send({
|
|
534
564
|
type: "status",
|
|
535
565
|
data: {
|
|
536
|
-
fill: "green",
|
|
566
|
+
fill: hasFailures ? (directError ? "red" : "yellow") : "green",
|
|
537
567
|
shape: "dot",
|
|
538
|
-
text:
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
: `write ${writtenPaths[0]}`
|
|
568
|
+
text: hasFailures
|
|
569
|
+
? (directError ? "write failed" : "partial write failed")
|
|
570
|
+
: (writtenPaths.length > 1 ? `write ${writtenPaths.length} tags` : `write ${writtenPaths[0]}`)
|
|
542
571
|
},
|
|
543
572
|
nodeId
|
|
544
573
|
});
|
|
@@ -582,6 +611,7 @@ class OpcUaServerProcess {
|
|
|
582
611
|
text: "failed write"
|
|
583
612
|
},
|
|
584
613
|
error: error.message,
|
|
614
|
+
originalMsg: msg,
|
|
585
615
|
nodeId
|
|
586
616
|
});
|
|
587
617
|
}
|
|
@@ -880,6 +910,222 @@ class OpcUaServerProcess {
|
|
|
880
910
|
}
|
|
881
911
|
}
|
|
882
912
|
|
|
913
|
+
async readActiveSessions(msg, nodeId) {
|
|
914
|
+
try {
|
|
915
|
+
await this.ensureReady();
|
|
916
|
+
const server = this.node && this.node.server;
|
|
917
|
+
if (!server || !server.engine) {
|
|
918
|
+
throw new Error("OPC UA server is not available");
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const rawSessions = server.engine._sessions || {};
|
|
922
|
+
const sessions = Object.values(rawSessions).map((session) => buildSessionSnapshot(session));
|
|
923
|
+
|
|
924
|
+
if (msg) {
|
|
925
|
+
const outMsg = Object.assign({}, msg);
|
|
926
|
+
outMsg.payload = sessions;
|
|
927
|
+
|
|
928
|
+
process.send({
|
|
929
|
+
type: "send",
|
|
930
|
+
data: outMsg,
|
|
931
|
+
nodeId: nodeId
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
process.send({
|
|
936
|
+
type: "status",
|
|
937
|
+
data: {
|
|
938
|
+
fill: "green",
|
|
939
|
+
shape: "dot",
|
|
940
|
+
text: sessions.length === 1
|
|
941
|
+
? "1 session"
|
|
942
|
+
: sessions.length + " sessions"
|
|
943
|
+
},
|
|
944
|
+
nodeId: nodeId
|
|
945
|
+
});
|
|
946
|
+
} catch (error) {
|
|
947
|
+
if (msg) {
|
|
948
|
+
process.send({
|
|
949
|
+
type: "error",
|
|
950
|
+
data: { fill: "red", shape: "ring", text: "failed getSessions" },
|
|
951
|
+
error: error.message,
|
|
952
|
+
originalMsg: msg,
|
|
953
|
+
nodeId: nodeId
|
|
954
|
+
});
|
|
955
|
+
} else {
|
|
956
|
+
process.send({
|
|
957
|
+
type: "status",
|
|
958
|
+
data: { fill: "red", shape: "ring", text: "failed getSessions: " + error.message },
|
|
959
|
+
nodeId: nodeId
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
async deleteActiveSessions(msg, nodeId) {
|
|
966
|
+
try {
|
|
967
|
+
await this.ensureReady();
|
|
968
|
+
const server = this.node && this.node.server;
|
|
969
|
+
if (!server || !server.engine) {
|
|
970
|
+
throw new Error("OPC UA server is not available");
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const payload = msg && Array.isArray(msg.payload) ? msg.payload : [];
|
|
974
|
+
if (!payload.length) {
|
|
975
|
+
throw new Error("msg.payload must be a non-empty array of { sessionId } objects");
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const engine = server.engine;
|
|
979
|
+
const rawSessions = engine._sessions || {};
|
|
980
|
+
|
|
981
|
+
const results = payload.map((item) => {
|
|
982
|
+
const requestedId = String(item && item.sessionId || "").trim();
|
|
983
|
+
if (!requestedId) {
|
|
984
|
+
return { sessionId: requestedId, status: "error", error: "sessionId is required" };
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Sessions are keyed by authenticationToken; find by matching nodeId (the GUID sessionId)
|
|
988
|
+
const found = Object.values(rawSessions).find(
|
|
989
|
+
(s) => safeToString(s.nodeId) === requestedId
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
if (!found) {
|
|
993
|
+
return { sessionId: requestedId, status: "not_found" };
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
try {
|
|
997
|
+
engine.closeSession(found.authenticationToken, true, "Forcing");
|
|
998
|
+
return { sessionId: requestedId, status: "deleted" };
|
|
999
|
+
} catch (closeError) {
|
|
1000
|
+
return { sessionId: requestedId, status: "error", error: closeError.message };
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
const deletedCount = results.filter((r) => r.status === "deleted").length;
|
|
1005
|
+
const notFoundCount = results.filter((r) => r.status === "not_found").length;
|
|
1006
|
+
const errorCount = results.filter((r) => r.status === "error").length;
|
|
1007
|
+
|
|
1008
|
+
if (msg) {
|
|
1009
|
+
const outMsg = Object.assign({}, msg);
|
|
1010
|
+
outMsg.payload = results;
|
|
1011
|
+
|
|
1012
|
+
process.send({
|
|
1013
|
+
type: "send",
|
|
1014
|
+
data: outMsg,
|
|
1015
|
+
nodeId: nodeId
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const statusParts = [];
|
|
1020
|
+
if (deletedCount) statusParts.push("deleted " + deletedCount);
|
|
1021
|
+
if (notFoundCount) statusParts.push("not found " + notFoundCount);
|
|
1022
|
+
if (errorCount) statusParts.push("error " + errorCount);
|
|
1023
|
+
|
|
1024
|
+
process.send({
|
|
1025
|
+
type: "status",
|
|
1026
|
+
data: {
|
|
1027
|
+
fill: errorCount ? "red" : (notFoundCount ? "yellow" : "green"),
|
|
1028
|
+
shape: "dot",
|
|
1029
|
+
text: statusParts.join(", ") || "no sessions"
|
|
1030
|
+
},
|
|
1031
|
+
nodeId: nodeId
|
|
1032
|
+
});
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
if (msg) {
|
|
1035
|
+
process.send({
|
|
1036
|
+
type: "error",
|
|
1037
|
+
data: { fill: "red", shape: "ring", text: "failed deleteSessions" },
|
|
1038
|
+
error: error.message,
|
|
1039
|
+
originalMsg: msg,
|
|
1040
|
+
nodeId: nodeId
|
|
1041
|
+
});
|
|
1042
|
+
} else {
|
|
1043
|
+
process.send({
|
|
1044
|
+
type: "status",
|
|
1045
|
+
data: { fill: "red", shape: "ring", text: "failed deleteSessions: " + error.message },
|
|
1046
|
+
nodeId: nodeId
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Serializes a node-opcua ServerSession into a plain, IPC-safe object.
|
|
1056
|
+
*/
|
|
1057
|
+
function buildSessionSnapshot(session) {
|
|
1058
|
+
return {
|
|
1059
|
+
sessionId: safeToString(session.nodeId),
|
|
1060
|
+
sessionName: String(session.sessionName || ""),
|
|
1061
|
+
status: String(session.__status || ""),
|
|
1062
|
+
creationDate: session.creationDate instanceof Date ? session.creationDate.toISOString() : null,
|
|
1063
|
+
sessionTimeout: safeNumber(session.sessionTimeout),
|
|
1064
|
+
clientLastContactTime: safeNumber(session.clientLastContactTime),
|
|
1065
|
+
channelId: session.channelId != null ? session.channelId : null,
|
|
1066
|
+
clientDescription: buildClientDescription(session.clientDescription),
|
|
1067
|
+
userIdentityToken: buildUserIdentityToken(session.userIdentityToken),
|
|
1068
|
+
channel: buildChannelInfo(session.channel),
|
|
1069
|
+
currentSubscriptionCount: safeNumber(session.currentSubscriptionCount),
|
|
1070
|
+
cumulatedSubscriptionCount: safeNumber(session.cumulatedSubscriptionCount),
|
|
1071
|
+
currentMonitoredItemCount: safeNumber(session.currentMonitoredItemCount),
|
|
1072
|
+
aborted: Boolean(session.aborted)
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function safeToString(value) {
|
|
1077
|
+
try {
|
|
1078
|
+
return value != null ? String(value) : null;
|
|
1079
|
+
} catch (_) {
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function safeNumber(value) {
|
|
1085
|
+
const n = Number(value);
|
|
1086
|
+
return Number.isFinite(n) ? n : null;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function buildClientDescription(desc) {
|
|
1090
|
+
if (!desc || typeof desc !== "object") {
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
applicationUri: safeToString(desc.applicationUri),
|
|
1095
|
+
productUri: safeToString(desc.productUri),
|
|
1096
|
+
applicationName: desc.applicationName && desc.applicationName.text
|
|
1097
|
+
? String(desc.applicationName.text)
|
|
1098
|
+
: safeToString(desc.applicationName),
|
|
1099
|
+
applicationType: safeToString(desc.applicationType)
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function buildUserIdentityToken(token) {
|
|
1104
|
+
if (!token || typeof token !== "object") {
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
return {
|
|
1108
|
+
policyId: safeToString(token.policyId),
|
|
1109
|
+
userName: safeToString(token.userName),
|
|
1110
|
+
// Never expose passwords or raw credential bytes
|
|
1111
|
+
tokenType: safeToString(token.schema && token.schema.name)
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function buildChannelInfo(channel) {
|
|
1116
|
+
if (!channel || typeof channel !== "object") {
|
|
1117
|
+
return null;
|
|
1118
|
+
}
|
|
1119
|
+
return {
|
|
1120
|
+
channelId: channel.channelId != null ? channel.channelId : null,
|
|
1121
|
+
remoteAddress: safeToString(channel.remoteAddress),
|
|
1122
|
+
remotePort: safeNumber(channel.remotePort),
|
|
1123
|
+
bytesRead: safeNumber(channel.bytesRead),
|
|
1124
|
+
bytesWritten: safeNumber(channel.bytesWritten),
|
|
1125
|
+
transactionsCount: safeNumber(channel.transactionsCount),
|
|
1126
|
+
securityMode: safeToString(channel.securityMode),
|
|
1127
|
+
securityPolicy: safeToString(channel.securityPolicy)
|
|
1128
|
+
};
|
|
883
1129
|
}
|
|
884
1130
|
|
|
885
1131
|
/**
|
|
@@ -932,14 +1178,33 @@ process.on("message", async (msg) => {
|
|
|
932
1178
|
break;
|
|
933
1179
|
|
|
934
1180
|
case "buildServerSnapshot":
|
|
935
|
-
|
|
936
|
-
|
|
1181
|
+
try {
|
|
1182
|
+
await serverProcess.ensureReady();
|
|
1183
|
+
OpcUaServerStatusNode(serverProcess.node, msg.msg, msg.nodeId);
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
process.send({
|
|
1186
|
+
type: "status",
|
|
1187
|
+
data: {
|
|
1188
|
+
fill: msg.msg ? "red" : "yellow",
|
|
1189
|
+
shape: "ring",
|
|
1190
|
+
text: msg.msg ? "Status: " + error.message : "waiting for server"
|
|
1191
|
+
},
|
|
1192
|
+
nodeId: msg.nodeId
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
break;
|
|
937
1196
|
case "eventsServer":
|
|
938
1197
|
eventsServer(serverProcess.node, msg.node, msg.nodeId)
|
|
939
1198
|
break;
|
|
940
1199
|
case "readActiveAlarms":
|
|
941
1200
|
serverProcess.readActiveAlarms(msg.msg, msg.nodeId)
|
|
942
1201
|
break;
|
|
1202
|
+
case "readActiveSessions":
|
|
1203
|
+
await serverProcess.readActiveSessions(msg.msg, msg.nodeId);
|
|
1204
|
+
break;
|
|
1205
|
+
case "deleteActiveSessions":
|
|
1206
|
+
await serverProcess.deleteActiveSessions(msg.msg, msg.nodeId);
|
|
1207
|
+
break;
|
|
943
1208
|
|
|
944
1209
|
|
|
945
1210
|
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
const path = require("path");
|
|
5
6
|
const {
|
|
6
7
|
OPCUAServer,
|
|
7
8
|
UserTokenType,
|
|
8
9
|
buildApplicationUri,
|
|
9
10
|
makeRoles,
|
|
10
11
|
WellKnownRoles,
|
|
11
|
-
resolveNodeId
|
|
12
|
+
resolveNodeId,
|
|
13
|
+
OPCUACertificateManager,
|
|
14
|
+
SecurityPolicy,
|
|
15
|
+
MessageSecurityMode
|
|
12
16
|
} = require("./opcua-constants");
|
|
13
17
|
const { OpcUaAddressSpaceBuilder } = require("./opcua-address-space-builder");
|
|
14
18
|
const { OpcUaServerMethods } = require("./opcua-server-methods");
|
|
@@ -29,13 +33,22 @@ class OpcUaServerRuntime {
|
|
|
29
33
|
this.serverName = options.settings.serverName;
|
|
30
34
|
this.port = options.settings.port;
|
|
31
35
|
this.maxConnections = options.settings.maxConnections;
|
|
36
|
+
this.minSessionTimeout = options.settings.minSessionTimeout;
|
|
37
|
+
this.defaultSessionTimeout = options.settings.defaultSessionTimeout;
|
|
38
|
+
this.maxSessionTimeout = options.settings.maxSessionTimeout;
|
|
32
39
|
this.namespaceUri = options.settings.namespaceUri;
|
|
33
40
|
this.resourcePath = options.settings.resourcePath;
|
|
34
41
|
this.allowAnonymous = options.settings.allowAnonymous;
|
|
42
|
+
this.automaticallyAcceptUnknownCertificate = options.settings.automaticallyAcceptUnknownCertificate;
|
|
43
|
+
this.certificatesFolder = options.settings.certificatesFolder;
|
|
35
44
|
this.groups = options.settings.groups;
|
|
36
45
|
this.users = options.settings.users;
|
|
37
|
-
this.
|
|
38
|
-
|
|
46
|
+
this.securityPolicies = Array.isArray(options.settings.securityPolicies) && options.settings.securityPolicies.length > 0
|
|
47
|
+
? options.settings.securityPolicies
|
|
48
|
+
: [SecurityPolicy.None];
|
|
49
|
+
this.securityModes = Array.isArray(options.settings.securityModes) && options.settings.securityModes.length > 0
|
|
50
|
+
? options.settings.securityModes
|
|
51
|
+
: [MessageSecurityMode.None];
|
|
39
52
|
this.treeConfig = options.settings.treeConfig;
|
|
40
53
|
|
|
41
54
|
this.server = null;
|
|
@@ -52,6 +65,28 @@ class OpcUaServerRuntime {
|
|
|
52
65
|
return;
|
|
53
66
|
}
|
|
54
67
|
|
|
68
|
+
const certificateFolder = this.certificatesFolder || path.resolve(__dirname, "..", "..", "certificates");
|
|
69
|
+
this.serverCertificateManager = new OPCUACertificateManager({
|
|
70
|
+
rootFolder: certificateFolder,
|
|
71
|
+
automaticallyAcceptUnknownCertificate: this.automaticallyAcceptUnknownCertificate
|
|
72
|
+
});
|
|
73
|
+
await this.serverCertificateManager.initialize();
|
|
74
|
+
|
|
75
|
+
// Ensure directories exist immediately on startup
|
|
76
|
+
const fs = require("fs");
|
|
77
|
+
try {
|
|
78
|
+
const trustedDir = path.join(certificateFolder, "trusted", "certs");
|
|
79
|
+
const rejectedDir = path.join(certificateFolder, "rejected");
|
|
80
|
+
if (!fs.existsSync(trustedDir)) {
|
|
81
|
+
fs.mkdirSync(trustedDir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
if (!fs.existsSync(rejectedDir)) {
|
|
84
|
+
fs.mkdirSync(rejectedDir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
// Ignore directory creation errors
|
|
88
|
+
}
|
|
89
|
+
|
|
55
90
|
this.server = new OPCUAServer(this.buildServerOptions());
|
|
56
91
|
await this.server.initialize();
|
|
57
92
|
|
|
@@ -74,6 +109,7 @@ class OpcUaServerRuntime {
|
|
|
74
109
|
|
|
75
110
|
this.addressSpaceBuilder.rebuild(this.treeConfig);
|
|
76
111
|
await this.server.start();
|
|
112
|
+
|
|
77
113
|
this.registry.registerServer(this);
|
|
78
114
|
|
|
79
115
|
//Methods
|
|
@@ -200,9 +236,17 @@ class OpcUaServerRuntime {
|
|
|
200
236
|
});
|
|
201
237
|
}
|
|
202
238
|
|
|
239
|
+
const certificatesFolder = this.certificatesFolder || path.resolve(__dirname, "..", "..", "certificates");
|
|
240
|
+
|
|
203
241
|
return {
|
|
204
242
|
port: this.port,
|
|
243
|
+
minSessionTimeout: this.minSessionTimeout !== undefined ? this.minSessionTimeout : 100,
|
|
244
|
+
defaultSessionTimeout: this.defaultSessionTimeout !== undefined ? this.defaultSessionTimeout : 30000,
|
|
245
|
+
maxSessionTimeout: this.maxSessionTimeout !== undefined ? this.maxSessionTimeout : 3000000,
|
|
205
246
|
resourcePath: this.resourcePath,
|
|
247
|
+
serverCertificateManager: this.serverCertificateManager,
|
|
248
|
+
certificateFile: path.join(certificatesFolder, "own", "certs", "server_selfsigned_cert_2048.pem"),
|
|
249
|
+
privateKeyFile: path.join(certificatesFolder, "own", "private", "private_key.pem"),
|
|
206
250
|
buildInfo: {
|
|
207
251
|
productName: "opc-ua-server",
|
|
208
252
|
buildNumber: "1",
|
|
@@ -216,8 +260,8 @@ class OpcUaServerRuntime {
|
|
|
216
260
|
applicationUri: buildApplicationUri(this.serverName),
|
|
217
261
|
productUri: "urn:node-red:opc-ua-server"
|
|
218
262
|
},
|
|
219
|
-
securityPolicies:
|
|
220
|
-
securityModes:
|
|
263
|
+
securityPolicies: this.securityPolicies,
|
|
264
|
+
securityModes: this.securityModes,
|
|
221
265
|
allowAnonymous: this.allowAnonymous,
|
|
222
266
|
userManager: {
|
|
223
267
|
isValidUser: (username, password) => this.isValidUser(username, password),
|
|
@@ -10,13 +10,6 @@ function OpcUaServerStatusNode(node, msg, nodeId) {
|
|
|
10
10
|
const serverNode = resolveRegisteredServer(node, msg, registry);
|
|
11
11
|
const snapshot = buildServerSnapshot(serverNode);
|
|
12
12
|
|
|
13
|
-
msg.payload = snapshot;
|
|
14
|
-
msg.opcua = msg.opcua || {};
|
|
15
|
-
msg.opcua.server = snapshot.identity.serverRef;
|
|
16
|
-
msg.opcua.endpointUrl = snapshot.endpointUrl;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
13
|
process.send({
|
|
21
14
|
type: "status",
|
|
22
15
|
data: {
|
|
@@ -27,11 +20,18 @@ function OpcUaServerStatusNode(node, msg, nodeId) {
|
|
|
27
20
|
nodeId: nodeId
|
|
28
21
|
});
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
if (msg) {
|
|
24
|
+
msg.payload = snapshot;
|
|
25
|
+
msg.opcua = msg.opcua || {};
|
|
26
|
+
msg.opcua.server = snapshot.identity.serverRef;
|
|
27
|
+
msg.opcua.endpointUrl = snapshot.endpointUrl;
|
|
28
|
+
|
|
29
|
+
process.send({
|
|
30
|
+
type: "send",
|
|
31
|
+
data: msg,
|
|
32
|
+
nodeId: nodeId
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
} catch (error) {
|
|
@@ -41,9 +41,9 @@ function OpcUaServerStatusNode(node, msg, nodeId) {
|
|
|
41
41
|
process.send({
|
|
42
42
|
type: "status",
|
|
43
43
|
data: {
|
|
44
|
-
fill: "red",
|
|
44
|
+
fill: msg ? "red" : "yellow",
|
|
45
45
|
shape: "ring",
|
|
46
|
-
text: "Status: " + error
|
|
46
|
+
text: msg ? "Status: " + error.message : "waiting for server"
|
|
47
47
|
},
|
|
48
48
|
nodeId: nodeId
|
|
49
49
|
});
|
|
File without changes
|
package/server/nodered/simple_opcua/server/certificates/own/certs/server_selfsigned_cert_2048.pem
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIENTCCAx2gAwIBAgIHAXgZZJFWWTANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG
|
|
3
|
+
EwJGUjEQMA4GA1UEBxMHT3JsZWFuczERMA8GA1UEChMIU3RlcmZpdmUxFjAUBgNV
|
|
4
|
+
BAMMDW15U2VydmVyMUB0dWYwHhcNMjYwNjIwMTQxNTE1WhcNMzYwNjE3MTQxNTE1
|
|
5
|
+
WjBKMQswCQYDVQQGEwJGUjEQMA4GA1UEBxMHT3JsZWFuczERMA8GA1UEChMIU3Rl
|
|
6
|
+
cmZpdmUxFjAUBgNVBAMMDW15U2VydmVyMUB0dWYwggEiMA0GCSqGSIb3DQEBAQUA
|
|
7
|
+
A4IBDwAwggEKAoIBAQC7IYbSbk1mosq3NdM494XdHfuLulEf07RmRRcXhv53/iPC
|
|
8
|
+
HTzTpyyaT9P2KItUEMf1+1m26O53gl8BlwbS+oCuDt34ORO0dWDoftgBXjP0uUWz
|
|
9
|
+
seaZ7AVwXL1yorYAK11VLHU4cxfzVLKK5WbiB+qEiQSUG+EKHlEtmyIEbrsOGh8s
|
|
10
|
+
ZZCE4rFjavzSaCGN7bwqNMtGyFPS5FGZ8HvvtLqhzyz0ew0TaZKpnaFQGxtvGI68
|
|
11
|
+
6yICgutmSXqyII8OHCXQ751gLzbhvADVUf/CvhX60ZCDMcGBQcFeLPkZF4CfQ2OP
|
|
12
|
+
2OZkm/2ulS+NZXWqDFX6pvN/Zsy57q4RKR+x+nDFAgMBAAGjggEeMIIBGjBVBglg
|
|
13
|
+
hkgBhvhCAQ0ESAxGU2VsZi1zaWduZWQgY2VydGlmaWNhdGUgZ2VuZXJhdGVkIGJ5
|
|
14
|
+
IE5vZGUtT1BDVUEgQ2VydGlmaWNhdGUgdXRpbGl0eSBWMjAMBgNVHRMBAf8EAjAA
|
|
15
|
+
MCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAOBgNVHQ8BAf8EBAMC
|
|
16
|
+
AvQwHQYDVR0OBBYEFF3kmynfd5siPu9cso8NLIdURx8+MB8GA1UdIwQYMBaAFF3k
|
|
17
|
+
mynfd5siPu9cso8NLIdURx8+MEEGA1UdEQQ6MDiCA1RVRoIDdHVmhwTAqGQBhwTA
|
|
18
|
+
qA9khwTAqBEBhhp1cm46dHVmOm5vZGUtcmVkOm15c2VydmVyMTANBgkqhkiG9w0B
|
|
19
|
+
AQsFAAOCAQEAkDutnUHoeK6CQmYlTV2pJPW+1qcJ9QDCrDEJjoMReWY97CQqrPxm
|
|
20
|
+
9/nTQtPiHedsiF7LbOqsPpiK/chEACSaQO+OYzGtMydKehlC89KUG26TH+OgtVjM
|
|
21
|
+
zVi3sfSpo33jJ1T1Z8YKw9rnOfjkWzfMa6RW4+4p1DNR4RJcvKsNRTkh/C8RDFr0
|
|
22
|
+
ZPave1jxaYpxPQGHKD1lqQD6uIGaYwAogkQDn41Bl1/PwC79gOgs0WmKDZuQ3BpR
|
|
23
|
+
wGSa6Bcqzj3zzBQapSs5fFjuUyowaFsGI48N3mJJx4LlYB17yuOD5R6a6nzVJyaL
|
|
24
|
+
QsKqGvKkNFP0c3F1H+pkUGFGgR9PkkA5Xg==
|
|
25
|
+
-----END CERTIFICATE-----
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
##################################################################################################
|
|
2
|
+
## SIMPLE OPENSSL CONFIG FILE FOR SELF-SIGNED CERTIFICATE GENERATION
|
|
3
|
+
################################################################################################################
|
|
4
|
+
|
|
5
|
+
distinguished_name = req_distinguished_name
|
|
6
|
+
default_md = sha1
|
|
7
|
+
|
|
8
|
+
default_md = sha256 # The default digest algorithm
|
|
9
|
+
|
|
10
|
+
[ v3_ca ]
|
|
11
|
+
subjectKeyIdentifier = hash
|
|
12
|
+
authorityKeyIdentifier = keyid:always,issuer:always
|
|
13
|
+
|
|
14
|
+
# authorityKeyIdentifier = keyid
|
|
15
|
+
basicConstraints = CA:TRUE
|
|
16
|
+
keyUsage = critical, cRLSign, keyCertSign
|
|
17
|
+
nsComment = "Self-signed Certificate for CA generated by Node-OPCUA Certificate utility"
|
|
18
|
+
#nsCertType = sslCA, emailCA
|
|
19
|
+
#subjectAltName = email:copy
|
|
20
|
+
#issuerAltName = issuer:copy
|
|
21
|
+
#obj = DER:02:03
|
|
22
|
+
# crlDistributionPoints = @crl_info
|
|
23
|
+
# [ crl_info ]
|
|
24
|
+
# URI.0 = http://localhost:8900/crl.pem
|
|
25
|
+
subjectAltName = $ENV::ALTNAME
|
|
26
|
+
|
|
27
|
+
[ req ]
|
|
28
|
+
days = 390
|
|
29
|
+
req_extensions = v3_req
|
|
30
|
+
x509_extensions = v3_ca
|
|
31
|
+
|
|
32
|
+
[v3_req]
|
|
33
|
+
basicConstraints = CA:false
|
|
34
|
+
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment
|
|
35
|
+
subjectAltName = $ENV::ALTNAME
|
|
36
|
+
|
|
37
|
+
[ v3_ca_signed]
|
|
38
|
+
subjectKeyIdentifier = hash
|
|
39
|
+
authorityKeyIdentifier = keyid,issuer
|
|
40
|
+
basicConstraints = critical, CA:FALSE
|
|
41
|
+
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment
|
|
42
|
+
extendedKeyUsage = clientAuth,serverAuth
|
|
43
|
+
nsComment = "certificate generated by Node-OPCUA Certificate utility and signed by a CA"
|
|
44
|
+
subjectAltName = $ENV::ALTNAME
|
|
45
|
+
[ v3_selfsigned]
|
|
46
|
+
subjectKeyIdentifier = hash
|
|
47
|
+
authorityKeyIdentifier = keyid,issuer
|
|
48
|
+
basicConstraints = critical, CA:FALSE
|
|
49
|
+
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment
|
|
50
|
+
extendedKeyUsage = clientAuth,serverAuth
|
|
51
|
+
nsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility"
|
|
52
|
+
subjectAltName = $ENV::ALTNAME
|
|
53
|
+
[ req_distinguished_name ]
|
|
54
|
+
countryName = Country Name (2 letter code)
|
|
55
|
+
countryName_default = FR
|
|
56
|
+
countryName_min = 2
|
|
57
|
+
countryName_max = 2
|
|
58
|
+
# stateOrProvinceName = State or Province Name (full name)
|
|
59
|
+
# stateOrProvinceName_default = Ile de France
|
|
60
|
+
# localityName = Locality Name (city, district)
|
|
61
|
+
# localityName_default = Paris
|
|
62
|
+
organizationName = Organization Name (company)
|
|
63
|
+
organizationName_default = NodeOPCUA
|
|
64
|
+
# organizationalUnitName = Organizational Unit Name (department, division)
|
|
65
|
+
# organizationalUnitName_default = R&D
|
|
66
|
+
commonName = Common Name (hostname, FQDN, IP, or your name)
|
|
67
|
+
commonName_max = 256
|
|
68
|
+
commonName_default = NodeOPCUA
|
|
69
|
+
# emailAddress = Email Address
|
|
70
|
+
# emailAddress_max = 40
|
|
71
|
+
# emailAddress_default = node-opcua (at) node-opcua (dot) com
|
|
72
|
+
subjectAltName = $ENV::ALTNAME
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-----BEGIN PRIVATE KEY-----
|
|
2
|
+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7IYbSbk1mosq3
|
|
3
|
+
NdM494XdHfuLulEf07RmRRcXhv53/iPCHTzTpyyaT9P2KItUEMf1+1m26O53gl8B
|
|
4
|
+
lwbS+oCuDt34ORO0dWDoftgBXjP0uUWzseaZ7AVwXL1yorYAK11VLHU4cxfzVLKK
|
|
5
|
+
5WbiB+qEiQSUG+EKHlEtmyIEbrsOGh8sZZCE4rFjavzSaCGN7bwqNMtGyFPS5FGZ
|
|
6
|
+
8HvvtLqhzyz0ew0TaZKpnaFQGxtvGI686yICgutmSXqyII8OHCXQ751gLzbhvADV
|
|
7
|
+
Uf/CvhX60ZCDMcGBQcFeLPkZF4CfQ2OP2OZkm/2ulS+NZXWqDFX6pvN/Zsy57q4R
|
|
8
|
+
KR+x+nDFAgMBAAECggEAGofM0Q3BXIFNAz+W1DdHm8tR7AP812nQ5ET8WqIRdtqp
|
|
9
|
+
oC47/vfvG2ug7f/ejQsaBnZdyFBq6repl5Sda5EHaGYEM1qIQCf6FsxQ/JSqAhK4
|
|
10
|
+
XpskygLL3JvRYizOZ+S+BULZ1ah/p0iFTarrdLVRmvvAEe2H8MLOmIwrfPxj+cId
|
|
11
|
+
Lb02xSX3FEf7TC4fUkTJQP52PDNT/oIZhUE807tBmlCLhAIKg3DaZhnAXILpY9q6
|
|
12
|
+
HWbR1lZffzz912XY2fOeHhZI6yKH2BcciVMbP3MEuFLaEQKyYpQCYIraBlcYcGNT
|
|
13
|
+
8pyIPBukaejOJ08MHZQrh6PFYzZrISRJHVriFYDfuwKBgQDm3T1UOq3DBOOo7TvG
|
|
14
|
+
VgNSfxZaXRCAua12qM9f0iz7DA5waFQ8l+0oHcBikRtuvaxGq9kfmINFqUQEow4w
|
|
15
|
+
YXloR5AdNgub4Q8Wb/kMYr1Qq750q5MvavhxT2PKNzONQ5f0g9E2DciSVoZeVSK4
|
|
16
|
+
6q+ZyuodXw2ahLS5pJUSBCCFIwKBgQDPgVUakeePBL+g0Oy5i8DuuTErj1h2J/dA
|
|
17
|
+
IYDvA3MvccQMJNf1tKkDfTdXZd/CG4JL5JNYlCzPT7V2KInXrL8sFWi6/Y+t1MXA
|
|
18
|
+
E+LHI33Il4jbBBvWx9ItyBS6oouANGaO4pQZL0c720hGffK246P0RiER8T1fdeS3
|
|
19
|
+
JPv+ylPU9wKBgDeQFbuY58sg+R3mAtXoS6JmPd3/ugIRLiN523cnYXYGX89D/Moc
|
|
20
|
+
kpJuHqhaXizX74eOwpHtJeL1Kw6mo7qXKx4i4xd0s3SPxQ7UYi9N8FxjCVKRHLpz
|
|
21
|
+
11mGDvFTOdAM7ZyGwSpuRNCbjHlVqiaxLRQplxD8mIyQ1eI8LziHz7/bAoGBAMBy
|
|
22
|
+
VWh/+v8ES2kteu8Wcwe0D6szlbp3lHMQ35BMZc6Rt13/6Z6CP+Hxhpry65QNiUkz
|
|
23
|
+
o4gaXHikl0oPjM/O8bpD3M7XjSKN3B0pFEDWZLjd+VoOtHb8+avmDXuOdsyfTKKl
|
|
24
|
+
9u5oj6su0xg1hR4jf5J0XAVU9DONlmJY1bFXGmq/AoGAZmCwBdOA830uETdh3AOb
|
|
25
|
+
cI8F1SozJOqGbTrl7GzyzOUvFxsMkD9NOHCI4LD6sDRdJM1CE1As33hNKG2v9B+C
|
|
26
|
+
/MTpbWsTWCAmMhAy7QUXMqDq/g5J2WbfhcXErGps4fQxJzVt1sapedIzFBw9m5XP
|
|
27
|
+
QVMSTmXF6C3wg3QbzTVsQTM=
|
|
28
|
+
-----END PRIVATE KEY-----
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIEKjCCAxKgAwIBAgIHAXdVh2g4JzANBgkqhkiG9w0BAQsFADBRMQswCQYDVQQG
|
|
3
|
+
EwJGUjEQMA4GA1UEBxMHT3JsZWFuczERMA8GA1UEChMIU3RlcmZpdmUxHTAbBgNV
|
|
4
|
+
BAMMFE5vZGVPUENVQS1DbGllbnRAdHVmMB4XDTI2MDQwNzE4NDgwM1oXDTM2MDQw
|
|
5
|
+
NDE4NDgwM1owUTELMAkGA1UEBhMCRlIxEDAOBgNVBAcTB09ybGVhbnMxETAPBgNV
|
|
6
|
+
BAoTCFN0ZXJmaXZlMR0wGwYDVQQDDBROb2RlT1BDVUEtQ2xpZW50QHR1ZjCCASIw
|
|
7
|
+
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMhBslFMtRXQ2Ww7JAtPLoM7I1s
|
|
8
|
+
QP7mmHOv0U7mcSLib/yksMNT0G2U5J1EHI+Km1JwTYXujJnzi/ouuuQsWUsm3zP6
|
|
9
|
+
KAO1XzQuIYf8iEBblzOCA53eTvo056zNfeDoUb9l1CfQH/H6sL2js786dUpuJCG9
|
|
10
|
+
fuCfgrExRbzZQrELcduwRPuVio770NK086orAzFIXaRM20F4HMkTtorsRoGUIU5s
|
|
11
|
+
u6Fqy9nmdKY4w4JHQMafO3L1lHEbGuVwZmmkXmTQtBIS6W8tPSa9fh7GrlHJP4Zc
|
|
12
|
+
cgcp/xuXcpWfHjJaAmUQtCuNprlywJBChlstWkJ09zj1VvqoAWd0GL7ehQkCAwEA
|
|
13
|
+
AaOCAQUwggEBMFUGCWCGSAGG+EIBDQRIDEZTZWxmLXNpZ25lZCBjZXJ0aWZpY2F0
|
|
14
|
+
ZSBnZW5lcmF0ZWQgYnkgTm9kZS1PUENVQSBDZXJ0aWZpY2F0ZSB1dGlsaXR5IFYy
|
|
15
|
+
MAwGA1UdEwEB/wQCMAAwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC
|
|
16
|
+
MA4GA1UdDwEB/wQEAwIC9DAdBgNVHQ4EFgQUVOT0LzYI7XAMQ11FOV/+tUCUkvYw
|
|
17
|
+
HwYDVR0jBBgwFoAUVOT0LzYI7XAMQ11FOV/+tUCUkvYwKAYDVR0RBCEwH4IDdHVm
|
|
18
|
+
hhh1cm46dHVmOk5vZGVPUENVQS1DbGllbnQwDQYJKoZIhvcNAQELBQADggEBABiN
|
|
19
|
+
x0XNf1nDyRyJQCQ4GMS9e/xrjO3FV106IJRPEosoB10R7zBL92ra4+3AilGnNOeM
|
|
20
|
+
1O5eIel67DNL2kjw+5ggagtOmhJ3g3ZXFMpF08BeKnMwavoLs74yBZVFDq+2fOxs
|
|
21
|
+
kDdMX5TOpC4kZHMG5G9Cha9mcWjYSAYWVko/WCfby4D+MQiwraAraDL12uFcRVnP
|
|
22
|
+
htDTuNxs+dfKefiIsTgSlkJMisfGEV/EzmwSsTpvtuUZ5DwKddolRGsB/cXqTjc0
|
|
23
|
+
vGxHFjsuqp77gUyb9LNqE8ENi7pOZQFFhAmjSXTXcklWVynycgQ1E0TSNNQio4Xi
|
|
24
|
+
QZtA1NgPnLI6b2kubUQ=
|
|
25
|
+
-----END CERTIFICATE-----
|