homebridge-roborock-vacuum 1.2.3 → 1.3.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/Plan.md ADDED
@@ -0,0 +1,54 @@
1
+ # Plan
2
+
3
+ This plan adds a 2FA Email authentication flow, a Homebridge Config UI settings page (with a “Send 2FA Email” button and toast-based status messages), and encrypted token persistence in `config.json` so Docker restarts do not force re-login. The flow follows ioBroker’s 2FA pattern and integrates with the existing login/state logic.
4
+
5
+ ## Requirements
6
+ - UI provides a “Send 2FA Email” button and shows login results via toast.
7
+ - UI calls the plugin UI backend directly for login and 2FA.
8
+ - Token is encrypted before storing in `config.json`; if present and valid, skip login.
9
+ - Code comments and UI copy are in English.
10
+
11
+ ## Scope
12
+ - In: 2FA Email flow, UI backend endpoints, UI form, encrypted token persistence, login short-circuit, toast UI.
13
+ - Out: Device control, MQTT/data handling changes.
14
+
15
+ ## Files and entry points
16
+ - roborockLib/roborockAPI.js
17
+ - src/platform.ts
18
+ - config.schema.json
19
+ - package.json (Homebridge UI integration)
20
+ - UI assets/backend (e.g. src/ui/ or ui/)
21
+
22
+ ## Data model / API changes
23
+ - Add `encryptedToken` (and any needed expiry metadata) to config.
24
+ - Add encryption/decryption logic for the token (key derived from Homebridge context or user-provided secret).
25
+ - UI backend endpoints:
26
+ - POST /auth/send-2fa-email
27
+ - POST /auth/verify-2fa-code
28
+ - POST /auth/login
29
+ - GET /auth/status
30
+ - UI uses toast notifications for success/failure/status.
31
+
32
+ ## Action items
33
+ [ ] Review ioBroker `httpApi.ts` 2FA Email flow and map required endpoints/params to current login flow.
34
+ [ ] Implement 2FA flow in `roborockLib/roborockAPI.js` (send email, verify code, clear status/error handling).
35
+ [ ] Add encrypted token persistence and recovery flow (decrypt on startup; fallback to login on failure/expiry).
36
+ [ ] Update `src/platform.ts` to short-circuit login when a valid token is present.
37
+ [ ] Build a dedicated Homebridge Config UI page with English copy, “Send 2FA Email” button, 2FA input, and toast status display.
38
+ [ ] Add UI backend endpoints for send/verify/login and update config with encrypted token.
39
+ [ ] Update `config.schema.json` and types to align UI and config fields.
40
+
41
+ ## Testing and validation
42
+ - Clicking “Send 2FA Email” triggers email delivery and shows toast feedback.
43
+ - 2FA code verification returns token and displays success toast.
44
+ - Encrypted token in config allows login skip after restart.
45
+ - Decryption failure or token expiry falls back to full login and refreshes token.
46
+
47
+ ## Risks and edge cases
48
+ - 2FA endpoints may differ across regions/accounts.
49
+ - Key management errors can make tokens undecryptable.
50
+ - Token expiry must be detected and trigger re-login.
51
+
52
+ ## Open questions
53
+ - Should the encryption key be derived from Homebridge machine identity or a user-provided secret?
54
+ - Should toast notifications use a lightweight custom component or an existing UI library?
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "pluginAlias": "RoborockVacuumPlatform",
3
3
  "pluginType": "platform",
4
+ "singular": true,
5
+ "customUi": true,
6
+ "customUiPath": "./homebridge-ui",
4
7
  "schema": {
5
8
  "type": "object",
6
9
  "properties": {
@@ -16,15 +19,21 @@
16
19
  "placeholder": "Password",
17
20
  "description": "Roborock account password"
18
21
  },
22
+ "encryptedToken": {
23
+ "title": "Encrypted Token",
24
+ "type": "string",
25
+ "description": "Encrypted Roborock session data (managed by the UI)",
26
+ "readOnly": true
27
+ },
19
28
  "skipDevices": {
20
- "title": "Skip Device",
29
+ "title": "Skip Devices",
21
30
  "type": "string",
22
- "description": "Device ID to skip"
31
+ "description": "Comma-separated device IDs to skip"
23
32
  },
24
33
  "baseURL": {
25
34
  "type": "string",
26
- "default": "usiot.roborock.com",
27
- "description": "Roborock API endpoint USA: usiot.roborock.com, Europe: euiot.roborock.com"
35
+ "default": "https://usiot.roborock.com",
36
+ "description": "Roborock API endpoint: https://usiot.roborock.com (US), https://euiot.roborock.com (EU), https://cniot.roborock.com (CN), https://api.roborock.com (Asia)"
28
37
  }
29
38
  ,
30
39
  "debugMode": {
@@ -34,6 +43,6 @@
34
43
  "default": false
35
44
  }
36
45
  },
