asab_webui_shell 25.1.1
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/CHANGELOG.md +218 -0
- package/LICENSE +28 -0
- package/README.md +3 -0
- package/dist/actions.js +22 -0
- package/dist/components/branding/BrandImage.js +72 -0
- package/dist/components/locationReplace.js +51 -0
- package/dist/config/ConfigReducer.js +22 -0
- package/dist/config/ConfigService.js +169 -0
- package/dist/containers/AdvancedMode/reducer.js +28 -0
- package/dist/containers/Alerts/alerts.scss +9 -0
- package/dist/containers/Alerts/index.js +81 -0
- package/dist/containers/Alerts/reducer.js +68 -0
- package/dist/containers/Application.js +773 -0
- package/dist/containers/Application.scss +104 -0
- package/dist/containers/AttentionRequired/reducer.js +30 -0
- package/dist/containers/FullscreenMode/reducer.js +28 -0
- package/dist/containers/Header/ApplicationScreenTitle/ApplicationScreenTitle.js +47 -0
- package/dist/containers/Header/ApplicationScreenTitle/ApplicationScreenTitle.scss +8 -0
- package/dist/containers/Header/ApplicationScreenTitle/index.js +31 -0
- package/dist/containers/Header/Help/HelpButton.js +58 -0
- package/dist/containers/Header/Help/HelpButton.scss +15 -0
- package/dist/containers/Header/Help/HelpService.js +34 -0
- package/dist/containers/Header/Preview/PreviewFlag.js +34 -0
- package/dist/containers/Header/Preview/PreviewService.js +34 -0
- package/dist/containers/Header/header.scss +24 -0
- package/dist/containers/Header/index.js +39 -0
- package/dist/containers/Header/reducer.js +53 -0
- package/dist/containers/Navigation/index.js +66 -0
- package/dist/containers/Navigation/reducer.js +56 -0
- package/dist/containers/RouteErrorHandler/ErrorContainer.js +46 -0
- package/dist/containers/RouteErrorHandler/index.js +80 -0
- package/dist/containers/Router/ApplicationRouter.js +78 -0
- package/dist/containers/Router/index.js +50 -0
- package/dist/containers/Router/reducer.js +28 -0
- package/dist/containers/Sidebar/NavbarBrand.js +59 -0
- package/dist/containers/Sidebar/SidebarBottomItem.js +55 -0
- package/dist/containers/Sidebar/SidebarIcon.js +21 -0
- package/dist/containers/Sidebar/SidebarItem.js +209 -0
- package/dist/containers/Sidebar/SidebarItemRenderer.js +30 -0
- package/dist/containers/Sidebar/index.js +86 -0
- package/dist/containers/Sidebar/reducer.js +35 -0
- package/dist/containers/Sidebar/sidebar.scss +176 -0
- package/dist/containers/Sidepanel/Sidepanel.js +60 -0
- package/dist/containers/Sidepanel/Sidepanel.scss +20 -0
- package/dist/containers/Toast/ToastComponent.js +31 -0
- package/dist/containers/Toast/ToastComponent.jsx +28 -0
- package/dist/containers/Toast/ToastContainer.js +80 -0
- package/dist/containers/Toast/ToastContainer.jsx +59 -0
- package/dist/containers/Toast/reducer.js +55 -0
- package/dist/containers/Toast/reducer.jsx +45 -0
- package/dist/index.js +48 -0
- package/dist/modules/about/AboutScreen.js +84 -0
- package/dist/modules/about/index.js +41 -0
- package/dist/modules/auth/actions.js +11 -0
- package/dist/modules/auth/api.js +214 -0
- package/dist/modules/auth/components/SessionExpirationAlert.js +35 -0
- package/dist/modules/auth/header.js +109 -0
- package/dist/modules/auth/header.scss +9 -0
- package/dist/modules/auth/index.js +761 -0
- package/dist/modules/auth/reducer.js +36 -0
- package/dist/modules/auth/screens/AccessControlScreen.js +78 -0
- package/dist/modules/auth/screens/InvitationScreen.js +161 -0
- package/dist/modules/i18n/actions.js +7 -0
- package/dist/modules/i18n/dropdown.js +77 -0
- package/dist/modules/i18n/index.js +44 -0
- package/dist/modules/i18n/reducer.js +26 -0
- package/dist/modules/i18n/service.js +158 -0
- package/dist/modules/tenant/TenantDropdown.js +53 -0
- package/dist/modules/tenant/access/AccessDeniedCard.js +107 -0
- package/dist/modules/tenant/access/AccessDeniedCard.scss +6 -0
- package/dist/modules/tenant/actions.js +10 -0
- package/dist/modules/tenant/index.js +41 -0
- package/dist/modules/tenant/reducer.js +26 -0
- package/dist/modules/tenant/service.js +228 -0
- package/dist/screens/InvalidRouteScreen.js +30 -0
- package/dist/screens/InvalidRouteScreen.scss +19 -0
- package/dist/screens/SuspenseScreen.js +43 -0
- package/dist/screens/SuspenseScreen.scss +9 -0
- package/dist/screens/UnauthorizedAccessScreen.js +79 -0
- package/dist/screens/UnauthorizedAccessScreen.scss +19 -0
- package/dist/services/AttentionRequiredService.js +169 -0
- package/dist/services/BrandingService.js +104 -0
- package/dist/services/HeaderService.js +84 -0
- package/dist/services/ReduxService.js +25 -0
- package/dist/services/TitleService.js +45 -0
- package/dist/theme/ThemeButton.js +39 -0
- package/dist/theme/ThemeReducer.js +20 -0
- package/dist/theme/ThemeService.js +52 -0
- package/dist/theme/actions.js +7 -0
- package/package.json +41 -0
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports["default"] = void 0;
|
|
8
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
9
|
+
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
|
|
10
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
11
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
12
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
13
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
14
|
+
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
|
|
15
|
+
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
|
|
16
|
+
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
|
|
17
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
18
|
+
var _asab_webui_components = require("asab_webui_components");
|
|
19
|
+
var _header = require("./header");
|
|
20
|
+
var _reducer = _interopRequireDefault(require("./reducer"));
|
|
21
|
+
var _actions = require("./actions");
|
|
22
|
+
var _actions2 = require("../../actions");
|
|
23
|
+
var _api = require("./api");
|
|
24
|
+
var _locationReplace = require("../../components/locationReplace");
|
|
25
|
+
var _SessionExpirationAlert = require("./components/SessionExpirationAlert");
|
|
26
|
+
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2["default"])(o), (0, _possibleConstructorReturn2["default"])(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2["default"])(t).constructor) : o.apply(t, e)); }
|
|
27
|
+
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
|
|
28
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
|
|
29
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != (0, _typeof2["default"])(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } // TODO: Use SessionExpirationAlert only after proper fix of the component
|
|
30
|
+
var AccessControlScreen = /*#__PURE__*/(0, _react.lazy)(function () {
|
|
31
|
+
return Promise.resolve().then(function () {
|
|
32
|
+
return _interopRequireWildcard(require("./screens/AccessControlScreen"));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
var InvitationScreen = /*#__PURE__*/(0, _react.lazy)(function () {
|
|
36
|
+
return Promise.resolve().then(function () {
|
|
37
|
+
return _interopRequireWildcard(require("./screens/InvitationScreen"));
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
var AuthModule = exports["default"] = /*#__PURE__*/function (_Module) {
|
|
41
|
+
function AuthModule(app, name) {
|
|
42
|
+
var _this;
|
|
43
|
+
(0, _classCallCheck2["default"])(this, AuthModule);
|
|
44
|
+
_this = _callSuper(this, AuthModule, [app, "AuthModule"]);
|
|
45
|
+
_this.OAuthToken = JSON.parse(sessionStorage.getItem('SeaCatOAuth2Token'));
|
|
46
|
+
_this.UserInfo = null;
|
|
47
|
+
_this.Api = new _api.SeaCatAuthApi(app);
|
|
48
|
+
_this.RedirectURL = window.location.href;
|
|
49
|
+
_this.MustAuthenticate = true; // Setting this to false means, that we can operate without authenticated user
|
|
50
|
+
app.ReduxService.addReducer("auth", _reducer["default"]);
|
|
51
|
+
_this.App.addSplashScreenRequestor(_this);
|
|
52
|
+
_this.Authorization = _this.App.Config.get("authorization"); // Get authorization settings from configuration
|
|
53
|
+
|
|
54
|
+
_this.SessionExpiration = null; // Session expiration as defined in user info
|
|
55
|
+
_this.sessionValidationInterval = null; // Initialize session validation interval
|
|
56
|
+
|
|
57
|
+
// Access control screen
|
|
58
|
+
app.Router.addRoute({
|
|
59
|
+
path: '/auth/access-control',
|
|
60
|
+
end: true,
|
|
61
|
+
name: 'Access control',
|
|
62
|
+
component: AccessControlScreen,
|
|
63
|
+
resource: "*"
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Invitation screen
|
|
67
|
+
app.Router.addRoute({
|
|
68
|
+
path: '/auth/invite',
|
|
69
|
+
end: true,
|
|
70
|
+
name: 'Invite',
|
|
71
|
+
component: InvitationScreen,
|
|
72
|
+
resource: "seacat:tenant:assign"
|
|
73
|
+
});
|
|
74
|
+
return _this;
|
|
75
|
+
}
|
|
76
|
+
(0, _inherits2["default"])(AuthModule, _Module);
|
|
77
|
+
return (0, _createClass2["default"])(AuthModule, [{
|
|
78
|
+
key: "initialize",
|
|
79
|
+
value: function () {
|
|
80
|
+
var _initialize = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee() {
|
|
81
|
+
var headerService, qs, authorization_code, errorType, stateIndex, state, reloadUrl, result, force_login_prompt, tenantAuthorized, _force_login_prompt, currentTenant, resources, _force_login_prompt2, _force_login_prompt3;
|
|
82
|
+
return _regenerator["default"].wrap(function _callee$(_context) {
|
|
83
|
+
while (1) switch (_context.prev = _context.next) {
|
|
84
|
+
case 0:
|
|
85
|
+
headerService = this.App.locateService("HeaderService");
|
|
86
|
+
headerService.addComponent({
|
|
87
|
+
component: _header.AuthHeaderInvitation,
|
|
88
|
+
componentProps: {
|
|
89
|
+
AuthModule: this
|
|
90
|
+
},
|
|
91
|
+
order: 200
|
|
92
|
+
});
|
|
93
|
+
headerService.addComponent({
|
|
94
|
+
component: _header.AuthHeaderDropdown,
|
|
95
|
+
componentProps: {
|
|
96
|
+
AuthModule: this
|
|
97
|
+
},
|
|
98
|
+
order: 700
|
|
99
|
+
});
|
|
100
|
+
if (!(typeof MOCK_USERINFO !== 'undefined')) {
|
|
101
|
+
_context.next = 8;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
_context.next = 6;
|
|
105
|
+
return this.simulateUserinfo(MOCK_USERINFO);
|
|
106
|
+
case 6:
|
|
107
|
+
_context.next = 63;
|
|
108
|
+
break;
|
|
109
|
+
case 8:
|
|
110
|
+
// Check the query string for 'code'
|
|
111
|
+
qs = new URLSearchParams(window.location.search);
|
|
112
|
+
authorization_code = qs.get('code'); // Checking error type in params
|
|
113
|
+
errorType = qs.get('error');
|
|
114
|
+
if (!(errorType != undefined && errorType != 'tenant_access_denied')) {
|
|
115
|
+
_context.next = 13;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
return _context.abrupt("return");
|
|
119
|
+
case 13:
|
|
120
|
+
if (!(authorization_code !== null)) {
|
|
121
|
+
_context.next = 25;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
_context.next = 16;
|
|
125
|
+
return this._updateToken(authorization_code);
|
|
126
|
+
case 16:
|
|
127
|
+
// Remove 'code' from a query string
|
|
128
|
+
qs["delete"]('code');
|
|
129
|
+
stateIndex = qs.get("state");
|
|
130
|
+
state = ""; // Check if stateIndex is present, eventually return empty state string
|
|
131
|
+
if (stateIndex != undefined) {
|
|
132
|
+
// Check if oauth2_state_ is present, eventually return empty string
|
|
133
|
+
if (localStorage.getItem("oauth2_state_".concat(stateIndex)) != undefined) {
|
|
134
|
+
state = localStorage.getItem("oauth2_state_".concat(stateIndex));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
localStorage.removeItem("oauth2_state_".concat(stateIndex));
|
|
138
|
+
|
|
139
|
+
// Remove 'state' from a query string
|
|
140
|
+
qs["delete"]("state");
|
|
141
|
+
|
|
142
|
+
// Construct the new URL without `code` in the query string
|
|
143
|
+
// For this case, condition on empty qs string is sufficient and tested
|
|
144
|
+
|
|
145
|
+
if (qs.toString() == '') {
|
|
146
|
+
// Remove `?` part from URL completely, if empty
|
|
147
|
+
reloadUrl = window.location.pathname + state; // part form localstorage instead of hash
|
|
148
|
+
} else {
|
|
149
|
+
reloadUrl = window.location.pathname + '?' + qs.toString() + state; // part form localstorage instead of hash
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Reload the app with `code` removed
|
|
153
|
+
_context.next = 25;
|
|
154
|
+
return (0, _locationReplace.locationReplace)(reloadUrl);
|
|
155
|
+
case 25:
|
|
156
|
+
if (!(this.OAuthToken != null)) {
|
|
157
|
+
_context.next = 58;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
_context.next = 28;
|
|
161
|
+
return this.updateUserInfo();
|
|
162
|
+
case 28:
|
|
163
|
+
result = _context.sent;
|
|
164
|
+
if (result) {
|
|
165
|
+
_context.next = 35;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
// User info not found - go to login
|
|
169
|
+
sessionStorage.removeItem('SeaCatOAuth2Token');
|
|
170
|
+
force_login_prompt = true;
|
|
171
|
+
_context.next = 34;
|
|
172
|
+
return this.Api.login(this.RedirectURL, force_login_prompt);
|
|
173
|
+
case 34:
|
|
174
|
+
return _context.abrupt("return");
|
|
175
|
+
case 35:
|
|
176
|
+
// Add interceptor with Bearer token in the Header into axios calls
|
|
177
|
+
this.App.addAxiosInterceptor(this.authInterceptor());
|
|
178
|
+
// Add webSocket interceptor with Bearer token into websocket calls
|
|
179
|
+
this.App.addWebSocketInterceptor(this.webSocketAuthInterceptor());
|
|
180
|
+
|
|
181
|
+
// Authorization of the user based on tenant access
|
|
182
|
+
if (!(this.App.Config.get("authorization") !== "disabled" && this.App.Services.TenantService)) {
|
|
183
|
+
_context.next = 44;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
// Tenant access validation
|
|
187
|
+
tenantAuthorized = this.validateTenant();
|
|
188
|
+
if (tenantAuthorized) {
|
|
189
|
+
_context.next = 44;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
// If tenant not authorized, redirect to Access denied card
|
|
193
|
+
_force_login_prompt = false;
|
|
194
|
+
_context.next = 43;
|
|
195
|
+
return this.Api.login(this.RedirectURL, _force_login_prompt);
|
|
196
|
+
case 43:
|
|
197
|
+
return _context.abrupt("return");
|
|
198
|
+
case 44:
|
|
199
|
+
if (!(this.App.Store && this.App.Services.TenantService && this.UserInfo !== null)) {
|
|
200
|
+
_context.next = 55;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
currentTenant = this.App.Services.TenantService.getCurrentTenant();
|
|
204
|
+
resources = this.UserInfo.resources ? this.UserInfo.resources[currentTenant] : [];
|
|
205
|
+
/*
|
|
206
|
+
When switching between tenants,
|
|
207
|
+
we need to force authorization to obtain
|
|
208
|
+
correct data (resources) for particular
|
|
209
|
+
tenant (this is unavailable until auth token is updated)
|
|
210
|
+
*/
|
|
211
|
+
if (!(resources == undefined)) {
|
|
212
|
+
_context.next = 52;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
_force_login_prompt2 = false;
|
|
216
|
+
_context.next = 51;
|
|
217
|
+
return this.Api.login(this.RedirectURL, _force_login_prompt2);
|
|
218
|
+
case 51:
|
|
219
|
+
return _context.abrupt("return");
|
|
220
|
+
case 52:
|
|
221
|
+
if (this.App.Store != null) {
|
|
222
|
+
this.App.Store.dispatch({
|
|
223
|
+
type: _actions.types.AUTH_RESOURCES,
|
|
224
|
+
resources: resources
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
_context.next = 55;
|
|
228
|
+
return this.validateNavigation();
|
|
229
|
+
case 55:
|
|
230
|
+
if (!(this.UserInfo != null)) {
|
|
231
|
+
_context.next = 58;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
_context.next = 58;
|
|
235
|
+
return this._startSessionExpirationValidation();
|
|
236
|
+
case 58:
|
|
237
|
+
if (!(this.UserInfo == null && this.MustAuthenticate)) {
|
|
238
|
+
_context.next = 63;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
// TODO: force_login_prompt = true to break authentication failure loop
|
|
242
|
+
_force_login_prompt3 = false;
|
|
243
|
+
_context.next = 62;
|
|
244
|
+
return this.Api.login(this.RedirectURL, _force_login_prompt3);
|
|
245
|
+
case 62:
|
|
246
|
+
return _context.abrupt("return");
|
|
247
|
+
case 63:
|
|
248
|
+
this.App.removeSplashScreenRequestor(this);
|
|
249
|
+
case 64:
|
|
250
|
+
case "end":
|
|
251
|
+
return _context.stop();
|
|
252
|
+
}
|
|
253
|
+
}, _callee, this);
|
|
254
|
+
}));
|
|
255
|
+
function initialize() {
|
|
256
|
+
return _initialize.apply(this, arguments);
|
|
257
|
+
}
|
|
258
|
+
return initialize;
|
|
259
|
+
}()
|
|
260
|
+
}, {
|
|
261
|
+
key: "authInterceptor",
|
|
262
|
+
value: function authInterceptor() {
|
|
263
|
+
var _this2 = this;
|
|
264
|
+
var interceptor = function interceptor(config) {
|
|
265
|
+
config.headers['Authorization'] = 'Bearer ' + _this2.OAuthToken['access_token'];
|
|
266
|
+
return config;
|
|
267
|
+
};
|
|
268
|
+
return interceptor;
|
|
269
|
+
}
|
|
270
|
+
}, {
|
|
271
|
+
key: "webSocketAuthInterceptor",
|
|
272
|
+
value: function webSocketAuthInterceptor() {
|
|
273
|
+
return "access_token_".concat(this.OAuthToken['access_token']);
|
|
274
|
+
}
|
|
275
|
+
}, {
|
|
276
|
+
key: "simulateUserinfo",
|
|
277
|
+
value: function () {
|
|
278
|
+
var _simulateUserinfo = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee2(mock_userinfo) {
|
|
279
|
+
var mockParams, availableTenants;
|
|
280
|
+
return _regenerator["default"].wrap(function _callee2$(_context2) {
|
|
281
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
282
|
+
case 0:
|
|
283
|
+
/*
|
|
284
|
+
This method takes parameters from wepack.dev.js settings
|
|
285
|
+
plugins: [
|
|
286
|
+
...
|
|
287
|
+
new webpack.DefinePlugin({
|
|
288
|
+
'MOCK_USERINFO': JSON.stringify({ // Inject mocked UserInfo object, this means the application will NOT require user authorization
|
|
289
|
+
"username": "johndev",
|
|
290
|
+
"email": "dev@dev.de",
|
|
291
|
+
"phone": "123456789",
|
|
292
|
+
"resources": ["authz:superuser"],
|
|
293
|
+
"roles": ["default/Admin"],
|
|
294
|
+
"sub": "devdb:dev:1abc2def3456",
|
|
295
|
+
"tenants": ["default"]
|
|
296
|
+
})
|
|
297
|
+
}),
|
|
298
|
+
...
|
|
299
|
+
],
|
|
300
|
+
*/
|
|
301
|
+
this.App.addAlert("warning", "ASABAuthModule|You are using MOCK_USERINFO!", 1, true);
|
|
302
|
+
mockParams = mock_userinfo;
|
|
303
|
+
if (mockParams.resources) {
|
|
304
|
+
mockParams["resources"] = Object.values(mockParams.resources);
|
|
305
|
+
}
|
|
306
|
+
if (mockParams.roles) {
|
|
307
|
+
mockParams["roles"] = Object.values(mockParams.roles);
|
|
308
|
+
}
|
|
309
|
+
if (mockParams.tenants) {
|
|
310
|
+
mockParams["tenants"] = Object.values(mockParams.tenants);
|
|
311
|
+
}
|
|
312
|
+
if (this.App.Store != null) {
|
|
313
|
+
this.App.Store.dispatch({
|
|
314
|
+
type: _actions.types.AUTH_USERINFO,
|
|
315
|
+
payload: mockParams
|
|
316
|
+
});
|
|
317
|
+
this.App.Store.dispatch({
|
|
318
|
+
type: _actions.types.AUTH_RESOURCES,
|
|
319
|
+
resources: mockParams["resources"]
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Check for TenantService and pass tenants list obtained from userinfo */
|
|
324
|
+
availableTenants = mockParams.tenants;
|
|
325
|
+
if (!this.App.Services.TenantService) {
|
|
326
|
+
_context2.next = 10;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
_context2.next = 10;
|
|
330
|
+
return this.App.Services.TenantService.setTenants(availableTenants, this._getAuthorizedTenant(mockParams));
|
|
331
|
+
case 10:
|
|
332
|
+
case "end":
|
|
333
|
+
return _context2.stop();
|
|
334
|
+
}
|
|
335
|
+
}, _callee2, this);
|
|
336
|
+
}));
|
|
337
|
+
function simulateUserinfo(_x) {
|
|
338
|
+
return _simulateUserinfo.apply(this, arguments);
|
|
339
|
+
}
|
|
340
|
+
return simulateUserinfo;
|
|
341
|
+
}()
|
|
342
|
+
}, {
|
|
343
|
+
key: "logout",
|
|
344
|
+
value: function logout() {
|
|
345
|
+
this.App.addSplashScreenRequestor(this);
|
|
346
|
+
this._stopSessionExpirationValidation(); // Stop session validation and clear the timeout
|
|
347
|
+
|
|
348
|
+
sessionStorage.removeItem('SeaCatOAuth2Token');
|
|
349
|
+
var promise = this.Api.logout(this.OAuthToken['access_token']);
|
|
350
|
+
if (promise == null) {
|
|
351
|
+
window.location.reload();
|
|
352
|
+
}
|
|
353
|
+
promise.then(function (response) {
|
|
354
|
+
window.location.reload();
|
|
355
|
+
})["catch"](function (error) {
|
|
356
|
+
window.location.reload();
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// TODO: reconsider removing async/await with promise
|
|
361
|
+
}, {
|
|
362
|
+
key: "validateNavigation",
|
|
363
|
+
value: function () {
|
|
364
|
+
var _validateNavigation = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee4() {
|
|
365
|
+
var _state$navigation,
|
|
366
|
+
_state$auth,
|
|
367
|
+
_this3 = this;
|
|
368
|
+
var state, navItems, resources, authorizedNavItems;
|
|
369
|
+
return _regenerator["default"].wrap(function _callee4$(_context4) {
|
|
370
|
+
while (1) switch (_context4.prev = _context4.next) {
|
|
371
|
+
case 0:
|
|
372
|
+
state = this.App.Store.getState();
|
|
373
|
+
navItems = (_state$navigation = state.navigation) === null || _state$navigation === void 0 ? void 0 : _state$navigation.navItems;
|
|
374
|
+
resources = (_state$auth = state.auth) === null || _state$auth === void 0 ? void 0 : _state$auth.resources;
|
|
375
|
+
authorizedNavItems = [];
|
|
376
|
+
_context4.t0 = navItems;
|
|
377
|
+
if (!_context4.t0) {
|
|
378
|
+
_context4.next = 8;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
_context4.next = 8;
|
|
382
|
+
return Promise.all(navItems.map(/*#__PURE__*/function () {
|
|
383
|
+
var _ref = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee3(itm, idx) {
|
|
384
|
+
var access_auth;
|
|
385
|
+
return _regenerator["default"].wrap(function _callee3$(_context3) {
|
|
386
|
+
while (1) switch (_context3.prev = _context3.next) {
|
|
387
|
+
case 0:
|
|
388
|
+
if (!(resources.indexOf('authz:superuser') !== -1)) {
|
|
389
|
+
_context3.next = 4;
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
// If user is superuser, validate every navigation item
|
|
393
|
+
authorizedNavItems.push(itm);
|
|
394
|
+
_context3.next = 18;
|
|
395
|
+
break;
|
|
396
|
+
case 4:
|
|
397
|
+
if (!itm.resource) {
|
|
398
|
+
_context3.next = 13;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
if (!itm.children) {
|
|
402
|
+
_context3.next = 9;
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
_context3.next = 8;
|
|
406
|
+
return _this3._validateChildrenNav(itm, resources);
|
|
407
|
+
case 8:
|
|
408
|
+
itm.children = _context3.sent;
|
|
409
|
+
case 9:
|
|
410
|
+
// Item validation
|
|
411
|
+
access_auth = _this3._validateItemNav(itm.resource, resources);
|
|
412
|
+
if (access_auth == true) {
|
|
413
|
+
authorizedNavItems.push(itm);
|
|
414
|
+
}
|
|
415
|
+
_context3.next = 18;
|
|
416
|
+
break;
|
|
417
|
+
case 13:
|
|
418
|
+
if (!itm.children) {
|
|
419
|
+
_context3.next = 18;
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
_context3.next = 16;
|
|
423
|
+
return _this3._validateChildrenNav(itm, resources);
|
|
424
|
+
case 16:
|
|
425
|
+
itm.children = _context3.sent;
|
|
426
|
+
if (itm.children.length > 0) {
|
|
427
|
+
authorizedNavItems.push(itm);
|
|
428
|
+
}
|
|
429
|
+
case 18:
|
|
430
|
+
case "end":
|
|
431
|
+
return _context3.stop();
|
|
432
|
+
}
|
|
433
|
+
}, _callee3);
|
|
434
|
+
}));
|
|
435
|
+
return function (_x2, _x3) {
|
|
436
|
+
return _ref.apply(this, arguments);
|
|
437
|
+
};
|
|
438
|
+
}()));
|
|
439
|
+
case 8:
|
|
440
|
+
this.App.Store.dispatch({
|
|
441
|
+
type: _actions2.SET_NAVIGATION_ITEMS,
|
|
442
|
+
navItems: authorizedNavItems
|
|
443
|
+
});
|
|
444
|
+
case 9:
|
|
445
|
+
case "end":
|
|
446
|
+
return _context4.stop();
|
|
447
|
+
}
|
|
448
|
+
}, _callee4, this);
|
|
449
|
+
}));
|
|
450
|
+
function validateNavigation() {
|
|
451
|
+
return _validateNavigation.apply(this, arguments);
|
|
452
|
+
}
|
|
453
|
+
return validateNavigation;
|
|
454
|
+
}() // Validate sidebar's item children
|
|
455
|
+
}, {
|
|
456
|
+
key: "_validateChildrenNav",
|
|
457
|
+
value: function () {
|
|
458
|
+
var _validateChildrenNav2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee6(itm, resources) {
|
|
459
|
+
var _this4 = this;
|
|
460
|
+
var authorizedNavChildren;
|
|
461
|
+
return _regenerator["default"].wrap(function _callee6$(_context6) {
|
|
462
|
+
while (1) switch (_context6.prev = _context6.next) {
|
|
463
|
+
case 0:
|
|
464
|
+
// Item's children validation
|
|
465
|
+
authorizedNavChildren = [];
|
|
466
|
+
_context6.next = 3;
|
|
467
|
+
return Promise.all(itm.children.map(/*#__PURE__*/function () {
|
|
468
|
+
var _ref2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee5(child, id) {
|
|
469
|
+
var access_auth;
|
|
470
|
+
return _regenerator["default"].wrap(function _callee5$(_context5) {
|
|
471
|
+
while (1) switch (_context5.prev = _context5.next) {
|
|
472
|
+
case 0:
|
|
473
|
+
if (child.resource) {
|
|
474
|
+
access_auth = _this4._validateItemNav(child.resource, resources);
|
|
475
|
+
if (access_auth == true) {
|
|
476
|
+
authorizedNavChildren.push(child);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
case 1:
|
|
480
|
+
case "end":
|
|
481
|
+
return _context5.stop();
|
|
482
|
+
}
|
|
483
|
+
}, _callee5);
|
|
484
|
+
}));
|
|
485
|
+
return function (_x6, _x7) {
|
|
486
|
+
return _ref2.apply(this, arguments);
|
|
487
|
+
};
|
|
488
|
+
}()));
|
|
489
|
+
case 3:
|
|
490
|
+
return _context6.abrupt("return", authorizedNavChildren);
|
|
491
|
+
case 4:
|
|
492
|
+
case "end":
|
|
493
|
+
return _context6.stop();
|
|
494
|
+
}
|
|
495
|
+
}, _callee6);
|
|
496
|
+
}));
|
|
497
|
+
function _validateChildrenNav(_x4, _x5) {
|
|
498
|
+
return _validateChildrenNav2.apply(this, arguments);
|
|
499
|
+
}
|
|
500
|
+
return _validateChildrenNav;
|
|
501
|
+
}() // Validate sidebar items
|
|
502
|
+
}, {
|
|
503
|
+
key: "_validateItemNav",
|
|
504
|
+
value: function _validateItemNav(resource, resources) {
|
|
505
|
+
var valid = resources ? resources.indexOf(resource) !== -1 : false;
|
|
506
|
+
// If user is superuser, then item is enabled
|
|
507
|
+
if (resources.indexOf('authz:superuser') !== -1) {
|
|
508
|
+
valid = true;
|
|
509
|
+
}
|
|
510
|
+
return valid;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Validate tenant access
|
|
514
|
+
}, {
|
|
515
|
+
key: "validateTenant",
|
|
516
|
+
value: function validateTenant() {
|
|
517
|
+
var resources = [];
|
|
518
|
+
var tenants = [];
|
|
519
|
+
var currentTenant = this.App.Services.TenantService.getCurrentTenant();
|
|
520
|
+
if (this.UserInfo !== null) {
|
|
521
|
+
resources = this.UserInfo.resources ? this.UserInfo.resources[currentTenant] : [];
|
|
522
|
+
tenants = this.UserInfo.tenants ? this.UserInfo.tenants : [];
|
|
523
|
+
}
|
|
524
|
+
var valid = tenants ? tenants.indexOf(currentTenant) !== -1 : false;
|
|
525
|
+
// If user is superuser, then tenant access is granted
|
|
526
|
+
if (resources && resources.indexOf('authz:superuser') !== -1) {
|
|
527
|
+
valid = true;
|
|
528
|
+
}
|
|
529
|
+
return valid;
|
|
530
|
+
}
|
|
531
|
+
}, {
|
|
532
|
+
key: "updateUserInfo",
|
|
533
|
+
value: function () {
|
|
534
|
+
var _updateUserInfo = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee7() {
|
|
535
|
+
var _response$data;
|
|
536
|
+
var response, availableTenants;
|
|
537
|
+
return _regenerator["default"].wrap(function _callee7$(_context7) {
|
|
538
|
+
while (1) switch (_context7.prev = _context7.next) {
|
|
539
|
+
case 0:
|
|
540
|
+
_context7.prev = 0;
|
|
541
|
+
_context7.next = 3;
|
|
542
|
+
return this.Api.userinfo(this.OAuthToken.access_token);
|
|
543
|
+
case 3:
|
|
544
|
+
response = _context7.sent;
|
|
545
|
+
_context7.next = 12;
|
|
546
|
+
break;
|
|
547
|
+
case 6:
|
|
548
|
+
_context7.prev = 6;
|
|
549
|
+
_context7.t0 = _context7["catch"](0);
|
|
550
|
+
console.error("Failed to update user info", _context7.t0);
|
|
551
|
+
this.UserInfo = null;
|
|
552
|
+
if (this.App.Store != null) {
|
|
553
|
+
this.App.Store.dispatch({
|
|
554
|
+
type: _actions.types.AUTH_USERINFO,
|
|
555
|
+
payload: this.UserInfo
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
return _context7.abrupt("return", false);
|
|
559
|
+
case 12:
|
|
560
|
+
this.UserInfo = response.data;
|
|
561
|
+
this.SessionExpiration = (_response$data = response.data) === null || _response$data === void 0 ? void 0 : _response$data.exp;
|
|
562
|
+
if (this.App.Store != null) {
|
|
563
|
+
this.App.Store.dispatch({
|
|
564
|
+
type: _actions.types.AUTH_USERINFO,
|
|
565
|
+
payload: this.UserInfo
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** Check for TenantService and pass tenants list obtained from userinfo */
|
|
570
|
+
availableTenants = this.UserInfo.tenants;
|
|
571
|
+
if (!this.App.Services.TenantService) {
|
|
572
|
+
_context7.next = 19;
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
_context7.next = 19;
|
|
576
|
+
return this.App.Services.TenantService.setTenants(availableTenants, this._getAuthorizedTenant(this.UserInfo));
|
|
577
|
+
case 19:
|
|
578
|
+
return _context7.abrupt("return", true);
|
|
579
|
+
case 20:
|
|
580
|
+
case "end":
|
|
581
|
+
return _context7.stop();
|
|
582
|
+
}
|
|
583
|
+
}, _callee7, this, [[0, 6]]);
|
|
584
|
+
}));
|
|
585
|
+
function updateUserInfo() {
|
|
586
|
+
return _updateUserInfo.apply(this, arguments);
|
|
587
|
+
}
|
|
588
|
+
return updateUserInfo;
|
|
589
|
+
}()
|
|
590
|
+
}, {
|
|
591
|
+
key: "_updateToken",
|
|
592
|
+
value: function () {
|
|
593
|
+
var _updateToken2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee8(authorization_code) {
|
|
594
|
+
var response;
|
|
595
|
+
return _regenerator["default"].wrap(function _callee8$(_context8) {
|
|
596
|
+
while (1) switch (_context8.prev = _context8.next) {
|
|
597
|
+
case 0:
|
|
598
|
+
_context8.prev = 0;
|
|
599
|
+
_context8.next = 3;
|
|
600
|
+
return this.Api.token_authorization_code(authorization_code, this.RedirectURL);
|
|
601
|
+
case 3:
|
|
602
|
+
response = _context8.sent;
|
|
603
|
+
_context8.next = 10;
|
|
604
|
+
break;
|
|
605
|
+
case 6:
|
|
606
|
+
_context8.prev = 6;
|
|
607
|
+
_context8.t0 = _context8["catch"](0);
|
|
608
|
+
console.error("Failed to update token", _context8.t0);
|
|
609
|
+
return _context8.abrupt("return", false);
|
|
610
|
+
case 10:
|
|
611
|
+
this.OAuthToken = response.data;
|
|
612
|
+
sessionStorage.setItem('SeaCatOAuth2Token', JSON.stringify(response.data));
|
|
613
|
+
return _context8.abrupt("return", true);
|
|
614
|
+
case 13:
|
|
615
|
+
case "end":
|
|
616
|
+
return _context8.stop();
|
|
617
|
+
}
|
|
618
|
+
}, _callee8, this, [[0, 6]]);
|
|
619
|
+
}));
|
|
620
|
+
function _updateToken(_x8) {
|
|
621
|
+
return _updateToken2.apply(this, arguments);
|
|
622
|
+
}
|
|
623
|
+
return _updateToken;
|
|
624
|
+
}()
|
|
625
|
+
}, {
|
|
626
|
+
key: "_getAuthorizedTenant",
|
|
627
|
+
value: function _getAuthorizedTenant(userInfo) {
|
|
628
|
+
/*
|
|
629
|
+
Return the authorized tenant ID from userinfo.
|
|
630
|
+
Pick the first one if there are multiple tenants.
|
|
631
|
+
*/
|
|
632
|
+
if (userInfo !== null && userInfo !== void 0 && userInfo.resources) {
|
|
633
|
+
for (var tenantId in userInfo.resources) {
|
|
634
|
+
if (tenantId != '*' && userInfo.resources.hasOwnProperty(tenantId)) {
|
|
635
|
+
return tenantId;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
return undefined;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Loop validating session expiration
|
|
644
|
+
}, {
|
|
645
|
+
key: "_startSessionExpirationValidation",
|
|
646
|
+
value: function () {
|
|
647
|
+
var _startSessionExpirationValidation2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee10() {
|
|
648
|
+
var _this5 = this;
|
|
649
|
+
var warningDisplayed, _validateSession;
|
|
650
|
+
return _regenerator["default"].wrap(function _callee10$(_context10) {
|
|
651
|
+
while (1) switch (_context10.prev = _context10.next) {
|
|
652
|
+
case 0:
|
|
653
|
+
if (this.SessionExpiration) {
|
|
654
|
+
_context10.next = 3;
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
console.warn("Session expiration is not set.");
|
|
658
|
+
return _context10.abrupt("return");
|
|
659
|
+
case 3:
|
|
660
|
+
warningDisplayed = false; // Tracks if the "about to expire" warning has been shown
|
|
661
|
+
_validateSession = /*#__PURE__*/function () {
|
|
662
|
+
var _ref3 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee9() {
|
|
663
|
+
var currentTime, timeRemaining, isUserInfoUpdated;
|
|
664
|
+
return _regenerator["default"].wrap(function _callee9$(_context9) {
|
|
665
|
+
while (1) switch (_context9.prev = _context9.next) {
|
|
666
|
+
case 0:
|
|
667
|
+
currentTime = Date.now() / 1000; // Convert milliseconds to seconds
|
|
668
|
+
timeRemaining = _this5.SessionExpiration - currentTime; // Time difference for triggering "about to expire" warning
|
|
669
|
+
// Prevent glitches on very large time remaining values
|
|
670
|
+
if (timeRemaining >= Math.pow(2, 31)) {
|
|
671
|
+
// Set timeout to maximum allowed value
|
|
672
|
+
timeRemaining = Math.pow(2, 31) - 1;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// If remaining time is between 1 and 60s, repetitivelly ask for userInfo and trigger "about to expire" warning
|
|
676
|
+
if (!(timeRemaining <= 60 && timeRemaining > 0)) {
|
|
677
|
+
_context9.next = 7;
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
if (!warningDisplayed) {
|
|
681
|
+
_this5.App.addAlert("info", "General|Your session will expire soon", 30, true);
|
|
682
|
+
warningDisplayed = true; // Mark the warning as displayed
|
|
683
|
+
}
|
|
684
|
+
_context9.next = 7;
|
|
685
|
+
return _this5.updateUserInfo();
|
|
686
|
+
case 7:
|
|
687
|
+
if (!(_this5.SessionExpiration <= currentTime)) {
|
|
688
|
+
_context9.next = 17;
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
_context9.next = 10;
|
|
692
|
+
return _this5.updateUserInfo();
|
|
693
|
+
case 10:
|
|
694
|
+
isUserInfoUpdated = _context9.sent;
|
|
695
|
+
if (isUserInfoUpdated) {
|
|
696
|
+
_context9.next = 17;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
// Stop further checks
|
|
700
|
+
clearTimeout(_this5.sessionValidationInterval);
|
|
701
|
+
_this5.sessionValidationInterval = null;
|
|
702
|
+
_this5.App.addAlert("info", "ASABAuthModule|Your session has expired.", 3600 * 1000, true, function (alert, store) {
|
|
703
|
+
return /*#__PURE__*/_react["default"].createElement(_SessionExpirationAlert.SessionExpirationAlert, {
|
|
704
|
+
alert: alert,
|
|
705
|
+
store: store
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
// Disable UI elements
|
|
709
|
+
if (_this5.App.Store) {
|
|
710
|
+
_this5.App.Store.dispatch({
|
|
711
|
+
type: _actions.types.AUTH_SESSION_EXPIRATION,
|
|
712
|
+
sessionExpired: true
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
// Disable UI elements
|
|
716
|
+
(0, _toConsumableArray2["default"])(document.querySelectorAll('#app-sidebar .nav-link, [class^="btn"]:not(.alert-button), [class*=" btn"]:not(.alert-button), .btn-group a, .page-item, input, select')).forEach(function (i) {
|
|
717
|
+
i.classList.add("disabled");
|
|
718
|
+
i.setAttribute("disabled", "");
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
// Reload on navigation actions
|
|
722
|
+
window.addEventListener("popstate", function () {
|
|
723
|
+
window.location.reload();
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
return _context9.abrupt("return");
|
|
727
|
+
case 17:
|
|
728
|
+
// Re-trigger validation after 10 seconds
|
|
729
|
+
_this5.sessionValidationInterval = setTimeout(_validateSession, 10000);
|
|
730
|
+
case 18:
|
|
731
|
+
case "end":
|
|
732
|
+
return _context9.stop();
|
|
733
|
+
}
|
|
734
|
+
}, _callee9);
|
|
735
|
+
}));
|
|
736
|
+
return function validateSession() {
|
|
737
|
+
return _ref3.apply(this, arguments);
|
|
738
|
+
};
|
|
739
|
+
}(); // Start the first validation cycle
|
|
740
|
+
_validateSession();
|
|
741
|
+
case 6:
|
|
742
|
+
case "end":
|
|
743
|
+
return _context10.stop();
|
|
744
|
+
}
|
|
745
|
+
}, _callee10, this);
|
|
746
|
+
}));
|
|
747
|
+
function _startSessionExpirationValidation() {
|
|
748
|
+
return _startSessionExpirationValidation2.apply(this, arguments);
|
|
749
|
+
}
|
|
750
|
+
return _startSessionExpirationValidation;
|
|
751
|
+
}() // Stop looping on session expiration validation
|
|
752
|
+
}, {
|
|
753
|
+
key: "_stopSessionExpirationValidation",
|
|
754
|
+
value: function _stopSessionExpirationValidation() {
|
|
755
|
+
if (this.sessionValidationInterval) {
|
|
756
|
+
clearInterval(this.sessionValidationInterval);
|
|
757
|
+
this.sessionValidationInterval = null;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}]);
|
|
761
|
+
}(_asab_webui_components.Module);
|