alexa-cookie2 4.0.1 → 4.1.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/.eslintrc.json +40 -0
- package/.releaseconfig.json +3 -0
- package/LICENSE +1 -1
- package/README.md +15 -1
- package/alexa-cookie.js +84 -82
- package/example/example.js +2 -1
- package/lib/proxy.js +34 -36
- package/package.json +7 -5
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"root": true,
|
|
3
|
+
"env": {
|
|
4
|
+
"es6": true,
|
|
5
|
+
"node": true,
|
|
6
|
+
"mocha": true
|
|
7
|
+
},
|
|
8
|
+
"extends": [
|
|
9
|
+
"eslint:recommended"
|
|
10
|
+
],
|
|
11
|
+
"plugins": [],
|
|
12
|
+
"rules": {
|
|
13
|
+
"indent": [
|
|
14
|
+
"error",
|
|
15
|
+
4,
|
|
16
|
+
{
|
|
17
|
+
"SwitchCase": 1
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"no-console": "off",
|
|
21
|
+
"no-var": "error",
|
|
22
|
+
"no-trailing-spaces": "error",
|
|
23
|
+
"prefer-const": "error",
|
|
24
|
+
"quotes": [
|
|
25
|
+
"error",
|
|
26
|
+
"single",
|
|
27
|
+
{
|
|
28
|
+
"avoidEscape": true,
|
|
29
|
+
"allowTemplateLiterals": true
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"semi": [
|
|
33
|
+
"error",
|
|
34
|
+
"always"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"parserOptions": {
|
|
38
|
+
"ecmaVersion": 2018
|
|
39
|
+
}
|
|
40
|
+
}
|
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2018-
|
|
3
|
+
Copyright (c) 2018-2022 Apollon77 <ingo@fischer-ka.de>
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
Library to generate/retrieve a cookie including a csrf for alexa remote
|
|
8
8
|
|
|
9
|
+
## Disclaimer
|
|
10
|
+
**All product and company names or logos are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them or any associated subsidiaries! This personal project is maintained in spare time and has no business goal.**
|
|
11
|
+
**ALEXA is a trademark of AMAZON TECHNOLOGIES, INC.**
|
|
12
|
+
|
|
9
13
|
## Description
|
|
10
14
|
This library can be used to get the cookies needed to access Amazon Alexa services from outside. It authenticates with Amazon and gathers all needed details. These details are returned in the callback.
|
|
11
15
|
If the automatic authentication fails (which is more common case in the meantime because of security checks from amazon like a needed Captcha or because you enabled two factor authentication) the library can also setup a proxy server to allow the manual login and will catch the cookie by itself. Using this proxy you can enter needed 2FA codes or solve captchas and still do not need to trick around to get the cookie.
|
|
@@ -45,6 +49,16 @@ Partly based on [Amazon Alexa Remote Control](http://blog.loetzimmer.de/2017/10/
|
|
|
45
49
|
Thank you for that work.
|
|
46
50
|
|
|
47
51
|
## Changelog:
|
|
52
|
+
### 4.1.0 (2022-07-18)
|
|
53
|
+
* (Apollon77) Allow to overwrite the used App-Name for the Amazon App Registration.
|
|
54
|
+
* (Apollon77) Include the used app name also in the response
|
|
55
|
+
|
|
56
|
+
### 4.0.3 (2022-07-06)
|
|
57
|
+
* (Apollon77) Update some request meta data to match current Alexa Apps
|
|
58
|
+
|
|
59
|
+
### 4.0.2 (2022-06-30)
|
|
60
|
+
* (Apollon77) Prevent potential crash cases
|
|
61
|
+
|
|
48
62
|
### 4.0.1 (2021-10-11)
|
|
49
63
|
* (Apollon77) Adjust call headers
|
|
50
64
|
|
|
@@ -140,4 +154,4 @@ Thank you for that work.
|
|
|
140
154
|
* (Apollon77) 0.1.1: update to get it working again and sync to [alexa-remote-control](https://github.com/thorsten-gehrig/alexa-remote-control)
|
|
141
155
|
|
|
142
156
|
### 0.0.x
|
|
143
|
-
* Versions by soef
|
|
157
|
+
* Versions by soef
|
package/alexa-cookie.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/* jshint -W097 */
|
|
2
|
-
/* jshint -W030 */
|
|
3
|
-
/* jshint strict: false */
|
|
4
|
-
/* jslint node: true */
|
|
5
|
-
/* jslint esversion: 6 */
|
|
6
|
-
"use strict";
|
|
7
|
-
|
|
8
1
|
/**
|
|
9
2
|
* partly based on Amazon Alexa Remote Control (PLAIN shell)
|
|
10
3
|
* http://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html AND on
|
|
@@ -25,6 +18,10 @@ const defaultUserAgentLinux = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.3
|
|
|
25
18
|
//const defaultUserAgentMacOs = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36';
|
|
26
19
|
const defaultAcceptLanguage = 'de-DE';
|
|
27
20
|
|
|
21
|
+
const apiCallVersion = '2.2.483723.0';
|
|
22
|
+
const apiCallUserAgent = 'AmazonWebView/Amazon Alexa/2.2.483723.0/iOS/15.5/iPhone';
|
|
23
|
+
const defaultAppName = 'ioBroker Alexa2';
|
|
24
|
+
|
|
28
25
|
const csrfOptions = [
|
|
29
26
|
'/api/language',
|
|
30
27
|
'/spa/index.html',
|
|
@@ -57,8 +54,7 @@ function AlexaCookie() {
|
|
|
57
54
|
}
|
|
58
55
|
}
|
|
59
56
|
Cookie = '';
|
|
60
|
-
for (
|
|
61
|
-
if (!cookies.hasOwnProperty(name)) continue;
|
|
57
|
+
for (const name of Object.keys(cookies)) {
|
|
62
58
|
Cookie += name + '=' + cookies[name] + '; ';
|
|
63
59
|
}
|
|
64
60
|
Cookie = Cookie.replace(/[; ]*$/, '');
|
|
@@ -83,14 +79,14 @@ function AlexaCookie() {
|
|
|
83
79
|
removeContentLength = true;
|
|
84
80
|
}
|
|
85
81
|
|
|
86
|
-
|
|
87
|
-
let body =
|
|
82
|
+
const req = https.request(options, (res) => {
|
|
83
|
+
let body = '';
|
|
88
84
|
info.requests.push({options: options, response: res});
|
|
89
85
|
|
|
90
86
|
if (options.followRedirects !== false && res.statusCode >= 300 && res.statusCode < 400) {
|
|
91
87
|
_options.logger && _options.logger('Alexa-Cookie: Response (' + res.statusCode + ')' + (res.headers.location ? ' - Redirect to ' + res.headers.location : ''));
|
|
92
88
|
//options.url = res.headers.location;
|
|
93
|
-
|
|
89
|
+
const u = url.parse(res.headers.location);
|
|
94
90
|
if (u.host) options.host = u.host;
|
|
95
91
|
options.path = u.path;
|
|
96
92
|
options.method = 'GET';
|
|
@@ -127,11 +123,11 @@ function AlexaCookie() {
|
|
|
127
123
|
const getFields = body => {
|
|
128
124
|
body = body.replace(/[\n\r]/g, ' ');
|
|
129
125
|
let re = /^.*?("hidden"\s*name=".*$)/;
|
|
130
|
-
|
|
126
|
+
const ar = re.exec(body);
|
|
131
127
|
if (!ar || ar.length < 2) return {};
|
|
132
128
|
let h;
|
|
133
129
|
re = /.*?name="([^"]+)"[\s^\s]*value="([^"]+).*?"/g;
|
|
134
|
-
|
|
130
|
+
const data = {};
|
|
135
131
|
while ((h = re.exec(ar[1])) !== null) {
|
|
136
132
|
if (h[1] !== 'rememberMe') {
|
|
137
133
|
data[h[1]] = h[2];
|
|
@@ -149,6 +145,9 @@ function AlexaCookie() {
|
|
|
149
145
|
_options.baseAmazonPage = _options.baseAmazonPage || 'amazon.com';
|
|
150
146
|
_options.logger && _options.logger('Alexa-Cookie: Use as Base-Amazon-URL: ' + _options.baseAmazonPage);
|
|
151
147
|
|
|
148
|
+
_options.deviceAppName = _options.deviceAppName || defaultAppName;
|
|
149
|
+
_options.logger && _options.logger('Alexa-Cookie: Use as Device-App-Name: ' + _options.deviceAppName);
|
|
150
|
+
|
|
152
151
|
if (!_options.baseAmazonPageHandle && _options.baseAmazonPageHandle !== '') {
|
|
153
152
|
const amazonDomain = _options.baseAmazonPage.substr(_options.baseAmazonPage.lastIndexOf('.') + 1);
|
|
154
153
|
if (amazonDomain === 'jp') {
|
|
@@ -164,7 +163,7 @@ function AlexaCookie() {
|
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
if (!_options.userAgent) {
|
|
167
|
-
|
|
166
|
+
const platform = os.platform();
|
|
168
167
|
if (platform === 'win32') {
|
|
169
168
|
_options.userAgent = defaultUserAgent;
|
|
170
169
|
}
|
|
@@ -205,13 +204,13 @@ function AlexaCookie() {
|
|
|
205
204
|
|
|
206
205
|
function csrfTry() {
|
|
207
206
|
const path = csrfUrls.shift();
|
|
208
|
-
|
|
207
|
+
const options = {
|
|
209
208
|
'host': 'alexa.' + _options.amazonPage,
|
|
210
209
|
'path': path,
|
|
211
210
|
'method': 'GET',
|
|
212
211
|
'headers': {
|
|
213
212
|
'DNT': '1',
|
|
214
|
-
'User-Agent':
|
|
213
|
+
'User-Agent': _options.userAgent,
|
|
215
214
|
'Connection': 'keep-alive',
|
|
216
215
|
'Referer': 'https://alexa.' + _options.amazonPage + '/spa/index.html',
|
|
217
216
|
'Cookie': cookie,
|
|
@@ -223,8 +222,8 @@ function AlexaCookie() {
|
|
|
223
222
|
_options.logger && _options.logger('Alexa-Cookie: Step 4: get CSRF via ' + path);
|
|
224
223
|
request(options, (error, response) => {
|
|
225
224
|
cookie = addCookies(cookie, response ? response.headers : null);
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
const ar = /csrf=([^;]+)/.exec(cookie);
|
|
226
|
+
const csrf = ar ? ar[1] : undefined;
|
|
228
227
|
_options.logger && _options.logger('Alexa-Cookie: Result: csrf=' + csrf + ', Cookie=' + cookie);
|
|
229
228
|
if (!csrf && csrfUrls.length) {
|
|
230
229
|
csrfTry();
|
|
@@ -268,7 +267,7 @@ function AlexaCookie() {
|
|
|
268
267
|
|
|
269
268
|
if (!_options.proxyOnly) {
|
|
270
269
|
// get first cookie and write redirection target into referer
|
|
271
|
-
|
|
270
|
+
const options = {
|
|
272
271
|
host: 'alexa.' + _options.amazonPage,
|
|
273
272
|
path: '',
|
|
274
273
|
method: 'GET',
|
|
@@ -288,10 +287,10 @@ function AlexaCookie() {
|
|
|
288
287
|
return;
|
|
289
288
|
}
|
|
290
289
|
|
|
291
|
-
|
|
290
|
+
const lastRequestOptions = info.requests[info.requests.length - 1].options;
|
|
292
291
|
// login empty to generate session
|
|
293
292
|
Cookie = addCookies(Cookie, response.headers);
|
|
294
|
-
|
|
293
|
+
const options = {
|
|
295
294
|
host: 'www.' + _options.amazonPage,
|
|
296
295
|
path: '/ap/signin',
|
|
297
296
|
method: 'POST',
|
|
@@ -322,7 +321,7 @@ function AlexaCookie() {
|
|
|
322
321
|
options.path = '/ap/signin';
|
|
323
322
|
options.method = 'POST';
|
|
324
323
|
options.headers.Cookie = Cookie = addCookies(Cookie, response.headers);
|
|
325
|
-
|
|
324
|
+
const ar = options.headers.Cookie.match(/session-id=([^;]+)/);
|
|
326
325
|
options.headers.Referer = `https://www.${_options.amazonPage}/ap/signin/${ar[1]}`;
|
|
327
326
|
options.body = getFields(body);
|
|
328
327
|
options.body.email = email || '';
|
|
@@ -336,7 +335,7 @@ function AlexaCookie() {
|
|
|
336
335
|
return;
|
|
337
336
|
}
|
|
338
337
|
|
|
339
|
-
|
|
338
|
+
const lastRequestOptions = info.requests[info.requests.length - 1].options;
|
|
340
339
|
|
|
341
340
|
// check whether the login has been successful or exit otherwise
|
|
342
341
|
if (!lastRequestOptions.host.startsWith('alexa') || !lastRequestOptions.path.endsWith('.html')) {
|
|
@@ -397,10 +396,15 @@ function AlexaCookie() {
|
|
|
397
396
|
}
|
|
398
397
|
};
|
|
399
398
|
|
|
399
|
+
this.getDeviceAppName = () => {
|
|
400
|
+
return _options.deviceAppName || defaultAppName;
|
|
401
|
+
};
|
|
400
402
|
|
|
401
403
|
const handleTokenRegistration = (_options, loginData, callback) => {
|
|
402
404
|
_options.logger && _options.logger('Handle token registration Start: ' + JSON.stringify(loginData));
|
|
403
405
|
|
|
406
|
+
loginData.deviceAppName = _options.deviceAppName;
|
|
407
|
+
|
|
404
408
|
let deviceSerial;
|
|
405
409
|
if (!_options.formerRegistrationData || !_options.formerRegistrationData.deviceSerial) {
|
|
406
410
|
const deviceSerialBuffer = Buffer.alloc(16);
|
|
@@ -422,69 +426,68 @@ function AlexaCookie() {
|
|
|
422
426
|
*/
|
|
423
427
|
|
|
424
428
|
const registerData = {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
429
|
+
'requested_extensions': [
|
|
430
|
+
'device_info',
|
|
431
|
+
'customer_info'
|
|
428
432
|
],
|
|
429
|
-
|
|
430
|
-
|
|
433
|
+
'cookies': {
|
|
434
|
+
'website_cookies': [
|
|
431
435
|
/*{
|
|
432
436
|
"Value": cookies["session-id-time"],
|
|
433
437
|
"Name": "session-id-time"
|
|
434
438
|
}*/
|
|
435
439
|
],
|
|
436
|
-
|
|
440
|
+
'domain': '.' + _options.baseAmazonPage
|
|
437
441
|
},
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
442
|
+
'registration_data': {
|
|
443
|
+
'domain': 'Device',
|
|
444
|
+
'app_version': apiCallVersion,
|
|
445
|
+
'device_type': 'A2IVLV5VM2W81',
|
|
446
|
+
'device_name': '%FIRST_NAME%\u0027s%DUPE_STRATEGY_1ST%ioBroker Alexa2',
|
|
447
|
+
'os_version': '15.5',
|
|
448
|
+
'device_serial': deviceSerial,
|
|
449
|
+
'device_model': 'iPhone',
|
|
450
|
+
'app_name': _options.deviceAppName,
|
|
451
|
+
'software_version': '1'
|
|
448
452
|
},
|
|
449
|
-
|
|
453
|
+
'auth_data': {
|
|
450
454
|
// Filled below
|
|
451
455
|
},
|
|
452
|
-
|
|
453
|
-
|
|
456
|
+
'user_context_map': {
|
|
457
|
+
'frc': cookies.frc
|
|
454
458
|
},
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
+
'requested_token_type': [
|
|
460
|
+
'bearer',
|
|
461
|
+
'mac_dms',
|
|
462
|
+
'website_cookies'
|
|
459
463
|
]
|
|
460
464
|
};
|
|
461
465
|
if (loginData.accessToken) {
|
|
462
466
|
registerData.auth_data = {
|
|
463
|
-
|
|
467
|
+
'access_token': loginData.accessToken
|
|
464
468
|
};
|
|
465
469
|
} else if (loginData.authorization_code && loginData.verifier) {
|
|
466
470
|
registerData.auth_data = {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
471
|
+
'client_id' : loginData.deviceId,
|
|
472
|
+
'authorization_code' : loginData.authorization_code,
|
|
473
|
+
'code_verifier' : loginData.verifier,
|
|
474
|
+
'code_algorithm' : 'SHA-256',
|
|
475
|
+
'client_domain' : 'DeviceLegacy'
|
|
472
476
|
};
|
|
473
477
|
}
|
|
474
|
-
for (
|
|
475
|
-
if (!cookies.hasOwnProperty(key)) continue;
|
|
478
|
+
for (const key of Object.keys(cookies)) {
|
|
476
479
|
registerData.cookies.website_cookies.push({
|
|
477
|
-
|
|
478
|
-
|
|
480
|
+
'Value': cookies[key],
|
|
481
|
+
'Name': key
|
|
479
482
|
});
|
|
480
483
|
}
|
|
481
484
|
|
|
482
|
-
|
|
485
|
+
const options = {
|
|
483
486
|
host: 'api.' + _options.baseAmazonPage,
|
|
484
487
|
path: '/auth/register',
|
|
485
488
|
method: 'POST',
|
|
486
489
|
headers: {
|
|
487
|
-
'User-Agent':
|
|
490
|
+
'User-Agent': apiCallUserAgent,
|
|
488
491
|
'Accept-Language': _options.acceptLanguage,
|
|
489
492
|
'Accept-Charset': 'utf-8',
|
|
490
493
|
'Connection': 'keep-alive',
|
|
@@ -524,12 +527,12 @@ function AlexaCookie() {
|
|
|
524
527
|
Get Amazon Marketplace Country
|
|
525
528
|
*/
|
|
526
529
|
|
|
527
|
-
|
|
530
|
+
const options = {
|
|
528
531
|
host: 'alexa.' + _options.baseAmazonPage,
|
|
529
|
-
path: '/api/users/me?platform=ios&version=
|
|
532
|
+
path: '/api/users/me?platform=ios&version=' + apiCallVersion,
|
|
530
533
|
method: 'GET',
|
|
531
534
|
headers: {
|
|
532
|
-
'User-Agent':
|
|
535
|
+
'User-Agent': apiCallUserAgent,
|
|
533
536
|
'Accept-Language': _options.acceptLanguage,
|
|
534
537
|
'Accept-Charset': 'utf-8',
|
|
535
538
|
'Connection': 'keep-alive',
|
|
@@ -560,7 +563,7 @@ function AlexaCookie() {
|
|
|
560
563
|
} else if (error && (!_options || !_options.amazonPage)) {
|
|
561
564
|
callback && callback(error, null);
|
|
562
565
|
return;
|
|
563
|
-
} else if (error && !_options.formerRegistrationData.amazonPage && _options.amazonPage) {
|
|
566
|
+
} else if (error && (!_options.formerRegistrationData || !_options.formerRegistrationData.amazonPage) && _options.amazonPage) {
|
|
564
567
|
_options.logger && _options.logger('Continue with externally set amazonPage: ' + _options.amazonPage);
|
|
565
568
|
} else if (error) {
|
|
566
569
|
_options.logger && _options.logger('Ignore error while getting user data and amazonPage because previously set amazonPage is available');
|
|
@@ -600,7 +603,7 @@ function AlexaCookie() {
|
|
|
600
603
|
|
|
601
604
|
const exchangeParams = {
|
|
602
605
|
'di.os.name': 'iOS',
|
|
603
|
-
'app_version':
|
|
606
|
+
'app_version': apiCallVersion,
|
|
604
607
|
'domain': '.' + amazonPage,
|
|
605
608
|
'source_token': refreshToken,
|
|
606
609
|
'requested_token_type': 'auth_cookies',
|
|
@@ -611,12 +614,12 @@ function AlexaCookie() {
|
|
|
611
614
|
'app_name': 'Amazon Alexa',
|
|
612
615
|
'di.os.version': '11.4.1'
|
|
613
616
|
};
|
|
614
|
-
|
|
617
|
+
const options = {
|
|
615
618
|
host: 'www.' + amazonPage,
|
|
616
619
|
path: '/ap/exchangetoken',
|
|
617
620
|
method: 'POST',
|
|
618
621
|
headers: {
|
|
619
|
-
'User-Agent':
|
|
622
|
+
'User-Agent': apiCallUserAgent,
|
|
620
623
|
'Accept-Language': _options.acceptLanguage,
|
|
621
624
|
'Accept-Charset': 'utf-8',
|
|
622
625
|
'Connection': 'keep-alive',
|
|
@@ -665,8 +668,7 @@ function AlexaCookie() {
|
|
|
665
668
|
|
|
666
669
|
});
|
|
667
670
|
let localCookie = '';
|
|
668
|
-
for (
|
|
669
|
-
if (!cookies.hasOwnProperty(name)) continue;
|
|
671
|
+
for (const name of Object.keys(cookies)) {
|
|
670
672
|
localCookie += name + '=' + cookies[name] + '; ';
|
|
671
673
|
}
|
|
672
674
|
localCookie = localCookie.replace(/[; ]*$/, '');
|
|
@@ -693,26 +695,26 @@ function AlexaCookie() {
|
|
|
693
695
|
initConfig();
|
|
694
696
|
|
|
695
697
|
const refreshData = {
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
698
|
+
'app_name': 'ioBroker Alexa2',
|
|
699
|
+
'app_version': apiCallVersion,
|
|
700
|
+
'di.sdk.version': '6.10.0',
|
|
701
|
+
'source_token': _options.formerRegistrationData.refreshToken,
|
|
702
|
+
'package_name': 'com.amazon.echo',
|
|
703
|
+
'di.hw.version': 'iPhone',
|
|
704
|
+
'platform': 'iOS',
|
|
705
|
+
'requested_token_type': 'access_token',
|
|
706
|
+
'source_token_type': 'refresh_token',
|
|
707
|
+
'di.os.name': 'iOS',
|
|
708
|
+
'di.os.version': '14.8',
|
|
709
|
+
'current_version': '6.10.0'
|
|
708
710
|
};
|
|
709
711
|
|
|
710
|
-
|
|
712
|
+
const options = {
|
|
711
713
|
host: 'api.' + _options.baseAmazonPage,
|
|
712
714
|
path: '/auth/token',
|
|
713
715
|
method: 'POST',
|
|
714
716
|
headers: {
|
|
715
|
-
'User-Agent':
|
|
717
|
+
'User-Agent': apiCallUserAgent,
|
|
716
718
|
'Accept-Language': _options.acceptLanguage,
|
|
717
719
|
'Accept-Charset': 'utf-8',
|
|
718
720
|
'Connection': 'keep-alive',
|
package/example/example.js
CHANGED
|
@@ -16,9 +16,10 @@ const config = {
|
|
|
16
16
|
proxyOwnIp: '...', // required if proxy enabled: provide own IP or hostname to later access the proxy. needed to setup all rewriting and proxy stuff internally
|
|
17
17
|
proxyPort: 3456, // optional: use this port for the proxy, default is 0 means random port is selected
|
|
18
18
|
proxyListenBind: '0.0.0.0',// optional: set this to bind the proxy to a special IP, default is '0.0.0.0'
|
|
19
|
-
proxyLogLevel: 'info',
|
|
19
|
+
proxyLogLevel: 'info', // optional: Loglevel of Proxy, default 'warn'
|
|
20
20
|
baseAmazonPage: 'amazon.com', // optional: Change the Proxy Amazon Page - all "western countries" directly use amazon.com! Change to amazon.co.jp for Japan
|
|
21
21
|
amazonPageProxyLanguage: 'de_DE', // optional: language to be used for the Amazon Sign-in page the proxy calls. default is "de_DE")
|
|
22
|
+
deviceAppName: '...', // optional: name of the device app name which will be registered with Amazon, leave empty to use a default one
|
|
22
23
|
formerDataStorePath: '...', // optional: overwrite path where some of the formerRegistrationData are persisted to optimize against Amazon security measures
|
|
23
24
|
formerRegistrationData: { ... } // optional/preferred: provide the result object from subsequent proxy usages here and some generated data will be reused for next proxy call too
|
|
24
25
|
};
|
package/lib/proxy.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/* jshint strict: false */
|
|
4
4
|
/* jslint node: true */
|
|
5
5
|
/* jslint esversion: 6 */
|
|
6
|
-
|
|
6
|
+
'use strict';
|
|
7
7
|
|
|
8
8
|
const modifyResponse = require('http-proxy-response-rewrite');
|
|
9
9
|
const express = require('express');
|
|
@@ -27,8 +27,7 @@ function addCookies(Cookie, headers) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
Cookie = '';
|
|
30
|
-
for (
|
|
31
|
-
if (!cookies.hasOwnProperty(name)) continue;
|
|
30
|
+
for (const name of Object.keys(cookies)) {
|
|
32
31
|
Cookie += name + '=' + cookies[name] + '; ';
|
|
33
32
|
}
|
|
34
33
|
Cookie = Cookie.replace(/[; ]*$/, '');
|
|
@@ -90,7 +89,7 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
|
90
89
|
initialCookies.frc = _options.formerRegistrationData.frc;
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
if (!_options.formerRegistrationData || !_options.formerRegistrationData[
|
|
92
|
+
if (!_options.formerRegistrationData || !_options.formerRegistrationData['map-md']) {
|
|
94
93
|
initialCookies['map-md'] = Buffer.from('{"device_user_dictionary":[],"device_registration_data":{"software_version":"1"},"app_identifier":{"app_version":"2.2.223830","bundle_id":"com.amazon.echo"}}').toString('base64');
|
|
95
94
|
}
|
|
96
95
|
else {
|
|
@@ -123,19 +122,19 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
|
123
122
|
// ignore
|
|
124
123
|
}
|
|
125
124
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
125
|
+
function base64URLEncode(str) {
|
|
126
|
+
return str.toString('base64')
|
|
127
|
+
.replace(/\+/g, '-')
|
|
128
|
+
.replace(/\//g, '_')
|
|
129
|
+
.replace(/=/g, '');
|
|
130
|
+
}
|
|
131
|
+
function sha256(buffer) {
|
|
132
|
+
return crypto.createHash('sha256').update(buffer).digest();
|
|
133
|
+
}
|
|
134
|
+
const code_verifier = base64URLEncode(crypto.randomBytes(32));
|
|
135
|
+
const code_challenge = base64URLEncode(sha256(code_verifier));
|
|
137
136
|
|
|
138
|
-
let proxyCookies =
|
|
137
|
+
let proxyCookies = '';
|
|
139
138
|
|
|
140
139
|
// proxy middleware options
|
|
141
140
|
const optionsAlexa = {
|
|
@@ -151,19 +150,19 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
|
151
150
|
onProxyRes: onProxyRes,
|
|
152
151
|
onProxyReq: onProxyReq,
|
|
153
152
|
headers: {
|
|
154
|
-
'user-agent':
|
|
153
|
+
'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 PitanguiBridge/2.2.345247.0-[HARDWARE=iPhone10_4][SOFTWARE=13.5.1]',
|
|
155
154
|
'accept-language': _options.acceptLanguage,
|
|
156
155
|
'authority': `www.${_options.baseAmazonPage}`
|
|
157
156
|
},
|
|
158
157
|
cookieDomainRewrite: { // enhanced below
|
|
159
|
-
|
|
158
|
+
'*': ''
|
|
160
159
|
}
|
|
161
160
|
};
|
|
162
161
|
optionsAlexa.pathRewrite[`^/www.${_options.baseAmazonPage}`] = '';
|
|
163
162
|
optionsAlexa.pathRewrite[`^/alexa.${_options.baseAmazonPage}`] = '';
|
|
164
163
|
optionsAlexa.cookieDomainRewrite[`.${_options.baseAmazonPage}`] = _options.proxyOwnIp;
|
|
165
164
|
optionsAlexa.cookieDomainRewrite[_options.baseAmazonPage] = _options.proxyOwnIp;
|
|
166
|
-
if (_options.logger) optionsAlexa.logProvider = function logProvider(
|
|
165
|
+
if (_options.logger) optionsAlexa.logProvider = function logProvider() {
|
|
167
166
|
return {
|
|
168
167
|
log: _options.logger.log || _options.logger,
|
|
169
168
|
debug: _options.logger.debug || _options.logger,
|
|
@@ -190,7 +189,7 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
|
190
189
|
}
|
|
191
190
|
}
|
|
192
191
|
if (url === '/') { // initial redirect
|
|
193
|
-
|
|
192
|
+
returnedInitUrl = `https://www.${_options.baseAmazonPage}/ap/signin?openid.return_to=https%3A%2F%2Fwww.${_options.baseAmazonPage}%2Fap%2Fmaplanding&openid.assoc_handle=amzn_dp_project_dee_ios${_options.baseAmazonPageHandle}&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&pageId=amzn_dp_project_dee_ios${_options.baseAmazonPageHandle}&accountStatusPolicy=P1&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns.oa2=http%3A%2F%2Fwww.${_options.baseAmazonPage}%2Fap%2Fext%2Foauth%2F2&openid.oa2.client_id=device%3A${deviceId}&openid.ns.pape=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Fpape%2F1.0&openid.oa2.response_type=code&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.pape.max_auth_age=0&openid.oa2.scope=device_auth_access&openid.oa2.code_challenge_method=S256&openid.oa2.code_challenge=${code_challenge}&language=${_options.amazonPageProxyLanguage}`;
|
|
194
193
|
_options.logger && _options.logger('Alexa-Cookie: Initial Page Request: ' + returnedInitUrl);
|
|
195
194
|
return returnedInitUrl;
|
|
196
195
|
}
|
|
@@ -215,8 +214,8 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
|
215
214
|
|
|
216
215
|
function replaceHosts(data) {
|
|
217
216
|
//const dataOrig = data;
|
|
218
|
-
const amazonRegex = new RegExp(`https?://www.${_options.baseAmazonPage}:?[0-9]*/`.replace(/\./g,
|
|
219
|
-
const alexaRegex = new RegExp(`https?://alexa.${_options.baseAmazonPage}:?[0-9]*/`.replace(/\./g,
|
|
217
|
+
const amazonRegex = new RegExp(`https?://www.${_options.baseAmazonPage}:?[0-9]*/`.replace(/\./g, '\\.'), 'g');
|
|
218
|
+
const alexaRegex = new RegExp(`https?://alexa.${_options.baseAmazonPage}:?[0-9]*/`.replace(/\./g, '\\.'), 'g');
|
|
220
219
|
data = data.replace(///g, '/');
|
|
221
220
|
data = data.replace(amazonRegex, `http://${_options.proxyOwnIp}:${_options.proxyPort}/www.${_options.baseAmazonPage}/`);
|
|
222
221
|
data = data.replace(alexaRegex, `http://${_options.proxyOwnIp}:${_options.proxyPort}/alexa.${_options.baseAmazonPage}/`);
|
|
@@ -225,8 +224,8 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
|
225
224
|
}
|
|
226
225
|
|
|
227
226
|
function replaceHostsBack(data) {
|
|
228
|
-
const amazonRegex = new RegExp(`http://${_options.proxyOwnIp}:${_options.proxyPort}/www.${_options.baseAmazonPage}/`.replace(/\./g,
|
|
229
|
-
const alexaRegex = new RegExp(`http://${_options.proxyOwnIp}:${_options.proxyPort}/alexa.${_options.baseAmazonPage}/`.replace(/\./g,
|
|
227
|
+
const amazonRegex = new RegExp(`http://${_options.proxyOwnIp}:${_options.proxyPort}/www.${_options.baseAmazonPage}/`.replace(/\./g, '\\.'), 'g');
|
|
228
|
+
const alexaRegex = new RegExp(`http://${_options.proxyOwnIp}:${_options.proxyPort}/alexa.${_options.baseAmazonPage}/`.replace(/\./g, '\\.'), 'g');
|
|
230
229
|
data = data.replace(amazonRegex, `https://www.${_options.baseAmazonPage}/`);
|
|
231
230
|
data = data.replace(alexaRegex, `https://alexa.${_options.baseAmazonPage}/`);
|
|
232
231
|
if (data === `http://${_options.proxyOwnIp}:${_options.proxyPort}/`) {
|
|
@@ -235,7 +234,7 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
|
235
234
|
return data;
|
|
236
235
|
}
|
|
237
236
|
|
|
238
|
-
function onProxyReq(proxyReq, req
|
|
237
|
+
function onProxyReq(proxyReq, req/*, _res*/) {
|
|
239
238
|
const url = req.originalUrl || req.url;
|
|
240
239
|
if (url.endsWith('.ico') || url.endsWith('.js') || url.endsWith('.ttf') || url.endsWith('.svg') || url.endsWith('.png') || url.endsWith('.appcache')) return;
|
|
241
240
|
//if (url.startsWith('/ap/uedata')) return;
|
|
@@ -247,10 +246,9 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
|
247
246
|
_options.logger && _options.logger('Alexa-Cookie: Headers: ' + JSON.stringify(proxyReq.getHeaders()));
|
|
248
247
|
let reqCookie = proxyReq.getHeader('cookie');
|
|
249
248
|
if (reqCookie === undefined) {
|
|
250
|
-
reqCookie =
|
|
249
|
+
reqCookie = '';
|
|
251
250
|
}
|
|
252
|
-
for (
|
|
253
|
-
if (!initialCookies.hasOwnProperty(cookie)) continue;
|
|
251
|
+
for (const cookie of Object.keys(initialCookies)) {
|
|
254
252
|
if (!reqCookie.includes(cookie + '=')) {
|
|
255
253
|
reqCookie += '; ' + cookie + '=' + initialCookies[cookie];
|
|
256
254
|
}
|
|
@@ -270,7 +268,7 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
|
270
268
|
let modified = false;
|
|
271
269
|
if (req.method === 'POST') {
|
|
272
270
|
if (typeof proxyReq.getHeader === 'function' && proxyReq.getHeader('referer')) {
|
|
273
|
-
|
|
271
|
+
const fixedReferer = replaceHostsBack(proxyReq.getHeader('referer'));
|
|
274
272
|
if (fixedReferer ) {
|
|
275
273
|
proxyReq.setHeader('referer', fixedReferer);
|
|
276
274
|
_options.logger && _options.logger('Alexa-Cookie: Modify headers: Changed Referer: ' + fixedReferer);
|
|
@@ -330,12 +328,12 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
|
330
328
|
_options.logger && _options.logger('Alexa-Cookie: Proxy catched parameters: ' + JSON.stringify(queryParams));
|
|
331
329
|
|
|
332
330
|
callbackCookie && callbackCookie(null, {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
331
|
+
'loginCookie': proxyCookies,
|
|
332
|
+
'authorization_code': queryParams['openid.oa2.authorization_code'],
|
|
333
|
+
'frc': initialCookies.frc,
|
|
334
|
+
'map-md': initialCookies['map-md'],
|
|
335
|
+
'deviceId': deviceId,
|
|
336
|
+
'verifier': code_verifier
|
|
339
337
|
});
|
|
340
338
|
return;
|
|
341
339
|
}
|
|
@@ -376,7 +374,7 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
|
|
|
376
374
|
app.get('/cookie-success', function(req, res) {
|
|
377
375
|
res.send('<b>Amazon Alexa Cookie successfully retrieved. You can close the browser.</b>');
|
|
378
376
|
});
|
|
379
|
-
|
|
377
|
+
const server = app.listen(_options.proxyPort, _options.proxyListenBind, function() {
|
|
380
378
|
_options.logger && _options.logger('Alexa-Cookie: Proxy-Server listening on port ' + server.address().port);
|
|
381
379
|
callbackListening && callbackListening(server);
|
|
382
380
|
callbackListening = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alexa-cookie2",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Generate Cookie and CSRF for Alexa Remote",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Apollon77",
|
|
@@ -21,15 +21,17 @@
|
|
|
21
21
|
"layla.amazon.de"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"cookie": "^0.
|
|
25
|
-
"express": "^4.
|
|
26
|
-
"http-proxy-middleware": "^2.0.
|
|
24
|
+
"cookie": "^0.5.0",
|
|
25
|
+
"express": "^4.18.1",
|
|
26
|
+
"http-proxy-middleware": "^2.0.6",
|
|
27
27
|
"http-proxy-response-rewrite": "^0.0.1",
|
|
28
28
|
"https": "^1.0.0",
|
|
29
29
|
"querystring": "^0.2.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@alcalzone/release-script": "^3.
|
|
32
|
+
"@alcalzone/release-script": "^3.5.9",
|
|
33
|
+
"@alcalzone/release-script-plugin-license": "^3.5.9",
|
|
34
|
+
"eslint": "^8.20.0"
|
|
33
35
|
},
|
|
34
36
|
"repository": {
|
|
35
37
|
"type": "git",
|