iobroker.eos-admin 7.9.92 → 7.9.95

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 (33) hide show
  1. package/{INSTALL_TEST_V7.9.92.md → INSTALL_TEST_V7.9.95.md} +2 -5
  2. package/NEXOWATT_EOS_BUILD_INFO.json +18 -14
  3. package/NEXOWATT_EOS_STABLE_7.9.94.md +16 -0
  4. package/PUBLISH_STABLE_V7.9.95.md +16 -0
  5. package/README_STABILITY_V7.9.95.md +12 -0
  6. package/RELEASE_ACCEPTANCE_V7.9.95.md +11 -0
  7. package/RELEASE_NOTES_V7.9.95.md +12 -0
  8. package/adminWww/css/nexowatt-native-shell.css +2 -2
  9. package/adminWww/index.html +2 -0
  10. package/adminWww/js/eos-assistant.js +5 -124
  11. package/adminWww/static/js/nexowatt-stable-login.js +79 -0
  12. package/build/lib/nexowattStableAuth.js +74 -0
  13. package/build/lib/web.js +24 -7
  14. package/build/main.js +122 -1
  15. package/io-package.json +24 -15
  16. package/package.json +22 -31
  17. package/PUBLISH_STABLE_V7.9.92.md +0 -19
  18. package/README_STABILITY_V7.9.92.md +0 -14
  19. package/RELEASE_ACCEPTANCE_V7.9.92.md +0 -12
  20. package/RELEASE_NOTES_V7.9.92.md +0 -17
  21. package/tools/nexowatt-account-management-selftest.cjs +0 -61
  22. package/tools/nexowatt-clean-legacy-runtime.cjs +0 -147
  23. package/tools/nexowatt-ensure-release-defaults.cjs +0 -72
  24. package/tools/nexowatt-generate-eos-admin-repo-entry.cjs +0 -37
  25. package/tools/nexowatt-generate-repo-entry.cjs +0 -3
  26. package/tools/nexowatt-internal-reserve-selftest.cjs +0 -55
  27. package/tools/nexowatt-login-layout-selftest.cjs +0 -81
  28. package/tools/nexowatt-modern-ui-selftest.cjs +0 -50
  29. package/tools/nexowatt-patch-repo.cjs +0 -54
  30. package/tools/nexowatt-publish-channel-guard.cjs +0 -58
  31. package/tools/nexowatt-release-defaults-selftest.cjs +0 -29
  32. package/tools/nexowatt-runtime-cleanup-selftest.cjs +0 -47
  33. package/tools/nexowatt-validate-package.cjs +0 -246
@@ -1,10 +1,9 @@
1
- # Installation und Test 7.9.92
1
+ # Installationstest 7.9.95
2
2
 
3
3
  ```bash
4
4
  cd /opt/iobroker
5
- iobroker backup
6
5
  iobroker stop eos-admin.0
7
- npm install --no-save --omit=dev iobroker.eos-admin@latest
6
+ npm install --no-save --omit=dev iobroker.eos-admin@7.9.95
8
7
  iobroker upload eos-admin
9
8
  iobroker restart eos-admin.0
10
9
  ```
@@ -14,5 +13,3 @@ Version prüfen:
14
13
  ```bash
15
14
  node -p "require('/opt/iobroker/node_modules/iobroker.eos-admin/package.json').version"
16
15
  ```
