iobroker.eos-admin 7.10.3 → 7.10.5

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.
Files changed (39) hide show
  1. package/INSTALL_TEST_V7.10.5.md +8 -0
  2. package/MERGE_UPDATE_README_V7.10.5.md +7 -0
  3. package/NEXOWATT_EOS_BUILD_INFO.json +22 -18
  4. package/NEXOWATT_EOS_PREBUILT_MANIFEST.json +37 -36
  5. package/PUBLISH_STABLE_V7.10.5.md +8 -0
  6. package/README.md +4 -4
  7. package/README_STABILITY_V7.10.5.md +5 -0
  8. package/RELEASE_ACCEPTANCE_V7.10.5.md +11 -0
  9. package/RELEASE_NOTES_V7.10.5.md +11 -0
  10. package/SECURITY_ROLE_MATRIX_V7.10.5.md +12 -0
  11. package/adminWww/assets/Intro-DkwRiz1n-v84.js +2 -2
  12. package/adminWww/assets/Objects-DPan0bzw-v84.js +1 -1
  13. package/adminWww/assets/bootstrap-COulQZax-v84.js +2 -2
  14. package/adminWww/css/nexowatt-native-shell.css +12 -1
  15. package/adminWww/index.html +14 -14
  16. package/adminWww/js/eos-role-bootstrap.js +5 -5
  17. package/adminWww/js/eos-role-ui.js +130 -17
  18. package/adminWww/js/nexowatt-native-shell.js +1 -1
  19. package/adminWww/static/js/{nexowatt-stable-v7101.js → nexowatt-stable-v7105.js} +1 -1
  20. package/build/main.js +14 -11
  21. package/io-package.json +31 -9
  22. package/package.json +14 -11
  23. package/tools/nexowatt-account-password-flow-selftest.cjs +3 -3
  24. package/tools/nexowatt-clean-legacy-runtime.cjs +6 -0
  25. package/tools/nexowatt-internal-reserve-selftest.cjs +1 -1
  26. package/tools/nexowatt-login-layout-selftest.cjs +3 -3
  27. package/tools/nexowatt-merge-update-selftest.cjs +1 -1
  28. package/tools/nexowatt-modern-ui-selftest.cjs +2 -2
  29. package/tools/nexowatt-role-security-selftest.cjs +131 -0
  30. package/tools/nexowatt-runtime-cleanup-selftest.cjs +4 -0
  31. package/tools/nexowatt-stable-v7103-selftest.cjs +4 -4
  32. package/tools/nexowatt-ui-overview-runtime-selftest.cjs +2 -2
  33. package/tools/nexowatt-validate-package.cjs +9 -8
  34. package/INSTALL_TEST_V7.10.3.md +0 -7
  35. package/MERGE_UPDATE_README_V7.10.3.md +0 -3
  36. package/PUBLISH_STABLE_V7.10.3.md +0 -10
  37. package/README_STABILITY_V7.10.3.md +0 -5
  38. package/RELEASE_ACCEPTANCE_V7.10.3.md +0 -3
  39. package/RELEASE_NOTES_V7.10.3.md +0 -7
@@ -1184,12 +1184,23 @@ html.eos-login .eos-login-card-modern {
1184
1184
  /* Internal fallback services are never exposed outside Admin / Service. */
1185
1185
  html.eos-role-installer [data-instance^="admin."],
1186
1186
  html.eos-role-installer [data-instance^="backitup."],
1187
+ html.eos-role-installer [data-instance^="xterm."],
1187
1188
  html.eos-role-enduser [data-instance^="admin."],
1188
1189
  html.eos-role-enduser [data-instance^="backitup."],
1190
+ html.eos-role-enduser [data-instance^="xterm."],
1189
1191
  html.eos-role-installer [data-instance-id^="admin."],
1190
1192
  html.eos-role-installer [data-instance-id^="backitup."],
1193
+ html.eos-role-installer [data-instance-id^="xterm."],
1191
1194
  html.eos-role-enduser [data-instance-id^="admin."],
1192
- html.eos-role-enduser [data-instance-id^="backitup."] { display: none !important; }
1195
+ html.eos-role-enduser [data-instance-id^="backitup."],
1196
+ html.eos-role-enduser [data-instance-id^="xterm."] { display: none !important; }
1197
+
1198
+ html.eos-role-enduser #app-paper[data-eos-datapoints-readonly] [data-eos-object-writable="1"],
1199
+ html.eos-role-enduser #app-paper[data-eos-datapoints-readonly] [data-eos-enduser-write-blocked="1"] {
1200
+ pointer-events: none !important;
1201
+ cursor: default !important;
1202
+ opacity: 0.62 !important;
1203
+ }
1193
1204
 
1194
1205
 
1195
1206
 
@@ -8,7 +8,7 @@
8
8
  <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
9
9
  <meta http-equiv="Pragma" content="no-cache" />
10
10
  <meta http-equiv="Expires" content="0" />
11
- <script src="./js/eos-manual-write-policy.js?v=7101"></script>
11
+ <script src="./js/eos-manual-write-policy.js?v=7105"></script>
12
12
  <script type="module" src="./assets/hostInit-v84.js?v=84"></script>
13
13
 
14
14
  <meta charset="utf-8" />
@@ -36,8 +36,8 @@
36
36
  rel="stylesheet"
37
37
  href="css/leaflet.css"
38
38
  />
39
- <link rel="stylesheet" href="./css/nexowatt-native-shell.css?v=7101" />
40
- <link rel="stylesheet" href="./css/eos-ems-overview.css?v=7101" />
39
+ <link rel="stylesheet" href="./css/nexowatt-native-shell.css?v=7105" />
40
+ <link rel="stylesheet" href="./css/eos-ems-overview.css?v=7105" />
41
41
  <link rel="stylesheet" href="./css/eos-auto-update.css?v=7103" />
42
42
  <link
43
43
  rel="manifest"
@@ -160,22 +160,22 @@
160
160
  </script>
161
161
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js">
162
162
  <link rel="modulepreload" crossorigin href="./assets/iobroker_admin__mf_v__runtimeInit__mf_v__-g2X2zhAf.js">
