iobroker.eos-admin 7.9.99 → 7.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{INSTALL_TEST_V7.9.99.md → INSTALL_TEST_V7.10.0.md} +2 -2
- package/{MERGE_UPDATE_README_V7.9.99.md → MERGE_UPDATE_README_V7.10.0.md} +1 -1
- package/NEXOWATT_EOS_BUILD_INFO.json +21 -21
- package/{PUBLISH_STABLE_V7.9.99.md → PUBLISH_STABLE_V7.10.0.md} +2 -2
- package/README_STABILITY_V7.10.0.md +3 -0
- package/{RELEASE_ACCEPTANCE_V7.9.99.md → RELEASE_ACCEPTANCE_V7.10.0.md} +1 -1
- package/RELEASE_NOTES_V7.10.0.md +17 -0
- package/adminWww/assets/bootstrap-COulQZax-v84.js +1 -1
- package/adminWww/css/eos-auto-update.css +4 -0
- package/adminWww/css/eos-ems-overview.css +1 -1
- package/adminWww/css/nexowatt-native-shell.css +432 -2
- package/adminWww/index.html +21 -19
- package/adminWww/js/eos-account-management.js +1 -1
- package/adminWww/js/eos-assistant.js +1 -1
- package/adminWww/js/eos-auto-update.js +78 -0
- package/adminWww/js/eos-ems-overview.js +1 -1
- package/adminWww/js/eos-release-watch.js +1 -1
- package/adminWww/js/eos-role-bootstrap.js +69 -12
- package/adminWww/js/eos-role-ui.js +2 -2
- package/adminWww/js/nexowatt-native-shell.js +1 -1
- package/adminWww/static/js/nexowatt-stable-login.js +70 -43
- package/adminWww/static/js/nexowatt-stable-v7100.js +111 -0
- package/build/lib/eosAutoUpdate.js +43 -0
- package/build/lib/eosPassword.js +49 -39
- package/build/lib/web.js +142 -19
- package/build/main.js +1 -1
- package/io-package.json +7 -6
- package/package.json +20 -14
- package/src/lib/eosAutoUpdate.ts +320 -0
- package/tools/nexowatt-account-management-selftest.cjs +2 -2
- package/tools/nexowatt-auto-update-selftest.cjs +34 -0
- package/tools/nexowatt-cache-update-selftest.cjs +6 -5
- package/tools/nexowatt-login-layout-selftest.cjs +11 -10
- package/tools/nexowatt-merge-update-selftest.cjs +1 -1
- package/tools/nexowatt-modern-ui-selftest.cjs +1 -1
- package/tools/nexowatt-password-write-selftest.cjs +13 -41
- package/tools/nexowatt-runtime-cleanup-selftest.cjs +6 -4
- package/tools/nexowatt-stable-v7100-selftest.cjs +17 -0
- package/tools/nexowatt-sync-release-version.cjs +4 -1
- package/tools/nexowatt-ui-overview-runtime-selftest.cjs +1 -1
- package/tools/nexowatt-validate-package.cjs +11 -11
- package/README_STABILITY_V7.9.99.md +0 -18
- package/RELEASE_NOTES_V7.9.99.md +0 -9
- package/adminWww/static/js/nexowatt-stable-v99.js +0 -33
- package/tools/nexowatt-stable-v99-selftest.cjs +0 -25
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v7100-first-login-password-persisted';
|
|
5
5
|
const script = document.currentScript || document.querySelector('script[src*="eos-role-bootstrap.js"]');
|
|
6
6
|
const entry = script?.dataset?.eosEntry || '';
|
|
7
7
|
let launched = false;
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
required: 'Bitte beide Passwortfelder ausfüllen.', mismatch: 'Die Passwörter stimmen nicht überein.',
|
|
81
81
|
length: min => `Das Passwort muss mindestens ${min} Zeichen lang sein.`, complexity: 'Groß-/Kleinbuchstabe, Zahl und Sonderzeichen sind erforderlich.',
|
|
82
82
|
easy: 'Das Passwort ist zu leicht oder enthält den Kontonamen.', generic: 'Das Passwort konnte nicht gespeichert werden.', success: 'Passwort gespeichert. Die Anmeldung wird neu gestartet …',
|
|
83
|
-
backendErrors: { invalidRequest: 'Die Sicherheitsbestätigung fehlt. Bitte erneut anmelden.', invalidRequestOrigin: 'Die Sitzung oder aufgerufene Adresse hat sich geändert. Bitte erneut anmelden.', passwordApiUnavailable: 'Die ioBroker-Passwortverwaltung ist nicht verfügbar.', passwordNotPersisted: 'Das Passwort wurde
|
|
83
|
+
backendErrors: { invalidRequest: 'Die Sicherheitsbestätigung fehlt. Bitte erneut anmelden.', invalidRequestOrigin: 'Die Sitzung oder aufgerufene Adresse hat sich geändert. Bitte erneut anmelden.', passwordApiUnavailable: 'Die ioBroker-Passwortverwaltung ist nicht verfügbar.', passwordNotPersisted: 'Das Passwort wurde vom ioBroker-Controller nicht gespeichert. Bitte Systemlog prüfen.', passwordVerificationFailed: 'Das neue Passwort wurde geschrieben, aber nicht als gültige Anmeldung bestätigt.', passwordMetadataNotPersisted: 'Das Passwort wurde gespeichert, aber der Erstanmeldungsstatus konnte nicht abgeschlossen werden.', passwordSetupFailed: 'Das Passwort konnte serverseitig nicht gespeichert werden. Bitte das EOS-Systemlog prüfen.', passwordSetupTokenExpired: 'Die sichere Passwortfreigabe ist abgelaufen. Bitte erneut anmelden.', passwordSetupTokenMismatch: 'Die Passwortfreigabe gehört nicht zu dieser Sitzung. Bitte erneut anmelden.' },
|
|
84
84
|
securityTitle: 'Zugriff wird geprüft', securityIntro: 'Die sichere Anmeldung wird gerade geprüft. Die Oberfläche bleibt gesperrt, bis die Berechtigungen eindeutig geladen wurden.',
|
|
85
85
|
securityRetry: 'Jetzt erneut prüfen', securityLogout: 'Zur Anmeldung', securityAuto: seconds => `Automatischer neuer Versuch in ${seconds} Sekunden …`,
|
|
86
86
|
claimLauncher: 'Erstanmeldung', claimTitle: 'Konto erstmals aktivieren',
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
required: 'Please complete both password fields.', mismatch: 'The passwords do not match.',
|
|
101
101
|
length: min => `The password must contain at least ${min} characters.`, complexity: 'Upper/lower case, a number and a special character are required.',
|
|
102
102
|
easy: 'The password is too easy or contains the account name.', generic: 'The password could not be saved.', success: 'Password saved. Restarting sign-in …',
|
|
103
|
-
backendErrors: { invalidRequest: 'The security confirmation is missing. Sign in again.', invalidRequestOrigin: 'The session or address changed. Sign in again.', passwordApiUnavailable: 'The ioBroker password service is unavailable.', passwordNotPersisted: 'The
|
|
103
|
+
backendErrors: { invalidRequest: 'The security confirmation is missing. Sign in again.', invalidRequestOrigin: 'The session or address changed. Sign in again.', passwordApiUnavailable: 'The ioBroker password service is unavailable.', passwordNotPersisted: 'The ioBroker controller did not persist the password. Check the system log.', passwordVerificationFailed: 'The new password was written but could not be verified as a valid login.', passwordMetadataNotPersisted: 'The password was saved, but the first-login status could not be completed.', passwordSetupFailed: 'The password could not be saved on the server. Check the EOS system log.', passwordSetupTokenExpired: 'The secure password setup ticket expired. Sign in again.', passwordSetupTokenMismatch: 'The password setup ticket does not belong to this session. Sign in again.' },
|
|
104
104
|
securityTitle: 'Checking access', securityIntro: 'The secure sign-in is being verified. The interface stays locked until the permissions are loaded unambiguously.',
|
|
105
105
|
securityRetry: 'Check again now', securityLogout: 'Return to sign-in', securityAuto: seconds => `Retrying automatically in ${seconds} seconds …`,
|
|
106
106
|
claimLauncher: 'First sign-in', claimTitle: 'Activate account for the first time',
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
required: 'Vul beide wachtwoordvelden in.', mismatch: 'De wachtwoorden zijn niet gelijk.',
|
|
121
121
|
length: min => `Het wachtwoord moet minimaal ${min} tekens bevatten.`, complexity: 'Hoofdletter, kleine letter, cijfer en speciaal teken zijn verplicht.',
|
|
122
122
|
easy: 'Het wachtwoord is te eenvoudig of bevat de accountnaam.', generic: 'Het wachtwoord kon niet worden opgeslagen.', success: 'Wachtwoord opgeslagen. De aanmelding wordt opnieuw gestart …',
|
|
123
|
-
backendErrors: { invalidRequest: 'De beveiligingsbevestiging ontbreekt. Meld opnieuw aan.', invalidRequestOrigin: 'De sessie of het adres is gewijzigd. Meld opnieuw aan.', passwordApiUnavailable: 'De ioBroker-wachtwoordservice is niet beschikbaar.', passwordNotPersisted: '
|
|
123
|
+
backendErrors: { invalidRequest: 'De beveiligingsbevestiging ontbreekt. Meld opnieuw aan.', invalidRequestOrigin: 'De sessie of het adres is gewijzigd. Meld opnieuw aan.', passwordApiUnavailable: 'De ioBroker-wachtwoordservice is niet beschikbaar.', passwordNotPersisted: 'De ioBroker-controller heeft het wachtwoord niet opgeslagen. Controleer het systeemlogboek.', passwordVerificationFailed: 'Het nieuwe wachtwoord is geschreven maar kon niet als geldige aanmelding worden bevestigd.', passwordMetadataNotPersisted: 'Het wachtwoord is opgeslagen, maar de eerste-aanmeldstatus kon niet worden afgerond.', passwordSetupFailed: 'Het wachtwoord kon server-side niet worden opgeslagen. Controleer het EOS-systeemlogboek.', passwordSetupTokenExpired: 'De beveiligde wachtwoordtoestemming is verlopen. Meld opnieuw aan.', passwordSetupTokenMismatch: 'De wachtwoordtoestemming hoort niet bij deze sessie. Meld opnieuw aan.' },
|
|
124
124
|
securityTitle: 'Toegang wordt gecontroleerd', securityIntro: 'De veilige aanmelding wordt gecontroleerd. De interface blijft vergrendeld totdat de rechten eenduidig zijn geladen.',
|
|
125
125
|
securityRetry: 'Nu opnieuw controleren', securityLogout: 'Terug naar aanmelden', securityAuto: seconds => `Automatisch opnieuw proberen over ${seconds} seconden …`,
|
|
126
126
|
claimLauncher: 'Eerste aanmelding', claimTitle: 'Account voor het eerst activeren',
|
|
@@ -133,11 +133,32 @@
|
|
|
133
133
|
},
|
|
134
134
|
};
|
|
135
135
|
|
|
136
|
+
const waitForPasswordSocket = async (timeoutMs = 10000) => {
|
|
137
|
+
const started = Date.now();
|
|
138
|
+
while (Date.now() - started < timeoutMs) {
|
|
139
|
+
const socket = window.NEXOWATT_EOS_ADMIN_SOCKET || window.NEXOWATT_EOS_SOCKET;
|
|
140
|
+
if (socket && typeof socket.changePassword === 'function') return socket;
|
|
141
|
+
await new Promise(resolve => window.setTimeout(resolve, 120));
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const postWithTimeout = async (url, options, timeoutMs = 25000) => {
|
|
147
|
+
const controller = new AbortController();
|
|
148
|
+
const timer = window.setTimeout(() => controller.abort(), timeoutMs);
|
|
149
|
+
try {
|
|
150
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
151
|
+
} finally {
|
|
152
|
+
window.clearTimeout(timer);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
136
156
|
const showFirstLoginPassword = (policy, base) => {
|
|
137
157
|
const t = translations[language()];
|
|
138
158
|
const passwordSetup = policy?.passwordSetup || {};
|
|
139
159
|
const minLength = Number(passwordSetup.minLength || 10);
|
|
140
160
|
const userName = String(passwordSetup.userName || policy?.user || '').replace(/^system\.user\./, '') || 'user';
|
|
161
|
+
const setupToken = String(passwordSetup.setupToken || '');
|
|
141
162
|
const endpoint = new URL('nexowatt/account/first-password', base).href;
|
|
142
163
|
const logoutUrl = new URL('logout', base).href;
|
|
143
164
|
window.NEXOWATT_EOS_PASSWORD_SETUP_ACTIVE = true;
|
|
@@ -194,10 +215,31 @@
|
|
|
194
215
|
if (!/[a-z]/.test(password.value) || !/[A-Z]/.test(password.value) || !/\d/.test(password.value) || !/[^A-Za-z0-9]/.test(password.value)) return setStatus(t.complexity, 'error');
|
|
195
216
|
submit.disabled = true; submit.textContent = t.saving; setStatus('', '');
|
|
196
217
|
try {
|
|
197
|
-
const
|
|
218
|
+
const socket = await waitForPasswordSocket();
|
|
219
|
+
let passwordAlreadyWritten = false;
|
|
220
|
+
if (socket) {
|
|
221
|
+
try {
|
|
222
|
+
// Use the same native path as the working ioBroker user editor.
|
|
223
|
+
await socket.changePassword(`system.user.${userName}`, password.value);
|
|
224
|
+
passwordAlreadyWritten = true;
|
|
225
|
+
} catch (socketError) {
|
|
226
|
+
console.warn('[NexoWatt EOS] Native password write failed; using server fallback:', socketError);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const response = await postWithTimeout(endpoint, {
|
|
198
230
|
method: 'POST', credentials: 'same-origin', cache: 'no-store',
|
|
199
|
-
headers: {
|
|
200
|
-
|
|
231
|
+
headers: {
|
|
232
|
+
'Content-Type': 'application/json',
|
|
233
|
+
Accept: 'application/json',
|
|
234
|
+
'X-NexoWatt-EOS-First-Login': '1',
|
|
235
|
+
...(setupToken ? { 'X-NexoWatt-EOS-Password-Setup-Token': setupToken } : {}),
|
|
236
|
+
},
|
|
237
|
+
body: JSON.stringify({
|
|
238
|
+
password: password.value,
|
|
239
|
+
passwordRepeat: repeat.value,
|
|
240
|
+
setupToken,
|
|
241
|
+
passwordAlreadyWritten,
|
|
242
|
+
}),
|
|
201
243
|
});
|
|
202
244
|
const data = await response.json().catch(() => ({}));
|
|
203
245
|
if (!response.ok || data.error) {
|
|
@@ -208,7 +250,14 @@
|
|
|
208
250
|
window.setTimeout(() => window.location.replace(logoutUrl), 650);
|
|
209
251
|
} catch (error) {
|
|
210
252
|
submit.disabled = false; submit.textContent = t.submit;
|
|
211
|
-
|
|
253
|
+
const message = error?.name === 'AbortError'
|
|
254
|
+
? (language() === 'de'
|
|
255
|
+
? 'Das Speichern hat zu lange gedauert. Bitte erneut versuchen.'
|
|
256
|
+
: language() === 'nl'
|
|
257
|
+
? 'Opslaan duurde te lang. Probeer opnieuw.'
|
|
258
|
+
: 'Saving timed out. Please try again.')
|
|
259
|
+
: error?.message || t.generic;
|
|
260
|
+
setStatus(message, 'error');
|
|
212
261
|
}
|
|
213
262
|
});
|
|
214
263
|
window.setTimeout(() => password.focus(), 0);
|
|
@@ -318,9 +367,17 @@
|
|
|
318
367
|
const loginElements = () => {
|
|
319
368
|
const username = document.querySelector('input#username,input[name="username"]');
|
|
320
369
|
const password = document.querySelector('input#password,input[name="password"]');
|
|
321
|
-
const
|
|
322
|
-
const
|
|
323
|
-
|
|
370
|
+
const form = username?.closest?.('form') || password?.closest?.('form');
|
|
371
|
+
const card = form?.closest?.('.MuiPaper-root')
|
|
372
|
+
|| username?.closest?.('.MuiPaper-root')
|
|
373
|
+
|| password?.closest?.('.MuiPaper-root')
|
|
374
|
+
|| form
|
|
375
|
+
|| username?.closest?.('main,section')
|
|
376
|
+
|| password?.closest?.('main,section');
|
|
377
|
+
const submit = form?.querySelector?.('button[type="submit"]')
|
|
378
|
+
|| card?.querySelector?.('button[type="submit"]')
|
|
379
|
+
|| Array.from(card?.querySelectorAll?.('button') || []).find(button => /anmelden|login|sign in|aanmelden/.test(normalize(button.textContent || '')));
|
|
380
|
+
return { username, password, form, card, submit };
|
|
324
381
|
};
|
|
325
382
|
const statusNode = card => card?.querySelector?.('.eos-login-first-status');
|
|
326
383
|
const directChildWithin = (ancestor, node) => {
|
|
@@ -503,7 +560,7 @@
|
|
|
503
560
|
const { username, password, card, submit } = loginElements();
|
|
504
561
|
if (!username || !password || !card) return;
|
|
505
562
|
card.classList.add('eos-login-card-modern');
|
|
506
|
-
//
|
|
563
|
+
// v7100 normal-login first activation: do not add role buttons or a second panel.
|
|
507
564
|
// The account is entered in the existing Loginname field; a blank password is accepted only
|
|
508
565
|
// after the backend confirms that this exact managed account is eligible.
|
|
509
566
|
card.querySelectorAll('.eos-login-role-selector').forEach(element => element.remove());
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v7100-native-overview-ems-live-reserve-filter';
|
|
5
5
|
window.NEXOWATT_EOS_ROLE_UI_VERSION = VERSION;
|
|
6
6
|
|
|
7
7
|
const state = {
|
|
@@ -278,7 +278,7 @@
|
|
|
278
278
|
});
|
|
279
279
|
};
|
|
280
280
|
const ensureSafeOverview = () => safe(() => {
|
|
281
|
-
// 7.
|
|
281
|
+
// 7.10.0: never cover the real Admin Intro with a second tile surface.
|
|
282
282
|
// Installer and end-user use the native card component; security remains
|
|
283
283
|
// enforced by route/instance filters and backend permissions.
|
|
284
284
|
removeSafeOverview();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v7100-nexowatt-native-shell-native-overview';
|
|
5
5
|
const previous = window.NEXOWATT_NATIVE_SHELL;
|
|
6
6
|
if (previous?.version === VERSION) return;
|
|
7
7
|
previous?.destroy?.();
|
|
@@ -1,45 +1,72 @@
|
|
|
1
|
-
/* NexoWatt EOS stable runtime
|
|
1
|
+
/* NexoWatt EOS stable login runtime 7.10.0 */
|
|
2
2
|
(() => {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
3
|
+
'use strict';
|
|
4
|
+
const VERSION = 'v7100-clean-login';
|
|
5
|
+
const MANAGED_USERS = new Set(['installer', 'guest', 'user']);
|
|
6
|
+
const ASSIST_TEXT = /EOS\s*Assist/i;
|
|
7
|
+
const PASSWORDLESS_TEXT = /Erstanmeldung\s+ohne\s+Passwort|first\s+sign-?in\s+without\s+password/i;
|
|
8
|
+
const isLogin = () => /(?:\?|&)login(?:[=&]|$)/i.test(location.search) || /\/login(?:[/?#]|$)/i.test(location.pathname + location.hash);
|
|
9
|
+
const visible = element => !!element && element.getClientRects().length > 0;
|
|
10
|
+
const findInput = kind => {
|
|
11
|
+
const inputs = [...document.querySelectorAll('input')].filter(visible);
|
|
12
|
+
if (kind === 'password') return inputs.find(input => input.type === 'password');
|
|
13
|
+
return inputs.find(input => input.type !== 'password' && /user|login|name/i.test(`${input.name} ${input.id} ${input.autocomplete} ${input.getAttribute('aria-label') || ''}`)) || inputs.find(input => input.type !== 'password');
|
|
14
|
+
};
|
|
15
|
+
const findSubmit = () => [...document.querySelectorAll('button,input[type="submit"]')].find(element => visible(element) && (/anmelden|login|sign in/i.test((element.textContent || element.value || '').trim()) || element.type === 'submit'));
|
|
16
|
+
const normalizeUser = value => String(value || '').trim().toLowerCase().replace(/^system\.user\./, '');
|
|
17
|
+
const normalizeGeometry = () => {
|
|
18
|
+
document.querySelectorAll('[data-nw-login-card],[data-nw-login-shell]').forEach(element => {
|
|
19
|
+
if (!element.classList.contains('MuiPaper-root') && !element.classList.contains('eos-login-card-modern')) {
|
|
20
|
+
element.removeAttribute('data-nw-login-card');
|
|
21
|
+
element.removeAttribute('data-nw-login-shell');
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
for (const input of [findInput('user'), findInput('password')].filter(Boolean)) {
|
|
25
|
+
const control = input.closest('.MuiFormControl-root');
|
|
26
|
+
const label = control?.querySelector('.MuiInputLabel-root');
|
|
27
|
+
if (control && label) {
|
|
28
|
+
const localized = String(label.textContent || input.getAttribute('aria-label') || '').trim();
|
|
29
|
+
if (localized) control.dataset.eosLoginLabel = localized;
|
|
30
|
+
label.classList.add('MuiInputLabel-shrink');
|
|
31
|
+
label.dataset.shrink = 'true';
|
|
32
|
+
label.removeAttribute('aria-hidden');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const sync = () => {
|
|
37
|
+
if (!isLogin() && !document.querySelector('input#username,input[name="username"]')) return;
|
|
38
|
+
document.documentElement.classList.add('nw-eos-login-v7100');
|
|
39
|
+
document.body?.classList.add('nw-eos-login-v7100');
|
|
40
|
+
normalizeGeometry();
|
|
41
|
+
const username = findInput('user');
|
|
42
|
+
const password = findInput('password');
|
|
43
|
+
const submit = findSubmit();
|
|
44
|
+
if (submit && MANAGED_USERS.has(normalizeUser(username?.value)) && !String(password?.value || '')) {
|
|
45
|
+
submit.disabled = true;
|
|
46
|
+
submit.setAttribute('aria-disabled', 'true');
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const hideLegacyUi = () => {
|
|
50
|
+
for (const element of document.querySelectorAll('button,a,[role="button"],div,span')) {
|
|
51
|
+
const value = (element.textContent || '').trim();
|
|
52
|
+
if (!value || value.length > 100) continue;
|
|
53
|
+
if (ASSIST_TEXT.test(value) || PASSWORDLESS_TEXT.test(value)) {
|
|
54
|
+
const target = element.closest('button,a,[role="button"],[data-eos-assist],.eos-assist') || element;
|
|
55
|
+
target.style.setProperty('display', 'none', 'important');
|
|
56
|
+
target.setAttribute('aria-hidden', 'true');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const apply = () => { sync(); hideLegacyUi(); };
|
|
61
|
+
let scheduled = false;
|
|
62
|
+
const schedule = () => {
|
|
63
|
+
if (scheduled) return;
|
|
64
|
+
scheduled = true;
|
|
65
|
+
requestAnimationFrame(() => { scheduled = false; apply(); });
|
|
66
|
+
};
|
|
67
|
+
new MutationObserver(schedule).observe(document.documentElement, { subtree: true, childList: true });
|
|
68
|
+
document.addEventListener('input', schedule, true);
|
|
69
|
+
document.addEventListener('change', schedule, true);
|
|
70
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', apply, { once: true }); else apply();
|
|
71
|
+
window.NEXOWATT_EOS_STABLE_LOGIN = Object.freeze({ version: VERSION, refresh: apply });
|
|
45
72
|
})();
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
'use strict';
|
|
3
|
+
const VERSION = 'v7100-scroll-login-auto-update';
|
|
4
|
+
const MANAGED_USERS = new Set(['installer', 'guest', 'user']);
|
|
5
|
+
const normalizeUser = value => String(value || '').trim().toLowerCase().replace(/^system\.user\./, '');
|
|
6
|
+
const INTRO_STYLE_PROPERTIES = [
|
|
7
|
+
'position', 'top', 'right', 'bottom', 'left', 'width', 'height', 'min-height', 'max-height',
|
|
8
|
+
'margin', 'overflow-x', 'overflow-y', 'overscroll-behavior', 'scrollbar-gutter',
|
|
9
|
+
];
|
|
10
|
+
const isIntroRoute = () => {
|
|
11
|
+
const hash = decodeURIComponent(window.location.hash || '').toLowerCase();
|
|
12
|
+
const explicitTab = hash.match(/(?:^|[#/])(tab-[a-z0-9_-]+)(?:[/?&]|$)/)?.[1] || '';
|
|
13
|
+
if (explicitTab) return explicitTab === 'tab-intro';
|
|
14
|
+
return document.documentElement.classList.contains('eos-route-intro')
|
|
15
|
+
|| !hash
|
|
16
|
+
|| hash === '#'
|
|
17
|
+
|| hash === '#/';
|
|
18
|
+
};
|
|
19
|
+
const bindScrollInput = paper => {
|
|
20
|
+
if (paper.dataset.nexowattScrollBound === 'true') return;
|
|
21
|
+
paper.dataset.nexowattScrollBound = 'true';
|
|
22
|
+
paper.addEventListener('wheel', event => {
|
|
23
|
+
if (!event.deltaY || paper.scrollHeight <= paper.clientHeight + 1) return;
|
|
24
|
+
const before = paper.scrollTop;
|
|
25
|
+
paper.scrollTop += event.deltaY;
|
|
26
|
+
if (paper.scrollTop !== before) event.preventDefault();
|
|
27
|
+
}, { passive: false });
|
|
28
|
+
paper.addEventListener('keydown', event => {
|
|
29
|
+
if (paper.scrollHeight <= paper.clientHeight + 1) return;
|
|
30
|
+
const step = Math.max(120, Math.round(paper.clientHeight * .78));
|
|
31
|
+
if (event.key === 'PageDown') paper.scrollTop += step;
|
|
32
|
+
else if (event.key === 'PageUp') paper.scrollTop -= step;
|
|
33
|
+
else if (event.key === 'Home' && event.ctrlKey) paper.scrollTop = 0;
|
|
34
|
+
else if (event.key === 'End' && event.ctrlKey) paper.scrollTop = paper.scrollHeight;
|
|
35
|
+
else return;
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const disableCockpitScroll = () => {
|
|
40
|
+
const root = document.documentElement;
|
|
41
|
+
root.classList.remove('eos-intro-scroll-enabled');
|
|
42
|
+
const paper = document.getElementById('app-paper');
|
|
43
|
+
if (!paper || paper.dataset.nexowattScrollable !== 'true') return;
|
|
44
|
+
delete paper.dataset.nexowattScrollable;
|
|
45
|
+
for (const property of INTRO_STYLE_PROPERTIES) paper.style.removeProperty(property);
|
|
46
|
+
};
|
|
47
|
+
const ensureCockpitScroll = () => {
|
|
48
|
+
const root = document.documentElement;
|
|
49
|
+
const paper = document.getElementById('app-paper');
|
|
50
|
+
if (!paper || !root.classList.contains('eos-app')) return;
|
|
51
|
+
if (!isIntroRoute()) {
|
|
52
|
+
disableCockpitScroll();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// eos-route-* is owned by the native shell; only add our dedicated scroll flag.
|
|
56
|
+
root.classList.add('eos-intro-scroll-enabled');
|
|
57
|
+
paper.dataset.nexowattScrollable = 'true';
|
|
58
|
+
if (paper.tabIndex < 0) paper.tabIndex = 0;
|
|
59
|
+
const styles = {
|
|
60
|
+
position: 'fixed',
|
|
61
|
+
top: 'var(--nx-content-top, 154px)',
|
|
62
|
+
right: 'var(--nx-page-x, 12px)',
|
|
63
|
+
bottom: '8px',
|
|
64
|
+
left: 'var(--nx-page-x, 12px)',
|
|
65
|
+
width: 'auto',
|
|
66
|
+
height: 'auto',
|
|
67
|
+
minHeight: '0',
|
|
68
|
+
maxHeight: 'none',
|
|
69
|
+
margin: '0',
|
|
70
|
+
overflowX: 'hidden',
|
|
71
|
+
overflowY: 'scroll',
|
|
72
|
+
overscrollBehavior: 'contain',
|
|
73
|
+
scrollbarGutter: 'stable',
|
|
74
|
+
};
|
|
75
|
+
for (const [name, value] of Object.entries(styles)) {
|
|
76
|
+
paper.style.setProperty(name.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`), value, 'important');
|
|
77
|
+
}
|
|
78
|
+
bindScrollInput(paper);
|
|
79
|
+
};
|
|
80
|
+
const apply = () => {
|
|
81
|
+
document.documentElement.classList.add('eos-assist-disabled');
|
|
82
|
+
document.querySelectorAll('.eos-assist-root,.eos-assist-header-root,[data-eos-assist-root],.eos-passwordless-launcher')
|
|
83
|
+
.forEach(element => element.remove());
|
|
84
|
+
const username = document.querySelector('input#username,input[name="username"]');
|
|
85
|
+
const password = document.querySelector('input#password,input[name="password"]');
|
|
86
|
+
const submit = document.querySelector('button[type="submit"],input[type="submit"]');
|
|
87
|
+
if (submit && MANAGED_USERS.has(normalizeUser(username?.value)) && !String(password?.value || '')) {
|
|
88
|
+
submit.disabled = true;
|
|
89
|
+
submit.classList.add('Mui-disabled');
|
|
90
|
+
submit.setAttribute('aria-disabled', 'true');
|
|
91
|
+
}
|
|
92
|
+
ensureCockpitScroll();
|
|
93
|
+
};
|
|
94
|
+
let scheduled = false;
|
|
95
|
+
const schedule = () => {
|
|
96
|
+
if (scheduled) return;
|
|
97
|
+
scheduled = true;
|
|
98
|
+
requestAnimationFrame(() => { scheduled = false; apply(); });
|
|
99
|
+
};
|
|
100
|
+
const start = () => {
|
|
101
|
+
apply();
|
|
102
|
+
new MutationObserver(schedule).observe(document.documentElement, { subtree: true, childList: true });
|
|
103
|
+
document.addEventListener('input', schedule, true);
|
|
104
|
+
document.addEventListener('change', schedule, true);
|
|
105
|
+
window.addEventListener('hashchange', schedule);
|
|
106
|
+
window.addEventListener('resize', schedule, { passive: true });
|
|
107
|
+
window.setInterval(ensureCockpitScroll, 750);
|
|
108
|
+
};
|
|
109
|
+
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', start, { once: true }) : start();
|
|
110
|
+
window.NEXOWATT_EOS_STABLE_V7100 = Object.freeze({ version: VERSION, refresh: apply, ensureCockpitScroll, disableCockpitScroll });
|
|
111
|
+
})();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NexoWattStableUpdateManager = void 0;
|
|
4
|
+
exports.isNexoWattRepositoryEntry = isNexoWattRepositoryEntry;
|
|
5
|
+
const ADMIN_USER = 'system.user.admin';
|
|
6
|
+
const POLICY = 'major';
|
|
7
|
+
const RECONCILE_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
8
|
+
const REPOSITORY_REFRESH_DELAY_MS = 2500;
|
|
9
|
+
const clone = value => JSON.parse(JSON.stringify(value));
|
|
10
|
+
const normalizedName = value => String(value || '').trim().replace(/^system\.adapter\./, '').replace(/\.\d+$/, '');
|
|
11
|
+
const stableVersion = value => /^\d+\.\d+\.\d+$/.test(String(value || '').trim());
|
|
12
|
+
function isNexoWattRepositoryEntry(name, entry) {
|
|
13
|
+
const adapterName = normalizedName(entry?.name || name).toLowerCase();
|
|
14
|
+
if (adapterName === 'eos-admin' || adapterName.startsWith('nexowatt-') || adapterName.startsWith('eos-'))
|
|
15
|
+
return true;
|
|
16
|
+
const evidence = JSON.stringify({ name, adapterName, title: entry?.title, desc: entry?.desc, author: entry?.author, authors: entry?.authors, publisher: entry?.publisher, url: entry?.url, meta: entry?.meta, readme: entry?.readme, extIcon: entry?.extIcon, licenseInformation: entry?.licenseInformation }).toLowerCase();
|
|
17
|
+
return evidence.includes('nexowatt') || evidence.includes('github.com/nexowatt/');
|
|
18
|
+
}
|
|
19
|
+
class NexoWattStableUpdateManager {
|
|
20
|
+
adapter;
|
|
21
|
+
timer;
|
|
22
|
+
reconcilePromise;
|
|
23
|
+
refreshTimer;
|
|
24
|
+
retryTimer;
|
|
25
|
+
constructor(adapter) { this.adapter = adapter; }
|
|
26
|
+
get instanceId() { return `system.adapter.${this.adapter.namespace}`; }
|
|
27
|
+
async start() { const status = await this.reconcile('startup'); if (!this.timer) { this.timer = setInterval(() => void this.reconcile('interval'), RECONCILE_INTERVAL_MS); this.timer.unref?.(); } return status; }
|
|
28
|
+
stop() { if (this.timer) clearInterval(this.timer); if (this.refreshTimer) clearTimeout(this.refreshTimer); if (this.retryTimer) clearTimeout(this.retryTimer); this.timer = undefined; this.refreshTimer = undefined; this.retryTimer = undefined; }
|
|
29
|
+
async getStatus(refresh = false) { if (refresh) return this.reconcile('status'); const instance = await this.adapter.getForeignObjectAsync(this.instanceId); return this.statusFromNative(instance?.native || {}); }
|
|
30
|
+
async setEnabled(enabled) { await this.adapter.extendForeignObjectAsync(this.instanceId, { native: { eosNexoWattAutoUpdate: enabled } }, { user: ADMIN_USER }); return this.reconcile(enabled ? 'enabled' : 'disabled'); }
|
|
31
|
+
async reconcile(reason = 'manual') { if (this.reconcilePromise) return this.reconcilePromise; this.reconcilePromise = this.reconcileInternal(reason).finally(() => { this.reconcilePromise = undefined; }); return this.reconcilePromise; }
|
|
32
|
+
statusFromNative(native) { const state = native.eosNexoWattAutoUpdateState || {}; return { enabled: native.eosNexoWattAutoUpdate !== false, repository: String(state.selectedRepository || ''), managedAdapters: Array.isArray(state.managedAdapters) ? state.managedAdapters.map(String).sort() : [], availableStableAdapters: Array.isArray(state.availableStableAdapters) ? state.availableStableAdapters.map(String).sort() : [], lastSync: Number(state.lastSync || 0), lastRepositoryRefresh: Number(state.lastRepositoryRefresh || 0), error: String(state.error || ''), policy: POLICY, source: 'ioBroker-native-auto-upgrade' }; }
|
|
33
|
+
async reconcileInternal(reason) { const instance = await this.adapter.getForeignObjectAsync(this.instanceId); const native = instance?.native || {}; const enabled = native.eosNexoWattAutoUpdate !== false; const state = clone(native.eosNexoWattAutoUpdateState || {}); try { if (!enabled) return await this.disable(state, reason); return await this.enable(state, reason); } catch (error) { const message = error instanceof Error ? error.message : String(error || 'unknownError'); state.error = message; state.lastSync = Date.now(); await this.persist(false, state); this.adapter.log.warn(`[NexoWatt stable updates] ${message}`); return this.statusFromNative({ eosNexoWattAutoUpdate: enabled, eosNexoWattAutoUpdateState: state }); } }
|
|
34
|
+
async readInstalledAdapters() { const view = await this.adapter.getObjectViewAsync('system', 'adapter', { startkey: 'system.adapter.', endkey: 'system.adapter.\u9999' }, { user: ADMIN_USER }); const result = new Map(); for (const row of view?.rows || []) { const object = row?.value || row?.doc; const name = normalizedName(object?.common?.name || row?.id || object?._id); if (name && object?.common?.version) result.set(name, object); } return result; }
|
|
35
|
+
async readRepositorySelection(systemConfig) { const repositories = await this.adapter.getForeignObjectAsync('system.repositories'); const rows = repositories?.native?.repositories || {}; const active = Array.isArray(systemConfig?.common?.activeRepo) ? systemConfig.common.activeRepo.map(String) : []; const candidates = Object.entries(rows).map(([name, repo]) => { const entries = repo?.json && typeof repo.json === 'object' ? repo.json : {}; const stableNames = Object.entries(entries).filter(([entryName, entry]) => stableVersion(entry?.version) && isNexoWattRepositoryEntry(entryName, entry)).map(([entryName, entry]) => normalizedName(entry?.name || entryName)).filter(Boolean); return { name, entries, stableNames: [...new Set(stableNames)].sort(), activeIndex: active.indexOf(name) }; }).filter(row => row.stableNames.length > 0); if (!candidates.length) throw new Error('nexowattStableRepositoryNotFound'); candidates.sort((a, b) => { const aa = a.activeIndex < 0 ? Number.MAX_SAFE_INTEGER : a.activeIndex; const bb = b.activeIndex < 0 ? Number.MAX_SAFE_INTEGER : b.activeIndex; return aa - bb || b.stableNames.length - a.stableNames.length || a.name.localeCompare(b.name); }); return candidates[0]; }
|
|
36
|
+
async enable(state, reason) { const systemConfig = await this.adapter.getForeignObjectAsync('system.config'); if (!systemConfig?.common) throw new Error('systemConfigUnavailable'); const selected = await this.readRepositorySelection(systemConfig); const installed = await this.readInstalledAdapters(); const managedNames = selected.stableNames.filter(name => installed.has(name)).sort(); if (!managedNames.length) throw new Error('noInstalledNexoWattStableAdapters'); state.previousPolicies ||= {}; for (const name of managedNames) { if (!(name in state.previousPolicies)) { const current = installed.get(name)?.common?.automaticUpgrade; state.previousPolicies[name] = current == null ? null : current; } await this.writeAdapterPolicy(name, POLICY); } const common = systemConfig.common; const activeRepo = Array.isArray(common.activeRepo) ? [...common.activeRepo] : []; const auto = common.adapterAutoUpgrade && typeof common.adapterAutoUpgrade === 'object' ? clone(common.adapterAutoUpgrade) : { repositories: {}, defaultPolicy: 'none' }; auto.repositories = auto.repositories && typeof auto.repositories === 'object' ? { ...auto.repositories } : {}; if (state.previousRepositoryEnabled === undefined) state.previousRepositoryEnabled = Object.prototype.hasOwnProperty.call(auto.repositories, selected.name) ? Boolean(auto.repositories[selected.name]) : null; auto.repositories[selected.name] = true; if (!auto.defaultPolicy) auto.defaultPolicy = 'none'; common.adapterAutoUpgrade = auto; if (activeRepo[0] !== selected.name) { if (!state.previousActiveRepo) state.previousActiveRepo = [...activeRepo]; state.ownsActiveRepoOrder = true; common.activeRepo = [selected.name, ...activeRepo.filter(name => name !== selected.name)]; } await this.adapter.setForeignObjectAsync('system.config', systemConfig, { user: ADMIN_USER }); state.selectedRepository = selected.name; state.availableStableAdapters = selected.stableNames; state.managedAdapters = managedNames; state.lastSync = Date.now(); state.error = ''; await this.persist(true, state); this.scheduleRepositoryRefresh(selected.name, state); this.adapter.log.info(`[NexoWatt stable updates] Enabled for ${managedNames.join(', ')} from repository "${selected.name}" (${reason})`); return this.statusFromNative({ eosNexoWattAutoUpdate: true, eosNexoWattAutoUpdateState: state }); }
|
|
37
|
+
async disable(state, reason) { for (const [name, previous] of Object.entries(state.previousPolicies || {})) { const current = await this.adapter.getForeignObjectAsync(`system.adapter.${normalizedName(name)}`); if (current?.common?.automaticUpgrade === POLICY) await this.writeAdapterPolicy(name, previous); } const systemConfig = await this.adapter.getForeignObjectAsync('system.config'); if (systemConfig?.common) { const auto = systemConfig.common.adapterAutoUpgrade && typeof systemConfig.common.adapterAutoUpgrade === 'object' ? clone(systemConfig.common.adapterAutoUpgrade) : null; if (auto && state.selectedRepository) { auto.repositories = auto.repositories && typeof auto.repositories === 'object' ? { ...auto.repositories } : {}; if (auto.repositories[state.selectedRepository] === true) { if (state.previousRepositoryEnabled === null || state.previousRepositoryEnabled === undefined) delete auto.repositories[state.selectedRepository]; else auto.repositories[state.selectedRepository] = state.previousRepositoryEnabled; } systemConfig.common.adapterAutoUpgrade = auto; } if (state.ownsActiveRepoOrder && Array.isArray(state.previousActiveRepo) && Array.isArray(systemConfig.common.activeRepo) && systemConfig.common.activeRepo[0] === state.selectedRepository) systemConfig.common.activeRepo = [...state.previousActiveRepo]; await this.adapter.setForeignObjectAsync('system.config', systemConfig, { user: ADMIN_USER }); } state.previousPolicies = {}; state.previousRepositoryEnabled = undefined; state.previousActiveRepo = undefined; state.ownsActiveRepoOrder = false; state.managedAdapters = []; state.lastSync = Date.now(); state.error = ''; await this.persist(false, state); this.adapter.log.info(`[NexoWatt stable updates] Disabled and previous policies restored (${reason})`); return this.statusFromNative({ eosNexoWattAutoUpdate: false, eosNexoWattAutoUpdateState: state }); }
|
|
38
|
+
async writeAdapterPolicy(name, policy) { const id = `system.adapter.${normalizedName(name)}`; const object = await this.adapter.getForeignObjectAsync(id); if (!object?.common) return; if (policy === null) delete object.common.automaticUpgrade; else object.common.automaticUpgrade = policy; await this.adapter.setForeignObjectAsync(id, object, { user: ADMIN_USER }); }
|
|
39
|
+
async persist(enabled, state) { await this.adapter.extendForeignObjectAsync(this.instanceId, { native: { eosNexoWattAutoUpdate: enabled, eosNexoWattAutoUpdateState: state } }, { user: ADMIN_USER }); }
|
|
40
|
+
scheduleRetry() { if (this.retryTimer) clearTimeout(this.retryTimer); this.retryTimer = setTimeout(() => void this.reconcile('retry'), 60000); this.retryTimer.unref?.(); }
|
|
41
|
+
scheduleRepositoryRefresh(repository, state) { if (!this.adapter.sendToHostAsync || !this.adapter.host) return; if (this.refreshTimer) clearTimeout(this.refreshTimer); this.refreshTimer = setTimeout(() => { void this.adapter.sendToHostAsync?.(this.adapter.host, 'getRepository', { repo: repository, update: true }).then(async () => { const current = await this.adapter.getForeignObjectAsync(this.instanceId); if (current?.native?.eosNexoWattAutoUpdate === false) return; state.lastRepositoryRefresh = Date.now(); await this.persist(true, state); }).catch(error => this.adapter.log.warn(`[NexoWatt stable updates] Repository refresh failed: ${error instanceof Error ? error.message : error}`)); }, REPOSITORY_REFRESH_DELAY_MS); this.refreshTimer.unref?.(); }
|
|
42
|
+
}
|
|
43
|
+
exports.NexoWattStableUpdateManager = NexoWattStableUpdateManager;
|
package/build/lib/eosPassword.js
CHANGED
|
@@ -2,61 +2,71 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.normalizeEosPasswordTarget = normalizeEosPasswordTarget;
|
|
4
4
|
exports.setEosUserPasswordWithVerification = setEosUserPasswordWithVerification;
|
|
5
|
+
exports.verifyEosUserPasswordCredential = verifyEosUserPasswordCredential;
|
|
5
6
|
function normalizeEosPasswordTarget(value) {
|
|
6
7
|
const raw = String(value || '').trim();
|
|
7
8
|
const userId = raw.startsWith('system.user.') ? raw : `system.user.${raw}`;
|
|
8
9
|
const userName = userId.replace(/^system\.user\./, '').trim();
|
|
9
|
-
if (!userName || userName.includes('.') || !/^system\.user\.[^.]+$/.test(userId))
|
|
10
|
-
throw new Error('invalidPasswordTarget');
|
|
11
|
-
}
|
|
10
|
+
if (!userName || userName.includes('.') || !/^system\.user\.[^.]+$/.test(userId)) throw new Error('invalidPasswordTarget');
|
|
12
11
|
return { userId, userName };
|
|
13
12
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
14
|
+
const accepted = result => result === true || (Array.isArray(result) && result[0] === true);
|
|
15
|
+
async function readPasswordHash(adapter, userId) {
|
|
16
|
+
if (typeof adapter.getForeignObjectAsync !== 'function') return '';
|
|
17
|
+
try {
|
|
18
|
+
const object = await adapter.getForeignObjectAsync(userId);
|
|
19
|
+
return typeof object?.common?.password === 'string' ? object.common.password : '';
|
|
19
20
|
}
|
|
21
|
+
catch { return ''; }
|
|
22
|
+
}
|
|
23
|
+
async function callPasswordApi(adapter, target, password, serviceUser, withOptions) {
|
|
24
|
+
const options = withOptions ? { user: serviceUser } : null;
|
|
25
|
+
if (typeof adapter.setPasswordAsync === 'function') { await adapter.setPasswordAsync(target, password, options); return; }
|
|
20
26
|
if (typeof adapter.setPassword === 'function') {
|
|
21
|
-
await new Promise((resolve, reject) =>
|
|
22
|
-
adapter.setPassword?.(userName, password, options, error => (error ? reject(error) : resolve()));
|
|
23
|
-
});
|
|
27
|
+
await new Promise((resolve, reject) => adapter.setPassword?.(target, password, options, error => error ? reject(error) : resolve()));
|
|
24
28
|
return;
|
|
25
29
|
}
|
|
26
30
|
throw new Error('passwordApiUnavailable');
|
|
27
31
|
}
|
|
28
|
-
async function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
async function verifyAfterWrite(adapter, normalized, password, serviceUser) {
|
|
33
|
+
const hasCredentialCheck = typeof adapter.checkPasswordAsync === 'function';
|
|
34
|
+
for (let attempt = 0; attempt < 12; attempt++) {
|
|
35
|
+
if (hasCredentialCheck) {
|
|
36
|
+
for (const candidate of [normalized.userId, normalized.userName]) {
|
|
37
|
+
try {
|
|
38
|
+
if (accepted(await adapter.checkPasswordAsync?.(candidate, password, { user: serviceUser }))) return;
|
|
39
|
+
}
|
|
40
|
+
catch { }
|
|
41
|
+
}
|
|
36
42
|
}
|
|
43
|
+
else if (await readPasswordHash(adapter, normalized.userId)) return;
|
|
44
|
+
if (attempt < 11) await delay(55 + attempt * 35);
|
|
37
45
|
}
|
|
38
|
-
throw new Error('passwordNotPersisted');
|
|
46
|
+
throw new Error(hasCredentialCheck ? 'passwordVerificationFailed' : 'passwordNotPersisted');
|
|
39
47
|
}
|
|
40
|
-
async function
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for (let attempt = 0; attempt < 5; attempt++) {
|
|
45
|
-
const result = await adapter.checkPasswordAsync(userName, password, { user: serviceUser });
|
|
46
|
-
const accepted = result === true || (Array.isArray(result) && result[0] === true);
|
|
47
|
-
if (accepted) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (attempt < 4) {
|
|
51
|
-
await new Promise(resolve => setTimeout(resolve, 50 * (attempt + 1)));
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
throw new Error('passwordVerificationFailed');
|
|
48
|
+
async function verifyEosUserPasswordCredential(adapter, target, password, serviceUser = 'system.user.admin') {
|
|
49
|
+
const normalized = normalizeEosPasswordTarget(target);
|
|
50
|
+
await verifyAfterWrite(adapter, normalized, password, serviceUser);
|
|
51
|
+
return normalized;
|
|
55
52
|
}
|
|
56
53
|
async function setEosUserPasswordWithVerification(adapter, target, password, serviceUser = 'system.user.admin') {
|
|
57
54
|
const normalized = normalizeEosPasswordTarget(target);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
const attempts = [
|
|
56
|
+
{ apiTarget: normalized.userId, withOptions: true },
|
|
57
|
+
{ apiTarget: normalized.userName, withOptions: true },
|
|
58
|
+
{ apiTarget: normalized.userId, withOptions: false },
|
|
59
|
+
{ apiTarget: normalized.userName, withOptions: false },
|
|
60
|
+
];
|
|
61
|
+
let lastError = null;
|
|
62
|
+
for (const attempt of attempts) {
|
|
63
|
+
try {
|
|
64
|
+
await callPasswordApi(adapter, attempt.apiTarget, password, serviceUser, attempt.withOptions);
|
|
65
|
+
await verifyAfterWrite(adapter, normalized, password, serviceUser);
|
|
66
|
+
return { ...normalized, apiTarget: attempt.apiTarget };
|
|
67
|
+
}
|
|
68
|
+
catch (error) { lastError = error; }
|
|
69
|
+
}
|
|
70
|
+
if (lastError instanceof Error) throw lastError;
|
|
71
|
+
throw new Error('passwordNotPersisted');
|
|
62
72
|
}
|