17
-
18
- Erwartet: `7.9.92`.
@@ -1,27 +1,31 @@
1
1
  {
2
- "version": "7.9.92",
2
+ "version": "7.9.95",
3
3
  "channel": "stable-latest",
4
4
  "baseFrontend": "validated Admin 7 runtime with NexoWatt EOS compact login, role, overview and service-reserve hardening",
5
5
  "runtimeEntry": "v84",
6
- "releaseType": "stable-sales-product-release-default-normalization",
7
- "release": "stable-sales-product-release-default-normalization",
8
- "label": "v92",
6
+ "releaseType": "stable-merge-safe-packaging",
7
+ "release": "stable-merge-safe-packaging",
8
+ "label": "v95",
9
9
  "notes": [
10
- "7.9.92 is the accepted stable sales-product release and is intended for npm dist-tag latest.",
11
- "Mandatory sales defaults are normalized before package validation and npm publication.",
12
- "Internal admin and BackItUp emergency reserves default to Admin/Service-only visibility.",
13
- "Authentication remains enabled and the EOS Admin default port remains 8081.",
14
- "The compact login, first activation, clickable overview and role-safe services from 7.9.91 are unchanged."
10
+ "7.9.95 is the merge-safe NexoWatt EOS Admin release for npm dist-tag latest.",
11
+ "The flat update ZIP can be extracted directly over an existing repository folder.",
12
+ "All version-bearing release files are synchronized automatically before validation and npm publishing.",
13
+ "The 7.9.94 login, roles, password bootstrap, port 8081 and disabled EOS Assist remain functionally unchanged."
15
14
  ],
16
15
  "changes": [
17
- "Adds a deterministic release-default normalizer that repairs missing canonical native defaults.",
18
- "Runs normalization before package checks, stability checks and npm publication.",
19
- "Adds an isolated self-test proving that missing or unsafe defaults are corrected.",
20
- "Keeps frontend runtime and cache assets unchanged because this release only hardens packaging."
16
+ "Adds deterministic synchronization for package, io-package, lockfiles, frontend version, build info and repository entry.",
17
+ "Adds guarded Windows merge verification scripts without deleting .git or node_modules.",
18
+ "Adds a regression test proving that a stale 7.9.92 io-package is repaired before publication.",
19
+ "Adds a flat merge ZIP intended for direct extraction into the existing working folder.",
20
+ "Cleans the npm files manifest and rejects missing distribution entries before publication.",
21
+ "Aligns account-management and modern-UI self-tests with the documented stable initial-password and disabled-assistant contracts.",
22
+ "Synchronizes package-lock root scripts and release policy with package.json and validates lock metadata drift.",
23
+ "Builds a genuinely flat merge ZIP with repository files at archive root.",
24
+ "Keeps source-only validation helpers out of the installed npm runtime while retaining the supported update-repair helper."
21
25
  ],
22
26
  "brandingCacheVersion": 91,
23
27
  "shellCacheVersion": 91,
24
28
  "nativeShellVersion": 91,
25
29
  "shellCacheTag": "91",
26
- "repositoryEntry": "nexowatt-eos-admin-repository-entry-v92.json"
30
+ "repositoryEntry": "nexowatt-eos-admin-repository-entry-v95.json"
27
31
  }