37
- "required": ["email", "password"]
46
+ "required": ["email"]
38
47
  }
39
- }
48
+ }
package/dist/crypto.js ADDED
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.encryptSession = encryptSession;
7
+ exports.decryptSession = decryptSession;
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const KEY_FILENAME = 'roborock.token.key';
12
+ function loadOrCreateKey(storagePath) {
13
+ const keyPath = path_1.default.join(storagePath, KEY_FILENAME);
14
+ try {
15
+ const existing = fs_1.default.readFileSync(keyPath);
16
+ if (existing.length === 32) {
17
+ return existing;
18
+ }
19
+ }
20
+ catch (error) {
21
+ // Ignore and create a new key.
22
+ }
23
+ const key = crypto_1.default.randomBytes(32);
24
+ fs_1.default.mkdirSync(storagePath, { recursive: true });
25
+ fs_1.default.writeFileSync(keyPath, key, { mode: 0o600 });
26
+ return key;
27
+ }
28
+ function encryptSession(session, storagePath) {
29
+ const key = loadOrCreateKey(storagePath);
30
+ const iv = crypto_1.default.randomBytes(12);
31
+ const cipher = crypto_1.default.createCipheriv('aes-256-gcm', key, iv);
32
+ const plaintext = Buffer.from(JSON.stringify(session), 'utf8');
33
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
34
+ const tag = cipher.getAuthTag();
35
+ const payload = {
36
+ iv: iv.toString('base64'),
37
+ tag: tag.toString('base64'),
38
+ data: ciphertext.toString('base64'),
39
+ };
40
+ return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64');
41
+ }
42
+ function decryptSession(encrypted, storagePath) {
43
+ if (!encrypted) {
44
+ return null;
45
+ }
46
+ try {
47
+ const key = loadOrCreateKey(storagePath);
48
+ const raw = Buffer.from(encrypted, 'base64').toString('utf8');
49
+ const payload = JSON.parse(raw);
50
+ const iv = Buffer.from(payload.iv, 'base64');
51
+ const tag = Buffer.from(payload.tag, 'base64');
52
+ const ciphertext = Buffer.from(payload.data, 'base64');
53
+ const decipher = crypto_1.default.createDecipheriv('aes-256-gcm', key, iv);
54
+ decipher.setAuthTag(tag);
55
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
56
+ return JSON.parse(plaintext);
57
+ }
58
+ catch (error) {
59
+ return null;
60
+ }
61
+ }
62
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
package/dist/platform.js CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const vacuum_accessory_1 = __importDefault(require("./vacuum_accessory"));
7
7
  const logger_1 = __importDefault(require("./logger"));
8
8
  const settings_1 = require("./settings");
9
+ const crypto_1 = require("./crypto");
9
10
  const Roborock = require("../roborockLib/roborockAPI").Roborock;
