node-opcua-server 2.78.0 → 2.79.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.
@@ -173,6 +173,7 @@ import { ISocketData } from "./i_socket_data";
173
173
  import { IChannelData } from "./i_channel_data";
174
174
  import { UAUserManagerBase, makeUserManager, UserManagerOptions } from "./user_manager";
175
175
  import { bindRoleSet } from "./user_manager_ua";
176
+ import { SamplingFunc } from "./sampling_func";
176
177
 
177
178
  function isSubscriptionIdInvalid(subscriptionId: number): boolean {
178
179
  return subscriptionId < 0 || subscriptionId >= 0xffffffff;
@@ -403,7 +404,7 @@ function thumbprint(certificate?: Certificate): string {
403
404
  */
404
405
  function monitoredItem_read_and_record_value(
405
406
  self: MonitoredItem,
406
- context: ISessionContext | null,
407
+ context: ISessionContext,
407
408
  oldValue: DataValue,
408
409
  node: UAVariable,
409
410
  itemToMonitor: any,
@@ -447,13 +448,7 @@ function monitoredItem_read_and_record_value_async(
447
448
  });
448
449
  }
449
450
 
450
- function build_scanning_node_function(
451
- context: ISessionContext,
452
- addressSpace: AddressSpace,
453
- monitoredItem: MonitoredItem,
454
- itemToMonitor: any
455
- ): (dataValue: DataValue, callback: (err: Error | null, dataValue?: DataValue) => void) => void {
456
- assert(context instanceof SessionContext);
451
+ function build_scanning_node_function(addressSpace: AddressSpace, itemToMonitor: any): SamplingFunc {
457
452
  assert(itemToMonitor instanceof ReadValueId);
458
453
 
459
454
  const node = addressSpace.findNode(itemToMonitor.nodeId) as UAVariable;
@@ -462,7 +457,11 @@ function build_scanning_node_function(
462
457
  if (!node) {
463
458
  errorLog(" INVALID NODE ID , ", itemToMonitor.nodeId.toString());
464
459
  dump(itemToMonitor);
465
- return (oldData: DataValue, callback: (err: Error | null, dataValue?: DataValue) => void) => {
460
+ return (
461
+ _sessionContext: ISessionContext,
462
+ _oldData: DataValue,
463
+ callback: (err: Error | null, dataValue?: DataValue) => void
464
+ ) => {
466
465
  callback(
467
466
  null,
468
467
  new DataValue({
@@ -483,13 +482,14 @@ function build_scanning_node_function(
483
482
 
484
483
  return function func(
485
484
  this: MonitoredItem,
485
+ sessionContext: ISessionContext,
486
486
  oldDataValue: DataValue,
487
487
  callback: (err: Error | null, dataValue?: DataValue) => void
488
488
  ) {
489
489
  assert(this instanceof MonitoredItem);
490
490
  assert(oldDataValue instanceof DataValue);
491
491
  assert(typeof callback === "function");
492
- monitoredItem_read_and_record_value_func(this, context, oldDataValue, node, itemToMonitor, callback);
492
+ monitoredItem_read_and_record_value_func(this, sessionContext, oldDataValue, node, itemToMonitor, callback);
493
493
  };
494
494
  } else {
495
495
  // Attributes, other than the Value Attribute, are only monitored for a change in value.
@@ -499,13 +499,14 @@ function build_scanning_node_function(
499
499
  // only record value when it has changed
500
500
  return function func(
501
501
  this: MonitoredItem,
502
+ sessionContext: ISessionContext,
502
503
  oldDataValue: DataValue,
503
504
  callback: (err: Error | null, dataValue?: DataValue) => void
504
505
  ) {
505
506
  assert(this instanceof MonitoredItem);
506
507
  assert(oldDataValue instanceof DataValue);
507
508
  assert(typeof callback === "function");
508
- const newDataValue = node.readAttribute(null, itemToMonitor.attributeId);
509
+ const newDataValue = node.readAttribute(sessionContext, itemToMonitor.attributeId);
509
510
  callback(null, newDataValue);
510
511
  };
511
512
  }
@@ -513,7 +514,7 @@ function build_scanning_node_function(
513
514
 
514
515
  function prepareMonitoredItem(context: ISessionContext, addressSpace: AddressSpace, monitoredItem: MonitoredItem) {
515
516
  const itemToMonitor = monitoredItem.itemToMonitor;
516
- const readNodeFunc = build_scanning_node_function(context, addressSpace, monitoredItem, itemToMonitor);
517
+ const readNodeFunc = build_scanning_node_function(addressSpace, itemToMonitor);
517
518
  monitoredItem.samplingFunc = readNodeFunc;
518
519
  }
519
520
 
@@ -1574,7 +1575,6 @@ export class OPCUAServer extends OPCUABaseServer {
1574
1575
  comment: { dataType: DataType.String, value: certificateStatus.toString() }
1575
1576
  });
1576
1577
  break;
1577
-
1578
1578
  }
1579
1579
  }
1580
1580
  if (
@@ -1651,7 +1651,7 @@ export class OPCUAServer extends OPCUABaseServer {
1651
1651
  }
1652
1652
 
1653
1653
  const length = buff.readUInt32LE(0) - serverNonce.length;
1654
- password = buff.slice(4, 4 + length).toString("utf-8");
1654
+ password = buff.subarray(4, 4 + length).toString("utf-8");
1655
1655
  }
1656
1656
 
1657
1657
  this.userManager
@@ -1833,7 +1833,8 @@ export class OPCUAServer extends OPCUABaseServer {
1833
1833
  // see Release 1.02 27 OPC Unified Architecture, Part 4
1834
1834
  const session = this.createSession({
1835
1835
  clientDescription: request.clientDescription,
1836
- sessionTimeout: revisedSessionTimeout
1836
+ sessionTimeout: revisedSessionTimeout,
1837
+ server: this
1837
1838
  });
1838
1839
  session.endpoint = endpoint;
1839
1840
 
@@ -2101,8 +2102,6 @@ export class OPCUAServer extends OPCUABaseServer {
2101
2102
  return rejectConnection(this, StatusCodes.BadIdentityChangeNotSupported); // not sure about this code !
2102
2103
  }
2103
2104
  }
2104
-
2105
- moveSessionToChannel(session, channel);
2106
2105
  } else if (session.status === "screwed") {
2107
2106
  // session has been used before being activated => this should be detected and session should be dismissed.
2108
2107
  return rejectConnection(this, StatusCodes.BadSessionClosed);
@@ -2134,7 +2133,6 @@ export class OPCUAServer extends OPCUABaseServer {
2134
2133
  }
2135
2134
  return rejectConnection(this, statusCode);
2136
2135
  }
2137
- session.userIdentityToken = request.userIdentityToken as UserIdentityToken;
2138
2136
 
2139
2137
  // check if user access is granted
2140
2138
  this.isUserAuthorized(
@@ -2150,6 +2148,11 @@ export class OPCUAServer extends OPCUABaseServer {
2150
2148
  if (!authorized) {
2151
2149
  return rejectConnection(this, StatusCodes.BadUserAccessDenied);
2152
2150
  } else {
2151
+ if (session.status === "active") {
2152
+ moveSessionToChannel(session, channel);
2153
+ }
2154
+ session.userIdentityToken = request.userIdentityToken as UserIdentityToken;
2155
+
2153
2156
  // extract : OPC UA part 4 - 5.6.3
2154
2157
  // Once used, a serverNonce cannot be used again. For that reason, the Server returns a new
2155
2158
  // serverNonce each time the ActivateSession Service is called.
@@ -2160,13 +2163,6 @@ export class OPCUAServer extends OPCUABaseServer {
2160
2163
  response = new ActivateSessionResponse({ serverNonce: session.nonce });
2161
2164
  channel.send_response("MSG", response, message);
2162
2165
 
2163
- const userIdentityTokenPasswordRemoved = (userIdentityToken: any) => {
2164
- const a = userIdentityToken.clone();
2165
- // remove password
2166
- a.password = "*************";
2167
- return a;
2168
- };
2169
-
2170
2166
  // send OPCUA Event Notification
2171
2167
  // see part 5 : 6.4.3 AuditEventType
2172
2168
  // 6.4.7 AuditSessionEventType
@@ -2175,49 +2171,11 @@ export class OPCUAServer extends OPCUABaseServer {
2175
2171
  // xx assert(session.channel.clientCertificate instanceof Buffer);
2176
2172
  assert(session.sessionTimeout > 0);
2177
2173
 
2178
- if (this.isAuditing) {
2179
- this.raiseEvent("AuditActivateSessionEventType", {
2180
- /* part 5 - 6.4.3 AuditEventType */
2181
- actionTimeStamp: { dataType: "DateTime", value: new Date() },
2182
- status: { dataType: "Boolean", value: true },
2183
-
2184
- serverId: { dataType: "String", value: "" },
2185
-
2186
- // ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
2187
- clientAuditEntryId: { dataType: "String", value: "" },
2188
-
2189
- // The ClientUserId identifies the user of the client requesting an action.
2190
- // The ClientUserId can be obtained from the UserIdentityToken passed in the
2191
- // ActivateSession call.
2192
- clientUserId: { dataType: "String", value: "cc" },
2193
-
2194
- sourceName: { dataType: "String", value: "Session/ActivateSession" },
2195
-
2196
- /* part 5 - 6.4.7 AuditSessionEventType */
2197
- sessionId: { dataType: "NodeId", value: session.nodeId },
2198
-
2199
- /* part 5 - 6.4.10 AuditActivateSessionEventType */
2200
- clientSoftwareCertificates: {
2201
- arrayType: VariantArrayType.Array,
2202
- dataType: "ExtensionObject" /* SignedSoftwareCertificate */,
2203
- value: []
2204
- },
2205
- // UserIdentityToken reflects the userIdentityToken parameter of the ActivateSession
2206
- // Service call.
2207
- // For Username/Password tokens the password should NOT be included.
2208
- userIdentityToken: {
2209
- dataType: "ExtensionObject" /* UserIdentityToken */,
2210
- value: userIdentityTokenPasswordRemoved(session.userIdentityToken)
2211
- },
2212
-
2213
- // SecureChannelId shall uniquely identify the SecureChannel. The application shall
2214
- // use the same identifier in all AuditEvents related to the Session Service Set
2215
- // (AuditCreateSessionEventType, AuditActivateSessionEventType and their subtypes) and
2216
- // the SecureChannel Service Set (AuditChannelEventType and its subtypes).
2217
- secureChannelId: { dataType: "String", value: session.channel!.channelId!.toString() }
2218
- });
2219
- }
2220
- this.emit("session_activated", session, userIdentityTokenPasswordRemoved);
2174
+ raiseAuditActivateSessionEventType.call(this, session);
2175
+
2176
+ this.emit("session_activated", session, userIdentityTokenPasswordRemoved(session.userIdentityToken));
2177
+
2178
+ session.resendMonitoredItemInitialValues();
2221
2179
  }
2222
2180
  }
2223
2181
  );
@@ -2574,7 +2532,7 @@ export class OPCUAServer extends OPCUABaseServer {
2574
2532
  const requestedMaxReferencesPerNode = Math.min(9876, request.requestedMaxReferencesPerNode);
2575
2533
  assert(request.nodesToBrowse[0].schema.name === "BrowseDescription");
2576
2534
 
2577
- const context = new SessionContext({ session, server: this });
2535
+ const context = session.sessionContext;
2578
2536
 
2579
2537
  const f = callbackify(this.engine.browseWithAutomaticExpansion).bind(this.engine);
2580
2538
  (f as any)(request.nodesToBrowse, context, (err?: Error | null, results?: BrowseResult[]) => {
@@ -2676,7 +2634,7 @@ export class OPCUAServer extends OPCUABaseServer {
2676
2634
  message,
2677
2635
  channel,
2678
2636
  (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
2679
- const context = new SessionContext({ session, server: this });
2637
+ const context = session.sessionContext;
2680
2638
 
2681
2639
  let response;
2682
2640
 
@@ -2778,7 +2736,7 @@ export class OPCUAServer extends OPCUABaseServer {
2778
2736
  }
2779
2737
  }
2780
2738
 
2781
- const context = new SessionContext({ session, server: this });
2739
+ const context = session.sessionContext;
2782
2740
 
2783
2741
  // ask for a refresh of asynchronous variables
2784
2742
  this.engine.refreshValues(request.nodesToRead, 0, (err?: Error | null) => {
@@ -2849,7 +2807,7 @@ export class OPCUAServer extends OPCUABaseServer {
2849
2807
  nodeToWrite.nodeId = session.resolveRegisteredNode(nodeToWrite.nodeId);
2850
2808
  }
2851
2809
 
2852
- const context = new SessionContext({ session, server: this });
2810
+ const context = session.sessionContext;
2853
2811
 
2854
2812
  assert(request.nodesToWrite[0].schema.name === "WriteValue");
2855
2813
  this.engine.write(context, request.nodesToWrite, (err: Error | null, results?: StatusCode[]) => {
@@ -2882,7 +2840,7 @@ export class OPCUAServer extends OPCUABaseServer {
2882
2840
  message,
2883
2841
  channel,
2884
2842
  (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
2885
- const context = new SessionContext({ session, server: this });
2843
+ const context = session.sessionContext;
2886
2844
 
2887
2845
  if (session.currentSubscriptionCount >= this.engine.serverCapabilities.maxSubscriptionsPerSession) {
2888
2846
  return sendError(StatusCodes.BadTooManySubscriptions);
@@ -3417,7 +3375,7 @@ export class OPCUAServer extends OPCUABaseServer {
3417
3375
  /* jshint validthis: true */
3418
3376
  const addressSpace = this.engine.addressSpace!;
3419
3377
 
3420
- const context = new SessionContext({ session, server: this });
3378
+ const context = session.sessionContext;
3421
3379
 
3422
3380
  async.map(
3423
3381
  request.methodsToCall,
@@ -3623,6 +3581,58 @@ export class OPCUAServer extends OPCUABaseServer {
3623
3581
  }
3624
3582
  }
3625
3583
 
3584
+ const userIdentityTokenPasswordRemoved = (userIdentityToken: any) => {
3585
+ const a = userIdentityToken.clone();
3586
+ // remove password
3587
+ a.password = "*************";
3588
+ return a;
3589
+ };
3590
+
3591
+ function raiseAuditActivateSessionEventType(this: OPCUAServer, session: ServerSession) {
3592
+ if (this.isAuditing) {
3593
+ this.raiseEvent("AuditActivateSessionEventType", {
3594
+ /* part 5 - 6.4.3 AuditEventType */
3595
+ actionTimeStamp: { dataType: "DateTime", value: new Date() },
3596
+ status: { dataType: "Boolean", value: true },
3597
+
3598
+ serverId: { dataType: "String", value: "" },
3599
+
3600
+ // ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
3601
+ clientAuditEntryId: { dataType: "String", value: "" },
3602
+
3603
+ // The ClientUserId identifies the user of the client requesting an action.
3604
+ // The ClientUserId can be obtained from the UserIdentityToken passed in the
3605
+ // ActivateSession call.
3606
+ clientUserId: { dataType: "String", value: "cc" },
3607
+
3608
+ sourceName: { dataType: "String", value: "Session/ActivateSession" },
3609
+
3610
+ /* part 5 - 6.4.7 AuditSessionEventType */
3611
+ sessionId: { dataType: "NodeId", value: session.nodeId },
3612
+
3613
+ /* part 5 - 6.4.10 AuditActivateSessionEventType */
3614
+ clientSoftwareCertificates: {
3615
+ arrayType: VariantArrayType.Array,
3616
+ dataType: "ExtensionObject" /* SignedSoftwareCertificate */,
3617
+ value: []
3618
+ },
3619
+ // UserIdentityToken reflects the userIdentityToken parameter of the ActivateSession
3620
+ // Service call.
3621
+ // For Username/Password tokens the password should NOT be included.
3622
+ userIdentityToken: {
3623
+ dataType: "ExtensionObject" /* UserIdentityToken */,
3624
+ value: userIdentityTokenPasswordRemoved(session.userIdentityToken)
3625
+ },
3626
+
3627
+ // SecureChannelId shall uniquely identify the SecureChannel. The application shall
3628
+ // use the same identifier in all AuditEvents related to the Session Service Set
3629
+ // (AuditCreateSessionEventType, AuditActivateSessionEventType and their subtypes) and
3630
+ // the SecureChannel Service Set (AuditChannelEventType and its subtypes).
3631
+ secureChannelId: { dataType: "String", value: session.channel!.channelId!.toString() }
3632
+ });
3633
+ }
3634
+ }
3635
+
3626
3636
  export interface RaiseEventAuditEventData extends RaiseEventData {
3627
3637
  actionTimeStamp: PseudoVariantDateTime;
3628
3638
  status: PseudoVariantBoolean;
@@ -0,0 +1,8 @@
1
+ import { ISessionContext } from "node-opcua-address-space-base";
2
+ import { DataValue } from "node-opcua-data-value";
3
+
4
+ export type SamplingFunc = (
5
+ context: ISessionContext,
6
+ dataValue: DataValue,
7
+ callback: (err: Error | null, dataValue?: DataValue) => void
8
+ ) => void;
@@ -29,6 +29,7 @@ import {
29
29
  DTServerStatus,
30
30
  resolveOpaqueOnAddressSpace,
31
31
  ContinuationData,
32
+ IServerBase,
32
33
  UARole
33
34
  } from "node-opcua-address-space";
34
35
  import { generateAddressSpace } from "node-opcua-address-space/nodeJS";
@@ -95,6 +96,7 @@ import { ServerSession } from "./server_session";
95
96
  import { Subscription } from "./server_subscription";
96
97
  import { sessionsCompatibleForTransfer } from "./sessions_compatible_for_transfer";
97
98
  import { OPCUAServerOptions } from "./opcua_server";
99
+ import { IUserManager } from "node-opcua-address-space/source";
98
100
 
99
101
  const debugLog = make_debugLog(__filename);
100
102
  const errorLog = make_errorLog(__filename);
@@ -323,6 +325,7 @@ export interface ServerEngineOptions {
323
325
  export interface CreateSessionOption {
324
326
  clientDescription?: ApplicationDescription;
325
327
  sessionTimeout?: number;
328
+ server: IServerBase;
326
329
  }
327
330
 
328
331
  export type ClosingReason = "Timeout" | "Terminated" | "CloseSession" | "Forcing";
@@ -1677,7 +1680,7 @@ export class ServerEngine extends EventEmitter {
1677
1680
  */
1678
1681
  public createSession(options: CreateSessionOption): ServerSession {
1679
1682
  options = options || {};
1680
-
1683
+ options.server = options.server || {};
1681
1684
  debugLog("createSession : increasing serverDiagnosticsSummary cumulatedSessionCount/currentSessionCount ");
1682
1685
  this.serverDiagnosticsSummary.cumulatedSessionCount += 1;
1683
1686
  this.serverDiagnosticsSummary.currentSessionCount += 1;
@@ -1687,7 +1690,7 @@ export class ServerEngine extends EventEmitter {
1687
1690
  const sessionTimeout = options.sessionTimeout || 1000;
1688
1691
  assert(typeof sessionTimeout === "number");
1689
1692
 
1690
- const session = new ServerSession(this, sessionTimeout);
1693
+ const session = new ServerSession(this, options.server.userManager!, sessionTimeout);
1691
1694
 
1692
1695
  debugLog("createSession :sessionTimeout = ", session.sessionTimeout);
1693
1696
 
@@ -6,6 +6,7 @@
6
6
  import * as crypto from "crypto";
7
7
  import { EventEmitter } from "events";
8
8
 
9
+ import { assert } from "node-opcua-assert";
9
10
  import {
10
11
  addElement,
11
12
  AddressSpace,
@@ -19,10 +20,11 @@ import {
19
20
  UASessionDiagnosticsVariable,
20
21
  UASessionSecurityDiagnostics,
21
22
  DTSessionDiagnostics,
22
- DTSessionSecurityDiagnostics
23
+ DTSessionSecurityDiagnostics,
24
+ SessionContext,
25
+ IUserManager
23
26
  } from "node-opcua-address-space";
24
-
25
- import { assert } from "node-opcua-assert";
27
+ import { ISessionContext } from "node-opcua-address-space-base";
26
28
  import { minOPCUADate, randomGuid } from "node-opcua-basic-types";
27
29
  import { SessionDiagnosticsDataType, SessionSecurityDiagnosticsDataType, SubscriptionDiagnosticsDataType } from "node-opcua-common";
28
30
  import { QualifiedName, NodeClass } from "node-opcua-data-model";
@@ -119,6 +121,7 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
119
121
  public clientDescription?: ApplicationDescription;
120
122
  public channelId?: number | null;
121
123
  public continuationPointManager: ContinuationPointManager;
124
+ public sessionContext: ISessionContext;
122
125
 
123
126
  // ISubscriber
124
127
  public _watchDog?: WatchDog;
@@ -134,13 +137,18 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
134
137
 
135
138
  private channel_abort_event_handler: any;
136
139
 
137
- constructor(parent: ServerEngine, sessionTimeout: number) {
140
+ constructor(parent: ServerEngine, userManager: IUserManager, sessionTimeout: number) {
138
141
  super();
139
142
 
140
143
  this.parent = parent; // SessionEngine
141
144
 
142
145
  ServerSession.registry.register(this);
143
146
 
147
+ this.sessionContext = new SessionContext({
148
+ session: this,
149
+ server: { userManager }
150
+ });
151
+
144
152
  assert(isFinite(sessionTimeout));
145
153
  assert(sessionTimeout >= 0, " sessionTimeout");
146
154
  this.sessionTimeout = sessionTimeout;
@@ -846,6 +854,12 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
846
854
  return this.sessionObject;
847
855
  }
848
856
 
857
+ public async resendMonitoredItemInitialValues(): Promise<void> {
858
+ for (const subscription of this.publishEngine.subscriptions) {
859
+ await subscription.resendInitialValues();
860
+ }
861
+ }
862
+
849
863
  /**
850
864
  *
851
865
  * @private
@@ -783,7 +783,7 @@ export class Subscription extends EventEmitter {
783
783
  }
784
784
  assert(this.monitoredItemCount === 0);
785
785
 
786
- if (this.$session) {
786
+ if (this.$session && this.$session._unexposeSubscriptionDiagnostics) {
787
787
  this.$session._unexposeSubscriptionDiagnostics(this);
788
788
  }
789
789
  this.state = SubscriptionState.CLOSED;
@@ -1,8 +1,11 @@
1
1
  import { IUserManager, UARoleSet } from "node-opcua-address-space";
2
2
  import { NodeId } from "node-opcua-nodeid";
3
3
  import { IdentityMappingRuleType } from "node-opcua-types";
4
+ import { make_errorLog } from "node-opcua-debug";
4
5
  import { ServerSession } from "./server_session";
5
6
 
7
+ const errorLog = make_errorLog(__filename);
8
+
6
9
  export type ValidUserFunc = (this: ServerSession, username: string, password: string) => boolean;
7
10
  export type ValidUserAsyncFunc = (
8
11
  this: ServerSession,
@@ -45,7 +48,18 @@ export class UAUserManager1 extends UAUserManagerBase {
45
48
  super();
46
49
  }
47
50
  getUserRoles(user: string): NodeId[] {
48
- return this.options.getUserRoles != null ? this.options.getUserRoles(user) : [];
51
+ if (!this.options.getUserRoles) return [];
52
+ try {
53
+ return this.options.getUserRoles(user);
54
+ } catch (err) {
55
+ if (err instanceof Error) {
56
+ errorLog(
57
+ "[NODE-OPCUA-E27] userManager provided getUserRoles method has thrown an exception, please fix your code! "
58
+ );
59
+ errorLog(err.message, "\n", (err as Error).stack?.split("\n").slice(0,2).join("\n"));
60
+ }
61
+ return [];
62
+ }
49
63
  }
50
64
 
51
65
  async isValidUser(session: ServerSession, username: string, password: string): Promise<boolean> {
@@ -56,9 +70,19 @@ export class UAUserManager1 extends UAUserManagerBase {
56
70
  resolve(isAuthorized!);
57
71
  });
58
72
  });
59
- } else if(typeof this.options.isValidUser === "function") {
60
- const authorized = this.options.isValidUser!.call(session, username, password);
61
- return authorized;
73
+ } else if (typeof this.options.isValidUser === "function") {
74
+ try {
75
+ const authorized = this.options.isValidUser!.call(session, username, password);
76
+ return authorized;
77
+ } catch (err) {
78
+ if (err instanceof Error) {
79
+ errorLog(
80
+ "[NODE-OPCUA-E26] userManager provided isValidUser method has thrown an exception, please fix your code!"
81
+ );
82
+ errorLog(err.message, "\n", (err as Error).stack?.split("\n").slice(0,2).join("\n"));
83
+ }
84
+ return false;
85
+ }
62
86
  } else {
63
87
  return false;
64
88
  }