eufy-security-client 4.0.0 → 4.1.0-dev.37

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 (53) hide show
  1. package/README.md +32 -0
  2. package/build/eufysecurity.d.ts +15 -0
  3. package/build/eufysecurity.js +58 -6
  4. package/build/eufysecurity.js.map +1 -1
  5. package/build/http/api.js +1 -1
  6. package/build/http/api.js.map +1 -1
  7. package/build/http/decodeImageV2.d.ts +12 -8
  8. package/build/http/decodeImageV2.js +17 -12
  9. package/build/http/decodeImageV2.js.map +1 -1
  10. package/build/http/index.d.ts +2 -0
  11. package/build/http/index.js +2 -0
  12. package/build/http/index.js.map +1 -1
  13. package/build/http/interfaces.d.ts +2 -0
  14. package/build/http/megaApi.d.ts +186 -0
  15. package/build/http/megaApi.js +513 -0
  16. package/build/http/megaApi.js.map +1 -0
  17. package/build/http/megaCrypto.d.ts +84 -0
  18. package/build/http/megaCrypto.js +129 -0
  19. package/build/http/megaCrypto.js.map +1 -0
  20. package/build/http/megaInterfaces.d.ts +83 -0
  21. package/build/http/megaInterfaces.js +3 -0
  22. package/build/http/megaInterfaces.js.map +1 -0
  23. package/build/http/megaTransition.d.ts +103 -0
  24. package/build/http/megaTransition.js +203 -0
  25. package/build/http/megaTransition.js.map +1 -0
  26. package/build/http/station.d.ts +1 -0
  27. package/build/http/station.js +4 -0
  28. package/build/http/station.js.map +1 -1
  29. package/build/http/types.d.ts +7 -1
  30. package/build/http/types.js +6 -0
  31. package/build/http/types.js.map +1 -1
  32. package/build/http/utils.d.ts +10 -0
  33. package/build/http/utils.js +34 -10
  34. package/build/http/utils.js.map +1 -1
  35. package/build/interfaces.d.ts +2 -0
  36. package/build/p2p/interfaces.d.ts +2 -0
  37. package/build/p2p/session.js +17 -0
  38. package/build/p2p/session.js.map +1 -1
  39. package/build/p2p/types.d.ts +1 -0
  40. package/build/p2p/types.js +1 -0
  41. package/build/p2p/types.js.map +1 -1
  42. package/coverage/clover.xml +15420 -0
  43. package/coverage/coverage-final.json +37 -0
  44. package/coverage/lcov-report/base.css +224 -0
  45. package/coverage/lcov-report/block-navigation.js +87 -0
  46. package/coverage/lcov-report/favicon.png +0 -0
  47. package/coverage/lcov-report/index.html +176 -0
  48. package/coverage/lcov-report/prettify.css +1 -0
  49. package/coverage/lcov-report/prettify.js +2 -0
  50. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  51. package/coverage/lcov-report/sorter.js +210 -0
  52. package/coverage/lcov.info +29542 -0
  53. package/package.json +1 -1
