iobroker.eos-admin 7.9.97 → 7.9.98

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 (42) hide show
  1. package/INSTALL_TEST_V7.9.98.md +39 -0
  2. package/MERGE_UPDATE_README_V7.9.98.md +29 -0
  3. package/NEXOWATT_EOS_BUILD_INFO.json +20 -20
  4. package/PUBLISH_STABLE_V7.9.98.md +33 -0
  5. package/README.md +16 -6
  6. package/README_STABILITY_V7.9.98.md +22 -0
  7. package/RELEASE_ACCEPTANCE_V7.9.98.md +48 -0
  8. package/RELEASE_NOTES_V7.9.98.md +32 -0
  9. package/adminWww/assets/bootstrap-COulQZax-v84.js +2 -2
  10. package/adminWww/css/eos-ems-overview.css +1 -1
  11. package/adminWww/css/nexowatt-native-shell.css +117 -0
  12. package/adminWww/index.html +15 -15
  13. package/adminWww/js/eos-account-management.js +26 -13
  14. package/adminWww/js/eos-ems-overview.js +1 -1
  15. package/adminWww/js/eos-role-bootstrap.js +7 -7
  16. package/adminWww/js/eos-role-ui.js +1 -1
  17. package/adminWww/js/nexowatt-native-shell.js +1 -1
  18. package/adminWww/static/js/nexowatt-stable-login.js +3 -3
  19. package/adminWww/static/js/{nexowatt-stable-v97.js → nexowatt-stable-v98.js} +2 -2
  20. package/build/lib/web.js +112 -79
  21. package/build/main.js +1 -1
  22. package/io-package.json +10 -5
  23. package/package.json +14 -12
  24. package/tools/nexowatt-account-management-selftest.cjs +16 -57
  25. package/tools/nexowatt-account-password-flow-selftest.cjs +186 -0
  26. package/tools/nexowatt-login-layout-selftest.cjs +8 -8
  27. package/tools/nexowatt-merge-update-selftest.cjs +1 -1
  28. package/tools/nexowatt-modern-ui-selftest.cjs +2 -2
  29. package/tools/nexowatt-password-write-selftest.cjs +48 -0
  30. package/tools/nexowatt-runtime-cleanup-selftest.cjs +5 -5
  31. package/tools/nexowatt-stable-v98-selftest.cjs +14 -0
  32. package/tools/nexowatt-sync-release-version.cjs +42 -1
  33. package/tools/nexowatt-ui-overview-runtime-selftest.cjs +1 -1
  34. package/tools/nexowatt-validate-package.cjs +20 -11
  35. package/tools/nexowatt-version-sync-selftest.cjs +18 -1
  36. package/INSTALL_TEST_V7.9.97.md +0 -11
  37. package/MERGE_UPDATE_README_V7.9.97.md +0 -5
  38. package/PUBLISH_STABLE_V7.9.97.md +0 -15
  39. package/README_STABILITY_V7.9.97.md +0 -18
  40. package/RELEASE_ACCEPTANCE_V7.9.97.md +0 -11
  41. package/RELEASE_NOTES_V7.9.97.md +0 -19
  42. package/tools/nexowatt-stable-v97-selftest.cjs +0 -16
package/build/lib/web.js CHANGED
@@ -713,22 +713,53 @@ class Web {
713
713
  }
714
714
  return { valid: true, password: value };
715
715
  }
