iobroker.eos-admin 7.9.93 → 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 (45) hide show
  1. package/INSTALL_TEST_V7.9.95.md +15 -0
  2. package/NEXOWATT_EOS_BUILD_INFO.json +23 -18
  3. package/NEXOWATT_EOS_STABLE_7.9.94.md +16 -0
  4. package/{PUBLISH_STABLE_V7.9.93.md → PUBLISH_STABLE_V7.9.95.md} +2 -5
  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/assets/Adapters-B5_jQ7DE-v84.js +1 -1
  9. package/adminWww/css/nexowatt-native-shell.css +4 -156
  10. package/adminWww/index.html +13 -11
  11. package/adminWww/js/eos-account-management.js +1 -1
  12. package/adminWww/js/eos-assistant.js +5 -556
  13. package/adminWww/js/eos-basic-settings.js +1 -1
  14. package/adminWww/js/eos-branding-sanitizer.js +1 -3
  15. package/adminWww/js/eos-native-security.js +1 -1
  16. package/adminWww/js/eos-policy-client.js +1 -1
  17. package/adminWww/js/eos-role-bootstrap.js +2 -2
  18. package/adminWww/js/eos-role-ui.js +1 -1
  19. package/adminWww/js/nexowatt-native-shell.js +1 -1
  20. package/adminWww/static/js/nexowatt-stable-login.js +79 -0
  21. package/build/lib/chat/assistantKnowledge.js +6 -21
  22. package/build/lib/chat/chatOrchestrator.js +38 -33
  23. package/build/lib/chat/mcpClientManager.js +1 -1
  24. package/build/lib/nexowattStableAuth.js +74 -0
  25. package/build/lib/web.js +24 -7
  26. package/build/main.js +123 -308
  27. package/io-package.json +20 -16
  28. package/package.json +23 -32
  29. package/INSTALL_TEST_V7.9.93.md +0 -12
  30. package/README_STABILITY_V7.9.93.md +0 -15
  31. package/RELEASE_ACCEPTANCE_V7.9.93.md +0 -15
  32. package/RELEASE_NOTES_V7.9.93.md +0 -10
  33. package/tools/nexowatt-account-management-selftest.cjs +0 -61
  34. package/tools/nexowatt-clean-legacy-runtime.cjs +0 -147
  35. package/tools/nexowatt-ensure-release-defaults.cjs +0 -72
  36. package/tools/nexowatt-generate-eos-admin-repo-entry.cjs +0 -37
  37. package/tools/nexowatt-generate-repo-entry.cjs +0 -3
  38. package/tools/nexowatt-internal-reserve-selftest.cjs +0 -57
  39. package/tools/nexowatt-login-layout-selftest.cjs +0 -62
  40. package/tools/nexowatt-modern-ui-selftest.cjs +0 -50
  41. package/tools/nexowatt-patch-repo.cjs +0 -54
  42. package/tools/nexowatt-publish-channel-guard.cjs +0 -58
  43. package/tools/nexowatt-release-defaults-selftest.cjs +0 -29
  44. package/tools/nexowatt-runtime-cleanup-selftest.cjs +0 -47
  45. package/tools/nexowatt-validate-package.cjs +0 -246
@@ -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
+ })();
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Curated "wiki" of NexoWatt EOS knowledge that is injected into the assistant's system prompt.
3
+ * Curated "wiki" of ioBroker how-to knowledge that is injected into the assistant's system prompt.
4
4
  *
5
5
  * This is meant to grow over time: add a new {@link KnowledgeSection} to {@link KNOWLEDGE_SECTIONS}
6
6
  * for each task the assistant should know how to guide the user through. Keep entries short and
@@ -10,21 +10,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.KNOWLEDGE_SECTIONS = void 0;
11
11
  exports.renderAssistantKnowledge = renderAssistantKnowledge;