163
- <script defer src="./js/eos-role-bootstrap.js?v=7101" data-eos-entry="./assets/index-CQZugZ1z-v84.js?v=84"></script>
164
- <script defer src="./js/eos-policy-client.js?v=7101"></script>
165
- <script defer src="./js/eos-branding-sanitizer.js?v=7101"></script>
166
- <script defer src="./js/nexowatt-native-shell.js?v=7101"></script>
167
- <script defer src="./js/eos-native-security.js?v=7101"></script>
168
- <script defer src="./js/eos-basic-settings.js?v=7101"></script>
169
- <script defer src="./js/eos-role-ui.js?v=7101"></script>
170
- <script defer src="./js/eos-ems-overview.js?v=7101"></script>
163
+ <script defer src="./js/eos-role-bootstrap.js?v=7105" data-eos-entry="./assets/index-CQZugZ1z-v84.js?v=84"></script>
164
+ <script defer src="./js/eos-policy-client.js?v=7105"></script>
165
+ <script defer src="./js/eos-branding-sanitizer.js?v=7105"></script>
166
+ <script defer src="./js/nexowatt-native-shell.js?v=7105"></script>
167
+ <script defer src="./js/eos-native-security.js?v=7105"></script>
168
+ <script defer src="./js/eos-basic-settings.js?v=7105"></script>
169
+ <script defer src="./js/eos-role-ui.js?v=7105"></script>
170
+ <script defer src="./js/eos-ems-overview.js?v=7105"></script>
171
171
 
172
172
  <script defer src="./js/eos-auto-update.js?v=7103"></script>
173
- <script defer src="./js/eos-assistant.js?v=7101"></script>
173
+ <script defer src="./js/eos-assistant.js?v=7105"></script>
174
174
  </head>
175
175
  <body>
176
176
  <noscript>Für NexoWatt EOS muss JavaScript aktiviert sein.</noscript>
177
177
  <div id="root"></div>
178
- <script defer src="static/js/nexowatt-stable-login.js?v=7101"></script>
179
- <script defer src="static/js/nexowatt-stable-v7101.js?v=7101"></script>
178
+ <script defer src="static/js/nexowatt-stable-login.js?v=7105"></script>
179
+ <script defer src="static/js/nexowatt-stable-v7105.js?v=7105"></script>
180
180
  </body>
181
181
  </html>
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v7101-standard-password-native-overview';
4
+ const VERSION = 'v7105-admin-authoritative-rbac';
5
5
  const script = document.currentScript || document.querySelector('script[src*="eos-role-bootstrap.js"]');
6
6
  const entry = script?.dataset?.eosEntry || '';
7
7
  let launched = false;
@@ -16,7 +16,7 @@
16
16
 
17
17
  const roleFromPolicy = policy => {
18
18
  const raw = normalize(policy?.role || policy?.eosRole || policy?.accessRole || '');
19
- if (policy?.isAdmin || policy?.isEosAdminGroup || policy?.isAdministrator || /^(?:admin|administrator)$/.test(raw)) return 'admin';
19
+ if (policy?.user === 'system.user.admin' || policy?.userId === 'system.user.admin' || policy?.isAdmin || policy?.isEosAdminGroup || policy?.isAdministrator || /^(?:admin|administrator)$/.test(raw)) return 'admin';
20
20
  if (/nexowatt service|eos service|service admin|service administrator/.test(raw)) return 'admin';
21
21
  if (policy?.isInstaller || /installateur|installer|installation|inbetriebnahme|techniker|technician|integrator|partner/.test(raw)) return 'installer';
22
22
  return 'enduser';
@@ -25,11 +25,11 @@
25
25
  const isCustomerBackupTab = tab => /^(?:tab-)?(?:nexowatt-backup|eos-backup|nexowatt-sicherung)(?:-|$)/.test(normalize(tab));
26
26
  const isReleasedEndUserTab = tab => /^(?:tab-)?(?:nexowatt-ui|nexowatt-cockpit|eos-cockpit|eos-dashboard|kunden-cockpit|endkunden-cockpit|lovelace|jarvis|vis|iqontrol|material)(?:-|$)/.test(normalize(tab));
27
27
  const isOfficialReserveTab = tab => /^(?:tab-)?(?:admin|backitup)(?:-\d+)?$/.test(normalize(tab));
28
- const isInstallerDenied = tab => /(?:tab-hosts|tab-files|tab-xterm|tab-xtrem|tab-system|hosts|files|console|terminal|security)/.test(normalize(tab)) || isOfficialReserveTab(tab);
28
+ const isInstallerDenied = tab => /(?:tab-users|tab-hosts|tab-files|tab-xterm|tab-xtrem|tab-system|users|hosts|files|console|terminal|security)/.test(normalize(tab)) || isOfficialReserveTab(tab);
29
29
  const isAllowed = (role, route) => route !== 'easy' && (role === 'admin'
30
30
  || (role === 'installer'
31
- ? route === 'tab-intro' || route === 'tab-users' || isCustomerBackupTab(route) || !isInstallerDenied(route)
32
- : route === 'tab-intro' || route === 'tab-enums' || isCustomerBackupTab(route) || isReleasedEndUserTab(route)));
31
+ ? route === 'tab-intro' || isCustomerBackupTab(route) || !isInstallerDenied(route)
32
+ : route === 'tab-intro' || route === 'tab-enums' || route === 'tab-objects' || isCustomerBackupTab(route) || isReleasedEndUserTab(route)));
33
33
  const defaultTab = () => 'tab-intro';