716
+ getEosPasswordUserName(userId) {
717
+ const userName = String(userId || '').trim().replace(/^system\.user\./, '');
718
+ if (!/^[A-Za-z0-9_.@-]+$/.test(userName)) {
719
+ throw new Error('invalidPasswordTarget');
720
+ }
721
+ return userName;
722
+ }
716
723
  async setEosUserPassword(userId, password) {
717
- // The authenticated route is strictly self-service: the target user comes from the current
718
- // session and cannot be supplied by the browser. Use the trusted EOS service context for the
719
- // actual password write so installer/end-user groups do not need global users.write rights.
724
+ // ioBroker's password API expects the account name, not the system.user.* object id.
725
+ const userName = this.getEosPasswordUserName(userId);
726
+ // The server-side Admin/Service context performs the controller password write, so managed
727
+ // Installer and End User accounts do not need global users.write rights in the browser.
720
728
  const options = { user: EOS_PASSWORD_SERVICE_USER };
721
729
  if (typeof this.adapter.setPasswordAsync === 'function') {
722
- await this.adapter.setPasswordAsync(userId, password, options);
723
- return;
730
+ await this.adapter.setPasswordAsync(userName, password, options);
724
731
  }
725
- if (typeof this.adapter.setPassword === 'function') {
732
+ else if (typeof this.adapter.setPassword === 'function') {
726
733
  await new Promise((resolve, reject) => {
727
- this.adapter.setPassword?.(userId, password, options, error => (error ? reject(error) : resolve()));
734
+ this.adapter.setPassword?.(userName, password, options, error => (error ? reject(error) : resolve()));
728
735
  });
736
+ }
737
+ else {
738
+ throw new Error('passwordApiUnavailable');
739
+ }
740
+ if (typeof this.adapter.checkPasswordAsync === 'function') {
741
+ const result = await this.adapter.checkPasswordAsync(userName, password, options);
742
+ const valid = Array.isArray(result) ? result[0] === true : result === true;
743
+ if (!valid) {
744
+ throw new Error('passwordVerificationFailed');
745
+ }
729
746
  return;
730
747
  }
731
- throw new Error('passwordApiUnavailable');
748
+ const userObject = (await this.adapter.getForeignObjectAsync(userId));
749
+ if (!String(userObject?.common?.password || '').trim()) {
750
+ throw new Error('passwordWriteFailed');
751
+ }
752
+ }
753
+ async updateEosAccountMetadata(userId, updater) {
754
+ const userObject = (await this.adapter.getForeignObjectAsync(userId));
755
+ if (!userObject) {
756
+ throw new Error('userObjectUnavailableAfterPasswordChange');
757
+ }
758
+ const native = { ...(userObject.native || {}) };
759
+ const account = { ...(native.nexowattEosAccount || {}) };
760
+ native.nexowattEosAccount = account;
761
+ updater(native, account);
762
+ await this.adapter.extendForeignObjectAsync(userId, { native }, { user: EOS_PASSWORD_SERVICE_USER });
732
763
  }
733
764
  async destroyEosRequestSessions(req) {
734
765
  const token = this.readAccessTokenFromRequest(req);
@@ -777,29 +808,24 @@ class Web {
777
808
  return;
778
809
  }
779
810
  await this.setEosUserPassword(access.userId, validation.password);
780
- const userObject = (await this.adapter.getForeignObjectAsync(access.userId));
781
- if (!userObject) {
782
- throw new Error('userObjectUnavailableAfterPasswordChange');
783
- }
784
- const native = (userObject.native ||= {});
785
- const account = (native.nexowattEosAccount ||= {});
786
811
  const now = new Date().toISOString();
787
- account.passwordInitialized = true;
788
- account.passwordInitializedAt = Date.now();
789
- account.passwordInitializationVersion = 1;
790
- account.passwordInitializedBy = 'self';
791
- account.passwordSetupVersion = 1;
792
- account.passwordSetAt = now;
793
- account.firstLoginCompletedAt = now;
794
- account.forcePasswordChange = false;
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;
802
- await this.adapter.setForeignObjectAsync(access.userId, userObject);
812
+ await this.updateEosAccountMetadata(access.userId, (native, account) => {
813
+ account.passwordInitialized = true;
814
+ account.passwordInitializedAt = Date.now();
815
+ account.passwordInitializationVersion = 1;
816
+ account.passwordInitializedBy = 'self';
817
+ account.passwordSetupVersion = 1;
818
+ account.passwordSetAt = now;
819
+ account.firstLoginCompletedAt = now;
820
+ account.forcePasswordChange = false;
821
+ account.passwordlessFirstLoginAllowed = false;
822
+ native.nexowattPasswordChangeRequired = false;
823
+ native.eosPasswordChangeRequired = false;
824
+ native.nexowattFirstLoginPending = false;
825
+ native.eosFirstLoginRequired = false;
826
+ native.nexowattInitialPasswordApplied = false;
827
+ native.nexowattPasswordChangedAt = now;
828
+ });
803
829
  await this.destroyEosRequestSessions(req);
804
830
  for (const cookie of ['access_token', 'refresh_token', 'connect.sid']) {
805
831
  res.clearCookie(cookie);
@@ -1023,24 +1049,25 @@ class Web {
1023
1049
  return;
1024
1050
  }
1025
1051
  await this.setEosUserPassword(claim.userId, validation.password);
1026
- const user = (await this.adapter.getForeignObjectAsync(claim.userId));
1027
- if (!user) {
1028
- throw new Error('userObjectUnavailableAfterPasswordChange');
1029
- }
1030
- const native = (user.native ||= {});
1031
- const account = (native.nexowattEosAccount ||= {});
1032
1052
  const now = new Date().toISOString();
1033
- account.passwordInitialized = true;
1034
- account.passwordInitializedAt = Date.now();
1035
- account.passwordInitializationVersion = 1;
1036
- account.passwordInitializedBy = 'passwordless-first-activation';
1037
- account.passwordSetupVersion = 1;
1038
- account.passwordSetAt = now;
1039
- account.firstLoginCompletedAt = now;
1040
- account.forcePasswordChange = false;
1041
- account.passwordlessFirstLoginAllowed = false;
1042
- account.passwordlessClaimCompletedAt = now;
1043
- await this.adapter.setForeignObjectAsync(claim.userId, user);
1053
+ await this.updateEosAccountMetadata(claim.userId, (native, account) => {
1054
+ account.passwordInitialized = true;
1055
+ account.passwordInitializedAt = Date.now();
1056
+ account.passwordInitializationVersion = 1;
1057
+ account.passwordInitializedBy = 'passwordless-first-activation';
1058
+ account.passwordSetupVersion = 1;
1059
+ account.passwordSetAt = now;
1060
+ account.firstLoginCompletedAt = now;
1061
+ account.forcePasswordChange = false;
1062
+ account.passwordlessFirstLoginAllowed = false;
1063
+ account.passwordlessClaimCompletedAt = now;
1064
+ native.nexowattPasswordChangeRequired = false;
1065
+ native.eosPasswordChangeRequired = false;
1066
+ native.nexowattFirstLoginPending = false;
1067
+ native.eosFirstLoginRequired = false;
1068
+ native.nexowattInitialPasswordApplied = false;
1069
+ native.nexowattPasswordChangedAt = now;
1070
+ });
1044
1071
  this.invalidateEosPasswordClaimsForUser(claim.userId);
1045
1072
  res.clearCookie('nexowatt_eos_first_login', { path: '/nexowatt/account' });
1046
1073
  this.adapter.log.info(`EOS passwordless first activation completed for ${claim.userId}`);
@@ -1106,13 +1133,14 @@ class Web {
1106
1133
  async resetEosAccountPassword(req, res) {
1107
1134
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
1108
1135
  const access = await this.getEosRequestAccess(req);
1109
- if (!access.userId
1110
- || (access.role !== 'admin' && access.role !== 'installer')
1111
- || !this.isEosSameOriginWrite(req)
1112
- || req.headers['x-nexowatt-eos-account-reset'] !== '1') {
1136
+ if (!access.userId || (access.role !== 'admin' && access.role !== 'installer')) {
1113
1137
  res.status(403).json({ error: 'permissionError' });
1114
1138
  return;
1115
1139
  }
1140
+ if (!this.isEosSameOriginWrite(req) || req.headers['x-nexowatt-eos-account-reset'] !== '1') {
1141
+ res.status(403).json({ error: 'invalidRequestOrigin' });
1142
+ return;
1143
+ }
1116
1144
  const targetUserId = this.normalizeEosUserId(req.body?.user);
1117
1145
  if (!targetUserId || targetUserId === 'system.user.admin' || targetUserId === access.userId) {
1118
1146
  res.status(400).json({ error: 'invalidTarget' });
@@ -1144,31 +1172,26 @@ class Web {
1144
1172
  return;
1145
1173
  }
1146
1174
  await this.setEosUserPassword(targetUserId, 'nexowatt');
1147
- const user = (await this.adapter.getForeignObjectAsync(targetUserId));
1148
- if (!user) {
1149
- throw new Error('userObjectUnavailableAfterPasswordReset');
1150
- }
1151
- const native = (user.native ||= {});
1152
- const account = (native.nexowattEosAccount ||= {});
1153
1175
  const now = new Date().toISOString();
1154
- account.passwordInitialized = false;
1155
- account.passwordSetupVersion = 0;
1156
- account.passwordInitializationVersion = 0;
1157
- account.forcePasswordChange = true;
1158
- account.passwordlessFirstLoginAllowed = false;
1159
- account.passwordResetAt = now;
1160
- account.passwordResetBy = access.userId;
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;
1169
- delete account.passwordSetAt;
1170
- delete account.firstLoginCompletedAt;
1171
- await this.adapter.setForeignObjectAsync(targetUserId, user);
1176
+ await this.updateEosAccountMetadata(targetUserId, (native, account) => {
1177
+ account.passwordInitialized = false;
1178
+ account.passwordSetupVersion = 0;
1179
+ account.passwordInitializationVersion = 0;
1180
+ account.forcePasswordChange = true;
1181
+ account.passwordlessFirstLoginAllowed = false;
1182
+ account.passwordResetAt = now;
1183
+ account.passwordResetBy = access.userId;
1184
+ account.passwordResetMode = 'initial-password';
1185
+ account.passwordSetAt = null;
1186
+ account.firstLoginCompletedAt = null;
1187
+ native.nexowattPasswordChangeRequired = true;
1188
+ native.eosPasswordChangeRequired = true;
1189
+ native.nexowattFirstLoginPending = true;
1190
+ native.eosFirstLoginRequired = true;
1191
+ native.nexowattInitialPasswordApplied = true;
1192
+ native.nexowattInitialPasswordVersion = 1;
1193
+ native.nexowattStableInitialCredentialVersion = 1;
1194
+ });
1172
1195
  this.invalidateEosPasswordClaimsForUser(targetUserId);
1173
1196
  this.adapter.log.warn(`EOS account ${targetUserId} was reset to the mandatory initial-password flow by ${access.userId}`);
1174
1197
  res.status(200).json({
@@ -1245,14 +1268,24 @@ class Web {
1245
1268
  });
1246
1269
  }
1247
1270
  isEosSameOriginWrite(req) {
1271
+ const fetchSite = String(req.headers['sec-fetch-site'] || '').trim().toLowerCase();
1272
+ if (fetchSite === 'cross-site') {
1273
+ return false;
1274
+ }
1248
1275
  const origin = String(req.headers.origin || '').trim();
1249
1276
  if (!origin) {
1250
1277
  return true;
1251
1278
  }
1252
- const forwardedProto = String(req.headers['x-forwarded-proto'] || '').split(',')[0].trim();
1253
- const protocol = forwardedProto || (this.settings.secure ? 'https' : 'http');
1254
- const host = String(req.headers.host || '').trim();
1255
- return !!host && origin === `${protocol}://${host}`;
1279
+ try {
1280
+ const originUrl = new URL(origin);
1281
+ const directHost = String(req.headers.host || '').trim().toLowerCase();
1282
+ const forwardedHost = String(req.headers['x-forwarded-host'] || '').split(',')[0].trim().toLowerCase();
1283
+ const allowedHosts = new Set([directHost, forwardedHost].filter(Boolean));
1284
+ return allowedHosts.has(originUrl.host.toLowerCase());
1285
+ }
1286
+ catch {
1287
+ return false;
1288
+ }
1256
1289
  }
1257
1290
  async saveEosBasicSettings(req, res) {
1258
1291
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
package/build/main.js CHANGED
@@ -1152,7 +1152,7 @@ class Admin extends adapter_core_1.Adapter {
1152
1152
  }
1153
1153
  if (String(user.common.password || '') === '') {
1154
1154
  const bootstrapSecret = `${(0, node_crypto_1.randomBytes)(48).toString('base64url')}!aA1`;
1155
- await this.setPasswordAsync(userId, bootstrapSecret, { user: ADMIN_OWNER_USER });
1155
+ await this.setPasswordAsync(userId.replace(/^system\.user\./, ''), bootstrapSecret, { user: ADMIN_OWNER_USER });
1156
1156
  user = (await this.getForeignObjectAsync(userId));
1157
1157
  if (!user) {
1158
1158
  return;
package/io-package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "eos-admin",
4
- "version": "7.9.97",
4
+ "version": "7.9.98",
5
5
  "titleLang": {
6
6
  "en": "NexoWatt EOS Admin",
7
7
  "de": "NexoWatt EOS Admin",
@@ -18,6 +18,11 @@
18
18
  "connectionType": "local",
19
19
  "dataSource": "push",
20
20
  "news": {
21
+ "7.9.98": {
22
+ "de": "Stable-Kandidat 7.9.98 ergänzt den vertikalen Cockpit-Scrollbereich, bereinigt die Login-Felder und korrigiert Passwort-Reset sowie persönliche Erstpasswortvergabe über die echte ioBroker-Passwort-API.",
23
+ "en": "Stable candidate 7.9.98 adds vertical cockpit scrolling, cleans up login fields and fixes password reset plus first personal password setup through the real ioBroker password API.",
24
+ "nl": "Stable-kandidaat 7.9.98 voegt verticaal scrollen in het cockpit toe, herstelt de loginvelden en corrigeert wachtwoordreset en het eerste persoonlijke wachtwoord via de echte ioBroker-wachtwoord-API."
25
+ },
21
26
  "7.9.95": {
22
27
  "de": "Stable 7.9.95 macht den Release-Ordner mergesicher: Die flache Update-ZIP kann direkt über einen bestehenden Arbeitsordner entpackt werden. Vor Paketprüfung und npm-Veröffentlichung werden package.json, io-package.json, beide Lockdateien, die Frontend-Version, Build-Informationen und der aktuelle Repository-Eintrag automatisch auf dieselbe Version gebracht. Dadurch wird insbesondere ein stehen gebliebenes io-package.json aus 7.9.92/7.9.94 automatisch repariert. Die Funktionen aus 7.9.94 bleiben unverändert.",
23
28
  "en": "Stable 7.9.95 makes the release folder merge-safe: the flat update ZIP can be extracted directly over an existing working directory. Before package validation and npm publishing, package.json, io-package.json, both lockfiles, the frontend version, build information and the current repository entry are synchronized automatically. In particular, a stale io-package.json from 7.9.92/7.9.94 is repaired automatically. Functional behavior from 7.9.94 remains unchanged.",
@@ -329,7 +334,7 @@
329
334
  "icon": "admin.svg",
330
335
  "messagebox": true,
331
336
  "enabled": true,
332
- "extIcon": "https://unpkg.com/iobroker.eos-admin@7.9.97/admin/admin.svg",
337
+ "extIcon": "https://unpkg.com/iobroker.eos-admin@7.9.98/admin/admin.svg",
333
338
  "keywords": [
334
339
  "NexoWatt",
335
340
  "EOS",
@@ -340,7 +345,7 @@
340
345
  "licensed"
341
346
  ],
342
347
  "compact": true,
343
- "readme": "https://unpkg.com/iobroker.eos-admin@7.9.97/README.md",
348
+ "readme": "https://unpkg.com/iobroker.eos-admin@7.9.98/README.md",
344
349
  "authors": [
345
350
  "NexoWatt"
346
351
  ],
@@ -408,7 +413,7 @@
408
413
  "nondeletable": false,
409
414
  "allowAdapterUpdate": true,
410
415
  "allowAdapterDelete": false,
411
- "meta": "https://unpkg.com/iobroker.eos-admin@7.9.97/io-package.json",
416
+ "meta": "https://unpkg.com/iobroker.eos-admin@7.9.98/io-package.json",
412
417
  "npmPackage": "iobroker.eos-admin",
413
418
  "packetName": "iobroker.eos-admin",
414
419
  "publisher": "NexoWatt"
@@ -1117,5 +1122,5 @@
1117
1122
  }
1118
1123
  }
1119
1124
  },
1120
- "version": "7.9.97"
1125
+ "version": "7.9.98"
1121
1126
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iobroker.eos-admin",
3
- "description": "NexoWatt EOS Admin – stable sales release with orderly login, role-safe overview and live EMS diagnosis.",
4
- "version": "7.9.97",
3
+ "description": "NexoWatt EOS Admin – stable sales candidate with scrollable cockpit, clean login and verified account password writes.",
4
+ "version": "7.9.98",
5
5
  "contributors": [
6
6
  "bluefox <dogafox@gmail.com>",
7
7
  "apollon77",
@@ -118,19 +118,21 @@
118
118
  "tools/nexowatt-version-sync-selftest.cjs",
119
119
  "tools/nexowatt-merge-update-selftest.cjs",
120
120
  "tools/nexowatt-ems-overview-selftest.cjs",
121
- "README_STABILITY_V7.9.97.md",
122
- "RELEASE_NOTES_V7.9.97.md",
123
- "PUBLISH_STABLE_V7.9.97.md",
124
- "INSTALL_TEST_V7.9.97.md",
125
- "RELEASE_ACCEPTANCE_V7.9.97.md",
126
- "MERGE_UPDATE_README_V7.9.97.md",
121
+ "README_STABILITY_V7.9.98.md",
122
+ "RELEASE_NOTES_V7.9.98.md",
123
+ "PUBLISH_STABLE_V7.9.98.md",
124
+ "INSTALL_TEST_V7.9.98.md",
125
+ "RELEASE_ACCEPTANCE_V7.9.98.md",
126
+ "MERGE_UPDATE_README_V7.9.98.md",
127
127
  "README_EMS_OVERVIEW_MERGE.md",
128
128
  "RC73_EOS_ADMIN_MERGE_VALIDATION.md",
129
129
  "VALIDATION_EMS_OVERVIEW_MERGE.md",
130
130
  "NexoWatt_EOS_Admin_EMS_OVERVIEW_MERGE_VALIDATION.md",
131
131
  "tools/nexowatt-apply-ems-overview.cjs",
132
132
  "tools/nexowatt-ui-overview-runtime-selftest.cjs",
133
- "tools/nexowatt-stable-v97-selftest.cjs"
133
+ "tools/nexowatt-stable-v98-selftest.cjs",
134
+ "tools/nexowatt-password-write-selftest.cjs",
135
+ "tools/nexowatt-account-password-flow-selftest.cjs"
134
136
  ],
135
137
  "scripts": {
136
138
  "npm": "npm i -f && cd src-admin && npm i -f",
@@ -165,7 +167,7 @@
165
167
  "nexowatt:repo-entry": "node tools/nexowatt-generate-repo-entry.cjs",
166
168
  "nexowatt:patch-repo": "node tools/nexowatt-patch-repo.cjs",
167
169
  "nexowatt:repair-update": "node tools/nexowatt-repair-eos-admin-update.cjs",
168
- "check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-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-first-login-selftest.cjs && node tools/nexowatt-account-management-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 && npm run check:eos-stable-v97 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs",
170
+ "check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-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-first-login-selftest.cjs && node tools/nexowatt-account-management-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-v98 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs",
169
171
  "nexowatt:patch-built-frontend": "node tools/nexowatt-patch-built-frontend.cjs",
170
172
  "clean:eos-runtime": "node tools/nexowatt-clean-legacy-runtime.cjs",
171
173
  "precheck:eos-package": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
@@ -189,7 +191,7 @@
189
191
  "sync:eos-version": "node tools/nexowatt-sync-release-version.cjs",
190
192
  "test:eos-version-sync": "node tools/nexowatt-version-sync-selftest.cjs",
191
193
  "verify:eos-merge": "node tools/nexowatt-merge-update-selftest.cjs",
192
- "check:eos-stable-v97": "node tools/nexowatt-stable-v97-selftest.cjs"
194
+ "check:eos-stable-v98": "node tools/nexowatt-stable-v98-selftest.cjs"
193
195
  },
194
196
  "author": "NexoWatt",
195
197
  "private": false,
@@ -200,7 +202,7 @@
200
202
  },
201
203
  "nexowattReleasePolicy": {
202
204
  "distTag": "latest",
203
- "purpose": "accepted stable NexoWatt EOS sales-product release 7.9.97"
205
+ "purpose": "accepted stable NexoWatt EOS sales-product release 7.9.98"
204
206
  },
205
207
  "publisher": "NexoWatt"
206
208
  }
@@ -1,60 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- const fs = require('fs');
4
- const path = require('path');
5
- const root = path.resolve(__dirname, '..');
6
- const read = rel => fs.readFileSync(path.join(root, rel), 'utf8');
7
- const json = rel => JSON.parse(read(rel));
8
- let bad = false;
9
- const fail = msg => { console.error(`[NexoWatt EOS account management] ${msg}`); bad = true; };
10
-
11
- const mainSource = read('src/main.ts');
12
- const mainBuilt = read('build/main.js');
13
- const webSource = read('src/lib/web.ts');
14
- const webBuilt = read('build/lib/web.js');
15
- const sourceUi = read('src-admin/public/js/eos-account-management.js');
16
- const builtUi = read('adminWww/js/eos-account-management.js');
17
- const roleUi = read('adminWww/js/eos-role-ui.js');
18
- const css = read('adminWww/css/nexowatt-native-shell.css');
19
- const index = read('adminWww/index.html');
20
- const info = json('NEXOWATT_EOS_BUILD_INFO.json');
21
- const shellTag = String(info.shellCacheTag || info.shellCacheVersion);
22
-
23
- for (const code of [mainSource, mainBuilt]) {
24
- for (const marker of [
25
- 'system.user.installer', 'system.user.guest', 'ensureEosDefaultRoleUsers', 'unknown random bootstrap secret',
26
- 'eosPasswordSetupRequired', 'personal password setup is required',
27
- ]) if (!code.includes(marker)) fail(`main account marker missing: ${marker}`);
3
+ const fs=require('fs');const path=require('path');const root=path.resolve(__dirname,'..');const read=rel=>fs.readFileSync(path.join(root,rel),'utf8');const json=rel=>JSON.parse(read(rel));let bad=false;const fail=msg=>{console.error(`[NexoWatt EOS account management] ${msg}`);bad=true;};
4
+ const mainSource=read('src/main.ts'),mainBuilt=read('build/main.js'),webSource=read('src/lib/web.ts'),webBuilt=read('build/lib/web.js'),sourceUi=read('src-admin/public/js/eos-account-management.js'),builtUi=read('adminWww/js/eos-account-management.js'),roleUi=read('adminWww/js/eos-role-ui.js'),css=read('adminWww/css/nexowatt-native-shell.css'),index=read('adminWww/index.html'),info=json('NEXOWATT_EOS_BUILD_INFO.json'),shellTag=String(info.shellCacheTag||info.shellCacheVersion);
5
+ for(const code of [mainSource,mainBuilt])for(const marker of ['system.user.installer','system.user.guest','ensureEosDefaultRoleUsers','unknown random bootstrap secret','eosPasswordSetupRequired','personal password setup is required'])if(!code.includes(marker))fail(`main marker missing: ${marker}`);
6
+ for(const code of [webSource,webBuilt]){
7
+ for(const marker of ['getEosManagedAccounts','sendEosAccountManagement','resetEosAccountPassword','/nexowatt/account/manage','/nexowatt/account/reset','x-nexowatt-eos-account-reset','canResetInstaller','canResetEndUser',"targetUserId === 'system.user.admin'",'targetUserId === access.userId',"access.role === 'installer' && explicitlyManagedRole !== 'enduser'",'passwordResetAt','passwordResetBy',"passwordResetMode = 'initial-password'","setEosUserPassword(targetUserId, 'nexowatt')",'passwordlessFirstLoginAllowed = false','initialPasswordRequired: true','accountDisabled','getEosPasswordUserName','setPasswordAsync(userName, password','checkPasswordAsync(userName, password','updateEosAccountMetadata','extendForeignObjectAsync','invalidRequestOrigin'])if(!code.includes(marker))fail(`web marker missing: ${marker}`);
8
+ if(!code.includes('const explicitInstaller = targetAccess.groups.some')||!code.includes('const explicitEndUser = targetAccess.groups.some'))fail('explicit managed-group guard missing');
9
+ const reset=code.slice(code.indexOf('resetEosAccountPassword'),code.indexOf('getEosHistoryInstances'));
10
+ if(reset.includes('setForeignObjectAsync(targetUserId'))fail('reset replaces the complete user object after password write');
28
11
  }
29
- for (const code of [webSource, webBuilt]) {
30
- for (const marker of [
31
- 'getEosManagedAccounts', 'sendEosAccountManagement', 'resetEosAccountPassword',
32
- '/nexowatt/account/manage', '/nexowatt/account/reset', 'x-nexowatt-eos-account-reset',
33
- 'canResetInstaller', 'canResetEndUser', "targetUserId === 'system.user.admin'", 'targetUserId === access.userId',
34
- "access.role === 'installer' && explicitlyManagedRole !== 'enduser'",
35
- 'explicit member of a managed EOS installer/end-user group', 'passwordResetAt', 'passwordResetBy',
36
- "passwordResetMode = 'initial-password'", "setEosUserPassword(targetUserId, 'nexowatt')", 'passwordlessFirstLoginAllowed = false', 'initialPasswordRequired: true', 'accountDisabled',
37
- ]) if (!code.includes(marker)) fail(`web account marker missing: ${marker}`);
38
- if (!code.includes('const explicitInstaller = targetAccess.groups.some')) fail('reset target is not restricted to explicit installer membership');
39
- if (!code.includes('const explicitEndUser = targetAccess.groups.some')) fail('reset target is not restricted to explicit end-user membership');
40
- }
41
-
42
- for (const marker of [
43
- 'v91-account-management-under-access-rights', "['admin', 'installer']", 'nexowatt/account/manage',
44
- 'nexowatt/account/reset', 'X-NexoWatt-EOS-Account-Reset', 'data.canResetInstaller === true',
45
- "account.role === 'enduser'", 'Admin/Service', 'Installateure', 'NEXOWATT_EOS_ACCOUNT_MANAGEMENT',
46
- 'ensureEntrySurface', "currentRoute() !== 'tab-users'", 'eos-account-management-entry',
47
- ]) if (!sourceUi.includes(marker)) fail(`account UI marker missing: ${marker}`);
48
- if (sourceUi.includes("launcher.className = 'eos-account-management-launcher'")) fail('standalone account launcher is still created');
49
- if (sourceUi.includes('new MutationObserver')) fail('account management adds a second broad MutationObserver');
50
- if (sourceUi !== builtUi) fail('account management source/build drift');
51
- if (!roleUi.includes("route === 'tab-users'")) fail('installer cannot reach Zugänge & Rechte');
52
- if (roleUi.includes('NEXOWATT_EOS_ACCOUNT_MANAGEMENT.open')) fail('role overview opens account management outside Zugänge & Rechte');
53
- if (!index.includes(`eos-account-management.js?v=${shellTag}`)) fail('account management asset is not active with the current cache tag');
54
- for (const marker of [
55
- '.eos-account-management-entry', '.eos-account-management-overlay', '.eos-account-management-dialog',
56
- '.eos-account-row', '.eos-account-reset', 'html.eos-account-page-installer',
57
- ]) if (!css.includes(marker)) fail(`account management CSS marker missing: ${marker}`);
58
-
59
- if (bad) process.exit(1);
60
- console.log('[NexoWatt EOS account management] OK');
12
+ for(const marker of ['v98-account-management-real-password-write',"['admin', 'installer']",'nexowatt/account/manage','nexowatt/account/reset','X-NexoWatt-EOS-Account-Reset','data.canResetInstaller === true',"account.role === 'enduser'",'Startpasswort „nexowatt“','accountErrorText','invalidRequestOrigin','credentials: \'include\'','NEXOWATT_EOS_ACCOUNT_MANAGEMENT','ensureEntrySurface',"currentRoute() !== 'tab-users'",'eos-account-management-entry'])if(!sourceUi.includes(marker))fail(`account UI marker missing: ${marker}`);
13
+ if(sourceUi.includes("launcher.className = 'eos-account-management-launcher'"))fail('standalone account launcher remains');
14
+ if(sourceUi.includes('new MutationObserver'))fail('account management adds a second broad MutationObserver');
15
+ if(sourceUi!==builtUi)fail('account UI source/build drift');
16
+ if(!roleUi.includes("route === 'tab-users'"))fail('installer cannot reach access page');
17
+ if(!index.includes(`eos-account-management.js?v=${shellTag}`))fail('account asset cache tag mismatch');
18
+ for(const marker of ['.eos-account-management-entry','.eos-account-management-overlay','.eos-account-management-dialog','.eos-account-row','.eos-account-reset'])if(!css.includes(marker))fail(`account CSS missing: ${marker}`);
19
+ if(bad)process.exit(1);console.log('[NexoWatt EOS account management] OK');