node-opcua-role-set-server 2.174.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/LICENSE +22 -0
- package/README.md +188 -0
- package/dist/audit.d.ts +33 -0
- package/dist/audit.js +28 -0
- package/dist/audit.js.map +1 -0
- package/dist/bind_restriction_methods.d.ts +31 -0
- package/dist/bind_restriction_methods.js +94 -0
- package/dist/bind_restriction_methods.js.map +1 -0
- package/dist/bind_role_methods.d.ts +48 -0
- package/dist/bind_role_methods.js +143 -0
- package/dist/bind_role_methods.js.map +1 -0
- package/dist/bind_user_management.d.ts +58 -0
- package/dist/bind_user_management.js +153 -0
- package/dist/bind_user_management.js.map +1 -0
- package/dist/harden.d.ts +35 -0
- package/dist/harden.js +28 -0
- package/dist/harden.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/install_role_based_security.d.ts +78 -0
- package/dist/install_role_based_security.js +41 -0
- package/dist/install_role_based_security.js.map +1 -0
- package/dist/install_role_set.d.ts +73 -0
- package/dist/install_role_set.js +308 -0
- package/dist/install_role_set.js.map +1 -0
- package/dist/install_user_management.d.ts +68 -0
- package/dist/install_user_management.js +158 -0
- package/dist/install_user_management.js.map +1 -0
- package/dist/role_set_resolver.d.ts +23 -0
- package/dist/role_set_resolver.js +30 -0
- package/dist/role_set_resolver.js.map +1 -0
- package/dist/security_checks.d.ts +25 -0
- package/dist/security_checks.js +41 -0
- package/dist/security_checks.js.map +1 -0
- package/dist/user_management_user_manager.d.ts +37 -0
- package/dist/user_management_user_manager.js +72 -0
- package/dist/user_management_user_manager.js.map +1 -0
- package/dist/variant_args.d.ts +13 -0
- package/dist/variant_args.js +18 -0
- package/dist/variant_args.js.map +1 -0
- package/package.json +54 -0
- package/source/audit.ts +53 -0
- package/source/bind_restriction_methods.ts +117 -0
- package/source/bind_role_methods.ts +196 -0
- package/source/bind_user_management.ts +215 -0
- package/source/harden.ts +46 -0
- package/source/index.ts +48 -0
- package/source/install_role_based_security.ts +130 -0
- package/source/install_role_set.ts +426 -0
- package/source/install_user_management.ts +250 -0
- package/source/role_set_resolver.ts +38 -0
- package/source/security_checks.ts +47 -0
- package/source/user_management_user_manager.ts +104 -0
- package/source/variant_args.ts +22 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-role-set-server
|
|
3
|
+
*
|
|
4
|
+
* Method handlers for the UserManagementType Methods (OPC 10000-18 §5.2):
|
|
5
|
+
* AddUser, ModifyUser, RemoveUser and ChangePassword, backed by an
|
|
6
|
+
* {@link IUserManagementStore}.
|
|
7
|
+
*
|
|
8
|
+
* AddUser / ModifyUser / RemoveUser require the SecurityAdmin Role and an
|
|
9
|
+
* encrypted channel. ChangePassword is callable by the Session user itself but
|
|
10
|
+
* still requires an encrypted channel and a USERNAME user token (§5.2.8).
|
|
11
|
+
*/
|
|
12
|
+
import type { ISessionContext, UAMethod } from "node-opcua-address-space-base";
|
|
13
|
+
import type { NodeId } from "node-opcua-nodeid";
|
|
14
|
+
import type { IUserManagementStore } from "node-opcua-role-set-common";
|
|
15
|
+
import type { CallMethodResultOptions } from "node-opcua-service-call";
|
|
16
|
+
import { type StatusCode } from "node-opcua-status-code";
|
|
17
|
+
import type { Variant } from "node-opcua-variant";
|
|
18
|
+
/**
|
|
19
|
+
* Details of a user-management change for {@link BindUserManagementOptions.onAudit}.
|
|
20
|
+
* Deliberately carries **no password** — only who did what to whom and the result —
|
|
21
|
+
* so an AuditUpdateMethodEventType can be raised without leaking secrets.
|
|
22
|
+
*/
|
|
23
|
+
export interface UserManagementAudit {
|
|
24
|
+
method: "AddUser" | "ModifyUser" | "RemoveUser" | "ChangePassword";
|
|
25
|
+
/** The user the operation targets (the Session user for ChangePassword). */
|
|
26
|
+
targetUserName: string;
|
|
27
|
+
/** The Session user who invoked the Method. */
|
|
28
|
+
callerUserName: string;
|
|
29
|
+
methodNodeId: NodeId;
|
|
30
|
+
statusCode: StatusCode;
|
|
31
|
+
}
|
|
32
|
+
export interface BindUserManagementOptions {
|
|
33
|
+
store: IUserManagementStore;
|
|
34
|
+
/** Called after every successful mutation so the caller can persist / refresh. */
|
|
35
|
+
onMutation?: () => Promise<void>;
|
|
36
|
+
/** Called after an authorized Method attempt to raise an audit event (no secrets). */
|
|
37
|
+
onAudit?: (audit: UserManagementAudit) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Called after a user is effectively deactivated — disabled via ModifyUser
|
|
40
|
+
* (§5.2.6) or deleted via RemoveUser (§5.2.7) — so the caller can terminate
|
|
41
|
+
* that user's still-active sessions (a disabled user must lose access, not
|
|
42
|
+
* merely be barred from re-authenticating).
|
|
43
|
+
*/
|
|
44
|
+
onUserDeactivated?: (userName: string) => void | Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
/** Create an AddUser Method handler (§5.2.5). */
|
|
47
|
+
export declare function makeAddUserHandler(options: BindUserManagementOptions): (this: UAMethod, inputArguments: Variant[], context: ISessionContext) => Promise<CallMethodResultOptions>;
|
|
48
|
+
/** Create a ModifyUser Method handler (§5.2.6). */
|
|
49
|
+
export declare function makeModifyUserHandler(options: BindUserManagementOptions): (this: UAMethod, inputArguments: Variant[], context: ISessionContext) => Promise<CallMethodResultOptions>;
|
|
50
|
+
/** Create a RemoveUser Method handler (§5.2.7). */
|
|
51
|
+
export declare function makeRemoveUserHandler(options: BindUserManagementOptions): (this: UAMethod, inputArguments: Variant[], context: ISessionContext) => Promise<CallMethodResultOptions>;
|
|
52
|
+
/**
|
|
53
|
+
* Create a ChangePassword Method handler (§5.2.8).
|
|
54
|
+
*
|
|
55
|
+
* Operates on the Session user, requires an encrypted channel and a USERNAME
|
|
56
|
+
* user token (`Bad_InvalidState` otherwise). Does **not** require SecurityAdmin.
|
|
57
|
+
*/
|
|
58
|
+
export declare function makeChangePasswordHandler(options: BindUserManagementOptions): (this: UAMethod, inputArguments: Variant[], context: ISessionContext) => Promise<CallMethodResultOptions>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeAddUserHandler = makeAddUserHandler;
|
|
4
|
+
exports.makeModifyUserHandler = makeModifyUserHandler;
|
|
5
|
+
exports.makeRemoveUserHandler = makeRemoveUserHandler;
|
|
6
|
+
exports.makeChangePasswordHandler = makeChangePasswordHandler;
|
|
7
|
+
const node_opcua_status_code_1 = require("node-opcua-status-code");
|
|
8
|
+
const node_opcua_types_1 = require("node-opcua-types");
|
|
9
|
+
const security_checks_js_1 = require("./security_checks.js");
|
|
10
|
+
const variant_args_js_1 = require("./variant_args.js");
|
|
11
|
+
const isDisabled = (mask) => (mask & node_opcua_types_1.UserConfigurationMask.Disabled) === node_opcua_types_1.UserConfigurationMask.Disabled;
|
|
12
|
+
/** Create an AddUser Method handler (§5.2.5). */
|
|
13
|
+
function makeAddUserHandler(options) {
|
|
14
|
+
const { store, onMutation, onAudit } = options;
|
|
15
|
+
return async function _addUser(inputArguments, context) {
|
|
16
|
+
const insecure = (0, security_checks_js_1.checkEncryptedChannel)(context);
|
|
17
|
+
if (insecure)
|
|
18
|
+
return insecure;
|
|
19
|
+
const denied = (0, security_checks_js_1.checkSecurityAdminAccess)(context);
|
|
20
|
+
if (denied)
|
|
21
|
+
return denied;
|
|
22
|
+
const userName = (0, variant_args_js_1.asString)(inputArguments[0]);
|
|
23
|
+
const password = (0, variant_args_js_1.asString)(inputArguments[1]);
|
|
24
|
+
if (userName === null || password === null) {
|
|
25
|
+
return { statusCode: node_opcua_status_code_1.StatusCodes.BadInvalidArgument };
|
|
26
|
+
}
|
|
27
|
+
const userConfiguration = (0, variant_args_js_1.asMask)(inputArguments[2]);
|
|
28
|
+
const description = (0, variant_args_js_1.asString)(inputArguments[3]) ?? "";
|
|
29
|
+
const statusCode = store.addUser(userName, password, userConfiguration, description);
|
|
30
|
+
if (statusCode === node_opcua_status_code_1.StatusCodes.Good && onMutation) {
|
|
31
|
+
await onMutation();
|
|
32
|
+
}
|
|
33
|
+
onAudit?.({
|
|
34
|
+
method: "AddUser",
|
|
35
|
+
targetUserName: userName,
|
|
36
|
+
callerUserName: context.getUserName(),
|
|
37
|
+
methodNodeId: this.nodeId,
|
|
38
|
+
statusCode
|
|
39
|
+
});
|
|
40
|
+
return { statusCode };
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Create a ModifyUser Method handler (§5.2.6). */
|
|
44
|
+
function makeModifyUserHandler(options) {
|
|
45
|
+
const { store, onMutation, onAudit, onUserDeactivated } = options;
|
|
46
|
+
return async function _modifyUser(inputArguments, context) {
|
|
47
|
+
const insecure = (0, security_checks_js_1.checkEncryptedChannel)(context);
|
|
48
|
+
if (insecure)
|
|
49
|
+
return insecure;
|
|
50
|
+
const denied = (0, security_checks_js_1.checkSecurityAdminAccess)(context);
|
|
51
|
+
if (denied)
|
|
52
|
+
return denied;
|
|
53
|
+
const userName = (0, variant_args_js_1.asString)(inputArguments[0]);
|
|
54
|
+
if (userName === null) {
|
|
55
|
+
return { statusCode: node_opcua_status_code_1.StatusCodes.BadInvalidArgument };
|
|
56
|
+
}
|
|
57
|
+
const modifyUserConfiguration = (0, variant_args_js_1.asBoolean)(inputArguments[3]);
|
|
58
|
+
const userConfiguration = (0, variant_args_js_1.asMask)(inputArguments[4]);
|
|
59
|
+
const statusCode = store.modifyUser(userName, {
|
|
60
|
+
modifyPassword: (0, variant_args_js_1.asBoolean)(inputArguments[1]),
|
|
61
|
+
password: (0, variant_args_js_1.asString)(inputArguments[2]) ?? "",
|
|
62
|
+
modifyUserConfiguration,
|
|
63
|
+
userConfiguration,
|
|
64
|
+
modifyDescription: (0, variant_args_js_1.asBoolean)(inputArguments[5]),
|
|
65
|
+
description: (0, variant_args_js_1.asString)(inputArguments[6]) ?? ""
|
|
66
|
+
}, context.getUserName());
|
|
67
|
+
if (statusCode === node_opcua_status_code_1.StatusCodes.Good) {
|
|
68
|
+
if (onMutation)
|
|
69
|
+
await onMutation();
|
|
70
|
+
// a freshly disabled user must lose any live session, not just be barred from re-login
|
|
71
|
+
if (modifyUserConfiguration && isDisabled(userConfiguration) && onUserDeactivated) {
|
|
72
|
+
await onUserDeactivated(userName);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
onAudit?.({
|
|
76
|
+
method: "ModifyUser",
|
|
77
|
+
targetUserName: userName,
|
|
78
|
+
callerUserName: context.getUserName(),
|
|
79
|
+
methodNodeId: this.nodeId,
|
|
80
|
+
statusCode
|
|
81
|
+
});
|
|
82
|
+
return { statusCode };
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Create a RemoveUser Method handler (§5.2.7). */
|
|
86
|
+
function makeRemoveUserHandler(options) {
|
|
87
|
+
const { store, onMutation, onAudit, onUserDeactivated } = options;
|
|
88
|
+
return async function _removeUser(inputArguments, context) {
|
|
89
|
+
const insecure = (0, security_checks_js_1.checkEncryptedChannel)(context);
|
|
90
|
+
if (insecure)
|
|
91
|
+
return insecure;
|
|
92
|
+
const denied = (0, security_checks_js_1.checkSecurityAdminAccess)(context);
|
|
93
|
+
if (denied)
|
|
94
|
+
return denied;
|
|
95
|
+
const userName = (0, variant_args_js_1.asString)(inputArguments[0]);
|
|
96
|
+
if (userName === null) {
|
|
97
|
+
return { statusCode: node_opcua_status_code_1.StatusCodes.BadInvalidArgument };
|
|
98
|
+
}
|
|
99
|
+
const statusCode = store.removeUser(userName, context.getUserName());
|
|
100
|
+
if (statusCode === node_opcua_status_code_1.StatusCodes.Good) {
|
|
101
|
+
if (onMutation)
|
|
102
|
+
await onMutation();
|
|
103
|
+
// a removed user must lose any live session as well (§5.2.7)
|
|
104
|
+
if (onUserDeactivated)
|
|
105
|
+
await onUserDeactivated(userName);
|
|
106
|
+
}
|
|
107
|
+
onAudit?.({
|
|
108
|
+
method: "RemoveUser",
|
|
109
|
+
targetUserName: userName,
|
|
110
|
+
callerUserName: context.getUserName(),
|
|
111
|
+
methodNodeId: this.nodeId,
|
|
112
|
+
statusCode
|
|
113
|
+
});
|
|
114
|
+
return { statusCode };
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Create a ChangePassword Method handler (§5.2.8).
|
|
119
|
+
*
|
|
120
|
+
* Operates on the Session user, requires an encrypted channel and a USERNAME
|
|
121
|
+
* user token (`Bad_InvalidState` otherwise). Does **not** require SecurityAdmin.
|
|
122
|
+
*/
|
|
123
|
+
function makeChangePasswordHandler(options) {
|
|
124
|
+
const { store, onMutation, onAudit } = options;
|
|
125
|
+
return async function _changePassword(inputArguments, context) {
|
|
126
|
+
const insecure = (0, security_checks_js_1.checkEncryptedChannel)(context);
|
|
127
|
+
if (insecure)
|
|
128
|
+
return insecure;
|
|
129
|
+
const token = context.session?.userIdentityToken;
|
|
130
|
+
if (!(token instanceof node_opcua_types_1.UserNameIdentityToken)) {
|
|
131
|
+
return { statusCode: node_opcua_status_code_1.StatusCodes.BadInvalidState };
|
|
132
|
+
}
|
|
133
|
+
const userName = token.userName ?? "";
|
|
134
|
+
const oldPassword = (0, variant_args_js_1.asString)(inputArguments[0]);
|
|
135
|
+
const newPassword = (0, variant_args_js_1.asString)(inputArguments[1]);
|
|
136
|
+
if (oldPassword === null || newPassword === null) {
|
|
137
|
+
return { statusCode: node_opcua_status_code_1.StatusCodes.BadInvalidArgument };
|
|
138
|
+
}
|
|
139
|
+
const statusCode = store.changePassword(userName, oldPassword, newPassword);
|
|
140
|
+
if (statusCode === node_opcua_status_code_1.StatusCodes.Good && onMutation) {
|
|
141
|
+
await onMutation();
|
|
142
|
+
}
|
|
143
|
+
onAudit?.({
|
|
144
|
+
method: "ChangePassword",
|
|
145
|
+
targetUserName: userName,
|
|
146
|
+
callerUserName: userName,
|
|
147
|
+
methodNodeId: this.nodeId,
|
|
148
|
+
statusCode
|
|
149
|
+
});
|
|
150
|
+
return { statusCode };
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=bind_user_management.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind_user_management.js","sourceRoot":"","sources":["../source/bind_user_management.ts"],"names":[],"mappings":"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"}
|
package/dist/harden.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-role-set-server
|
|
3
|
+
*
|
|
4
|
+
* Browse / channel hardening for the security-sensitive RoleSet & User
|
|
5
|
+
* Management nodes (OPC 10000-18 §4.4.1 / §5), built entirely on the two
|
|
6
|
+
* mechanisms the address space already enforces — no custom Browse filter or
|
|
7
|
+
* read hook:
|
|
8
|
+
*
|
|
9
|
+
* - **RolePermissions** — a node that carries RolePermissions grants *nothing*
|
|
10
|
+
* to a Role that is not listed. Restricting a node to `SecurityAdmin` both
|
|
11
|
+
* hides it from a non-admin's Browse (`isBrowseAccessRestricted`) and denies
|
|
12
|
+
* Read/Call (`Bad_UserAccessDenied`).
|
|
13
|
+
* - **AccessRestrictions(EncryptionRequired)** — the node may only be
|
|
14
|
+
* read/called over a `SignAndEncrypt` channel, otherwise the core returns
|
|
15
|
+
* `Bad_SecurityModeInsufficient`, so identity/user data never leaves the
|
|
16
|
+
* server over an unencrypted channel.
|
|
17
|
+
*
|
|
18
|
+
* Both checks run in the core (`ua_variable_impl` / `ua_method_impl`) *before*
|
|
19
|
+
* the bound Method handler, so the per-Method `checkSecurityAdminAccess` /
|
|
20
|
+
* `checkEncryptedChannel` guards become defense-in-depth rather than the only
|
|
21
|
+
* line of defense.
|
|
22
|
+
*/
|
|
23
|
+
import type { BaseNode } from "node-opcua-address-space";
|
|
24
|
+
/**
|
|
25
|
+
* Restrict a node to the `SecurityAdmin` Role, reachable only over an encrypted
|
|
26
|
+
* channel — used for the admin-only configuration Methods and the sensitive
|
|
27
|
+
* identity/restriction/user Properties.
|
|
28
|
+
*/
|
|
29
|
+
export declare function hardenAdminOnly(node: BaseNode | null | undefined): void;
|
|
30
|
+
/**
|
|
31
|
+
* Require an encrypted channel for a node that must stay visible/callable to
|
|
32
|
+
* ordinary authenticated users (e.g. `ChangePassword`, §5.2.8) — no Role
|
|
33
|
+
* restriction, just `EncryptionRequired`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function hardenEncryptedOnly(node: BaseNode | null | undefined): void;
|
package/dist/harden.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hardenAdminOnly = hardenAdminOnly;
|
|
4
|
+
exports.hardenEncryptedOnly = hardenEncryptedOnly;
|
|
5
|
+
const node_opcua_data_model_1 = require("node-opcua-data-model");
|
|
6
|
+
const node_opcua_role_set_common_1 = require("node-opcua-role-set-common");
|
|
7
|
+
/**
|
|
8
|
+
* Restrict a node to the `SecurityAdmin` Role, reachable only over an encrypted
|
|
9
|
+
* channel — used for the admin-only configuration Methods and the sensitive
|
|
10
|
+
* identity/restriction/user Properties.
|
|
11
|
+
*/
|
|
12
|
+
function hardenAdminOnly(node) {
|
|
13
|
+
if (!node)
|
|
14
|
+
return;
|
|
15
|
+
node.setRolePermissions([{ roleId: node_opcua_role_set_common_1.WellKnownRoleIds.SecurityAdmin, permissions: node_opcua_data_model_1.allPermissions }]);
|
|
16
|
+
node.setAccessRestrictions(node_opcua_data_model_1.AccessRestrictionsFlag.EncryptionRequired);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Require an encrypted channel for a node that must stay visible/callable to
|
|
20
|
+
* ordinary authenticated users (e.g. `ChangePassword`, §5.2.8) — no Role
|
|
21
|
+
* restriction, just `EncryptionRequired`.
|
|
22
|
+
*/
|
|
23
|
+
function hardenEncryptedOnly(node) {
|
|
24
|
+
if (!node)
|
|
25
|
+
return;
|
|
26
|
+
node.setAccessRestrictions(node_opcua_data_model_1.AccessRestrictionsFlag.EncryptionRequired);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=harden.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harden.js","sourceRoot":"","sources":["../source/harden.ts"],"names":[],"mappings":";;AA+BA,0CAIC;AAOD,kDAGC;AAtBD,iEAA+E;AAC/E,2EAA8D;AAE9D;;;;GAIG;AACH,SAAgB,eAAe,CAAC,IAAiC;IAC7D,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,IAAI,CAAC,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,6CAAgB,CAAC,aAAa,EAAE,WAAW,EAAE,sCAAc,EAAE,CAAC,CAAC,CAAC;IACnG,IAAI,CAAC,qBAAqB,CAAC,8CAAsB,CAAC,kBAAkB,CAAC,CAAC;AAC1E,CAAC;AAED;;;;GAIG;AACH,SAAgB,mBAAmB,CAAC,IAAiC;IACjE,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,IAAI,CAAC,qBAAqB,CAAC,8CAAsB,CAAC,kBAAkB,CAAC,CAAC;AAC1E,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-role-set-server
|
|
3
|
+
*/
|
|
4
|
+
export { type AuditMethodEventFields, raiseAuditMethodEvent } from "./audit.js";
|
|
5
|
+
export { type BindRestrictionMethodsOptions, makeAddApplicationHandler, makeAddEndpointHandler, makeRemoveApplicationHandler, makeRemoveEndpointHandler } from "./bind_restriction_methods.js";
|
|
6
|
+
export { type BindRoleMethodsOptions, makeAddIdentityHandler, makeRemoveIdentityHandler, type RoleMappingRuleChangedAudit } from "./bind_role_methods.js";
|
|
7
|
+
export { type BindUserManagementOptions, makeAddUserHandler, makeChangePasswordHandler, makeModifyUserHandler, makeRemoveUserHandler, type UserManagementAudit } from "./bind_user_management.js";
|
|
8
|
+
export { type CreateRoleBasedSecurityOptions, createRoleBasedSecurity, type InstallRoleBasedSecurityOptions, type RoleBasedSecurity, type RoleBasedUser } from "./install_role_based_security.js";
|
|
9
|
+
export { type InstallRoleSetOptions, type InstallRoleSetResult, type IServerForRoleSet, installRoleSet } from "./install_role_set.js";
|
|
10
|
+
export { type InstallUserManagementOptions, type InstallUserManagementResult, type IServerForUserManagement, installUserManagement } from "./install_user_management.js";
|
|
11
|
+
export { RoleSetResolver } from "./role_set_resolver.js";
|
|
12
|
+
export { checkEncryptedChannel, checkSecurityAdminAccess } from "./security_checks.js";
|
|
13
|
+
export { createUserManager, type IManagedUserManager } from "./user_management_user_manager.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module node-opcua-role-set-server
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createUserManager = exports.checkSecurityAdminAccess = exports.checkEncryptedChannel = exports.RoleSetResolver = exports.installUserManagement = exports.installRoleSet = exports.createRoleBasedSecurity = exports.makeRemoveUserHandler = exports.makeModifyUserHandler = exports.makeChangePasswordHandler = exports.makeAddUserHandler = exports.makeRemoveIdentityHandler = exports.makeAddIdentityHandler = exports.makeRemoveEndpointHandler = exports.makeRemoveApplicationHandler = exports.makeAddEndpointHandler = exports.makeAddApplicationHandler = exports.raiseAuditMethodEvent = void 0;
|
|
7
|
+
var audit_js_1 = require("./audit.js");
|
|
8
|
+
Object.defineProperty(exports, "raiseAuditMethodEvent", { enumerable: true, get: function () { return audit_js_1.raiseAuditMethodEvent; } });
|
|
9
|
+
var bind_restriction_methods_js_1 = require("./bind_restriction_methods.js");
|
|
10
|
+
Object.defineProperty(exports, "makeAddApplicationHandler", { enumerable: true, get: function () { return bind_restriction_methods_js_1.makeAddApplicationHandler; } });
|
|
11
|
+
Object.defineProperty(exports, "makeAddEndpointHandler", { enumerable: true, get: function () { return bind_restriction_methods_js_1.makeAddEndpointHandler; } });
|
|
12
|
+
Object.defineProperty(exports, "makeRemoveApplicationHandler", { enumerable: true, get: function () { return bind_restriction_methods_js_1.makeRemoveApplicationHandler; } });
|
|
13
|
+
Object.defineProperty(exports, "makeRemoveEndpointHandler", { enumerable: true, get: function () { return bind_restriction_methods_js_1.makeRemoveEndpointHandler; } });
|
|
14
|
+
var bind_role_methods_js_1 = require("./bind_role_methods.js");
|
|
15
|
+
Object.defineProperty(exports, "makeAddIdentityHandler", { enumerable: true, get: function () { return bind_role_methods_js_1.makeAddIdentityHandler; } });
|
|
16
|
+
Object.defineProperty(exports, "makeRemoveIdentityHandler", { enumerable: true, get: function () { return bind_role_methods_js_1.makeRemoveIdentityHandler; } });
|
|
17
|
+
var bind_user_management_js_1 = require("./bind_user_management.js");
|
|
18
|
+
Object.defineProperty(exports, "makeAddUserHandler", { enumerable: true, get: function () { return bind_user_management_js_1.makeAddUserHandler; } });
|
|
19
|
+
Object.defineProperty(exports, "makeChangePasswordHandler", { enumerable: true, get: function () { return bind_user_management_js_1.makeChangePasswordHandler; } });
|
|
20
|
+
Object.defineProperty(exports, "makeModifyUserHandler", { enumerable: true, get: function () { return bind_user_management_js_1.makeModifyUserHandler; } });
|
|
21
|
+
Object.defineProperty(exports, "makeRemoveUserHandler", { enumerable: true, get: function () { return bind_user_management_js_1.makeRemoveUserHandler; } });
|
|
22
|
+
var install_role_based_security_js_1 = require("./install_role_based_security.js");
|
|
23
|
+
Object.defineProperty(exports, "createRoleBasedSecurity", { enumerable: true, get: function () { return install_role_based_security_js_1.createRoleBasedSecurity; } });
|
|
24
|
+
var install_role_set_js_1 = require("./install_role_set.js");
|
|
25
|
+
Object.defineProperty(exports, "installRoleSet", { enumerable: true, get: function () { return install_role_set_js_1.installRoleSet; } });
|
|
26
|
+
var install_user_management_js_1 = require("./install_user_management.js");
|
|
27
|
+
Object.defineProperty(exports, "installUserManagement", { enumerable: true, get: function () { return install_user_management_js_1.installUserManagement; } });
|
|
28
|
+
var role_set_resolver_js_1 = require("./role_set_resolver.js");
|
|
29
|
+
Object.defineProperty(exports, "RoleSetResolver", { enumerable: true, get: function () { return role_set_resolver_js_1.RoleSetResolver; } });
|
|
30
|
+
var security_checks_js_1 = require("./security_checks.js");
|
|
31
|
+
Object.defineProperty(exports, "checkEncryptedChannel", { enumerable: true, get: function () { return security_checks_js_1.checkEncryptedChannel; } });
|
|
32
|
+
Object.defineProperty(exports, "checkSecurityAdminAccess", { enumerable: true, get: function () { return security_checks_js_1.checkSecurityAdminAccess; } });
|
|
33
|
+
var user_management_user_manager_js_1 = require("./user_management_user_manager.js");
|
|
34
|
+
Object.defineProperty(exports, "createUserManager", { enumerable: true, get: function () { return user_management_user_manager_js_1.createUserManager; } });
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH,uCAAgF;AAA1C,iHAAA,qBAAqB,OAAA;AAC3D,6EAMuC;AAJnC,wIAAA,yBAAyB,OAAA;AACzB,qIAAA,sBAAsB,OAAA;AACtB,2IAAA,4BAA4B,OAAA;AAC5B,wIAAA,yBAAyB,OAAA;AAE7B,+DAKgC;AAH5B,8HAAA,sBAAsB,OAAA;AACtB,iIAAA,yBAAyB,OAAA;AAG7B,qEAOmC;AAL/B,6HAAA,kBAAkB,OAAA;AAClB,oIAAA,yBAAyB,OAAA;AACzB,gIAAA,qBAAqB,OAAA;AACrB,gIAAA,qBAAqB,OAAA;AAGzB,mFAM0C;AAJtC,yIAAA,uBAAuB,OAAA;AAK3B,6DAK+B;AAD3B,qHAAA,cAAc,OAAA;AAElB,2EAKsC;AADlC,mIAAA,qBAAqB,OAAA;AAEzB,+DAAyD;AAAhD,uHAAA,eAAe,OAAA;AACxB,2DAAuF;AAA9E,2HAAA,qBAAqB,OAAA;AAAE,8HAAA,wBAAwB,OAAA;AACxD,qFAAgG;AAAvF,oIAAA,iBAAiB,OAAA"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-role-set-server
|
|
3
|
+
*
|
|
4
|
+
* One-call wiring for role-based security (OPC 10000-18) with a **single source
|
|
5
|
+
* of truth**. It owns one user store and one identity store and exposes them
|
|
6
|
+
* through the server `userManager` bridge — the only integration point the
|
|
7
|
+
* server core uses to resolve a session's Roles (`getUserRoles`) and to back
|
|
8
|
+
* each Role's `Identities` Property (`getIdentitiesForRole`).
|
|
9
|
+
*
|
|
10
|
+
* Because the `userManager` is created **before** the server (it is a
|
|
11
|
+
* constructor option) but the RoleSet / User Management Methods are installed
|
|
12
|
+
* **after** `server.start()`, this comes in two phases:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* const security = createRoleBasedSecurity({
|
|
16
|
+
* users: [{ userName: "admin", password: "pw", roles: [WellKnownRoleIds.SecurityAdmin] }]
|
|
17
|
+
* });
|
|
18
|
+
* const server = new OPCUAServer({ ..., userManager: security.userManager });
|
|
19
|
+
* await server.start();
|
|
20
|
+
* await security.install(server, { persistencePath: "./role-set.json" });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Everything (resolution, the `Identities` Property, the AddIdentity/AddUser
|
|
24
|
+
* Methods, persistence) is backed by the same two stores, so the "legacy"
|
|
25
|
+
* (userManager) and "modern" (RoleSet Methods) views can never drift apart.
|
|
26
|
+
*/
|
|
27
|
+
import type { NodeId } from "node-opcua-nodeid";
|
|
28
|
+
import { ArchiveStore, InMemoryIdentityMappingStore, InMemoryUserManagementStore, type PasswordPolicy } from "node-opcua-role-set-common";
|
|
29
|
+
import { UserConfigurationMask } from "node-opcua-types";
|
|
30
|
+
import { type InstallRoleSetResult, type IServerForRoleSet } from "./install_role_set.js";
|
|
31
|
+
import { type InstallUserManagementResult, type IServerForUserManagement } from "./install_user_management.js";
|
|
32
|
+
import { type IManagedUserManager } from "./user_management_user_manager.js";
|
|
33
|
+
/** A user to seed, with the well-known/custom Roles it holds. */
|
|
34
|
+
export interface RoleBasedUser {
|
|
35
|
+
userName: string;
|
|
36
|
+
password: string;
|
|
37
|
+
/** Role NodeIds granted to this user (e.g. `WellKnownRoleIds.Operator`). */
|
|
38
|
+
roles?: NodeId[];
|
|
39
|
+
userConfiguration?: UserConfigurationMask;
|
|
40
|
+
description?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface CreateRoleBasedSecurityOptions {
|
|
43
|
+
/** Password policy enforced by the user store. */
|
|
44
|
+
policy?: PasswordPolicy;
|
|
45
|
+
/** Users to seed up front (more can be added later via the Methods). */
|
|
46
|
+
users?: RoleBasedUser[];
|
|
47
|
+
}
|
|
48
|
+
export interface InstallRoleBasedSecurityOptions {
|
|
49
|
+
/** A shared archive coordinator. When omitted, one is built from `persistencePath`. */
|
|
50
|
+
persistence?: ArchiveStore;
|
|
51
|
+
/** Path to a single consolidated archive for roles + users. */
|
|
52
|
+
persistencePath?: string;
|
|
53
|
+
/** Encrypt that archive at rest (AES-256-GCM, key derived from this secret). */
|
|
54
|
+
persistenceSecret?: string;
|
|
55
|
+
}
|
|
56
|
+
/** The shared stores + bridge produced by {@link createRoleBasedSecurity}. */
|
|
57
|
+
export interface RoleBasedSecurity {
|
|
58
|
+
/** The single user store (passwords / user lifecycle). */
|
|
59
|
+
userStore: InMemoryUserManagementStore;
|
|
60
|
+
/** The single identity store (UserName -> Role mappings). */
|
|
61
|
+
identityStore: InMemoryIdentityMappingStore;
|
|
62
|
+
/** The server `userManager` to pass to the `OPCUAServer` constructor. */
|
|
63
|
+
userManager: IManagedUserManager;
|
|
64
|
+
/**
|
|
65
|
+
* Install the RoleSet + User Management Methods on the running server,
|
|
66
|
+
* bound to the same two stores. Call **after** `server.start()`.
|
|
67
|
+
*/
|
|
68
|
+
install(server: IServerForRoleSet & IServerForUserManagement, options?: InstallRoleBasedSecurityOptions): Promise<{
|
|
69
|
+
roleSet: InstallRoleSetResult;
|
|
70
|
+
userManagement: InstallUserManagementResult;
|
|
71
|
+
}>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create the shared stores and the `userManager` bridge, seeding any initial
|
|
75
|
+
* users and their Roles. Pass `userManager` to the `OPCUAServer` constructor,
|
|
76
|
+
* then call {@link RoleBasedSecurity.install} after `server.start()`.
|
|
77
|
+
*/
|
|
78
|
+
export declare function createRoleBasedSecurity(options?: CreateRoleBasedSecurityOptions): RoleBasedSecurity;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createRoleBasedSecurity = createRoleBasedSecurity;
|
|
4
|
+
const node_opcua_role_set_common_1 = require("node-opcua-role-set-common");
|
|
5
|
+
const node_opcua_types_1 = require("node-opcua-types");
|
|
6
|
+
const install_role_set_js_1 = require("./install_role_set.js");
|
|
7
|
+
const install_user_management_js_1 = require("./install_user_management.js");
|
|
8
|
+
const user_management_user_manager_js_1 = require("./user_management_user_manager.js");
|
|
9
|
+
const userNameRule = (userName) => new node_opcua_types_1.IdentityMappingRuleType({ criteriaType: node_opcua_types_1.IdentityCriteriaType.UserName, criteria: userName });
|
|
10
|
+
/**
|
|
11
|
+
* Create the shared stores and the `userManager` bridge, seeding any initial
|
|
12
|
+
* users and their Roles. Pass `userManager` to the `OPCUAServer` constructor,
|
|
13
|
+
* then call {@link RoleBasedSecurity.install} after `server.start()`.
|
|
14
|
+
*/
|
|
15
|
+
function createRoleBasedSecurity(options) {
|
|
16
|
+
const userStore = new node_opcua_role_set_common_1.InMemoryUserManagementStore(options?.policy);
|
|
17
|
+
const identityStore = new node_opcua_role_set_common_1.InMemoryIdentityMappingStore();
|
|
18
|
+
for (const user of options?.users ?? []) {
|
|
19
|
+
userStore.addUser(user.userName, user.password, user.userConfiguration ?? node_opcua_types_1.UserConfigurationMask.None, user.description ?? "");
|
|
20
|
+
for (const role of user.roles ?? []) {
|
|
21
|
+
identityStore.addIdentity(role, userNameRule(user.userName));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const userManager = (0, user_management_user_manager_js_1.createUserManager)(userStore, identityStore);
|
|
25
|
+
return {
|
|
26
|
+
userStore,
|
|
27
|
+
identityStore,
|
|
28
|
+
userManager,
|
|
29
|
+
async install(server, installOptions) {
|
|
30
|
+
// one coordinator shared by both installers (so users + roles land in one file)
|
|
31
|
+
const persistence = installOptions?.persistence ??
|
|
32
|
+
(installOptions?.persistencePath
|
|
33
|
+
? new node_opcua_role_set_common_1.ArchiveStore(installOptions.persistencePath, { secret: installOptions.persistenceSecret })
|
|
34
|
+
: undefined);
|
|
35
|
+
const roleSet = await (0, install_role_set_js_1.installRoleSet)(server, { store: identityStore, persistence });
|
|
36
|
+
const userManagement = await (0, install_user_management_js_1.installUserManagement)(server, { store: userStore, persistence });
|
|
37
|
+
return { roleSet, userManagement };
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=install_role_based_security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install_role_based_security.js","sourceRoot":"","sources":["../source/install_role_based_security.ts"],"names":[],"mappings":";;AA8FA,0DAmCC;AAtGD,2EAKoC;AACpC,uDAAwG;AACxG,+DAA0G;AAC1G,6EAIsC;AACtC,uFAAgG;AA8ChG,MAAM,YAAY,GAAG,CAAC,QAAgB,EAA2B,EAAE,CAC/D,IAAI,0CAAuB,CAAC,EAAE,YAAY,EAAE,uCAAoB,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;AAErG;;;;GAIG;AACH,SAAgB,uBAAuB,CAAC,OAAwC;IAC5E,MAAM,SAAS,GAAG,IAAI,wDAA2B,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACnE,MAAM,aAAa,GAAG,IAAI,yDAA4B,EAAE,CAAC;IAEzD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,KAAK,IAAI,EAAE,EAAE,CAAC;QACtC,SAAS,CAAC,OAAO,CACb,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,iBAAiB,IAAI,wCAAqB,CAAC,IAAI,EACpD,IAAI,CAAC,WAAW,IAAI,EAAE,CACzB,CAAC;QACF,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;YAClC,aAAa,CAAC,WAAW,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QACjE,CAAC;IACL,CAAC;IAED,MAAM,WAAW,GAAG,IAAA,mDAAiB,EAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAEhE,OAAO;QACH,SAAS;QACT,aAAa;QACb,WAAW;QACX,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc;YAChC,gFAAgF;YAChF,MAAM,WAAW,GACb,cAAc,EAAE,WAAW;gBAC3B,CAAC,cAAc,EAAE,eAAe;oBAC5B,CAAC,CAAC,IAAI,yCAAY,CAAC,cAAc,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,cAAc,CAAC,iBAAiB,EAAE,CAAC;oBAChG,CAAC,CAAC,SAAS,CAAC,CAAC;YAErB,MAAM,OAAO,GAAG,MAAM,IAAA,oCAAc,EAAC,MAAM,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC,CAAC;YACpF,MAAM,cAAc,GAAG,MAAM,IAAA,kDAAqB,EAAC,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;YAC9F,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;QACvC,CAAC;KACJ,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-role-set-server
|
|
3
|
+
*
|
|
4
|
+
* Install RoleSet management on an OPC UA server.
|
|
5
|
+
*
|
|
6
|
+
* This function is designed to be called **after** the server has started
|
|
7
|
+
* (when the address space is available). It:
|
|
8
|
+
* - Creates an InMemoryIdentityMappingStore (optionally loaded from disk)
|
|
9
|
+
* - Registers a RoleSetResolver on server.roleResolvers
|
|
10
|
+
* - Binds AddIdentity / RemoveIdentity on each Role and keeps the Identities
|
|
11
|
+
* Property in sync with the store
|
|
12
|
+
* - Binds AddRole / RemoveRole (OPC 10000-18 §4.2): custom Roles are created
|
|
13
|
+
* as `ns=1;g=<uuid>` instances of RoleType (collision-proof, stable when
|
|
14
|
+
* persisted); well-known Roles cannot be removed.
|
|
15
|
+
*/
|
|
16
|
+
import type { IAddressSpace } from "node-opcua-address-space";
|
|
17
|
+
import { ArchiveStore, type IIdentityMappingStore, type IRoleRestrictionStore } from "node-opcua-role-set-common";
|
|
18
|
+
import { RoleSetResolver } from "./role_set_resolver.js";
|
|
19
|
+
export interface InstallRoleSetOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Path to a single consolidated archive file holding the whole RoleSet
|
|
22
|
+
* configuration (identity mappings, custom Role definitions and
|
|
23
|
+
* application/endpoint restrictions). Written atomically on every change.
|
|
24
|
+
*/
|
|
25
|
+
persistencePath?: string;
|
|
26
|
+
/**
|
|
27
|
+
* When set, the archive is encrypted at rest (AES-256-GCM, key derived from
|
|
28
|
+
* this operator-supplied secret). Omit to store plain JSON (relying on
|
|
29
|
+
* filesystem permissions); the stored password material is salted scrypt
|
|
30
|
+
* hashes either way.
|
|
31
|
+
*/
|
|
32
|
+
persistenceSecret?: string;
|
|
33
|
+
/**
|
|
34
|
+
* A shared {@link ArchiveStore} coordinating one consolidated file across
|
|
35
|
+
* `installRoleSet` and `installUserManagement`. Pass the **same** instance to
|
|
36
|
+
* both so users and roles live in one archive. When omitted, an internal one
|
|
37
|
+
* is created from `persistencePath`/`persistenceSecret` (role config only).
|
|
38
|
+
*/
|
|
39
|
+
persistence?: ArchiveStore;
|
|
40
|
+
/**
|
|
41
|
+
* Identity-mapping store to bind the RoleSet to. When omitted a new
|
|
42
|
+
* {@link InMemoryIdentityMappingStore} is created. Inject a shared store so the
|
|
43
|
+
* **same** mappings drive role resolution (e.g. via the server `userManager`),
|
|
44
|
+
* the `Identities` Property shown in clients, and the RoleSet Methods —
|
|
45
|
+
* keeping a single source of truth. Persisted mappings (if any) are merged in.
|
|
46
|
+
*/
|
|
47
|
+
store?: IIdentityMappingStore;
|
|
48
|
+
}
|
|
49
|
+
export interface InstallRoleSetResult {
|
|
50
|
+
/** The identity mapping store backing the role set. */
|
|
51
|
+
store: IIdentityMappingStore;
|
|
52
|
+
/** The per-Role application/endpoint restriction store (§4.4.1). */
|
|
53
|
+
restrictionStore: IRoleRestrictionStore;
|
|
54
|
+
/** The resolver registered on server.roleResolvers. */
|
|
55
|
+
resolver: RoleSetResolver;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The server-like object we need — just needs `roleResolvers` and
|
|
59
|
+
* access to the address space.
|
|
60
|
+
*/
|
|
61
|
+
export interface IServerForRoleSet {
|
|
62
|
+
roleResolvers: Array<{
|
|
63
|
+
resolveRoles(token: unknown): unknown[];
|
|
64
|
+
}>;
|
|
65
|
+
engine: {
|
|
66
|
+
addressSpace: IAddressSpace | null;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Install RoleSet management on an OPC UA server. Call this **after**
|
|
71
|
+
* `server.start()` so the address space is available.
|
|
72
|
+
*/
|
|
73
|
+
export declare function installRoleSet(server: IServerForRoleSet, options?: InstallRoleSetOptions): Promise<InstallRoleSetResult>;
|