34
34
  const currentRoute = () => {
35
35
  const hash = decodeURIComponent(window.location.hash || '').toLowerCase();
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v7101-native-overview-standard-users';
4
+ const VERSION = 'v7105-admin-authoritative-rbac';
5
5
  window.NEXOWATT_EOS_ROLE_UI_VERSION = VERSION;
6
6
 
7
7
  const state = {
@@ -37,7 +37,7 @@
37
37
 
38
38
  const roleFromPolicy = policy => {
39
39
  const raw = normalize(policy?.role || policy?.eosRole || policy?.accessRole || '');
40
- if (policy?.isAdmin || policy?.isEosAdminGroup || policy?.isAdministrator || /^(?:admin|administrator)$/.test(raw)) return 'admin';
40
+ if (policy?.user === 'system.user.admin' || policy?.userId === 'system.user.admin' || policy?.isAdmin || policy?.isEosAdminGroup || policy?.isAdministrator || /^(?:admin|administrator)$/.test(raw)) return 'admin';
41
41
  if (/nexowatt service|eos service|service admin|service administrator/.test(raw)) return 'admin';
42
42
  if (policy?.isInstaller || /installateur|installer|installation|inbetriebnahme|techniker|technician|integrator|partner/.test(raw)) return 'installer';
43
43
  return 'enduser';
@@ -47,10 +47,10 @@
47
47
  const isOfficialReserveTab = tab => isOfficialAdminTab(tab) || isOfficialBackupTab(tab);
48
48
  const isCustomerBackupTab = tab => /^tab-(?:nexowatt-backup|eos-backup|nexowatt-sicherung)(?:-\d+)?$/.test(normalize(tab));
49
49
  const endUserAllowedByText = value => /(eos cockpit|nexowatt ui|nexowatt cockpit|kunden cockpit|kundenbereich|endkundenbereich|visualisierung|visualisation|visu|dashboard|energie cockpit|energy cockpit|bedienung|smart home|smart-home|lovelace|jarvis|iqontrol|material)/.test(normalize(value));
50
- const endUserDeniedByText = value => /(module|dienste|datenpunkte|objekte|systemlogs|logs|zugange|rechte|benutzer|users|gruppen|groups|system hosts|system-hosts|hosts|dateien|files|konsole|console|terminal|xterm|adapter|instances?|instanzen|geraetesuche|discovery|systemschutz|security)/.test(normalize(value));
50
+ const endUserDeniedByText = value => /(module|dienste|systemlogs|logs|zugange|rechte|benutzer|users|gruppen|groups|system hosts|system-hosts|hosts|dateien|files|konsole|console|terminal|xterm|adapter|instances?|instanzen|geraetesuche|discovery|systemschutz|security)/.test(normalize(value));
51
51
  const isEndUserTab = (tab, label = '') => {
52
52
  const clean = normalize(tab); const text = `${clean} ${normalize(label)}`;
53
- if (clean === 'tab-intro' || clean === 'tab-enums' || isCustomerBackupTab(clean)) return true;
53
+ if (clean === 'tab-intro' || clean === 'tab-enums' || clean === 'tab-objects' || isCustomerBackupTab(clean)) return true;
54
54
  if (/^tab-(nexowatt-ui|nexowatt-cockpit|eos-cockpit|eos-dashboard|kunden-cockpit|endkunden-cockpit|lovelace|jarvis|vis|iqontrol|material)(?:-|$)/.test(clean)) return true;
55
55
  return !!clean && !endUserDeniedByText(text) && endUserAllowedByText(text);
56
56
  };
@@ -58,14 +58,14 @@
58
58
  const clean = normalize(tab); const text = `${clean} ${normalize(label)}`;
59
59
  if (isOfficialReserveTab(clean)) return true;
60
60
  if (isCustomerBackupTab(clean)) return false;
61
- return /(?:tab-hosts|tab-files|tab-xterm|tab-xtrem|tab-system|system-hosts|system hosts|hosts|dateien|files|konsole|console|terminal|xterm|systemschutz|security)/.test(text);
61
+ return /(?:tab-users|tab-hosts|tab-files|tab-xterm|tab-xtrem|tab-system|users|benutzer|gruppen|groups|system-hosts|system hosts|hosts|dateien|files|konsole|console|terminal|xterm|systemschutz|security)/.test(text);
62
62
  };
63
63
  const isRouteAllowed = (role, route, label = '') => {
64
64
  if (route === 'easy') return false;
65
65
  if (role === 'admin') return true;
66
66
  if (isOfficialReserveTab(route)) return false;
67
- if (role === 'installer') return route === 'tab-intro' || route === 'tab-users' || !isInstallerDenied(route, label);
68
- if (role === 'enduser') return route === 'tab-users' || isEndUserTab(route, label);
67
+ if (role === 'installer') return route === 'tab-intro' || (route !== 'tab-users' && !isInstallerDenied(route, label));
68
+ if (role === 'enduser') return isEndUserTab(route, label);
69
69
  return false;
70
70
  };
71
71
  const defaultTab = () => 'tab-intro';
@@ -133,7 +133,7 @@
133
133
  return normalize(`${button?.getAttribute?.('aria-label') || ''} ${button?.getAttribute?.('title') || ''} ${testIds}`);
134
134
  };
135
135
  const isSystemSettingsButton = button => /buildicon|system settings|systemeinstellungen|systeeminstellingen/.test(toolbarButtonSignature(button));
136
- const isExpertModeButton = button => /expert|experten/.test(toolbarButtonSignature(button));
136
+ const isExpertModeButton = button => button?.dataset?.eosExpertControl === '1' || /expert|experten/.test(toolbarButtonSignature(button));
137
137
  const markAdminOnlyToolbarControls = () => safe(() => {
138
138
  const toolbar = document.querySelector('.eos-top-toolbar,.MuiAppBar-root .MuiToolbar-root');
139
139
  if (!toolbar) return;
@@ -226,7 +226,6 @@
226
226
  ['Datenpunkte', 'Messwerte prüfen und freigegebene Werte schreiben', 'tab-objects', 'datapoints'],
227
227
  ['Struktur', 'Räume und Funktionen für Smart Home zuordnen', 'tab-enums', 'structure'],
228
228
  ['Geräte', 'Geräteintegration und Inbetriebnahme öffnen', 'tab-devicemanager', 'devices'],
229
- ['Zugänge & Rechte', 'Passwörter im nativen Benutzereditor verwalten', 'tab-users', 'rights'],
230
229
  ['Basis-Einstellungen', 'Standort, Sprache und Anlagenparameter', 'basic-settings', 'settings'],
231
230
  ...(backupTab ? [['NexoWatt Sicherung', 'Sicherung und Wiederherstellung des EOS-Systems', backupTab, 'backup']] : []),
232
231
  ['EOS Cockpit', 'Energie-, Lade- und Gebäudesteuerung öffnen', uiTab, 'eos'],
@@ -234,6 +233,7 @@
234
233
  }
235
234
  return [
236
235
  ['EOS Cockpit', 'Energie, Laden und Gebäude bedienen', uiTab, 'eos'],
236
+ ['Datenpunkte', 'Messwerte und Zustände ausschließlich lesen', 'tab-objects', 'datapoints'],
237
237
  ['Smart Home', 'Räume, Funktionen und freigegebene Geräte zuordnen', 'tab-enums', 'structure'],
238
238
  ...(backupTab ? [['NexoWatt Sicherung', 'Eigene Sicherungen ausführen und wiederherstellen', backupTab, 'backup']] : []),
239
239
  ];
@@ -320,7 +320,7 @@
320
320
  'tr', '.MuiTableRow-root', '.MuiAccordion-root', '.MuiCard-root', '.MuiListItem-root',
321
321
  '[role="row"]', '[data-instance-row]', '[data-testid="adapter-card"]', '[data-testid="instance-card"]',
322
322
  ].join(',');
323
- const reserveId = /^(?:system\.adapter\.)?(?:admin|backitup)\.\d+$/i;
323
+ const reserveId = /^(?:system\.adapter\.)?(?:admin|backitup|xterm)\.\d+$/i;
324
324
  const normalizeInstance = value => String(value || '').trim().replace(/^system\.adapter\./i, '');
325
325
  const identityValues = node => {
326
326
  if (!node) return [];
@@ -337,7 +337,7 @@
337
337
  .replace(/nexowatt-backup\.\d+/gi, '')
338
338
  .replace(/eos-backup\.\d+/gi, '')
339
339
  .replace(/system\.adapter\.(?:eos-admin|nexowatt-backup|eos-backup)\.\d+/gi, '');
340
- return /(?:^|[\s|,;])(?:system\.adapter\.)?(?:admin|backitup)\.\d+(?=$|[\s|,;])/i.test(text.trim());
340
+ return /(?:^|[\s|,;])(?:system\.adapter\.)?(?:admin|backitup|xterm)\.\d+(?=$|[\s|,;])/i.test(text.trim());
341
341
  };
342
342
  const rowIsReserve = row => identityValues(row).some(value => reserveId.test(value)) || rowTextContainsReserve(row);
343
343
  const setHidden = (row, hidden) => {
@@ -363,12 +363,12 @@
363
363
 
364
364
  paper.querySelectorAll(rowSelector).forEach(row => setHidden(row, rowIsReserve(row)));
365
365
  paper.querySelectorAll([
366
- '[data-id^="system.adapter.admin"]','[data-id^="system.adapter.backitup"]',
367
- '[data-instance^="admin."]','[data-instance^="backitup."]',
368
- '[data-instance-id^="admin."]','[data-instance-id^="backitup."]',
369
- '[data-name="admin"]','[data-name="backitup"]',
370
- 'a[href*="#tab-admin"]','a[href*="#tab-backitup"]',
371
- 'a[href*="system.adapter.admin"]','a[href*="system.adapter.backitup"]',
366
+ '[data-id^="system.adapter.admin"]','[data-id^="system.adapter.backitup"]','[data-id^="system.adapter.xterm"]',
367
+ '[data-instance^="admin."]','[data-instance^="backitup."]','[data-instance^="xterm."]',
368
+ '[data-instance-id^="admin."]','[data-instance-id^="backitup."]','[data-instance-id^="xterm."]',
369
+ '[data-name="admin"]','[data-name="backitup"]','[data-name="xterm"]',
370
+ 'a[href*="#tab-admin"]','a[href*="#tab-backitup"]','a[href*="#tab-xterm"]',
371
+ 'a[href*="system.adapter.admin"]','a[href*="system.adapter.backitup"]','a[href*="system.adapter.xterm"]',
372
372
  ].join(',')).forEach(node => setHidden(rowFor(node) || node, true));
373
373
 
374
374
  // Admin 7 table cells may have no metadata. Match only an exact visible instance ID and
@@ -429,6 +429,111 @@
429
429
  }
430
430
  });