@@ -0,0 +1,16 @@
1
+ # NexoWatt EOS Admin 7.9.94 – Stable candidate
2
+
3
+ ## Stable scope
4
+
5
+ - Standard port 8081 and authentication enabled.
6
+ - Initial commissioning credentials for `installer`, `guest`, and `user`: password `nexowatt`.
7
+ - A personal password is mandatory immediately after the first successful login.
8
+ - The old passwordless activation flow is disabled.
9
+ - Admin/Service credentials are never changed by the bootstrap.
10
+ - EOS Assist is disabled in this stable release and will return only after its separate read-only redesign and acceptance.
11
+ - The login card remains fully usable when validation or authentication errors are shown.
12
+ - Customer-visible publisher: NexoWatt; technical npm identity remains `iobroker.eos-admin` for update compatibility.
13
+
14
+ ## Security note
15
+
16
+ The shared commissioning password is only an initial credential. Commissioning is not complete until installer and customer accounts have replaced it with personal passwords. Shipping a system with the initial credential still active is not permitted.
@@ -0,0 +1,16 @@
1
+ # npm-Veröffentlichung 7.9.95
2
+
3
+ Nach dem Entpacken der Merge-ZIP zuerst `MERGE_UPDATE.cmd` ausführen. Danach:
4
+
5
+ ```powershell
6
+ npm login
7
+ npm whoami
8
+ npm publish --dry-run --tag latest
9
+ npm publish --tag latest
10
+ ```
11
+
12
+ Kontrolle:
13
+
14
+ ```powershell
15
+ npm view iobroker.eos-admin@latest version
16
+ ```
@@ -0,0 +1,12 @@
1
+ # NexoWatt EOS Admin 7.9.95 – Stabilität
2
+
3
+ Version 7.9.95 ist ein packaging-only Stable-Update. Die produktive Laufzeit, Rollenlogik, Erstanmeldung mit dem Startpasswort `nexowatt`, Standardport 8081 und die Deaktivierung des EOS Assist bleiben gegenüber 7.9.94 unverändert.
4
+
5
+ Neu ist die mergesichere Release-Vorbereitung: Alle versionsführenden Dateien werden vor Prüfung, Packen und npm-Veröffentlichung automatisch an `package.json` angeglichen.
6
+
7
+ Die npm-Dateiliste wird nun zusätzlich gegen das reale Dateisystem geprüft. Nicht vorhandene oder nicht gepackte Pflichtdateien brechen die Veröffentlichung vor dem Upload ab.
8
+
9
+
10
+ Zusätzlich werden die Rootmetadaten der Lockdateien gegen die zugehörigen `package.json`-Dateien geprüft. Dadurch können veraltete `prepublishOnly`-, `prepack`- oder Stabilitätsskripte nicht mehr unbemerkt in einer gemischten Arbeitskopie verbleiben.
11
+
12
+ Die Merge-Datei für bestehende Arbeitsordner wird ohne übergeordneten Versionsordner erzeugt. `package.json`, `io-package.json` und `MERGE_UPDATE.cmd` liegen unmittelbar auf der ersten ZIP-Ebene.
@@ -0,0 +1,11 @@
1
+ # Release-Abnahme 7.9.95
2
+
3
+ - [ ] Merge-ZIP direkt über einen alten Arbeitsordner entpackt
4
+ - [ ] `MERGE_UPDATE.cmd` vollständig grün
5
+ - [ ] Alle Versionsdateien melden 7.9.95
6
+ - [ ] `npm publish --dry-run --tag latest` erfolgreich
7
+ - [ ] Installation über npm erfolgreich
8
+ - [ ] Admin-Login unverändert
9
+ - [ ] Installer/User-Erstlogin mit Startpasswort `nexowatt` geprüft
10
+ - [ ] EOS Assist in Stable deaktiviert
11
+ - [ ] EOS Admin auf Port 8081 erreichbar
@@ -0,0 +1,12 @@
1
+ # Release Notes – 7.9.95
2
+
3
+ - Flache Merge-ZIP für das direkte Entpacken in einen vorhandenen Repository-Ordner.
4
+ - Automatischer Versionsabgleich für `io-package.json`, Lockdateien, Frontend-Version, Build-Info und Repository-Eintrag.
5
+ - Selbsttest mit absichtlich veraltetem Stand 7.9.92.
6
+ - Windows-Helfer `MERGE_UPDATE.cmd` und `MERGE_UPDATE.ps1`.
7
+ - Keine funktionale Änderung an Login, Rollen, Port oder EOS-Assist-Sperre.
8
+ - npm-Dateiliste bereinigt: keine nicht vorhandenen 7.9.94-Dateien mehr; aktuelle Stable-, Merge- und Prüfscripte werden vollständig gepackt.
9
+ - Selbsttests an den dokumentierten Stable-Vertrag angeglichen: Startpasswort `nexowatt` mit Pflichtwechsel; EOS Assist bleibt deaktiviert.
10
+ - `package-lock.json`-Rootmetadaten (Skripte und Release-Policy) werden gemeinsam mit `package.json` synchronisiert und validiert.
11
+ - Die veröffentlichte npm-Tarball-Dateiliste enthält nur Runtime-Dateien, aktuelle Release-Dokumente und den installierten Update-Reparaturhelfer; Source-only-Prüfwerkzeuge bleiben in der Merge-/Repository-ZIP.
12
+ - Die ausgelieferte Merge-ZIP wird tatsächlich flach erzeugt, damit `package.json` direkt im bestehenden Repository-Ordner landet.
@@ -856,7 +856,7 @@ html.eos-modern-login body::before {
856
856
 
857
857
 
858
858
  /* ==========================================================================
859
- NexoWatt EOS 7.9.92 – stable product layout
859
+ NexoWatt EOS 7.9.94 – stable product layout
860
860
  Integrated first login, header assistant, role-safe overview and account
861
861
  management exclusively below "Zugänge & Rechte".
862
862
  ========================================================================== */
@@ -1193,7 +1193,7 @@ html.eos-role-enduser [data-instance-id^="backitup."] { display: none !important
1193
1193
 
1194
1194
 
1195
1195
 
1196
- /* Final 7.9.92 interaction guards. */
1196
+ /* Final 7.9.94 interaction guards. */
1197
1197
  html.eos-safe-overview-active #app-paper { position: relative !important; }
1198
1198
  html.eos-safe-overview-active #app-paper > :not(#eos-role-safe-overview):not(.MuiSnackbar-root):not([role="presentation"]) {
1199
1199
  pointer-events: none !important;
@@ -171,5 +171,7 @@
171
171
  <body>
172
172
  <noscript>Für NexoWatt EOS muss JavaScript aktiviert sein.</noscript>
173
173
  <div id="root"></div>
174
+ <script defer src="static/js/nexowatt-stable-login.js?v=95"></script>
175
+ <script defer src="static/js/nexowatt-stable-v95.js?v=95"></script>
174
176
  </body>
175
177
  </html>
@@ -1,127 +1,8 @@
1
1
  (() => {
2
2
  'use strict';
3
-
4
- const VERSION = 'v91-header-eos-assist';
5
- const previous = window.NEXOWATT_EOS_ASSIST;
6
- if (previous?.version === VERSION) return;
7
- previous?.destroy?.();
8
-
9
- const scriptUrl = document.currentScript?.src || document.querySelector('script[src*="eos-assistant.js"]')?.src || location.href;
10
- const logoUrl = new URL('../img/eos/nexowatt-192.png', scriptUrl).href;
11
- const abort = new AbortController();
12
- let unsubscribeDom = null;
13
- let timer = 0;
14
- let destroyed = false;
15
-
16
- const currentContext = () => {
17
- const hash = String(location.hash || '').toLowerCase();
18
- if (hash.includes('tab-adapters')) return ['Module', 'Module suchen, Version prüfen, Instanz konfigurieren und Datenpunkte kontrollieren.'];
19
- if (hash.includes('tab-instances')) return ['Dienste', 'Status, Log und Abhängigkeiten prüfen, bevor ein Dienst neu gestartet wird.'];
20
- if (hash.includes('tab-objects')) return ['Datenpunkte', 'Read-DPs bleiben lesend. Write-DPs lassen sich typgerecht bedienen; sicherheitsrelevante Writes benötigen Admin/Service.'];
21
- if (hash.includes('tab-users')) return ['Zugänge & Rechte', 'Zugänge einer EOS-Rolle zuordnen und persönliche Passwörter sicher zurücksetzen.'];
22
- if (hash.includes('tab-intro')) return ['Übersicht', 'Anlagenstatus, Module, Dienste und die für deine Rolle freigegebenen Bereiche im Blick behalten.'];
23
- return ['EOS Assist', 'NexoWatt EOS unterstützt dich beim Einrichten, Prüfen und Absichern des Systems.'];
24
- };
25
-
26
- const answer = query => {
27
- const text = String(query || '').toLowerCase();
28
- if (/datenpunkt|write|schreib|wert|state/.test(text)) return 'Prüfe Schreibfreigabe, Datentyp und EOS-Rolle. Boolean und Trigger werden direkt bedient; Zahlen, Texte und Auswahlwerte öffnen den nativen Wertdialog.';
29
- if (/update|aktualis/.test(text)) return 'Warte, bis Module und Verbindung vollständig geladen sind. Danach das Update starten und den Befehlsdialog bis zum Abschluss geöffnet lassen.';
30
- if (/wallbox|ocpp|lade/.test(text)) return 'Für Ladepunkte zuerst Status und Frische der OCPP-Daten prüfen. Schreibbefehle nur über die vorgesehenen Steuerdatenpunkte senden.';
31
- if (/rechte|benutzer|rolle|passwort/.test(text)) return 'Admin/Service besitzt Vollzugriff. Installateur erhält Inbetriebnahme und Fehlersuche sowie Endkunden-Reset. Endkunde erhält freigegebene Bedien- und Smart-Home-Funktionen.';
32
- if (/backup|sicherung/.test(text)) return 'Für den normalen Betrieb die NexoWatt Sicherung verwenden. Die interne System-Notfallsicherung ist ausschließlich für Admin/Service sichtbar.';
33
- return 'Beschreibe Gerät, Datenpunkt oder Fehlerbild genauer. EOS Assist grenzt danach Oberfläche, Modul, Protokoll oder Gerätekommunikation ein.';
34
- };
35
-
36
- const findToolbar = () => document.querySelector('.eos-top-toolbar, #root > .MuiPaper-root > .MuiAppBar-root .MuiToolbar-root, .MuiAppBar-root .MuiToolbar-root');
37
- const findUserAnchor = toolbar => Array.from(toolbar?.children || []).find(child => child.querySelector?.('.MuiAvatar-root') || /\b(admin|installer|guest|endkunde|service)\b/i.test(child.textContent || '')) || null;
38
-
39
- const remove = () => document.getElementById('eos-assist-root')?.remove();
40
- const ensure = () => {
41
- if (destroyed) return;
42
- const hasApp = !!document.getElementById('app-paper');
43
- const login = document.documentElement.classList.contains('eos-login');
44
- const toolbar = findToolbar();
45
- if (!hasApp || login || !toolbar) {
46
- remove();
47
- return;
48
- }
49
- toolbar.classList.add('eos-top-toolbar');
50
- let root = document.getElementById('eos-assist-root');
51
- if (!root) {
52
- root = document.createElement('section');
53
- root.id = 'eos-assist-root';
54
- root.className = 'eos-assist-root eos-assist-header-root';
55
- root.innerHTML = `
56
- <button class="eos-assist-button" type="button" aria-expanded="false" title="EOS Assist">
57
- <span class="eos-assist-dot"></span><strong>EOS Assist</strong><small>Hilfe</small>
58
- </button>
59
- <div class="eos-assist-panel" role="dialog" aria-label="EOS Assist">
60
- <div class="eos-assist-head"><img src="${logoUrl}" alt="NexoWatt EOS"><div><strong class="eos-assist-title"></strong><span class="eos-assist-text"></span></div><button class="eos-assist-close" type="button" aria-label="Schließen">×</button></div>
61
- <div class="eos-assist-input-row"><input class="eos-assist-input" placeholder="Frage zu EOS, Modul oder Datenpunkt"><button class="eos-assist-send" type="button">Fragen</button></div>
62
- <div class="eos-assist-answer">Beschreibe kurz, was geprüft oder eingerichtet werden soll.</div>
63
- </div>`;
64
- const userAnchor = findUserAnchor(toolbar);
65
- toolbar.insertBefore(root, userAnchor);
66
- root.addEventListener('click', event => {
67
- const button = event.target?.closest?.('button');
68
- if (!button) return;
69
- if (button.classList.contains('eos-assist-button')) {
70
- const open = !root.classList.contains('eos-assist-open');
71
- root.classList.toggle('eos-assist-open', open);
72
- button.setAttribute('aria-expanded', open ? 'true' : 'false');
73
- if (open) setTimeout(() => root.querySelector('.eos-assist-input')?.focus(), 30);
74
- } else if (button.classList.contains('eos-assist-close')) {
75
- root.classList.remove('eos-assist-open');
76
- root.querySelector('.eos-assist-button')?.setAttribute('aria-expanded', 'false');
77
- } else if (button.classList.contains('eos-assist-send')) {
78
- const input = root.querySelector('.eos-assist-input');
79
- root.querySelector('.eos-assist-answer').textContent = answer(input?.value || '');
80
- }
81
- }, { signal: abort.signal });
82
- root.addEventListener('keydown', event => {
83
- if (event.key === 'Enter' && event.target?.classList?.contains('eos-assist-input')) {
84
- root.querySelector('.eos-assist-answer').textContent = answer(event.target.value || '');
85
- }
86
- if (event.key === 'Escape') {
87
- root.classList.remove('eos-assist-open');
88
- root.querySelector('.eos-assist-button')?.setAttribute('aria-expanded', 'false');
89
- }
90
- }, { signal: abort.signal });
91
- } else if (root.parentElement !== toolbar) {
92
- const userAnchor = findUserAnchor(toolbar);
93
- toolbar.insertBefore(root, userAnchor);
94
- }
95
- const [title, text] = currentContext();
96
- root.querySelector('.eos-assist-title').textContent = title;
97
- root.querySelector('.eos-assist-text').textContent = text;
98
- };
99
-
100
- const connect = () => {
101
- const coordinator = window.NEXOWATT_EOS_DOM_COORDINATOR;
102
- if (!coordinator?.subscribe) {
103
- timer = setTimeout(connect, 250);
104
- return;
105
- }
106
- unsubscribeDom = coordinator.subscribe(ensure);
107
- };
108
-
109
- window.addEventListener('hashchange', ensure, { signal: abort.signal });
110
- window.addEventListener('resize', ensure, { signal: abort.signal });
111
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ensure, { once: true, signal: abort.signal });
112
- else ensure();
113
- connect();
114
- [250, 800, 1600].forEach(delay => setTimeout(ensure, delay));
115
-
116
- window.NEXOWATT_EOS_ASSIST = {
117
- version: VERSION,
118
- refresh: ensure,
119
- destroy() {
120
- destroyed = true;
121
- abort.abort();
122
- unsubscribeDom?.();
123
- if (timer) clearTimeout(timer);
124
- remove();
125
- },
126
- };
3
+ // NexoWatt EOS stable 7.9.95: EOS Assist remains disabled until the
4
+ // read-only system assistant is rebuilt and accepted separately.
5
+ window.NEXOWATT_EOS_ASSIST_DISABLED = true;
6
+ document.documentElement.classList.add('eos-assist-disabled');
7
+ document.querySelectorAll('.eos-assist-root,.eos-assist-header-root,[data-eos-assist-root]').forEach(element => element.remove());
127
8
  })();
@@ -0,0 +1,79 @@
1
+ /* NexoWatt EOS stable runtime v94 */
2
+ (() => {
3
+ 'use strict';
4
+ const MANAGED_USERS = new Set(['installer', 'guest', 'user']);
5
+ const ASSIST_TEXT = /EOS\s*Assist/i;
6
+ const PASSWORDLESS_TEXT = /Erstanmeldung\s+ohne\s+Passwort|first\s+sign-?in\s+without\s+password/i;
7
+ const isLogin = () => /(?:\?|&)login(?:[=&]|$)/i.test(location.search) || /\/login(?:[/?#]|$)/i.test(location.pathname + location.hash);
8
+ const visible = el => !!el && el.getClientRects().length > 0;
9
+ const findInput = kind => {
10
+ const inputs = [...document.querySelectorAll('input')].filter(visible);
11
+ if (kind === 'password') return inputs.find(i => i.type === 'password');
12
+ return inputs.find(i => i.type !== 'password' && /user|login|name/i.test(`${i.name} ${i.id} ${i.autocomplete} ${i.getAttribute('aria-label')||''}`)) || inputs.find(i => i.type !== 'password');
13
+ };
14
+ const findSubmit = () => [...document.querySelectorAll('button,input[type="submit"]')].find(el => visible(el) && (/anmelden|login|sign in/i.test((el.textContent||el.value||'').trim()) || el.type === 'submit'));
15
+ const commonAncestor = (a,b) => {
16
+ if (!a || !b) return null;
17
+ const parents = new Set(); let n=a;
18
+ while(n){parents.add(n);n=n.parentElement;}
19
+ n=b; while(n&&!parents.has(n))n=n.parentElement;
20
+ return n;
21
+ };
22
+ const markLogin = () => {
23
+ if (!isLogin()) return;
24
+ document.documentElement.classList.add('nw-eos-login-v94');
25
+ document.body?.classList.add('nw-eos-login-v94');
26
+ const u=findInput('user'), p=findInput('password'), submit=findSubmit();
27
+ if (u && p) {
28
+ let card=commonAncestor(u,p);
29
+ while(card && card.parentElement && card.querySelectorAll('input').length < 2) card=card.parentElement;
30
+ if (card) {
31
+ card.setAttribute('data-nw-login-card','true');
32
+ let outer=card.parentElement;
33
+ for(let i=0;i<3 && outer?.parentElement;i++){
34
+ if(outer.getBoundingClientRect().width > card.getBoundingClientRect().width+20) break;
35
+ outer=outer.parentElement;
36
+ }
37
+ outer?.setAttribute('data-nw-login-shell','true');
38
+ }
39
+ const sync=()=>{
40
+ const name=(u.value||'').trim().toLowerCase();
41
+ const pass=p.value||'';
42
+ if(submit && MANAGED_USERS.has(name) && !pass){
43
+ submit.disabled=true;
44
+ submit.setAttribute('aria-disabled','true');
45
+ }
46
+ };
47
+ if(!u.dataset.nwV94){u.dataset.nwV94='1';u.addEventListener('input',sync);p.addEventListener('input',sync);sync();}
48
+ }
49
+ };
50
+ const hideLegacyUi = () => {
51
+ for (const el of document.querySelectorAll('button,a,[role="button"],div,span')) {
52
+ const text=(el.textContent||'').trim();
53
+ if(!text || text.length>100) continue;
54
+ if(ASSIST_TEXT.test(text) || PASSWORDLESS_TEXT.test(text)){
55
+ const target=el.closest('button,a,[role="button"],[data-eos-assist],.eos-assist') || el;
56
+ target.style.setProperty('display','none','important');
57
+ target.setAttribute('aria-hidden','true');
58
+ }
59
+ }
60
+ };
61
+ const apply=()=>{markLogin();hideLegacyUi();};
62
+ const style=document.createElement('style');
63
+ style.id='nw-eos-stable-v94-css';
64
+ style.textContent=`
65
+ html.nw-eos-login-v94,body.nw-eos-login-v94{min-height:100%;overflow-x:hidden!important;overflow-y:auto!important}
66
+ .nw-eos-login-v94 [data-nw-login-shell="true"]{width:min(410px,calc(100vw - 28px))!important;min-height:540px!important;height:auto!important;max-height:none!important;overflow:visible!important;box-sizing:border-box!important;padding:22px!important}
67
+ .nw-eos-login-v94 [data-nw-login-card="true"]{width:min(354px,calc(100vw - 56px))!important;min-height:474px!important;height:auto!important;max-height:none!important;overflow:visible!important;box-sizing:border-box!important;padding-bottom:26px!important}
68
+ .nw-eos-login-v94 [data-nw-login-card="true"] *{scrollbar-width:none}
69
+ .nw-eos-login-v94 [data-nw-login-card="true"] *::-webkit-scrollbar{display:none!important;width:0!important;height:0!important}
70
+ .nw-eos-login-v94 [data-nw-login-card="true"] [role="alert"]{position:static!important;max-height:none!important;overflow:visible!important;white-space:normal!important}
71
+ [data-eos-assist],.eos-assist,.nexowatt-eos-assist{display:none!important}
72
+ @media(max-height:620px){.nw-eos-login-v94 [data-nw-login-shell="true"]{min-height:0!important;margin:16px auto!important}.nw-eos-login-v94 [data-nw-login-card="true"]{min-height:0!important}}
73
+ `;
74
+ (document.head||document.documentElement).appendChild(style);
75
+ const observer=new MutationObserver(()=>requestAnimationFrame(apply));
76
+ observer.observe(document.documentElement,{subtree:true,childList:true,attributes:true,attributeFilter:['class','style']});
77
+ if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',apply,{once:true});else apply();
78
+ setInterval(apply,1500);
79
+ })();
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startNexowattStableAuth = startNexowattStableAuth;
4
+ const START_PASSWORD = 'nexowatt';
5
+ const MARKER_KEYS = ["eosFirstLoginPending", "eosMustChangePassword", "eosPasswordSetupRequired", "firstLoginRequired", "mustChangePassword", "nexowattFirstLoginPending", "nexowattMustChangePassword", "nexowattPasswordSetupRequired", "passwordChangeRequired"];
6
+ const timers = new WeakMap();
7
+ function pendingByNative(native) {
8
+ if (!native || typeof native !== 'object') return false;
9
+ return MARKER_KEYS.some(key => native[key] === true || native[key] === 'pending' || native[key] === 'required' || native[key] === 'starter');
10
+ }
11
+ async function setPassword(adapter, user, password) {
12
+ if (typeof adapter.setPasswordAsync === 'function') return adapter.setPasswordAsync(user, password, { user: 'system.user.admin' });
13
+ return new Promise((resolve, reject) => adapter.setPassword(user, password, { user: 'system.user.admin' }, err => err ? reject(err) : resolve()));
14
+ }
15
+ async function ensureGroupMembership(adapter, userId, groupId) {
16
+ const group = await adapter.getForeignObjectAsync(groupId);
17
+ if (!group || group.type !== 'group') return;
18
+ group.common = group.common || { name: groupId.split('.').pop(), members: [] };
19
+ group.common.members = Array.isArray(group.common.members) ? group.common.members : [];
20
+ if (!group.common.members.includes(userId)) {
21
+ group.common.members.push(userId);
22
+ await adapter.setForeignObjectAsync(groupId, group, { user: 'system.user.admin' });
23
+ }
24
+ }
25
+ async function createUser(adapter, name) {
26
+ const id = `system.user.${name}`;
27
+ await adapter.setForeignObjectAsync(id, {
28
+ _id: id, type: 'user',
29
+ common: { name, enabled: true, password: '' },
30
+ native: { nexowattManagedAccount: true, nexowattMustChangePassword: true, nexowattPasswordState: 'starter' }
31
+ }, { user: 'system.user.admin' });
32
+ return adapter.getForeignObjectAsync(id);
33
+ }
34
+ async function prepareOne(adapter, name, group, createMissing) {
35
+ const id = `system.user.${name}`;
36
+ let obj = await adapter.getForeignObjectAsync(id);
37
+ let created = false;
38
+ if (!obj && createMissing) { obj = await createUser(adapter, name); created = true; }
39
+ if (!obj || obj.type !== 'user') return;
40
+ obj.native = obj.native || {};
41
+ const empty = !obj.common || !obj.common.password;
42
+ const pending = created || empty || pendingByNative(obj.native) || obj.native.nexowattPasswordState === 'starter';
43
+ if (pending) {
44
+ await setPassword(adapter, name, START_PASSWORD);
45
+ obj = await adapter.getForeignObjectAsync(id) || obj;
46
+ obj.native = obj.native || {};
47
+ obj.native.nexowattManagedAccount = true;
48
+ obj.native.nexowattStarterPasswordActive = true;
49
+ obj.native.nexowattMustChangePassword = true;
50
+ obj.native.nexowattPasswordState = 'starter';
51
+ obj.native.nexowattStarterPasswordUpdatedAt = new Date().toISOString();
52
+ for (const key of MARKER_KEYS) {
53
+ if (/required|must|pending|first/i.test(key)) obj.native[key] = true;
54
+ }
55
+ await adapter.setForeignObjectAsync(id, obj, { user: 'system.user.admin' });
56
+ }
57
+ await ensureGroupMembership(adapter, id, group);
58
+ }
59
+ async function reconcile(adapter) {
60
+ try {
61
+ await prepareOne(adapter, 'installer', 'system.group.installer', true);
62
+ const guest = await adapter.getForeignObjectAsync('system.user.guest');
63
+ const user = await adapter.getForeignObjectAsync('system.user.user');
64
+ if (guest || !user) await prepareOne(adapter, 'guest', 'system.group.endkunden', true);
65
+ if (user) await prepareOne(adapter, 'user', 'system.group.endkunden', false);
66
+ } catch (e) { adapter.log.warn(`[NexoWatt EOS login] Starter-account reconciliation failed: ${e && e.message || e}`); }
67
+ }
68
+ function startNexowattStableAuth(adapter) {
69
+ if (timers.has(adapter)) return;
70
+ void reconcile(adapter);
71
+ const timer = setInterval(() => void reconcile(adapter), 5000);
72
+ if (timer.unref) timer.unref();
73
+ timers.set(adapter, timer);
74
+ }
package/build/lib/web.js CHANGED
@@ -675,7 +675,11 @@ class Web {
675
675
  const account = (native.nexowattEosAccount || {});
676
676
  const initialized = account.passwordInitialized === true
677
677
  || Number(account.passwordSetupVersion || account.passwordInitializationVersion || 0) >= 1;
678
- const forced = account.forcePasswordChange === true;
678
+ const stableForced = native.nexowattPasswordChangeRequired === true
679
+ || native.eosPasswordChangeRequired === true
680
+ || native.nexowattFirstLoginPending === true
681
+ || native.eosFirstLoginRequired === true;
682
+ const forced = account.forcePasswordChange === true || stableForced;
679
683
  return { required: forced || !initialized, initialized: initialized && !forced, minLength, version: 1 };
680
684
  }
681
685
  catch (e) {
@@ -789,6 +793,12 @@ class Web {
789
793
  account.firstLoginCompletedAt = now;
790
794
  account.forcePasswordChange = false;
791
795
  account.passwordlessFirstLoginAllowed = false;
796
+ native.nexowattPasswordChangeRequired = false;
797
+ native.eosPasswordChangeRequired = false;
798
+ native.nexowattFirstLoginPending = false;
799
+ native.eosFirstLoginRequired = false;
800
+ native.nexowattInitialPasswordApplied = false;
801
+ native.nexowattPasswordChangedAt = now;
792
802
  await this.adapter.setForeignObjectAsync(access.userId, userObject);
793
803
  await this.destroyEosRequestSessions(req);
794
804
  for (const cookie of ['access_token', 'refresh_token', 'connect.sid']) {
@@ -1133,8 +1143,7 @@ class Web {
1133
1143
  res.status(409).json({ error: 'accountDisabled' });
1134
1144
  return;
1135
1145
  }
1136
- const bootstrapSecret = `${(0, node_crypto_1.randomBytes)(48).toString('base64url')}!aA1`;
1137
- await this.setEosUserPassword(targetUserId, bootstrapSecret);
1146
+ await this.setEosUserPassword(targetUserId, 'nexowatt');
1138
1147
  const user = (await this.adapter.getForeignObjectAsync(targetUserId));
1139
1148
  if (!user) {
1140
1149
  throw new Error('userObjectUnavailableAfterPasswordReset');
@@ -1146,21 +1155,29 @@ class Web {
1146
1155
  account.passwordSetupVersion = 0;
1147
1156
  account.passwordInitializationVersion = 0;
1148
1157
  account.forcePasswordChange = true;
1149
- account.passwordlessFirstLoginAllowed = true;
1158
+ account.passwordlessFirstLoginAllowed = false;
1150
1159
  account.passwordResetAt = now;
1151
1160
  account.passwordResetBy = access.userId;
1152
- account.passwordResetMode = 'passwordless-first-activation';
1161
+ account.passwordResetMode = 'initial-password';
1162
+ native.nexowattPasswordChangeRequired = true;
1163
+ native.eosPasswordChangeRequired = true;
1164
+ native.nexowattFirstLoginPending = true;
1165
+ native.eosFirstLoginRequired = true;
1166
+ native.nexowattInitialPasswordApplied = true;
1167
+ native.nexowattInitialPasswordVersion = 1;
1168
+ native.nexowattStableInitialCredentialVersion = 1;
1153
1169
  delete account.passwordSetAt;
1154
1170
  delete account.firstLoginCompletedAt;
1155
1171
  await this.adapter.setForeignObjectAsync(targetUserId, user);
1156
1172
  this.invalidateEosPasswordClaimsForUser(targetUserId);
1157
- this.adapter.log.warn(`EOS account ${targetUserId} was reset for passwordless first activation by ${access.userId}`);
1173
+ this.adapter.log.warn(`EOS account ${targetUserId} was reset to the mandatory initial-password flow by ${access.userId}`);
1158
1174
  res.status(200).json({
1159
1175
  success: true,
1160
1176
  user: targetUserId,
1161
1177
  role: explicitlyManagedRole,
1162
1178
  firstLoginRequired: true,
1163
- passwordlessFirstLoginAllowed: true,
1179
+ passwordlessFirstLoginAllowed: false,
1180
+ initialPasswordRequired: true,
1164
1181
  });
1165
1182
  }
1166
1183
  async getEosHistoryInstances() {