12
12
  exports.KNOWLEDGE_SECTIONS = [
13
- {
14
- title: 'NexoWatt EOS product areas and role-aware support',
15
- body: [
16
- 'EOS Assist can answer questions about the live NexoWatt EOS system: Overview/Cockpit, Modules,',
17
- 'Services, Datapoints, Structure (rooms/functions), System logs, Access & Rights, NexoWatt EOS',
18
- 'applications, System hosts, Files and NexoWatt Backup. It can also inspect installed modules,',
19
- 'running services, object/state metadata, current values, device assignments, logs and history',
20
- 'through the available read tools. Always query real system data before making a system-specific claim.',
21
- 'Respect the authenticated role: Admin/Service may receive full system and service detail;',
22
- 'Installers may receive commissioning and troubleshooting detail available to their account;',
23
- 'End users receive only their permitted Smart Home, display and operating information.',
24
- 'Internal package names and compatibility identifiers are technical details; use NexoWatt EOS product',
25
- 'terminology in normal answers and show technical IDs only when they are required for a precise action.',
26
- ].join(' '),
27
- },
28
13
  {
29
14
  title: 'Open an adapter instance configuration',
30
15
  body: [
@@ -212,7 +197,7 @@ exports.KNOWLEDGE_SECTIONS = [
212
197
  {
213
198
  title: 'Run a script in the javascript adapter (run_javascript)',
214
199
  body: [
215
- 'To run logic WITH the full NexoWatt EOS scripting API, use run_javascript (act mode). It sends the',
200
+ 'To run logic WITH the full ioBroker scripting API, use run_javascript (act mode). It sends the',
216
201
  'code to the `javascript` adapter and runs it ONCE (not saved), returning { ok, error, output,',
217
202
  'logs }. Choose `language` "js" (default) or "ts". Inside the script the usual API is available:',
218
203
  '`getState("id").val`, `setState("id", value)`, `on({id, change:"ne"}, …)`, `schedule(...)`,',
@@ -225,10 +210,10 @@ exports.KNOWLEDGE_SECTIONS = [
225
210
  ].join('\n'),
226
211
  },
227
212
  {
228
- title: 'Automation options in NexoWatt EOS — pick the right tool',
213
+ title: 'Automation options in ioBroker — pick the right tool',
229
214
  body: [
230
- 'NexoWatt EOS can automate from simple to fully programmable. IMPORTANT: every option below needs a',
231
- 'SEPARATE adapter that is NOT part of a default NexoWatt EOS installation — `scenes`, `scheduler`,',
215
+ 'ioBroker can automate from simple to fully programmable. IMPORTANT: every option below needs a',
216
+ 'SEPARATE adapter that is NOT part of a default ioBroker installation — `scenes`, `scheduler`,',
232
217
  '`fullcalendar` and `javascript` (which provides Blockly, JavaScript and TypeScript). Before you',
233
218
  'recommend or use one, CHECK whether it is installed (list_adapters, and list_instances for a',
234
219
  'running instance). If it is missing, tell the user it must be installed first and offer',
@@ -286,7 +271,7 @@ exports.KNOWLEDGE_SECTIONS = [
286
271
  ].join('\n'),
287
272
  },
288
273
  {
289
- title: 'NexoWatt EOS JavaScript API (cheat-sheet for scripts and run_javascript)',
274
+ title: 'ioBroker JavaScript adapter API (cheat-sheet for scripts and run_javascript)',
290
275
  body: [
291
276
  'When you WRITE a script (for the Scripts tab or for run_javascript), use the ioBroker javascript',
292
277
  'API — it is NOT plain Node.js. Key functions:',
@@ -76,48 +76,53 @@ class ChatOrchestrator {
76
76
  this.mcp = mcp;
77
77
  this.adapter = adapter;
78
78
  }
79
- /** System prompt describing EOS Assist, tool usage and the authenticated role. */
80
- buildSystemPrompt(language, mode, uiContext, accessRole = 'enduser') {
81
- const roleText = accessRole === 'admin'
82
- ? 'Admin / NexoWatt Service with full service visibility'
83
- : accessRole === 'installer'
84
- ? 'Installer with commissioning and troubleshooting visibility'
85
- : 'End user with only explicitly permitted Smart Home and operating visibility';
79
+ /** System prompt describing the assistant's role, tool usage and the current permission mode. */
80
+ buildSystemPrompt(language, mode, uiContext) {
86
81
  const lines = [
87
- 'You are EOS Assist inside NexoWatt EOS.',
88
- 'Your job is to answer questions about the user\'s ACTUAL EOS system and guide them through the',
89
- 'NexoWatt EOS interface. You cover all settings and product areas: Cockpit/Overview, Modules,',
90
- 'Services, Datapoints, Structure (rooms/functions), System logs, Access & Rights, EOS applications,',
91
- 'System hosts, Files, NexoWatt Backup, licenses, devices, communication protocols and diagnostics.',
82
+ 'You are the ioBroker Admin Assistant, an in-app helper inside the ioBroker admin web UI.',
83
+ 'You help the user operate their ioBroker installation: explain how to do things in the UI,',
84
+ 'recommend which adapter to use for a device or service, and answer questions about the',
85
+ "user's actual system (their states, objects, devices grouped by room/function, installed",
86
+ 'adapters, running instances, logs and historical values).',
92
87
  '',
93
- `Authenticated role: ${roleText}.`,
94
- 'Never reveal data or instructions outside that role. The tool layer enforces ACLs; if data is not',
95
- 'available, state that it is not available to the current account instead of guessing.',
88
+ 'KEY CAPABILITY: you can navigate the admin UI yourself. When the user asks you to OPEN or GO to',
89
+ 'something, call the navigate_admin_ui tool with the hash route — it opens the tab OR the specific',
90
+ 'dialog immediately (no confirmation, it only changes the view), e.g.',
91
+ '{"hash":"#tab-hosts/base-settings/system.host.MSI"}. Otherwise (just pointing the user somewhere)',
92
+ 'offer a clickable Markdown hash link, e.g. [Base settings of MSI](#tab-hosts/base-settings/system.host.MSI).',
93
+ 'NEVER say a dialog can only be opened manually or that deep-linking a dialog is not possible.',
96
94
  '',
97
- 'Rules:',
98
- '- For every system-specific question, use the available read tools and base the answer on their results.',
99
- '- Never invent installed modules, running services, settings, datapoint values, devices or errors.',
100
- '- Use NexoWatt EOS product terminology in normal answers. Do not present the underlying platform name',
101
- ' as the product. Technical package IDs or command names may appear only in `code` when strictly needed.',
102
- '- Explain results clearly and practically. Use short lists or tables for multiple devices or values.',
103
- '- Object and state IDs are case-sensitive; show necessary IDs in `monospace`.',
104
- '- You may navigate the EOS interface with navigate_admin_ui when the user explicitly asks to open a',
105
- ' page. Use these EOS routes: Cockpit `#tab-intro`, Modules `#tab-adapters`, Services `#tab-instances`,',
106
- ' Datapoints `#tab-objects`, Structure `#tab-enums`, System logs `#tab-logs`, Access & Rights',
107
- ' `#tab-users`, and instance configuration `#tab-instances/config/<module>.<instance>`.',
108
- '- Do not claim an action was completed unless a tool result confirms it.',
109
- `- Answer in the user\'s language. The current EOS language is "${language}".`,
95
+ 'Guidelines:',
96
+ '- Use the available tools to look up real data from THIS ioBroker system instead of guessing.',
97
+ ' E.g. to list devices in a room call list_devices with the room name; to read a value call',
98
+ ' get_states; to find objects call search_objects.',
99
+ '- Base your answer on the tool results. If a tool returns an error or nothing, say so briefly',
100
+ ' and suggest what the user could check.',
101
+ '- Be concise and practical. Use short lists or tables when listing devices or states.',
102
+ '- Object IDs are case-sensitive (e.g. "hue.0.lights.1.on"); show them in `monospace`.',
103
+ '- IMPORTANT — this admin version SUPPORTS deep links that open specific DIALOGS directly,',
104
+ ' not just tabs. It is FALSE to say a dialog "can only be opened with the gear/cog icon" or',
105
+ ' that deep-linking a dialog is "technically not possible" — never say that. Instead always',
106
+ ' offer a clickable Markdown hash link and let the USER click it; do NOT claim you opened or',
107
+ ' navigated anywhere yourself. Examples: [Instances](#tab-instances), [Objects](#tab-objects),',
108
+ ' [hue.0 config](#tab-instances/config/hue.0), [edit this object](#tab-objects/edit/<id>),',
109
+ " and a host's base-settings dialog, e.g. MSI:",
110
+ ' [Base settings of MSI](#tab-hosts/base-settings/system.host.MSI). The exact dialog routes are',
111
+ ' listed in the knowledge below. Prefer these links over the navigate_admin_ui tool.',
112
+ `- Answer in the user's language. The admin UI language is currently "${language}".`,
110
113
  ];
111
114
  if (mode === 'act') {
112
- lines.push('- Changes are possible only through explicit write/action tools and confirmation. Propose the', ' smallest safe action and respect all EOS safety and role restrictions.');
115
+ lines.push('- You may change things using the write/action tools (set_state, create_user_state,', ' extend_object, assign_to_enums, install_adapter, run_command, run_node_script). Every such', ' call is shown to the user for explicit confirmation before it runs, so propose the smallest', ' sensible action.', '- create_user_state only works in "0_userdata.0." or "javascript.<n>." — never elsewhere.', '- extend_object merges common/native into an existing object — use it to start/stop an', ' instance (common.enabled), change instance settings (native) or set enum members.', '- assign_to_enums adds objects to room/function enums (add-only) — use it to sort devices.', '- run_command runs an ioBroker CLI command and streams its output to the user; use it for', ' maintenance actions (restart, upload, fix, ...), not to fetch data.', '- run_node_script executes a short Node.js script on the host to CHECK things (network, files,', ' environment). Prefer read-only checks, keep it short, and put results in console.log. Never', ' use it for destructive operations.', '- run_javascript runs a one-off JS/TS script INSIDE the javascript adapter (full ioBroker API:', ' on/getState/setState/schedule/sendTo). Use it to test automation logic or do an API-based', ' check/action; it is not saved. Use run_node_script instead for plain OS checks (no ioBroker API).');
113
116
  }
114
117
  else {
115
- lines.push('- This header assistant is READ-ONLY. It may inspect and explain the system, but it must not', ' change states, objects, settings, services, modules, passwords or backups.');
118
+ lines.push('- You currently have READ-ONLY access: you cannot change states, create objects or install', ' adapters. When a change is needed, explain the exact steps the user should take in the UI.');
116
119
  }
117
120
  if (uiContext?.hash) {
118
- lines.push('', `The user is currently on EOS route \`${uiContext.hash}\`. Consider this context.`);
121
+ lines.push('', `The user is currently in the admin UI at the route \`${uiContext.hash}\`. Take this into ` +
122
+ "account — don't tell them to open a tab/page they are already on, and refer to where " +
123
+ 'they are when helpful.');
119
124
  }
120
- lines.push('', 'NexoWatt EOS knowledge:', (0, assistantKnowledge_1.renderAssistantKnowledge)());
125
+ lines.push('', 'ioBroker how-to knowledge (use this to guide the user precisely):', (0, assistantKnowledge_1.renderAssistantKnowledge)());
121
126
  return { role: 'system', content: lines.join('\n') };
122
127
  }
123
128
  /** Assemble the tool list offered to the model for the given mode. */
@@ -149,7 +154,7 @@ class ChatOrchestrator {
149
154
  ? { ...message, content: parseTextToolCalls(message.content).content }
150
155
  : message);
151
156
  if (!messages.some(message => message.role === 'system')) {
152
- messages.unshift(this.buildSystemPrompt(language, mode, params.uiContext, params.accessRole));
157
+ messages.unshift(this.buildSystemPrompt(language, mode, params.uiContext));
153
158
  }
154
159
  const newMessages = [];
155
160
  const steps = [];
@@ -44,7 +44,7 @@ class McpClientManager {
44
44
  language: this.options.language,
45
45
  allowSetState: this.options.allowSetState ?? false,
46
46
  allowObjectChange: this.options.allowObjectChange ?? false,
47
- clientName: 'NexoWatt EOS Assist',
47
+ clientName: 'ioBroker.admin chat helper',
48
48
  clientVersion: '1.0.0',
49
49
  })
50
50
  .then(mcp => {
@@ -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() {