431
431
 
432
+
433
+ const privilegedFrameObservers = new WeakMap();
434
+ const privilegedActionPattern = /^(?:ems apps?(?: offnen| öffnen)?|app center|app-center|appcenter|simulation|simulator|lizenz|license)$/;
435
+ const isPrivilegedAction = element => {
436
+ const signature = normalize(textOf(element));
437
+ const href = normalize(element?.getAttribute?.('href') || element?.href || element?.getAttribute?.('data-href') || '');
438
+ return privilegedActionPattern.test(signature)
439
+ || /(?:^|[\/#?&_.-])(?:appcenter|app-center|simulation|simulator|license|lizenz)(?:$|[\/#?&=_.-])/.test(href);
440
+ };
441
+ const setPrivilegedActionHidden = (element, hidden) => {
442
+ if (!element) return;
443
+ const target = element.closest?.('a,button,[role="button"],[role="tab"],.MuiButtonBase-root') || element;
444
+ if (hidden) {
445
+ target.dataset.eosPrivilegedActionHidden = '1';
446
+ target.classList.add('eos-role-hidden');
447
+ target.setAttribute('aria-hidden', 'true');
448
+ if ('disabled' in target) target.disabled = true;
449
+ } else if (target.dataset.eosPrivilegedActionHidden === '1') {
450
+ delete target.dataset.eosPrivilegedActionHidden;
451
+ target.classList.remove('eos-role-hidden');
452
+ target.removeAttribute('aria-hidden');
453
+ if ('disabled' in target) target.disabled = false;
454
+ }
455
+ };
456
+ const applyPrivilegedActionsInDocument = doc => safe(() => {
457
+ if (!doc?.documentElement) return;
458
+ const admin = state.role === 'admin';
459
+ doc.querySelectorAll('[data-eos-privileged-action-hidden="1"]').forEach(element => {
460
+ if (admin || !isPrivilegedAction(element)) setPrivilegedActionHidden(element, false);
461
+ });
462
+ for (const element of doc.querySelectorAll('a,button,[role="button"],[role="tab"],.MuiButtonBase-root')) {
463
+ if (isPrivilegedAction(element)) setPrivilegedActionHidden(element, !admin);
464
+ }
465
+ if (!doc.documentElement.dataset.eosPrivilegedClickGuard) {
466
+ doc.documentElement.dataset.eosPrivilegedClickGuard = '1';
467
+ doc.addEventListener('click', event => {
468
+ if (state.role === 'admin' || state.role === 'unknown') return;
469
+ const target = event.target?.closest?.('a,button,[role="button"],[role="tab"],.MuiButtonBase-root');
470
+ if (!target || !isPrivilegedAction(target)) return;
471
+ event.preventDefault();
472
+ event.stopImmediatePropagation();
473
+ }, true);
474
+ }
475
+ if (!privilegedFrameObservers.has(doc)) {
476
+ const observer = new MutationObserver(() => {
477
+ window.setTimeout(() => applyPrivilegedActionsInDocument(doc), 0);
478
+ });
479
+ observer.observe(doc.documentElement, { childList: true, subtree: true });
480
+ privilegedFrameObservers.set(doc, observer);
481
+ }
482
+ });
483
+ const applyPrivilegedIframePolicy = () => safe(() => {
484
+ applyPrivilegedActionsInDocument(document);
485
+ document.querySelectorAll('iframe').forEach(frame => {
486
+ const applyFrame = () => safe(() => applyPrivilegedActionsInDocument(frame.contentDocument));
487
+ if (frame.dataset.eosRoleFrameGuard !== '1') {
488
+ frame.dataset.eosRoleFrameGuard = '1';
489
+ frame.addEventListener('load', applyFrame);
490
+ }
491
+ applyFrame();
492
+ });
493
+ });
494
+ const scrubInheritedUiAdminSession = () => safe(() => {
495
+ if (state.role === 'admin' || state.role === 'unknown') return;
496
+ const user = String(state.policy?.user || state.policy?.userId || '').replace(/^system\.user\./, '') || state.role;
497
+ const key = `eosUiAdminSessionScrubbed:v7105:${user}:${state.role}`;
498
+ if (sessionStorage.getItem(key) === '1') return;
499
+ sessionStorage.setItem(key, '1');
500
+ const frame = document.createElement('iframe');
501
+ frame.hidden = true;
502
+ frame.tabIndex = -1;
503
+ frame.setAttribute('aria-hidden', 'true');
504
+ frame.src = `${window.location.protocol}//${window.location.hostname}:8188/logout?source=eos-admin&role=${encodeURIComponent(state.role)}&t=${Date.now()}`;
505
+ frame.addEventListener('load', () => window.setTimeout(() => frame.remove(), 1200), { once: true });
506
+ document.body?.appendChild(frame);
507
+ window.setTimeout(() => frame.remove(), 7000);
508
+ });
509
+ const applyEndUserDatapointPolicy = () => safe(() => {
510
+ const paper = appPaper();
511
+ if (!paper) return;
512
+ const readonly = state.role === 'enduser' && currentRoute() === 'tab-objects';
513
+ paper.toggleAttribute('data-eos-datapoints-readonly', readonly);
514
+ if (!readonly) return;
515
+ paper.querySelectorAll('[data-eos-object-writable="1"],button[title*="edit" i],button[title*="delete" i],button[aria-label*="edit" i],button[aria-label*="delete" i]').forEach(control => {
516
+ control.setAttribute('aria-disabled', 'true');
517
+ control.setAttribute('data-eos-enduser-write-blocked', '1');
518
+ });
519
+ });
520
+ const clearLegacyHeuristicRestrictionsForAdmin = () => safe(() => {
521
+ if (state.role !== 'admin') return;
522
+ document.querySelectorAll('[data-eos-role-hidden]').forEach(element => {
523
+ element.removeAttribute('data-eos-role-hidden');
524
+ element.removeAttribute('aria-hidden');
525
+ element.style.removeProperty('display');
526
+ });
527
+ document.querySelectorAll('[data-eos-readonly],[data-eos-enduser-write-blocked]').forEach(element => {
528
+ element.removeAttribute('data-eos-readonly');
529
+ element.removeAttribute('data-eos-enduser-write-blocked');
530
+ element.removeAttribute('aria-disabled');
531
+ element.style.removeProperty('pointer-events');
532
+ element.style.removeProperty('opacity');
533
+ if ('disabled' in element) element.disabled = false;
534
+ });
535
+ });
536
+
432
537
  const apply = () => {
433
538
  if (!state.policy || state.role === 'unknown') return;
434
539
  setRoleClasses(state.role);
@@ -436,10 +541,14 @@
436
541
  applyMenuPolicy();
437
542
  markAdminOnlyToolbarControls();
438
543
  applySensitiveDialogPolicy();
544
+ clearLegacyHeuristicRestrictionsForAdmin();
439
545
  redirectIfNeeded();
440
546
  ensureSafeOverview();
441
547
  filterNativeUsersPage();
442
548
  hideOfficialReserveSurfaces();
549
+ applyEndUserDatapointPolicy();
550
+ applyPrivilegedIframePolicy();
551
+ scrubInheritedUiAdminSession();
443
552
  ensureReserveObserver();
444
553
  suppressPermissionErrors();
445
554
  window.NEXOWATT_EOS_ACCOUNT_MANAGEMENT?.refreshEntry?.();
@@ -491,6 +600,10 @@
491
600
  if (state.role === 'installer') window.NEXOWATT_EOS_BASIC_SETTINGS?.open?.();
492
601
  return;
493
602
  }
603
+ if (state.role === 'enduser' && currentRoute() === 'tab-objects') {
604
+ const writeTarget = event.target?.closest?.('[data-eos-object-writable="1"],[data-eos-enduser-write-blocked="1"],button[title*="edit" i],button[title*="delete" i],button[aria-label*="edit" i],button[aria-label*="delete" i]');
605
+ if (writeTarget) { event.preventDefault(); event.stopImmediatePropagation(); return; }
606
+ }
494
607
  const target = event.target?.closest?.('[data-eos-admin-only-control="1"],[data-eos-system-settings-control="1"],[data-eos-role-tab],a[href*="#tab-"],a[href*="#/tab-"]');
495
608
  if (!target) return;
496
609
  if (target.matches('[data-eos-admin-only-control="1"]')) { event.preventDefault(); event.stopImmediatePropagation(); return; }
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v7101-nexowatt-native-shell-native-overview';
4
+ const VERSION = 'v7105-nexowatt-native-shell-native-overview';
5
5
  const previous = window.NEXOWATT_NATIVE_SHELL;
6
6
  if (previous?.version === VERSION) return;
7
7
  previous?.destroy?.();
@@ -1,6 +1,6 @@
1
1
  (() => {
2
2
  'use strict';
3
- const VERSION = 'v7101-scroll-standard-password';
3
+ const VERSION = 'v7105-scroll-standard-password';
4
4
  const ensureScroll = () => {
5
5
  const root = document.documentElement;
6
6
  const paper = document.getElementById('app-paper');
package/build/main.js CHANGED
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+
2
3
  /**
3
4
  * Admin backend
4
5
  *
@@ -1267,14 +1268,9 @@ class Admin extends adapter_core_1.Adapter {
1267
1268
  return deny(socketClient, command, callback, 'technical diagnostics are installer/service-only');
1268
1269
  }
1269
1270
  if (command === 'changePassword') {
1270
- const targetRaw = String(args[0] || '').trim();
1271
- const targetUser = targetRaw.startsWith('system.user.') ? targetRaw : `system.user.${targetRaw}`;
1272
- const selfUser = String(userId || '');
1273
- const allowed = role === 'installer'
1274
- ? new Set([selfUser, 'system.user.installer', 'system.user.user', 'system.user.guest']).has(targetUser)
1275
- : targetUser === selfUser;
1276
- if (!allowed) return deny(socketClient, command, callback, 'password target outside EOS role scope');
1277
- return true;
1271
+ // Initial password activation has a separate, tightly scoped backend endpoint.
1272
+ // The normal Admin socket password editor remains Service/Admin-only.
1273
+ return deny(socketClient, command, callback, 'password administration is Service-only');
1278
1274
  }
1279
1275
  if (['addUser', 'delUser', 'addGroup', 'delGroup'].includes(command)) {
1280
1276
  return deny(socketClient, command, callback, 'account administration is Service-only');
@@ -1295,9 +1291,16 @@ class Admin extends adapter_core_1.Adapter {
1295
1291
  return deny(socketClient, command, callback, 'end users may only maintain Smart Home assignments');
1296
1292
  }
1297
1293
  }
1298
- if (['setState', 'delState', 'createState'].includes(command)
1299
- && /^(?:admin|backitup)\.\d+(?:\.|$)/.test(objectId)) {
1300
- return deny(socketClient, command, callback, 'internal Service reserve is Admin/Service-only');
1294
+ if (['setState', 'delState', 'createState'].includes(command)) {
1295
+ if (role === 'enduser') {
1296
+ return deny(socketClient, command, callback, 'end-user datapoints are read-only in EOS Admin');
1297
+ }
1298
+ if (objectId === `${this.namespace}.info.uiTabsVisible`) {
1299
+ return deny(socketClient, command, callback, 'navigation layout is Service-only');
1300
+ }
1301
+ if (/^(?:admin|backitup|xterm)\.\d+(?:\.|$)/.test(objectId)) {
1302
+ return deny(socketClient, command, callback, 'internal Service reserve is Admin/Service-only');
1303
+ }
1301
1304
  }
1302
1305
  return original(socketClient, command, callback, ...args);
1303
1306
  };
package/io-package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "eos-admin",
4
- "version": "7.10.3",
4
+ "version": "7.10.5",
5
5
  "titleLang": {
6
6
  "en": "NexoWatt EOS Admin",
7
7
  "de": "NexoWatt EOS Admin",
@@ -18,10 +18,15 @@
18
18
  "connectionType": "local",
19
19
  "dataSource": "push",
20
20
  "news": {
21
- "7.10.3": {
22
- "de": "Stable 7.10.3 verschiebt die Einstellung für automatische NexoWatt-Adapter-Updates vollständig in die Systemeinstellungen hinter dem Schraubenschlüssel. Auf Übersicht, Module und allen anderen Hauptseiten wird die Update-Karte nicht mehr eingeblendet. Die stabile Update-Logik und die Absturzkorrekturen aus 7.10.2 bleiben unverändert.",
23
- "en": "Stable 7.10.3 moves the automatic NexoWatt adapter update control entirely into System Settings behind the wrench button. The update card is no longer shown on Overview, Modules or any other main page. Stable update behavior and the 7.10.2 crash fixes remain unchanged.",
24
- "nl": "Stable 7.10.3 verplaatst de instelling voor automatische NexoWatt-adapterupdates volledig naar de systeeminstellingen achter de moersleutelknop. De updatekaart wordt niet meer weergegeven op Overzicht, Modules of andere hoofdpagina’s. De stabiele updatelogica en crashfixes uit 7.10.2 blijven ongewijzigd."
21
+ "7.10.5": {
22
+ "de": "Stable 7.10.5 stellt die vollständigen Administratorrechte wieder her und trennt die Rollen verbindlich über den authentifizierten Backend-Kontext. Admin darf Datenpunkte schreiben, den Expertenmodus in beide Richtungen schalten und sieht Admin-/XTerm-Kacheln. Endkunden sehen Datenpunkte ausschließlich lesend; App-Center, Lizenz, Simulation, Benutzerverwaltung, ioBroker Admin, XTerm und Expertenmodus bleiben für Installateur und Endkunde gesperrt. Navigationsänderungen werden neustartsicher im Adapter-State gespeichert; Sicherung erscheint nur bei aktiver Backup-Instanz.",
23
+ "en": "Stable 7.10.5 restores full administrator authority and binds roles exclusively to the authenticated backend context. Admin can write datapoints, toggle expert mode in both directions and sees the Admin/XTerm cards. End users can view datapoints read-only; App Center, License, Simulation, account management, ioBroker Admin, XTerm and expert mode remain blocked for Installer and End User. Navigation changes are stored restart-safely in an adapter state, and Backup is shown only for an enabled backup instance.",
24
+ "nl": "Stable 7.10.5 herstelt de volledige beheerdersrechten en bepaalt rollen uitsluitend via de geauthenticeerde backendcontext. Admin kan datapunten schrijven, de expertmodus in beide richtingen schakelen en ziet de Admin-/XTerm-kaarten. Eindgebruikers kunnen datapunten alleen lezen; App Center, Licentie, Simulatie, accountbeheer, ioBroker Admin, XTerm en expertmodus blijven geblokkeerd voor Installateur en Eindgebruiker. Navigatiewijzigingen worden herstartveilig in een adapterstatus opgeslagen en Back-up verschijnt alleen bij een ingeschakelde back-upinstantie."
25
+ },
26
+ "7.10.4": {
27
+ "de": "Stable 7.10.4 verschiebt die Einstellung für automatische NexoWatt-Adapter-Updates vollständig in die Systemeinstellungen hinter dem Schraubenschlüssel. Auf Übersicht, Module und allen anderen Hauptseiten wird die Update-Karte nicht mehr eingeblendet. Die stabile Update-Logik und die Absturzkorrekturen aus 7.10.2 bleiben unverändert.",
28
+ "en": "Stable 7.10.4 moves the automatic NexoWatt adapter update control entirely into System Settings behind the wrench button. The update card is no longer shown on Overview, Modules or any other main page. Stable update behavior and the 7.10.2 crash fixes remain unchanged.",
29
+ "nl": "Stable 7.10.4 verplaatst de instelling voor automatische NexoWatt-adapterupdates volledig naar de systeeminstellingen achter de moersleutelknop. De updatekaart wordt niet meer weergegeven op Overzicht, Modules of andere hoofdpagina’s. De stabiele updatelogica en crashfixes uit 7.10.2 blijven ongewijzigd."
25
30
  },
26
31
  "7.10.2": {
27
32
  "de": "Stable-Hotfix 7.10.2 beseitigt die EOS-Admin-Startschleife: Stable-Update-Status wird nicht mehr in die laufende Instanzkonfiguration geschrieben, der erste Abgleich startet verzögert und das Release prüft die vollständige Backend-Modulkette einschließlich eosRequestSecurity.",
@@ -349,7 +354,7 @@
349
354
  "icon": "admin.svg",
350
355
  "messagebox": true,
351
356
  "enabled": true,
352
- "extIcon": "https://unpkg.com/iobroker.eos-admin@7.10.3/admin/admin.svg",
357
+ "extIcon": "https://unpkg.com/iobroker.eos-admin@7.10.5/admin/admin.svg",
353
358
  "keywords": [
354
359
  "NexoWatt",
355
360
  "EOS",
@@ -360,7 +365,7 @@
360
365
  "licensed"
361
366
  ],
362
367
  "compact": true,
363
- "readme": "https://unpkg.com/iobroker.eos-admin@7.10.3/README.md",
368
+ "readme": "https://unpkg.com/iobroker.eos-admin@7.10.5/README.md",
364
369
  "authors": [
365
370
  "NexoWatt"
366
371
  ],
@@ -428,7 +433,7 @@
428
433
  "nondeletable": false,
429
434
  "allowAdapterUpdate": true,
430
435
  "allowAdapterDelete": false,
431
- "meta": "https://unpkg.com/iobroker.eos-admin@7.10.3/io-package.json",
436
+ "meta": "https://unpkg.com/iobroker.eos-admin@7.10.5/io-package.json",
432
437
  "npmPackage": "iobroker.eos-admin",
433
438
  "packetName": "iobroker.eos-admin",
434
439
  "publisher": "NexoWatt"
@@ -1001,6 +1006,23 @@
1001
1006
  "def": ""
1002
1007
  },
1003
1008
  "native": {}
1009
+ },
1010
+ {
1011
+ "_id": "info.uiTabsVisible",
1012
+ "type": "state",
1013
+ "common": {
1014
+ "name": {
1015
+ "en": "EOS navigation layout",
1016
+ "de": "EOS-Navigationslayout",
1017
+ "nl": "EOS-navigatielay-out"
1018
+ },
1019
+ "role": "json",
1020
+ "type": "string",
1021
+ "read": true,
1022
+ "write": true,
1023
+ "def": "[]"
1024
+ },
1025
+ "native": {}
1004
1026
  }
1005
1027
  ],
1006
1028
  "schemas": {
@@ -1188,5 +1210,5 @@
1188
1210
  }
1189
1211
  }
1190
1212
  },
1191
- "version": "7.10.3"
1213
+ "version": "7.10.5"
1192
1214
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iobroker.eos-admin",
3
3
  "description": "NexoWatt EOS Admin – repaired stable baseline with restart-safe automatic updates, native account passwords and persistent cockpit scrolling.",
4
- "version": "7.10.3",
4
+ "version": "7.10.5",
5
5
  "contributors": [
6
6
  "bluefox <dogafox@gmail.com>",
7
7
  "apollon77",
@@ -133,13 +133,15 @@
133
133
  "NEXOWATT_EOS_PREBUILT_MANIFEST.json",
134
134
  "tools/nexowatt-prebuilt-release-selftest.cjs",
135
135
  "tools/nexowatt-stable-v7103-selftest.cjs",
136
- "README_STABILITY_V7.10.3.md",
137
- "RELEASE_NOTES_V7.10.3.md",
138
- "PUBLISH_STABLE_V7.10.3.md",
139
- "INSTALL_TEST_V7.10.3.md",
140
- "RELEASE_ACCEPTANCE_V7.10.3.md",
141
- "MERGE_UPDATE_README_V7.10.3.md",
142
- "tools/nexowatt-auto-update-placement-selftest.cjs"
136
+ "README_STABILITY_V7.10.5.md",
137
+ "RELEASE_NOTES_V7.10.5.md",
138
+ "PUBLISH_STABLE_V7.10.5.md",
139
+ "INSTALL_TEST_V7.10.5.md",
140
+ "RELEASE_ACCEPTANCE_V7.10.5.md",
141
+ "MERGE_UPDATE_README_V7.10.5.md",
142
+ "tools/nexowatt-auto-update-placement-selftest.cjs",
143
+ "tools/nexowatt-role-security-selftest.cjs",
144
+ "SECURITY_ROLE_MATRIX_V7.10.5.md"
143
145
  ],
144
146
  "scripts": {
145
147
  "npm": "npm i -f && cd src-admin && npm i -f",
@@ -174,7 +176,7 @@
174
176
  "nexowatt:repo-entry": "node tools/nexowatt-generate-repo-entry.cjs",
175
177
  "nexowatt:patch-repo": "node tools/nexowatt-patch-repo.cjs",
176
178
  "nexowatt:repair-update": "node tools/nexowatt-repair-eos-admin-update.cjs",
177
- "check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-backend-runtime-selftest.cjs && node tools/nexowatt-prebuilt-release-selftest.cjs && node tools/nexowatt-import-integrity-selftest.cjs && node tools/nexowatt-entrypoint-smoke-selftest.cjs && node tools/nexowatt-frontend-graph-check.cjs && node tools/nexowatt-policy-selftest.cjs && node tools/nexowatt-publish-channel-selftest.cjs && node tools/nexowatt-release-defaults-selftest.cjs && node tools/nexowatt-role-access-selftest.cjs && node tools/nexowatt-internal-reserve-selftest.cjs && node tools/nexowatt-modern-ui-selftest.cjs && node tools/nexowatt-login-layout-selftest.cjs && node tools/nexowatt-branding-selftest.cjs && node tools/nexowatt-default-port-selftest.cjs && node tools/nexowatt-module-popup-selftest.cjs && node tools/nexowatt-datapoint-selftest.cjs && node tools/nexowatt-update-selftest.cjs && node tools/nexowatt-datapoint-interaction-selftest.cjs && node tools/nexowatt-manual-write-policy-selftest.cjs && node tools/nexowatt-value-column-selftest.cjs && node tools/nexowatt-native-shell-selftest.cjs && node tools/nexowatt-assistant-separation-selftest.cjs && node tools/nexowatt-runtime-cleanup-selftest.cjs && node tools/nexowatt-version-sync-selftest.cjs && node tools/nexowatt-merge-update-selftest.cjs && node tools/nexowatt-password-write-selftest.cjs && node tools/nexowatt-account-password-flow-selftest.cjs && npm run check:eos-stable-v7103 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs && node tools/nexowatt-auto-update-selftest.cjs && node tools/nexowatt-auto-update-placement-selftest.cjs",
179
+ "check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-backend-runtime-selftest.cjs && node tools/nexowatt-prebuilt-release-selftest.cjs && node tools/nexowatt-import-integrity-selftest.cjs && node tools/nexowatt-entrypoint-smoke-selftest.cjs && node tools/nexowatt-frontend-graph-check.cjs && node tools/nexowatt-policy-selftest.cjs && node tools/nexowatt-publish-channel-selftest.cjs && node tools/nexowatt-release-defaults-selftest.cjs && node tools/nexowatt-role-access-selftest.cjs && node tools/nexowatt-internal-reserve-selftest.cjs && node tools/nexowatt-modern-ui-selftest.cjs && node tools/nexowatt-login-layout-selftest.cjs && node tools/nexowatt-branding-selftest.cjs && node tools/nexowatt-default-port-selftest.cjs && node tools/nexowatt-module-popup-selftest.cjs && node tools/nexowatt-datapoint-selftest.cjs && node tools/nexowatt-update-selftest.cjs && node tools/nexowatt-datapoint-interaction-selftest.cjs && node tools/nexowatt-manual-write-policy-selftest.cjs && node tools/nexowatt-value-column-selftest.cjs && node tools/nexowatt-native-shell-selftest.cjs && node tools/nexowatt-assistant-separation-selftest.cjs && node tools/nexowatt-runtime-cleanup-selftest.cjs && node tools/nexowatt-version-sync-selftest.cjs && node tools/nexowatt-merge-update-selftest.cjs && node tools/nexowatt-password-write-selftest.cjs && node tools/nexowatt-account-password-flow-selftest.cjs && npm run check:eos-stable-v7103 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs && node tools/nexowatt-auto-update-selftest.cjs && node tools/nexowatt-auto-update-placement-selftest.cjs && node tools/nexowatt-role-security-selftest.cjs",
178
180
  "nexowatt:patch-built-frontend": "node tools/nexowatt-patch-built-frontend.cjs",
179
181
  "clean:eos-runtime": "node tools/nexowatt-clean-legacy-runtime.cjs",
180
182
  "precheck:eos-package": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
@@ -202,7 +204,8 @@
202
204
  "check:eos-prebuilt-release": "node tools/nexowatt-prebuilt-release-selftest.cjs",
203
205
  "seal:eos-prebuilt-release": "node tools/nexowatt-prebuilt-release-selftest.cjs --write",
204
206
  "check:eos-stable-v7103": "node tools/nexowatt-stable-v7103-selftest.cjs",
205
- "test:eos-auto-update-placement": "node tools/nexowatt-auto-update-placement-selftest.cjs"
207
+ "test:eos-auto-update-placement": "node tools/nexowatt-auto-update-placement-selftest.cjs",
208
+ "check:eos-role-security": "node tools/nexowatt-role-security-selftest.cjs"
206
209
  },
207
210
  "author": "NexoWatt",
208
211
  "private": false,
@@ -213,7 +216,7 @@
213
216
  },
214
217
  "nexowattReleasePolicy": {
215
218
  "distTag": "latest",
216
- "purpose": "accepted stable NexoWatt EOS sales-product candidate 7.10.3"
219
+ "purpose": "accepted stable NexoWatt EOS admin-rights and RBAC correction 7.10.5"
217
220
  },
218
221
  "publisher": "NexoWatt"
219
222
  }
@@ -6,10 +6,10 @@ for(const code of [source,built]){
6
6
  must(code.includes("EOS_STABLE_INITIAL_PASSWORD = 'nexowatt'"),'initial password constant missing');
7
7
  must(code.includes("if (created || !String(object.common?.password || '').trim())")||code.includes("if (created || !String(object.common?.password || '').trim())"),'password is not limited to new/passwordless accounts');
8
8
  for(const marker of ['nexowattPasswordChangeRequired: false','eosPasswordChangeRequired: false','nexowattFirstLoginPending: false','eosFirstLoginRequired: false','forcePasswordChange: false','standardPasswordMode: true']) must(code.includes(marker),`legacy first-login flag is not cleared: ${marker}`);
9
- must(code.includes("command === 'changePassword'")&&code.includes('password target outside EOS role scope'),'native password command role guard missing');
9
+ must(code.includes("command === 'changePassword'")&&code.includes('password administration is Service-only'),'native password command role guard missing');
10
10
  }
11
11
  must(io.native.eosRequireFirstLoginPassword===false&&io.native.eosRequireFirstLoginPasswordChange===false,'forced first-password defaults remain');
12
12
  must(!boot.includes('installIntegratedFirstLogin(base);')&&!boot.includes('showFirstLoginPassword(resolved, base);'),'forced password surface remains active');
13
13
  must(!index.includes('eos-account-management.js'),'custom reset runtime remains active');
14
- must(role.includes('filterNativeUsersPage')&&role.includes("route === 'tab-users'"),'role-scoped native Users page missing');
15
- console.log('[NexoWatt EOS standard password flow] OK (nexowatt only on creation, native password editor, no forced reset flow)');
14
+ must(role.includes("route !== 'tab-users'")&&role.includes('filterNativeUsersPage'),'native Users page is not blocked for Installer/End User');
15
+ console.log('[NexoWatt EOS standard password flow] OK (initial password on creation, Admin-only native password editor, no forced reset flow)');
@@ -23,6 +23,12 @@ const LEGACY_OVERLAYS = [
23
23
  'src-admin/public/js/eos-runtime-fixes.js',
24
24
  'src-admin/public/js/eos-hard-logout.js',
25
25
  'src-admin/public/css/eos-branding.css',
26
+ // Removed 7.10.4 role-security overlays. These files must be deleted when
27
+ // a complete repository ZIP is accidentally extracted over an older tree;
28
+ // otherwise the sealed-release check correctly rejects them as stale input.
29
+ 'adminWww/nexowatt-role-security.js',
30
+ 'src-admin/nexowatt-role-security.js',
31
+ 'build/lib/eosRoleSecurity.js',
26
32
  ];
27
33
 
28
34
  const readJson = file => JSON.parse(fs.readFileSync(file, 'utf8'));