@@ -0,0 +1,513 @@
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.MegaHTTPApi = exports.megaLoginHash = exports.MEGA_SERVER_STATIC_PUBKEY = void 0;
7
+ const crypto_1 = require("crypto");
8
+ const md5_1 = __importDefault(require("crypto-js/md5"));
9
+ const logging_1 = require("../logging");
10
+ const utils_1 = require("./utils");
11
+ const types_1 = require("./types");
12
+ const megaCrypto_1 = require("./megaCrypto");
13
+ /**
14
+ * Eufy "eufy_mega" v6 backend client.
15
+ *
16
+ * Talks to the new per-service `*.eufy.com` microservices (behind APISIX) that the
17
+ * official app 6.0.50+ uses. Each cluster (us-pr, eu-pr, …) requires its own
18
+ * key/exchange handshake → per-cluster {@link MegaIdentity} (keyIdent + sharedKey).
19
+ * Bodies are ECDH-encrypted (`X-Encryption-Info: algo_ecdh`) and every request is
20
+ * signed (`X-Signature`).
21
+ *
22
+ * This is intentionally a SEPARATE class from HTTPApi: the legacy backend still
23
+ * works for not-yet-migrated devices, so we don't want to risk that path.
24
+ *
25
+ * Heavily rate-limited (1 request / ~3s) — the Eufy WAF rate-limits aggressive probing.
26
+ */
27
+ /** Per-cluster server static public key (uncompressed hex) for the ECIES bootstrap. */
28
+ exports.MEGA_SERVER_STATIC_PUBKEY = "04ebc77a23c7191f8c97fb2a7676710f64ddadfe5305fa80c8855476b024c6ad3d8c18d4be9d720c5a578167f899e0818d3a19de2e804407034b4a88cfdb7ae995";
29
+ /** Login password ECDH server key — same constant the legacy HTTPApi uses. */
30
+ const LOGIN_SERVER_PUBLIC_KEY = "04c5c00c4f8d1197cc7c3167c52bf7acb054d722f0ef08dcd7e0883236e0d72a3868d9750cb47fa4619248f3d83f0f662671dadc6e2d31c2f41db0161651c7c076";
31
+ /**
32
+ * Salted change-detection hash for the persisted session: invalidate the cached mega session when
33
+ * the credentials change. Salted with the stable device id so a leaked persistent file isn't an
34
+ * offline-cracking aid the way a bare md5(user:pass) would be.
35
+ */
36
+ const megaLoginHash = (email, password, openudid) => (0, crypto_1.createHash)("sha256").update(`${openudid}:${email}:${password}`).digest("hex");
37
+ exports.megaLoginHash = megaLoginHash;
38
+ class MegaHTTPApi {
39
+ ab;
40
+ osType;
41
+ appName;
42
+ appVersion;
43
+ osVersion;
44
+ phoneModel;
45
+ minIntervalMs;
46
+ got;
47
+ throttle;
48
+ /** Resolved domains from estimate_domain (eufy_security, etc). */
49
+ domains = {};
50
+ megaDomain = "";
51
+ /** One identity per cluster host (e.g. app-openapi-eu-pr.eufy.com). */
52
+ identities = new Map();
53
+ /** eufy.com auth token (from passport/login on the new backend). */
54
+ authToken;
55
+ /** Unix seconds when authToken expires (from login `token_expires_at`). */
56
+ tokenExpiresAt;
57
+ userId;
58
+ constructor(opts) {
59
+ this.ab = opts.ab.toLowerCase();
60
+ this.osType = opts.osType ?? "android";
61
+ this.appName = opts.appName ?? "eufy_mega";
62
+ this.appVersion = opts.appVersion ?? (this.osType === "android" ? "6.0.51_26722" : "6.0.50_260612140706");
63
+ this.osVersion = opts.osVersion ?? (this.osType === "android" ? "14" : "26.5.1");
64
+ this.phoneModel = opts.phoneModel ?? (this.osType === "android" ? "Pixel 8" : "iPhone 17 Pro");
65
+ if (opts.openudid)
66
+ this.openudid = opts.openudid;
67
+ this.minIntervalMs = opts.minRequestIntervalMs ?? 3000;
68
+ }
69
+ async init() {
70
+ const { default: pThrottle } = await import("p-throttle");
71
+ const { default: got } = await import("got");
72
+ this.got = got;
73
+ this.throttle = pThrottle({ limit: 1, interval: this.minIntervalMs });
74
+ }
75
+ get gtoken() {
76
+ return this.userId ? (0, md5_1.default)(this.userId).toString() : undefined;
77
+ }
78
+ /** Stable per-install device id (uuid-ish, 32 hex). Restored from a persisted session. */
79
+ openudid = (0, megaCrypto_1.generateKeyIdent)();
80
+ /**
81
+ * Low-level signed/encrypted POST to a v6 host.
82
+ *
83
+ * X-Signature covers the ENCRYPTED VALUE (the base64 ciphertext), not the JSON wrapper: for
84
+ * key/exchange the body is `{"client_public_key":"<b64>"}` but only `<b64>` is signed; for
85
+ * regular requests the body IS the ciphertext, so signed value == body.
86
+ *
87
+ * No got-level retry: the timestamp/nonce/signature are computed once per call and the backend
88
+ * enforces a replay/timestamp window, so a got retry would resend a frozen signature and be
89
+ * rejected. Retry above this method (recomputing the signature) if needed.
90
+ *
91
+ * @param host full host (e.g. app-passport-eu-pr.eufy.com)
92
+ * @param path request path
93
+ * @param payload plaintext object (encrypted with the cluster identity sharedKey)
94
+ * @param identity cluster identity; if omitted, bootstrap mode (presetKey) is used
95
+ */
96
+ async signedPost(host, path, payload, identity, bootstrap) {
97
+ const ts = `${Math.floor(Date.now() / 1000)}`;
98
+ const nonce = (0, megaCrypto_1.generateKeyIdent)();
99
+ let body;
100
+ let signedValue;
101
+ let signingKey;
102
+ let keyIdent;
103
+ if (bootstrap !== undefined) {
104
+ body = JSON.stringify({ client_public_key: bootstrap.encryptedValue });
105
+ signedValue = bootstrap.encryptedValue;
106
+ signingKey = megaCrypto_1.MEGA_PRESET_KEY;
107
+ keyIdent = bootstrap.keyIdent;
108
+ }
109
+ else {
110
+ if (!identity)
111
+ throw new Error("signedPost: identity required for non-bootstrap request");
112
+ const aesKey = (0, megaCrypto_1.sharedKeyToAesKey)(identity.sharedKey);
113
+ body = (0, megaCrypto_1.megaEncryptBody)(JSON.stringify(payload), aesKey);
114
+ signedValue = body;
115
+ signingKey = (0, megaCrypto_1.sharedKeySigningKey)(identity.sharedKey);
116
+ keyIdent = identity.keyIdent;
117
+ }
118
+ const headers = {
119
+ accept: "application/json",
120
+ "accept-charset": "UTF-8",
121
+ "accept-language": `${this.ab}-${this.ab.toUpperCase()},${this.ab};q=0.9`,
122
+ "app-name": this.appName,
123
+ "app-version": this.appVersion,
124
+ app_version: this.appVersion,
125
+ "os-type": this.osType,
126
+ os_type: this.osType,
127
+ "os-version": this.osVersion,
128
+ os_version: this.osVersion,
129
+ "model-type": "PHONE",
130
+ "phone-model": this.phoneModel,
131
+ phone_model: this.phoneModel,
132
+ openudid: this.openudid,
133
+ "test-flag": "false",
134
+ priority: "u=3, i",
135
+ "user-agent": "ktor-client",
136
+ "content-type": "application/json",
137
+ "x-encryption-info": "algo_ecdh",
138
+ "x-key-ident": keyIdent,
139
+ "x-request-ts": ts,
140
+ "x-request-once": nonce,
141
+ "x-replay-info": "replay",
142
+ "x-signature": (0, megaCrypto_1.xSignature)(signingKey, ts, nonce, signedValue),
143
+ country: this.ab.toUpperCase(),
144
+ language: this.ab,
145
+ ab_code: this.ab,
146
+ };
147
+ if (this.gtoken)
148
+ headers.gtoken = this.gtoken;
149
+ if (this.authToken) {
150
+ headers["x-auth-token"] = this.authToken;
151
+ headers.authorization = this.authToken;
152
+ }
153
+ logging_1.rootHTTPLogger.debug("MegaApi request", { host, path, osType: this.osType, keyIdent });
154
+ const send = this.throttle(async () => {
155
+ return await this.got(`https://${host}${path}`, {
156
+ method: "POST",
157
+ headers,
158
+ body,
159
+ responseType: "text",
160
+ throwHttpErrors: false,
161
+ retry: { limit: 0 },
162
+ });
163
+ });
164
+ const resp = await send();
165
+ const responseText = resp.body ?? "";
166
+ let parsed;
167
+ try {
168
+ parsed = JSON.parse(responseText);
169
+ }
170
+ catch {
171
+ throw new Error(`${host}${path} → HTTP ${resp.statusCode}, non-JSON body (${responseText.length} bytes)`);
172
+ }
173
+ logging_1.rootHTTPLogger.debug("MegaApi response", { host, path, status: resp.statusCode, code: parsed.code });
174
+ if (parsed.code === types_1.ResponseErrorCode.CODE_NEED_NEGOTIATE_KEY ||
175
+ parsed.code === types_1.ResponseErrorCode.CODE_SIGNATURE_ERROR) {
176
+ logging_1.rootHTTPLogger.info("MegaApi identity rejected — evicting cached identities to force re-handshake", {
177
+ code: parsed.code,
178
+ });
179
+ this.identities.clear();
180
+ }
181
+ return parsed;
182
+ }
183
+ /** Decrypt a v6 response `data` field with a cluster identity sharedKey. */
184
+ decryptForCluster(identity, dataB64) {
185
+ return (0, megaCrypto_1.megaDecryptBody)(dataB64, (0, megaCrypto_1.sharedKeyToAesKey)(identity.sharedKey));
186
+ }
187
+ /** Resolve the region's mega domain + product domains. Body is cleartext JSON. */
188
+ async estimateDomain() {
189
+ const host = `mega-${this.ab === "us" ? "us" : "eu"}-pr.eufy.com`;
190
+ const send = this.throttle(async () => this.got(`https://${host}/passport/estimate_domain`, {
191
+ method: "POST",
192
+ headers: {
193
+ "app-name": this.appName,
194
+ "app-version": this.appVersion,
195
+ "os-type": this.osType,
196
+ "content-type": "application/json",
197
+ },
198
+ json: { ab: this.ab, mode: 1 },
199
+ responseType: "json",
200
+ throwHttpErrors: false,
201
+ }));
202
+ const resp = await send();
203
+ const result = resp.body;
204
+ if (result.code !== 0)
205
+ throw new Error(`estimate_domain failed: ${result.code} ${result.msg}`);
206
+ const data = result.data;
207
+ this.megaDomain = data.domain;
208
+ this.domains = data.product_domains;
209
+ logging_1.rootHTTPLogger.info("MegaApi estimate_domain", { megaDomain: this.megaDomain, domains: this.domains });
210
+ return this.domains;
211
+ }
212
+ /**
213
+ * Perform a key/exchange against a cluster's openapi host and cache the identity.
214
+ * @param openapiHost e.g. app-openapi-eu-pr.eufy.com
215
+ */
216
+ async keyExchange(openapiHost) {
217
+ const cached = this.identities.get(openapiHost);
218
+ if (cached)
219
+ return cached;
220
+ const { ecdh, clientPublicKeyBody, clientPublicKey, keyIdent } = (0, megaCrypto_1.buildKeyExchange)();
221
+ const result = await this.signedPost(openapiHost, "/openapi/oauth/key/exchange", undefined, undefined, {
222
+ keyIdent,
223
+ encryptedValue: clientPublicKeyBody,
224
+ });
225
+ if (result.code !== 0)
226
+ throw new Error(`key/exchange failed on ${openapiHost}: ${result.code} ${result.msg}`);
227
+ const serverPubEnc = result.data.server_public_key;
228
+ const identity = (0, megaCrypto_1.finalizeKeyExchange)(ecdh, serverPubEnc, keyIdent, clientPublicKey);
229
+ this.identities.set(openapiHost, identity);
230
+ logging_1.rootHTTPLogger.info("MegaApi key/exchange ok", { openapiHost, keyIdent });
231
+ return identity;
232
+ }
233
+ /**
234
+ * Region cluster host for a service, e.g. "passport" → app-passport-eu-pr.eufy.com.
235
+ *
236
+ * Derived from the `domain` returned by {@link estimateDomain} (the server decides the region,
237
+ * the client does not guess it) by replacing the `mega` prefix with `app-{service}` — the same
238
+ * transform the app does in MegaAppDomain.createByMega. Falls back to a us/eu guess only when
239
+ * estimate_domain has not run yet (e.g. the bootstrap key/exchange before login).
240
+ */
241
+ clusterHost(service) {
242
+ if (this.megaDomain && this.megaDomain.startsWith("mega-")) {
243
+ return this.megaDomain.replace(/^mega-/, `app-${service}-`);
244
+ }
245
+ const region = this.ab === "us" ? "us" : "eu";
246
+ return `app-${service}-${region}-pr.eufy.com`;
247
+ }
248
+ getDomains() {
249
+ return this.domains;
250
+ }
251
+ getIdentity(host) {
252
+ return this.identities.get(host);
253
+ }
254
+ setAuth(authToken, userId) {
255
+ this.authToken = authToken;
256
+ this.userId = userId;
257
+ }
258
+ /**
259
+ * Export the full session so a later run can resume WITHOUT a fresh login/2FA.
260
+ *
261
+ * Mirrors the legacy HTTPApi's `persistentData`: once authenticated you ARE authenticated —
262
+ * persist the token + user_id + the per-cluster ECDH identities (keyIdent/sharedKey) + the
263
+ * stable device id. As long as the token hasn't expired, `restoreSession()` lets every signed
264
+ * call go straight through (no estimate_domain / key/exchange / login replay).
265
+ */
266
+ exportSession(loginHash) {
267
+ return {
268
+ ab: this.ab,
269
+ openudid: this.openudid,
270
+ login_hash: loginHash,
271
+ cloud_token: this.authToken,
272
+ cloud_token_expiration: this.tokenExpiresAt,
273
+ user_id: this.userId,
274
+ domains: this.domains,
275
+ megaDomain: this.megaDomain,
276
+ identities: Object.fromEntries(this.identities),
277
+ };
278
+ }
279
+ /** Restore a session previously produced by {@link exportSession}. */
280
+ restoreSession(s) {
281
+ if (s.openudid)
282
+ this.openudid = s.openudid;
283
+ this.authToken = s.cloud_token;
284
+ this.userId = s.user_id;
285
+ this.tokenExpiresAt = s.cloud_token_expiration;
286
+ this.domains = s.domains ?? {};
287
+ this.megaDomain = s.megaDomain ?? "";
288
+ this.identities = new Map(Object.entries(s.identities ?? {}));
289
+ }
290
+ /**
291
+ * True if we hold a non-expired auth token (60s safety margin) → no login replay needed.
292
+ * An unknown expiry is treated as invalid (forces a relogin) rather than valid-forever.
293
+ */
294
+ hasValidSession() {
295
+ if (!this.authToken || !this.userId)
296
+ return false;
297
+ if (!this.tokenExpiresAt)
298
+ return false;
299
+ if (Date.now() / 1000 >= this.tokenExpiresAt - 60)
300
+ return false;
301
+ return true;
302
+ }
303
+ /** Generic signed/encrypted call once an identity exists for the host's cluster. */
304
+ async call(host, path, payload) {
305
+ const openapiHost = this.clusterHost("openapi");
306
+ const identity = await this.keyExchange(openapiHost);
307
+ return this.signedPost(host, path, payload, identity);
308
+ }
309
+ /** {@link call} + decrypt the `data` field. Throws on non-zero code. */
310
+ async callDecrypted(service, path, payload = {}) {
311
+ const openapiHost = this.clusterHost("openapi");
312
+ const identity = await this.keyExchange(openapiHost);
313
+ const result = await this.signedPost(this.clusterHost(service), path, payload, identity);
314
+ if (result.code !== 0)
315
+ throw new Error(`${path} failed: ${result.code} ${result.msg}`);
316
+ if (typeof result.data !== "string") {
317
+ logging_1.rootHTTPLogger.warn("MegaApi response data is not an encrypted string (protocol drift?)", { path });
318
+ return result.data;
319
+ }
320
+ return JSON.parse(this.decryptForCluster(identity, result.data));
321
+ }
322
+ /**
323
+ * MQTT connection info for the v6 backend (`devicemanage/get_user_mqtt_info`).
324
+ * This is how events are pushed in v6 — broker endpoint + credentials + topics, returned
325
+ * dynamically (unlike the legacy static `security-mqtt-eu.eufylife.com`). Decrypted.
326
+ */
327
+ async getUserMqttInfo() {
328
+ return (await this.callDecrypted("devicemanage", "/app/devicemanage/get_user_mqtt_info", {}));
329
+ }
330
+ /**
331
+ * Build the full AWS IoT MQTT connection config for the v6 event channel. Fetches the per-user
332
+ * certificate/credentials and combines them with the client identity so a consumer can connect
333
+ * without touching MegaHTTPApi internals.
334
+ *
335
+ * NOTE: SCAFFOLDING — there is no MQTT subscriber consuming this yet. The v6 AWS IoT channel
336
+ * only serves devices flagged `is_support_mqtt`; ordinary cameras/sensors are delivered over
337
+ * FCM ({@link registerPushToken}). This builder is shipped ahead of a follow-up MQTT-consumer
338
+ * change so that change won't need to touch MegaHTTPApi. It is NOT a live event path today.
339
+ *
340
+ * The `clientId` format is mandatory for the AWS IoT policy (decompiled `createClientId`):
341
+ * `android-{app_name}-{user_id}-{openudid}`. Topics carry `PN`/`SN` placeholders to fill per
342
+ * device. Requires a valid session.
343
+ */
344
+ async getMqttConnectConfig() {
345
+ const info = await this.getUserMqttInfo();
346
+ return {
347
+ endpoint: info.endpoint_addr,
348
+ port: 8883,
349
+ clientId: `android-${this.appName}-${info.user_id}-${this.openudid}`,
350
+ thingName: info.thing_name,
351
+ userId: info.user_id,
352
+ certificatePem: info.certificate_pem,
353
+ privateKey: info.private_key,
354
+ awsRootCaPem: info.aws_root_ca1_pem,
355
+ topics: {
356
+ subCmd: "cmd/eufy_security/PN/SN/res",
357
+ stateInfo: "synq/eufy_life/PN/SN/state_info",
358
+ pubCmd: "cmd/eufy_security/PN/SN/req",
359
+ },
360
+ };
361
+ }
362
+ /**
363
+ * Register an FCM push token on the v6 backend (`push/register_push_token`).
364
+ *
365
+ * Body (decompiled `PushManager.uploadToken`): `{token, is_notification_enable, voip_token}`.
366
+ * There is NO platform/type field in the body — the backend routes FCM vs APNs purely by the
367
+ * `os-type` header + the `x-key-ident` identity this request is signed under. So this MUST run
368
+ * on an `os-type: android` identity (the default here) for events to be delivered over FCM.
369
+ */
370
+ async registerPushToken(fcmToken) {
371
+ return this.call(this.clusterHost("push"), "/app/push/register_push_token", {
372
+ token: fcmToken,
373
+ is_notification_enable: true,
374
+ voip_token: fcmToken,
375
+ });
376
+ }
377
+ /** Encrypt the login password exactly like the legacy HTTPApi (ECDH vs LOGIN_SERVER_PUBLIC_KEY). */
378
+ encryptLoginPassword(password) {
379
+ const ecdh = (0, crypto_1.createECDH)("prime256v1");
380
+ ecdh.generateKeys();
381
+ const secret = ecdh.computeSecret(Buffer.from(LOGIN_SERVER_PUBLIC_KEY, "hex"));
382
+ return {
383
+ encrypted: (0, utils_1.encryptAPIData)(password, secret),
384
+ clientSecretPub: ecdh.getPublicKey("hex"),
385
+ };
386
+ }
387
+ /** Validate the email exists on the new backend (the app does this before login). */
388
+ async validateEmail(email) {
389
+ return this.call(this.clusterHost("passport"), "/passport/validate_email", { email, ab: this.ab });
390
+ }
391
+ /** The 2FA session id, shared between login (which triggers 2FA) and loginAfterTFA. */
392
+ loginId;
393
+ /**
394
+ * Obtain a `login_id` (2FA session id). The app calls `passport/get_login_id` before login and
395
+ * passes the SAME login_id to both the initial login and the post-2FA login. (Decompiled flow.)
396
+ */
397
+ async getLoginId() {
398
+ const result = await this.call(this.clusterHost("passport"), "/passport/get_login_id", {});
399
+ if (result.code !== 0)
400
+ throw new Error(`get_login_id failed: ${result.code} ${result.msg}`);
401
+ const openapiHost = this.clusterHost("openapi");
402
+ const identity = this.identities.get(openapiHost);
403
+ const decoded = JSON.parse(this.decryptForCluster(identity, result.data));
404
+ this.loginId = decoded.login_id;
405
+ logging_1.rootHTTPLogger.info("MegaApi get_login_id", { loginId: this.loginId });
406
+ return this.loginId;
407
+ }
408
+ /**
409
+ * Login against the v6 passport backend.
410
+ *
411
+ * The body sends `login_id: ""`: `get_login_id`/`getTouchId` is for biometric (TouchID) login
412
+ * only and must not be called in the email+2FA flow. On success the backend returns code:0 even
413
+ * when 2FA is still pending — the real state is in `fa_info.step` (26052), and the returned token
414
+ * is provisional but still used to authenticate the subsequent sendVerifyCode + final login.
415
+ *
416
+ * @param verifyCode email 2FA code (omit on first call; pass it on retry after code 26052)
417
+ * @param captcha picture-captcha answer (pass it after code 100032/100033 — see {@link generateCaptcha})
418
+ * @returns the full MegaResult. Codes the caller acts on: 26052 = email 2FA required;
419
+ * 100032 = captcha required, 100033 = captcha answer incorrect. On success, token+user stored.
420
+ */
421
+ async login(email, password, verifyCode, captcha) {
422
+ const { encrypted, clientSecretPub } = this.encryptLoginPassword(password);
423
+ const payload = {
424
+ email,
425
+ password: encrypted,
426
+ ab: this.ab,
427
+ client_secret_info: { public_key: clientSecretPub },
428
+ answer: captcha?.answer ?? "",
429
+ captcha_id: captcha?.captchaId ?? "",
430
+ verify_code: verifyCode ?? "",
431
+ login_id: "",
432
+ };
433
+ const result = await this.call(this.clusterHost("passport"), "/passport/login", payload);
434
+ if (result.code === types_1.ResponseErrorCode.LOGIN_NEED_CAPTCHA || result.code === types_1.ResponseErrorCode.LOGIN_CAPTCHA_ERROR) {
435
+ logging_1.rootHTTPLogger.info("MegaApi login needs a captcha", { code: result.code });
436
+ return result;
437
+ }
438
+ if (result.code === 0 && result.data) {
439
+ const openapiHost = this.clusterHost("openapi");
440
+ const identity = this.identities.get(openapiHost);
441
+ const decoded = JSON.parse(this.decryptForCluster(identity, result.data));
442
+ const authToken = (decoded.auth_token ?? decoded.token);
443
+ const userId = (decoded.user_id ?? decoded.userId);
444
+ this.setAuth(authToken, userId);
445
+ if (typeof decoded.token_expires_at === "number")
446
+ this.tokenExpiresAt = decoded.token_expires_at;
447
+ const faInfo = decoded.fa_info;
448
+ if (faInfo?.step === types_1.ResponseErrorCode.CODE_NEED_VERIFY_CODE) {
449
+ logging_1.rootHTTPLogger.info("MegaApi login needs 2FA (token stored for verify_code)", { step: faInfo.step });
450
+ return { ...result, code: types_1.ResponseErrorCode.CODE_NEED_VERIFY_CODE, msg: "2FA verify code required" };
451
+ }
452
+ logging_1.rootHTTPLogger.info("MegaApi login ok", { userId, keys: Object.keys(decoded) });
453
+ }
454
+ return result;
455
+ }
456
+ /**
457
+ * Request the email 2FA code (after login indicates fa_info.step 26052).
458
+ *
459
+ * Exact payload confirmed by decompiling the Ijiami-packed `SendVerifyCodeRequest`
460
+ * (dumped from process memory): the 2FA-login send is `{message_type, biz_type, transaction}`
461
+ * with `biz_type = 1004` (BIZ_TYPE_TFA) and `message_type = 2` (TYPE_EMAIL). No captcha for send.
462
+ */
463
+ async sendVerifyCode(_email) {
464
+ return this.call(this.clusterHost("push"), "/app/sendmsg/verify_code", {
465
+ message_type: 2, // TYPE_EMAIL
466
+ biz_type: 1004, // BIZ_TYPE_TFA
467
+ transaction: `${Date.now()}`,
468
+ });
469
+ }
470
+ /**
471
+ * Fetch a picture captcha (after login returns 100032/100033). Body
472
+ * `{captcha_type, biz_type}` (decompiled CaptchaManager.getCaptcha); the response `item` is a
473
+ * base64 image the user must solve, then pass the answer + captcha_id back into {@link login}.
474
+ */
475
+ async generateCaptcha() {
476
+ const result = await this.call(this.clusterHost("passport"), "/passport/generate/captcha", {
477
+ captcha_type: "PIC",
478
+ biz_type: 0,
479
+ });
480
+ if (result.code !== 0)
481
+ throw new Error(`generate/captcha failed: ${result.code} ${result.msg}`);
482
+ const openapiHost = this.clusterHost("openapi");
483
+ const identity = this.identities.get(openapiHost);
484
+ const data = typeof result.data === "string" ? JSON.parse(this.decryptForCluster(identity, result.data)) : result.data;
485
+ return data;
486
+ }
487
+ /**
488
+ * Fetch the Tuya/Thingclips device list (`get_things_list`) and DECRYPT it.
489
+ * This is the probe to see whether a given device (e.g. Kitchen sensor) has been
490
+ * migrated server-side onto the Tuya backend.
491
+ */
492
+ async getThingsListDecrypted(productCodes = []) {
493
+ const host = this.clusterHost("things");
494
+ const openapiHost = this.clusterHost("openapi");
495
+ const identity = await this.keyExchange(openapiHost);
496
+ const result = await this.signedPost(host, "/app/things/get_things_list", { product_codes: productCodes }, identity);
497
+ if (result.code !== 0)
498
+ throw new Error(`get_things_list failed: ${result.code} ${result.msg}`);
499
+ return JSON.parse(this.decryptForCluster(identity, result.data));
500
+ }
501
+ /** Eufy-side device list (`house/get_devs_list`), decrypted. The non-Tuya inventory. */
502
+ async getDevsListDecrypted() {
503
+ const host = this.clusterHost("house");
504
+ const openapiHost = this.clusterHost("openapi");
505
+ const identity = await this.keyExchange(openapiHost);
506
+ const result = await this.signedPost(host, "/app/house/get_devs_list", { device_sn: "", num: 100, orderby: "" }, identity);
507
+ if (result.code !== 0)
508
+ throw new Error(`get_devs_list failed: ${result.code} ${result.msg}`);
509
+ return JSON.parse(this.decryptForCluster(identity, result.data));
510
+ }
511
+ }
512
+ exports.MegaHTTPApi = MegaHTTPApi;
513
+ //# sourceMappingURL=megaApi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"megaApi.js","sourceRoot":"","sources":["../../src/http/megaApi.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ import { ECDH } from "crypto";
2
+ /**
3
+ * Eufy "eufy_mega" v6 transport crypto.
4
+ *
5
+ * Two layers:
6
+ * 1. Bootstrap (handshake): body + signature use a STATIC per-app `presetKey`.
7
+ * 2. Regular requests (post-handshake): body + signature use the per-cluster
8
+ * ECDH `sharedKey` derived from the key/exchange.
9
+ */
10
+ /** Static preset key for the `eufy_security` category (`*.eufy.com`). Extracted from the app's
11
+ * `ESIotAppConfig`, stored in `MegaAppDomain.presetKeyMap` (one key per product category). */
12
+ export declare const MEGA_PRESET_KEY = "2500a7d5617812f9d52515b2c8f20a3d";
13
+ /**
14
+ * X-Signature = HMAC-SHA256, hex lowercase, over `${ts}+${nonce}+${encryptedBody}`.
15
+ * The HMAC key is the **ASCII string** of the key material (NOT hex-decoded).
16
+ *
17
+ * @param keyAscii presetKey (bootstrap) or sharedKey hex string (regular requests)
18
+ */
19
+ export declare const xSignature: (keyAscii: string, ts: string, nonce: string, encryptedBody?: string) => string;
20
+ /** Random 32-hex client-generated X-Key-Ident (one per cluster identity). */
21
+ export declare const generateKeyIdent: () => string;
22
+ /**
23
+ * Encrypt a payload AES-128-CBC/PKCS7 under the preset key, output `base64(IV ++ ciphertext)`.
24
+ * The AES key is `bytes.fromhex(presetKey)` (16 bytes); a fresh random IV is prepended.
25
+ * Used to wrap the client's EC public key in the key/exchange request body.
26
+ */
27
+ export declare const presetEncrypt: (plaintext: string, presetKeyHex?: string) => string;
28
+ /** Inverse of {@link presetEncrypt}: decode `base64(IV ++ ciphertext)` → plaintext. */
29
+ export declare const presetDecrypt: (b64: string, presetKeyHex?: string) => string;
30
+ /** Result of a key/exchange handshake, cached per cluster host. */
31
+ export interface MegaIdentity {
32
+ keyIdent: string;
33
+ /** ECDH shared secret as lowercase hex (raw 32-byte X coordinate). */
34
+ sharedKey: string;
35
+ /** Our ephemeral public key (uncompressed `04…`, hex) sent in the exchange. */
36
+ clientPublicKey: string;
37
+ }
38
+ /**
39
+ * Build the key/exchange request material.
40
+ *
41
+ * The exchange is ECIES-bootstrapped: the client's ephemeral EC public key is wrapped
42
+ * with the static `presetKey`. The SESSION sharedKey is NOT derivable yet — it is
43
+ * `ECDH(clientPriv, server_public_key)` where server_public_key comes back in the
44
+ * response (see {@link finalizeKeyExchange}). We keep the ECDH object so the caller can
45
+ * finish the derivation once the server replies.
46
+ *
47
+ * @returns the ECDH object (holds the client private key), the wrapped client_public_key
48
+ * body value, the client public key hex, and a fresh client-generated keyIdent.
49
+ */
50
+ export declare const buildKeyExchange: () => {
51
+ ecdh: ECDH;
52
+ clientPublicKeyBody: string;
53
+ clientPublicKey: string;
54
+ keyIdent: string;
55
+ };
56
+ /**
57
+ * Finalize the handshake: derive the session sharedKey from our ECDH private key and the
58
+ * server's public key returned (preset-encrypted) in the key/exchange response.
59
+ *
60
+ * @param ecdh the ECDH object from {@link buildKeyExchange} (holds clientPriv)
61
+ * @param serverPublicKeyEnc base64 `server_public_key` from the response (preset-wrapped)
62
+ * @param keyIdent the client keyIdent used for this cluster
63
+ * @param clientPublicKey our public key hex (for reference)
64
+ */
65
+ export declare const finalizeKeyExchange: (ecdh: ECDH, serverPublicKeyEnc: string, keyIdent: string, clientPublicKey: string) => MegaIdentity;
66
+ /**
67
+ * Per-request key material derived from the handshake sharedKey.
68
+ *
69
+ * The sharedKey is the ECDH X-coordinate as a 64-char hex string. For regular requests:
70
+ * - **HMAC/signature key** = the first 32 hex CHARS of sharedKey, used as an ASCII string.
71
+ * - **AES body key** = `bytes.fromhex(sharedKey[:32])` → 16 bytes (AES-128); iv = key[:16].
72
+ *
73
+ * NOTE: only the first 32 hex chars (16 bytes) of the 64-char sharedKey are used.
74
+ */
75
+ export declare const sharedKeySigningKey: (sharedKeyHex: string) => string;
76
+ /** AES key buffer for body encryption: bytes.fromhex(sharedKey[:32]) = 16 bytes (AES-128). */
77
+ export declare const sharedKeyToAesKey: (sharedKeyHex: string) => Buffer;
78
+ /**
79
+ * Encrypt a regular (post-handshake) request/response body: AES-128-CBC/PKCS7 with a fresh
80
+ * RANDOM IV, output `base64(IV ++ ciphertext)` — same envelope as the key/exchange body.
81
+ */
82
+ export declare const megaEncryptBody: (plaintext: string, aesKey: Buffer) => string;
83
+ /** Inverse of {@link megaEncryptBody}: decode `base64(IV ++ ciphertext)` → plaintext. */
84
+ export declare const megaDecryptBody: (b64: string, aesKey: Buffer) => string;