10
11
  /**
11
12
  * Roborock App Platform Plugin for Homebridge
@@ -35,7 +36,19 @@ class RoborockPlatform {
35
36
  const password = this.platformConfig.password;
36
37
  const baseURL = this.platformConfig.baseURL;
37
38
  const debugMode = this.platformConfig.debugMode;
38
- this.roborockAPI = new Roborock({ username: username, password: password, debug: debugMode, baseURL: baseURL, log: this.log });
39
+ const storagePath = this.api.user.storagePath();
40
+ const decryptedSession = this.platformConfig.encryptedToken
41
+ ? (0, crypto_1.decryptSession)(this.platformConfig.encryptedToken, storagePath)
42
+ : null;
43
+ this.roborockAPI = new Roborock({
44
+ username: username,
45
+ password: password,
46
+ debug: debugMode,
47
+ baseURL: baseURL,
48
+ log: this.log,
49
+ userData: decryptedSession,
50
+ storagePath: storagePath,
51
+ });
39
52
  /**
40
53
  * When this event is fired it means Homebridge has restored all cached accessories from disk.
41
54
  * Dynamic Platform plugins should only register new accessories after this event was fired,
@@ -62,9 +75,9 @@ class RoborockPlatform {
62
75
  + 'Please set the field `email` in your config and restart Homebridge.');
63
76
  return;
64
77
  }
65
- if (!this.platformConfig.password) {
78
+ if (!this.platformConfig.password && !this.platformConfig.encryptedToken) {
66
79
  this.log.error('Password is not configured - aborting plugin start. '
67
- + 'Please set the field `password` in your config and restart Homebridge.');
80
+ + 'Please set `password` or complete login in the Config UI.');
68
81
  return;
69
82
  }
70
83
  const self = this;
@@ -1 +1 @@
1
- {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const crypto_1 = __importDefault(require("crypto"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const plugin_ui_utils_1 = require("@homebridge/plugin-ui-utils");
10
+ const crypto_2 = require("../crypto");
11
+ const roborockAuth = require('../../roborockLib/lib/roborockAuth');
12
+ class RoborockUiServer extends plugin_ui_utils_1.HomebridgePluginUiServer {
13
+ constructor() {
14
+ super();
15
+ this.onRequest('/auth/send-2fa-email', this.sendTwoFactorEmail.bind(this));
16
+ this.onRequest('/auth/verify-2fa-code', this.verifyTwoFactorCode.bind(this));
17
+ this.onRequest('/auth/login', this.loginWithPassword.bind(this));
18
+ this.onRequest('/auth/logout', this.logout.bind(this));
19
+ this.ready();
20
+ }
21
+ getStoragePath() {
22
+ return this.homebridgeStoragePath || process.cwd();
23
+ }
24
+ async getClientId() {
25
+ const storagePath = this.getStoragePath();
26
+ if (storagePath) {
27
+ const clientIdPath = path_1.default.join(storagePath, 'roborock.clientID');
28
+ try {
29
+ const stored = JSON.parse(fs_1.default.readFileSync(clientIdPath, 'utf8'));
30
+ if (stored && stored.val) {
31
+ return stored.val;
32
+ }
33
+ }
34
+ catch (error) {
35
+ // Ignore and generate a new client ID.
36
+ }
37
+ const clientId = crypto_1.default.randomUUID();
38
+ fs_1.default.mkdirSync(storagePath, { recursive: true });
39
+ fs_1.default.writeFileSync(clientIdPath, JSON.stringify({ val: clientId, ack: true }, null, 2), 'utf8');
40
+ return clientId;
41
+ }
42
+ return crypto_1.default.randomUUID();
43
+ }
44
+ async buildLoginApi(config) {
45
+ const clientID = await this.getClientId();
46
+ return roborockAuth.createLoginApi({
47
+ baseURL: config.baseURL || 'usiot.roborock.com',
48
+ username: config.email,
49
+ clientID,
50
+ language: 'en',
51
+ });
52
+ }
53
+ async sendTwoFactorEmail(payload) {
54
+ const email = payload.email;
55
+ if (!email) {
56
+ return { ok: false, message: 'Email is required.' };
57
+ }
58
+ try {
59
+ const loginApi = await this.buildLoginApi({ email, baseURL: payload.baseURL });
60
+ await roborockAuth.requestEmailCode(loginApi, email);
61
+ return { ok: true, message: 'Verification email sent.' };
62
+ }
63
+ catch (error) {
64
+ console.error('2FA email request failed:', (error === null || error === void 0 ? void 0 : error.message) || error);
65
+ return { ok: false, message: (error === null || error === void 0 ? void 0 : error.message) || 'Failed to send verification email.' };
66
+ }
67
+ }
68
+ async verifyTwoFactorCode(payload) {
69
+ const email = payload.email;
70
+ if (!email) {
71
+ return { ok: false, message: 'Email is required.' };
72
+ }
73
+ if (!payload.code) {
74
+ return { ok: false, message: 'Verification code is required.' };
75
+ }
76
+ let loginResult;
77
+ try {
78
+ const loginApi = await this.buildLoginApi({ email, baseURL: payload.baseURL });
79
+ const nonce = this.buildNonce();
80
+ const signData = await roborockAuth.signRequest(loginApi, nonce);
81
+ if (!signData || !signData.k) {
82
+ return { ok: false, message: 'Failed to create login signature.' };
83
+ }
84
+ const region = roborockAuth.getRegionConfig(payload.baseURL || 'usiot.roborock.com');
85
+ loginResult = await roborockAuth.loginWithCode(loginApi, {
86
+ email,
87
+ code: payload.code,
88
+ country: region.country,
89
+ countryCode: region.countryCode,
90
+ k: signData.k,
91
+ s: nonce,
92
+ });
93
+ }
94
+ catch (error) {
95
+ console.error('2FA verification request failed:', (error === null || error === void 0 ? void 0 : error.message) || error);
96
+ return { ok: false, message: (error === null || error === void 0 ? void 0 : error.message) || 'Verification failed.' };
97
+ }
98
+ if (loginResult && loginResult.code === 200 && loginResult.data) {
99
+ const encrypted = (0, crypto_2.encryptSession)(loginResult.data, this.getStoragePath());
100
+ return { ok: true, message: 'Login completed and token saved.', encryptedToken: encrypted };
101
+ }
102
+ console.error('2FA verification failed:', loginResult);
103
+ return { ok: false, message: (loginResult === null || loginResult === void 0 ? void 0 : loginResult.msg) || 'Verification failed.' };
104
+ }
105
+ async loginWithPassword(payload) {
106
+ const email = payload.email;
107
+ const password = payload.password;
108
+ if (!email || !password) {
109
+ return { ok: false, message: 'Email and password are required.' };
110
+ }
111
+ let loginResult;
112
+ try {
113
+ const loginApi = await this.buildLoginApi({ email, baseURL: payload.baseURL });
114
+ const nonce = this.buildNonce();
115
+ const signData = await roborockAuth.signRequest(loginApi, nonce);
116
+ if (!signData || !signData.k) {
117
+ return { ok: false, message: 'Failed to create login signature.' };
118
+ }
119
+ loginResult = await roborockAuth.loginByPassword(loginApi, {
120
+ email,
121
+ password,
122
+ k: signData.k,
123
+ s: nonce,
124
+ });
125
+ }
126
+ catch (error) {
127
+ console.error('Login request failed:', (error === null || error === void 0 ? void 0 : error.message) || error);
128
+ return { ok: false, message: (error === null || error === void 0 ? void 0 : error.message) || 'Login failed.' };
129
+ }
130
+ if (loginResult && loginResult.code === 200 && loginResult.data) {
131
+ const encrypted = (0, crypto_2.encryptSession)(loginResult.data, this.getStoragePath());
132
+ return { ok: true, message: 'Login successful. Token saved.', encryptedToken: encrypted };
133
+ }
134
+ if (loginResult && loginResult.code === 2031) {
135
+ return { ok: false, twoFactorRequired: true, message: 'Two-factor authentication required.' };
136
+ }
137
+ console.error('Login failed:', loginResult);
138
+ return { ok: false, message: (loginResult === null || loginResult === void 0 ? void 0 : loginResult.msg) || 'Login failed. Check your credentials.' };
139
+ }
140
+ async logout() {
141
+ const storagePath = this.getStoragePath();
142
+ if (!storagePath) {
143
+ return { ok: true, message: 'Logged out. Token cleared.' };
144
+ }
145
+ const userDataPath = path_1.default.join(storagePath, 'roborock.UserData');
146
+ try {
147
+ if (fs_1.default.existsSync(userDataPath)) {
148
+ fs_1.default.unlinkSync(userDataPath);
149
+ }
150
+ }
151
+ catch (error) {
152
+ // Ignore file removal errors.
153
+ }
154
+ return { ok: true, message: 'Logged out. Token cleared.' };
155
+ }
156
+ buildNonce() {
157
+ return crypto_1.default.randomBytes(12).toString('base64').substring(0, 16).replace(/\+/g, 'X').replace(/\//g, 'Y');
158
+ }
159
+ }
160
+ (() => new RoborockUiServer())();
161
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ <link rel="stylesheet" href="styles.css" />
2
+
3
+ <main class="container">
4
+ <header>
5
+ <h1>Roborock Vacuum</h1>
6
+ <p class="subtitle">Manage login and two-factor authentication for your Roborock account.</p>
7
+ </header>
8
+
9
+ <section class="card">
10
+ <h2>Account</h2>
11
+ <label>
12
+ Email
13
+ <input id="email" type="email" placeholder="name@example.com" />
14
+ </label>
15
+ <label id="password-row">
16
+ Password
17
+ <input id="password" type="password" placeholder="Password" />
18
+ </label>
19
+ <label>
20
+ Region
21
+ <select id="base-url">
22
+ <option value="https://usiot.roborock.com">US (usiot.roborock.com)</option>
23
+ <option value="https://euiot.roborock.com">EU (euiot.roborock.com)</option>
24
+ <option value="https://cniot.roborock.com">CN (cniot.roborock.com)</option>
25
+ <option value="https://api.roborock.com">Asia (api.roborock.com)</option>
26
+ </select>
27
+ </label>
28
+ <label>
29
+ Skip Devices
30
+ <input id="skip-devices" type="text" placeholder="Comma-separated device IDs" />
31
+ </label>
32
+ <label class="checkbox">
33
+ <input id="debug-mode" type="checkbox" />
34
+ Enable debug logging
35
+ </label>
36
+ <div class="actions">
37
+ <button id="login" class="secondary">Login</button>
38
+ <button id="logout" class="secondary hidden">Logout</button>
39
+ </div>
40
+ </section>
41
+
42
+ <section class="card" id="two-factor-section">
43
+ <h2>Two-Factor Authentication</h2>
44
+ <p class="help">If your account requires 2FA, send a verification email and enter the code below.</p>
45
+ <label id="code-row">
46
+ Verification Code
47
+ <input id="two-factor-code" type="text" placeholder="6-digit code" />
48
+ </label>
49
+ <div class="actions">
50
+ <button id="send-2fa" class="primary">Send 2FA Email</button>
51
+ <button id="verify-2fa" class="secondary">Verify Code</button>
52
+ </div>
53
+ </section>
54
+ </main>
55
+
56
+ <div id="toast-container" class="toast-container"></div>
57
+
58
+ <script src="index.js"></script>