alexa-cookie2 5.0.2 → 5.0.4

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018-2023 Apollon77 <ingo@fischer-ka.de>
3
+ Copyright (c) 2018-2026 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
@@ -55,6 +55,12 @@ Partly based on [Amazon Alexa Remote Control](http://blog.loetzimmer.de/2017/10/
55
55
  Thank you for that work.
56
56
 
57
57
  ## Changelog:
58
+ ### 5.0.4 (2026-07-05)
59
+ * (@fkhr79, @blabond) Fix Amazon login proxy auth flow
60
+
61
+ ### 5.0.3 (2025-07-13)
62
+ * (Apollon77) Update some version details to sync with Alexa App
63
+
58
64
  ### 5.0.2 (2023-11-25)
59
65
  * (Apollon77) Adjust some texts
60
66
 
package/alexa-cookie.js CHANGED
@@ -19,8 +19,8 @@ const defaultUserAgentLinux = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.3
19
19
  const defaultProxyCloseWindowHTML = '<b>Amazon Alexa Cookie successfully retrieved. You can close the browser.</b>';
20
20
  const defaultAcceptLanguage = 'de-DE';
21
21
 
22
- const apiCallVersion = '2.2.556530.0';
23
- const apiCallUserAgent = 'AmazonWebView/Amazon Alexa/2.2.556530.0/iOS/16.6/iPhone';
22
+ const apiCallVersion = '2.2.651540.0';
23
+ const apiCallUserAgent = 'AmazonWebView/Amazon Alexa/2.2.651540.0/iOS/18.3.1/iPhone';
24
24
  const defaultAppName = 'ioBroker Alexa2';
25
25
 
26
26
  const csrfOptions = [
@@ -39,6 +39,19 @@ function AlexaCookie() {
39
39
 
40
40
  let Cookie = '';
41
41
 
42
+ const isAmazonCookieName = name => /^(session-id|session-id-time|session-token|ubid-.+|lc-.+|x-.+|at-.+|sess-at-.+|frc|map-md|csrf|sid|csm-hit|i18n-prefs|sp-cdn|skin)$/.test(name);
43
+
44
+ const sanitizeAmazonCookie = cookie => {
45
+ const cookies = cookieTools.parse(cookie || '');
46
+ let sanitizedCookie = '';
47
+ for (const name of Object.keys(cookies)) {
48
+ if (isAmazonCookieName(name)) {
49
+ sanitizedCookie += `${name}=${cookies[name]}; `;
50
+ }
51
+ }
52
+ return sanitizedCookie.replace(/[; ]*$/, '');
53
+ };
54
+
42
55
  const addCookies = (Cookie, headers) => {
43
56
  if (!headers || !headers['set-cookie']) return Cookie;
44
57
  const cookies = cookieTools.parse(Cookie || '');
@@ -155,8 +168,7 @@ function AlexaCookie() {
155
168
  _options.baseAmazonPageHandle = `_${amazonDomain}`;
156
169
  }
157
170
  else if (amazonDomain !== 'com') {
158
- //_options.baseAmazonPageHandle = '_' + amazonDomain;
159
- _options.baseAmazonPageHandle = '';
171
+ _options.baseAmazonPageHandle = `_${amazonDomain}`;
160
172
  }
161
173
  else {
162
174
  _options.baseAmazonPageHandle = '';
@@ -408,6 +420,7 @@ function AlexaCookie() {
408
420
  _options.logger && _options.logger(`Handle token registration Start: ${JSON.stringify(loginData)}`);
409
421
 
410
422
  loginData.deviceAppName = _options.deviceAppName;
423
+ loginData.loginCookie = sanitizeAmazonCookie(loginData.loginCookie);
411
424
 
412
425
  let deviceSerial;
413
426
  if (!_options.formerRegistrationData || !_options.formerRegistrationData.deviceSerial) {
@@ -443,7 +456,7 @@ function AlexaCookie() {
443
456
  'app_version': apiCallVersion,
444
457
  'device_type': 'A2IVLV5VM2W81',
445
458
  'device_name': '%FIRST_NAME%\u0027s%DUPE_STRATEGY_1ST%' + _options.deviceAppName,
446
- 'os_version': '16.6',
459
+ 'os_version': '18.3.1',
447
460
  'device_serial': deviceSerial,
448
461
  'device_model': 'iPhone',
449
462
  'app_name': _options.deviceAppName,
@@ -520,6 +533,7 @@ function AlexaCookie() {
520
533
  Cookie = addCookies(Cookie, response.headers);
521
534
  loginData.refreshToken = body.response.success.tokens.bearer.refresh_token;
522
535
  const accessToken = body.response.success.tokens.bearer.access_token;
536
+ loginData.accessToken = accessToken;
523
537
  loginData.tokenDate = Date.now();
524
538
  loginData.macDms = body.response.success.tokens.mac_dms;
525
539
 
@@ -553,22 +567,34 @@ function AlexaCookie() {
553
567
  _options.logger && _options.logger(JSON.stringify(options));
554
568
  request(options, (error, response, body) => {
555
569
  if (!error) {
556
- try {
557
- if (typeof body !== 'object') body = JSON.parse(body);
558
- } catch (err) {
570
+ if (!response || response.statusCode < 200 || response.statusCode >= 300 || !body) {
571
+ _options.logger && _options.logger(`Get User data Response: status=${response && response.statusCode}, body=${JSON.stringify(body)}`);
572
+ if (!_options.amazonPage) {
573
+ callback && callback(new Error(`Could not get user data from Amazon: HTTP ${response && response.statusCode}`), null);
574
+ return;
575
+ }
576
+ loginData.amazonPage = _options.amazonPage;
577
+ } else {
578
+ try {
579
+ if (typeof body !== 'object') body = JSON.parse(body);
580
+ } catch (err) {
581
+ _options.logger && _options.logger(`Get User data Response: ${JSON.stringify(body)}`);
582
+ if (!_options.amazonPage) {
583
+ callback && callback(err, null);
584
+ return;
585
+ }
586
+ loginData.amazonPage = _options.amazonPage;
587
+ }
559
588
  _options.logger && _options.logger(`Get User data Response: ${JSON.stringify(body)}`);
560
- callback && callback(err, null);
561
- return;
562
- }
563
- _options.logger && _options.logger(`Get User data Response: ${JSON.stringify(body)}`);
564
589
 
565
- Cookie = addCookies(Cookie, response.headers);
590
+ Cookie = addCookies(Cookie, response.headers);
566
591
 
567
- if (body.marketPlaceDomainName) {
568
- const pos = body.marketPlaceDomainName.indexOf('.');
569
- if (pos !== -1) _options.amazonPage = body.marketPlaceDomainName.substr(pos + 1);
592
+ if (body && body.marketPlaceDomainName) {
593
+ const pos = body.marketPlaceDomainName.indexOf('.');
594
+ if (pos !== -1) _options.amazonPage = body.marketPlaceDomainName.substr(pos + 1);
595
+ }
596
+ loginData.amazonPage = _options.amazonPage;
570
597
  }
571
- loginData.amazonPage = _options.amazonPage;
572
598
  } else if (error && (!_options || !_options.amazonPage)) {
573
599
  callback && callback(error, null);
574
600
  return;
@@ -583,6 +609,7 @@ function AlexaCookie() {
583
609
  getLocalCookies(loginData.amazonPage, loginData.refreshToken, (err, localCookie) => {
584
610
  if (err) {
585
611
  callback && callback(err, null);
612
+ return;
586
613
  }
587
614
 
588
615
  loginData.localCookie = localCookie;
@@ -593,7 +620,6 @@ function AlexaCookie() {
593
620
  }
594
621
  loginData.localCookie = resData.cookie;
595
622
  loginData.csrf = resData.csrf;
596
- delete loginData.accessToken;
597
623
  delete loginData.authorization_code;
598
624
  delete loginData.verifier;
599
625
  loginData.dataVersion = 2;
@@ -623,6 +649,9 @@ function AlexaCookie() {
623
649
  'authorization': `Bearer ${accessToken}`,
624
650
  },
625
651
  body: '{"legacyFlags":{"SUPPORTS_COMMS":true,"SUPPORTS_ARBITRATION":true,"SCREEN_WIDTH":1170,"SUPPORTS_SCRUBBING":true,"SPEECH_SYNTH_SUPPORTS_TTS_URLS":false,"SUPPORTS_HOME_AUTOMATION":true,"SUPPORTS_DROPIN_OUTBOUND":true,"FRIENDLY_NAME_TEMPLATE":"VOX","SUPPORTS_SIP_OUTBOUND_CALLING":true,"VOICE_PROFILE_SWITCHING_DISABLED":true,"SUPPORTS_LYRICS_IN_CARD":false,"SUPPORTS_DATAMART_NAMESPACE":"Vox","SUPPORTS_VIDEO_CALLING":true,"SUPPORTS_PFM_CHANGED":true,"SUPPORTS_TARGET_PLATFORM":"TABLET","SUPPORTS_SECURE_LOCKSCREEN":false,"AUDIO_PLAYER_SUPPORTS_TTS_URLS":false,"SUPPORTS_KEYS_IN_HEADER":false,"SUPPORTS_MIXING_BEHAVIOR_FOR_AUDIO_PLAYER":false,"AXON_SUPPORT":true,"SUPPORTS_TTS_SPEECHMARKS":true},"envelopeVersion":"20160207","capabilities":[{"version":"0.1","interface":"CardRenderer","type":"AlexaInterface"},{"interface":"Navigation","type":"AlexaInterface","version":"1.1"},{"type":"AlexaInterface","version":"2.0","interface":"Alexa.Comms.PhoneCallController"},{"type":"AlexaInterface","version":"1.1","interface":"ExternalMediaPlayer"},{"type":"AlexaInterface","interface":"Alerts","configurations":{"maximumAlerts":{"timers":2,"overall":99,"alarms":2}},"version":"1.3"},{"version":"1.0","interface":"Alexa.Display.Window","type":"AlexaInterface","configurations":{"templates":[{"type":"STANDARD","id":"app_window_template","configuration":{"sizes":[{"id":"fullscreen","type":"DISCRETE","value":{"value":{"height":1440,"width":3200},"unit":"PIXEL"}}],"interactionModes":["mobile_mode","auto_mode"]}}]}},{"type":"AlexaInterface","interface":"AccessoryKit","version":"0.1"},{"type":"AlexaInterface","interface":"Alexa.AudioSignal.ActiveNoiseControl","version":"1.0","configurations":{"ambientSoundProcessingModes":[{"name":"ACTIVE_NOISE_CONTROL"},{"name":"PASSTHROUGH"}]}},{"interface":"PlaybackController","type":"AlexaInterface","version":"1.0"},{"version":"1.0","interface":"Speaker","type":"AlexaInterface"},{"version":"1.0","interface":"SpeechSynthesizer","type":"AlexaInterface"},{"version":"1.0","interface":"AudioActivityTracker","type":"AlexaInterface"},{"type":"AlexaInterface","interface":"Alexa.Camera.LiveViewController","version":"1.0"},{"type":"AlexaInterface","version":"1.0","interface":"Alexa.Input.Text"},{"type":"AlexaInterface","interface":"Alexa.PlaybackStateReporter","version":"1.0"},{"version":"1.1","interface":"Geolocation","type":"AlexaInterface"},{"interface":"Alexa.Health.Fitness","version":"1.0","type":"AlexaInterface"},{"interface":"Settings","type":"AlexaInterface","version":"1.0"},{"configurations":{"interactionModes":[{"dialog":"SUPPORTED","interactionDistance":{"value":18,"unit":"INCHES"},"video":"SUPPORTED","keyboard":"SUPPORTED","id":"mobile_mode","uiMode":"MOBILE","touch":"SUPPORTED"},{"video":"UNSUPPORTED","dialog":"SUPPORTED","interactionDistance":{"value":36,"unit":"INCHES"},"uiMode":"AUTO","touch":"SUPPORTED","id":"auto_mode","keyboard":"UNSUPPORTED"}]},"type":"AlexaInterface","interface":"Alexa.InteractionMode","version":"1.0"},{"type":"AlexaInterface","configurations":{"catalogs":[{"type":"IOS_APP_STORE","identifierTypes":["URI_HTTP_SCHEME","URI_CUSTOM_SCHEME"]}]},"version":"0.2","interface":"Alexa.Launcher"},{"interface":"System","version":"1.0","type":"AlexaInterface"},{"interface":"Alexa.IOComponents","type":"AlexaInterface","version":"1.4"},{"type":"AlexaInterface","interface":"Alexa.FavoritesController","version":"1.0"},{"version":"1.0","type":"AlexaInterface","interface":"Alexa.Mobile.Push"},{"type":"AlexaInterface","interface":"InteractionModel","version":"1.1"},{"interface":"Alexa.PlaylistController","type":"AlexaInterface","version":"1.0"},{"interface":"SpeechRecognizer","type":"AlexaInterface","version":"2.1"},{"interface":"AudioPlayer","type":"AlexaInterface","version":"1.3"},{"type":"AlexaInterface","version":"3.1","interface":"Alexa.RTCSessionController"},{"interface":"VisualActivityTracker","version":"1.1","type":"AlexaInterface"},{"interface":"Alexa.PlaybackController","version":"1.0","type":"AlexaInterface"},{"type":"AlexaInterface","interface":"Alexa.SeekController","version":"1.0"},{"interface":"Alexa.Comms.MessagingController","type":"AlexaInterface","version":"1.0"}]}'
652
+
653
+ // New
654
+ // {"envelopeVersion":"20160207","legacyFlags":{"SUPPORTS_TARGET_PLATFORM":"TABLET","SUPPORTS_SECURE_LOCKSCREEN":false,"SUPPORTS_DATAMART_NAMESPACE":"Vox","AXON_SUPPORT":true,"SUPPORTS_DROPIN_OUTBOUND":true,"SUPPORTS_LYRICS_IN_CARD":false,"VOICE_PROFILE_SWITCHING_DISABLED":true,"SUPPORTS_ARBITRATION":true,"SUPPORTS_HOME_AUTOMATION":true,"SUPPORTS_KEYS_IN_HEADER":false,"SUPPORTS_TTS_SPEECHMARKS":true,"AUDIO_PLAYER_SUPPORTS_TTS_URLS":false,"SUPPORTS_SIP_OUTBOUND_CALLING":true,"SUPPORTS_MIXING_BEHAVIOR_FOR_AUDIO_PLAYER":false,"SUPPORTS_COMMS":true,"SCREEN_WIDTH":1170,"SUPPORTS_VIDEO_CALLING":true,"FRIENDLY_NAME_TEMPLATE":"VOX","SUPPORTS_PFM_CHANGED":true,"SPEECH_SYNTH_SUPPORTS_TTS_URLS":false,"SUPPORTS_SCRUBBING":true},"capabilities":[{"type":"AlexaInterface","interface":"AudioPlayer","version":"1.3"},{"version":"1.0","type":"AlexaInterface","interface":"Settings"},{"interface":"System","type":"AlexaInterface","version":"1.0"},{"type":"AlexaInterface","interface":"AudioActivityTracker","version":"1.0"},{"interface":"SpeechRecognizer","version":"2.3","type":"AlexaInterface"},{"type":"AlexaInterface","interface":"Speaker","version":"1.0"},{"type":"AlexaInterface","version":"1.0","interface":"SpeechSynthesizer"},{"type":"AlexaInterface","version":"0.1","interface":"CardRenderer"},{"interface":"PlaybackController","type":"AlexaInterface","version":"1.0"},{"version":"1.1","type":"AlexaInterface","interface":"Navigation"},{"version":"1.1","type":"AlexaInterface","interface":"InteractionModel"},{"type":"AlexaInterface","version":"1.1","interface":"Geolocation"}]}
626
655
  };
627
656
  _options.logger && _options.logger('Alexa-Cookie: Register capabilities');
628
657
  _options.logger && _options.logger(JSON.stringify(options));
@@ -718,6 +747,42 @@ function AlexaCookie() {
718
747
  });
719
748
  };
720
749
 
750
+ const finishCookieRefresh = (loginData, callback) => {
751
+ _options.logger && _options.logger('Alexa-Cookie: Skip App registration during refresh and update local cookies');
752
+ const updateLocalCookies = () => {
753
+ const amazonPage = loginData.amazonPage || _options.amazonPage;
754
+ getLocalCookies(amazonPage, loginData.refreshToken, (err, localCookie) => {
755
+ if (err) {
756
+ callback && callback(err, null);
757
+ return;
758
+ }
759
+
760
+ loginData.localCookie = localCookie;
761
+ getCSRFFromCookies(loginData.localCookie, _options, (err, resData) => {
762
+ if (err) {
763
+ callback && callback(new Error(`Error getting csrf for ${amazonPage}`), null);
764
+ return;
765
+ }
766
+ loginData.localCookie = resData.cookie;
767
+ loginData.csrf = resData.csrf;
768
+ loginData.amazonPage = amazonPage;
769
+ loginData.tokenDate = Date.now();
770
+ delete loginData.authorization_code;
771
+ delete loginData.verifier;
772
+ loginData.dataVersion = 2;
773
+ _options.logger && _options.logger('Alexa-Cookie: Refresh finished with updated cookies and csrf');
774
+ callback && callback(null, loginData);
775
+ });
776
+ });
777
+ };
778
+
779
+ if (loginData.accessToken) {
780
+ registerTokenCapabilities(loginData.accessToken, updateLocalCookies);
781
+ return;
782
+ }
783
+ updateLocalCookies();
784
+ };
785
+
721
786
  this.refreshAlexaCookie = (__options, callback) => {
722
787
  if (!__options || !__options.formerRegistrationData || !__options.formerRegistrationData.loginCookie || !__options.formerRegistrationData.refreshToken) {
723
788
  callback && callback(new Error('No former registration data provided for Cookie Refresh'), null);
@@ -795,6 +860,7 @@ function AlexaCookie() {
795
860
  getLocalCookies(_options.baseAmazonPage, _options.formerRegistrationData.refreshToken, (err, comCookie) => {
796
861
  if (err) {
797
862
  callback && callback(err, null);
863
+ return;
798
864
  }
799
865
 
800
866
  // Restore frc and map-md
@@ -803,8 +869,8 @@ function AlexaCookie() {
803
869
  newCookie += `map-md=${initCookies['map-md']}; `;
804
870
  newCookie += comCookie;
805
871
 
806
- _options.formerRegistrationData.loginCookie = newCookie;
807
- handleTokenRegistration(_options, _options.formerRegistrationData, callback);
872
+ _options.formerRegistrationData.loginCookie = sanitizeAmazonCookie(newCookie);
873
+ finishCookieRefresh(_options.formerRegistrationData, callback);
808
874
  });
809
875
  });
810
876
  };
package/lib/proxy.js CHANGED
@@ -15,6 +15,8 @@ const path = require('path');
15
15
  const crypto = require('crypto');
16
16
 
17
17
  const FORMERDATA_STORE_VERSION = 4;
18
+ const ALEXA_APP_VERSION = '2.2.651540.0';
19
+ const EXTRA_AMAZON_PROXY_HOSTS = ['amazon.com', 'www.amazon.com', 'alexa.amazon.com'];
18
20
 
19
21
  function addCookies(Cookie, headers) {
20
22
  if (!headers || !headers['set-cookie']) return Cookie;
@@ -34,6 +36,21 @@ function addCookies(Cookie, headers) {
34
36
  return Cookie;
35
37
  }
36
38
 
39
+ function isAmazonCookieName(name) {
40
+ return /^(session-id|session-id-time|session-token|ubid-.+|lc-.+|x-.+|at-.+|sess-at-.+|frc|map-md|csrf|sid|csm-hit|i18n-prefs|sp-cdn|skin)$/.test(name);
41
+ }
42
+
43
+ function sanitizeAmazonCookie(cookie) {
44
+ const cookies = cookieTools.parse(cookie || '');
45
+ let sanitizedCookie = '';
46
+ for (const name of Object.keys(cookies)) {
47
+ if (isAmazonCookieName(name)) {
48
+ sanitizedCookie += `${name}=${cookies[name]}; `;
49
+ }
50
+ }
51
+ return sanitizedCookie.replace(/[; ]*$/, '');
52
+ }
53
+
37
54
  function customStringify(v, func, intent) {
38
55
  const cache = new Map();
39
56
  return JSON.stringify(v, function (key, value) {
@@ -55,6 +72,46 @@ function customStringify(v, func, intent) {
55
72
 
56
73
  function initAmazonProxy(_options, callbackCookie, callbackListening) {
57
74
  const initialCookies = {};
75
+ const proxyBase = `http://${_options.proxyOwnIp}:${_options.proxyPort}/`;
76
+ const defaultAmazonHost = `www.${_options.baseAmazonPage}`;
77
+ const amazonProxyHosts = [];
78
+
79
+ function escapeRegex(data) {
80
+ return data.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
81
+ }
82
+
83
+ function addAmazonProxyHost(host) {
84
+ if (host && !amazonProxyHosts.includes(host)) {
85
+ amazonProxyHosts.push(host);
86
+ }
87
+ }
88
+
89
+ function amazonRootFromHost(host) {
90
+ return host.replace(/^(www|alexa)\./, '');
91
+ }
92
+
93
+ addAmazonProxyHost(defaultAmazonHost);
94
+ addAmazonProxyHost(`alexa.${_options.baseAmazonPage}`);
95
+ addAmazonProxyHost(_options.baseAmazonPage);
96
+ EXTRA_AMAZON_PROXY_HOSTS.forEach(addAmazonProxyHost);
97
+
98
+ function amazonHostFromProxyPath(url) {
99
+ const match = (url || '').match(/^\/([^/?#]+)(?:[/?#]|$)/);
100
+ if (match && amazonProxyHosts.includes(match[1])) {
101
+ return match[1];
102
+ }
103
+ return null;
104
+ }
105
+
106
+ function amazonHostFromProxyUrl(url) {
107
+ if (!url) return null;
108
+ for (const host of amazonProxyHosts) {
109
+ if (url === `${proxyBase}${host}` || url.startsWith(`${proxyBase}${host}/`)) {
110
+ return host;
111
+ }
112
+ }
113
+ return null;
114
+ }
58
115
 
59
116
  const formerDataStorePath = _options.formerDataStorePath || path.join(__dirname, 'formerDataStore.json');
60
117
  let formerDataStoreValid = false;
@@ -90,7 +147,7 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
90
147
  }
91
148
 
92
149
  if (!_options.formerRegistrationData || !_options.formerRegistrationData['map-md']) {
93
- initialCookies['map-md'] = Buffer.from('{"device_user_dictionary":[],"device_registration_data":{"software_version":"1"},"app_identifier":{"app_version":"2.2.485407","bundle_id":"com.amazon.echo"}}').toString('base64');
150
+ initialCookies['map-md'] = Buffer.from(`{"device_user_dictionary":[],"device_registration_data":{"software_version":"1"},"app_identifier":{"app_version":"${ALEXA_APP_VERSION}","bundle_id":"com.amazon.echo"}}`).toString('base64');
94
151
  }
95
152
  else {
96
153
  _options.logger && _options.logger('Proxy Init: reuse map-md from former data');
@@ -135,13 +192,31 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
135
192
  const code_challenge = base64URLEncode(sha256(code_verifier));
136
193
 
137
194
  let proxyCookies = '';
195
+ let returnedInitUrl;
196
+
197
+ function buildInitialUrl() {
198
+ return `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}`;
199
+ }
200
+
201
+ function rewriteProxyPath(url, req) {
202
+ const urlHost = amazonHostFromProxyPath(url);
203
+ if (urlHost) {
204
+ return url.replace(new RegExp(`^/${escapeRegex(urlHost)}`), '') || '/';
205
+ }
206
+ if (req && req.headers && req.headers.host === `${_options.proxyOwnIp}:${_options.proxyPort}` && url === '/') {
207
+ const initialUrl = returnedInitUrl || buildInitialUrl();
208
+ const parsed = new URL(initialUrl);
209
+ return `${parsed.pathname}${parsed.search}`;
210
+ }
211
+ return url;
212
+ }
138
213
 
139
214
  // proxy middleware options
140
215
  const optionsAlexa = {
141
216
  target: `https://alexa.${_options.baseAmazonPage}`,
142
217
  changeOrigin: true,
143
218
  ws: false,
144
- pathRewrite: {}, // enhanced below
219
+ pathRewrite: rewriteProxyPath,
145
220
  router: router,
146
221
  hostRewrite: true,
147
222
  followRedirects: false,
@@ -150,18 +225,15 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
150
225
  onProxyRes: onProxyRes,
151
226
  onProxyReq: onProxyReq,
152
227
  headers: {
153
- 'user-agent': 'AppleWebKit PitanguiBridge/2.2.485407.0-[HARDWARE=iPhone10_4][SOFTWARE=15.5][DEVICE=iPhone]',
154
- 'accept-language': _options.acceptLanguage,
155
- 'authority': `www.${_options.baseAmazonPage}`
228
+ 'accept-language': _options.acceptLanguage
156
229
  },
157
- cookieDomainRewrite: { // enhanced below
230
+ cookieDomainRewrite: {
158
231
  '*': ''
232
+ },
233
+ cookiePathRewrite: {
234
+ '*': '/'
159
235
  }
160
236
  };
161
- optionsAlexa.pathRewrite[`^/www.${_options.baseAmazonPage}`] = '';
162
- optionsAlexa.pathRewrite[`^/alexa.${_options.baseAmazonPage}`] = '';
163
- optionsAlexa.cookieDomainRewrite[`.${_options.baseAmazonPage}`] = _options.proxyOwnIp;
164
- optionsAlexa.cookieDomainRewrite[_options.baseAmazonPage] = _options.proxyOwnIp;
165
237
  if (_options.logger) optionsAlexa.logProvider = function logProvider() {
166
238
  return {
167
239
  log: _options.logger.log || _options.logger,
@@ -171,30 +243,27 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
171
243
  error: _options.logger.error || _options.logger
172
244
  };
173
245
  };
174
- let returnedInitUrl;
175
246
 
176
247
  function router(req) {
177
248
  const url = (req.originalUrl || req.url);
178
249
  _options.logger && _options.logger(`Router: ${url} / ${req.method} / ${JSON.stringify(req.headers)}`);
179
250
  if (req.headers.host === `${_options.proxyOwnIp}:${_options.proxyPort}`) {
180
- if (url.startsWith(`/www.${_options.baseAmazonPage}/`)) {
181
- return `https://www.${_options.baseAmazonPage}`;
182
- } else if (url.startsWith(`/alexa.${_options.baseAmazonPage}/`)) {
183
- return `https://alexa.${_options.baseAmazonPage}`;
184
- } else if (req.headers.referer) {
185
- if (req.headers.referer.startsWith(`http://${_options.proxyOwnIp}:${_options.proxyPort}/www.${_options.baseAmazonPage}/`)) {
186
- return `https://www.${_options.baseAmazonPage}`;
187
- } else if (req.headers.referer.startsWith(`http://${_options.proxyOwnIp}:${_options.proxyPort}/alexa.${_options.baseAmazonPage}/`)) {
188
- return `https://alexa.${_options.baseAmazonPage}`;
189
- }
251
+ const urlHost = amazonHostFromProxyPath(url);
252
+ if (urlHost) {
253
+ return `https://${urlHost}`;
254
+ }
255
+ const refererHost = amazonHostFromProxyUrl(req.headers.referer);
256
+ if (refererHost) {
257
+ return `https://${refererHost}`;
190
258
  }
191
259
  if (url === '/') { // initial redirect
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}`;
260
+ returnedInitUrl = buildInitialUrl();
193
261
  _options.logger && _options.logger(`Alexa-Cookie: Initial Page Request: ${returnedInitUrl}`);
194
- return returnedInitUrl;
262
+ const parsedInitUrl = new URL(returnedInitUrl);
263
+ return `${parsedInitUrl.protocol}//${parsedInitUrl.host}`;
195
264
  }
196
265
  else {
197
- return `https://www.${_options.baseAmazonPage}`;
266
+ return `https://${defaultAmazonHost}`;
198
267
  }
199
268
  }
200
269
  return `https://alexa.${_options.baseAmazonPage}`;
@@ -214,26 +283,42 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
214
283
 
215
284
  function replaceHosts(data) {
216
285
  //const dataOrig = data;
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');
219
286
  data = data.replace(/&#x2F;/g, '/');
220
- data = data.replace(amazonRegex, `http://${_options.proxyOwnIp}:${_options.proxyPort}/www.${_options.baseAmazonPage}/`);
221
- data = data.replace(alexaRegex, `http://${_options.proxyOwnIp}:${_options.proxyPort}/alexa.${_options.baseAmazonPage}/`);
287
+ for (const host of amazonProxyHosts) {
288
+ const hostRegex = new RegExp(`https?://${escapeRegex(host)}:?[0-9]*/`, 'g');
289
+ data = data.replace(hostRegex, `${proxyBase}${host}/`);
290
+ }
222
291
  //_options.logger && _options.logger('REPLACEHOSTS: ' + dataOrig + ' --> ' + data);
223
292
  return data;
224
293
  }
225
294
 
226
295
  function replaceHostsBack(data) {
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');
229
- data = data.replace(amazonRegex, `https://www.${_options.baseAmazonPage}/`);
230
- data = data.replace(alexaRegex, `https://alexa.${_options.baseAmazonPage}/`);
231
- if (data === `http://${_options.proxyOwnIp}:${_options.proxyPort}/`) {
296
+ for (const host of amazonProxyHosts) {
297
+ const hostRegex = new RegExp(`${escapeRegex(proxyBase)}${escapeRegex(host)}/`, 'g');
298
+ data = data.replace(hostRegex, `https://${host}/`);
299
+ }
300
+ if (data === proxyBase) {
232
301
  data = returnedInitUrl;
233
302
  }
303
+ else if (data.startsWith(proxyBase)) {
304
+ data = `https://${defaultAmazonHost}/${data.slice(proxyBase.length)}`;
305
+ }
234
306
  return data;
235
307
  }
236
308
 
309
+ function parseQueryParams(data) {
310
+ const paramStart = data && data.indexOf('?');
311
+ if (paramStart === -1 || paramStart === undefined) return {};
312
+ return querystring.parse(data.substr(paramStart + 1));
313
+ }
314
+
315
+ function isProxySuccessUrl(data) {
316
+ if (!data) return false;
317
+ if (data.includes('/spa/index.html')) return true;
318
+ if (!data.includes('/ap/maplanding?')) return false;
319
+ return !!parseQueryParams(data)['openid.oa2.authorization_code'];
320
+ }
321
+
237
322
  function onProxyReq(proxyReq, req/*, _res*/) {
238
323
  const url = req.originalUrl || req.url;
239
324
  if (url.endsWith('.ico') || url.endsWith('.js') || url.endsWith('.ttf') || url.endsWith('.svg') || url.endsWith('.png') || url.endsWith('.appcache')) return;
@@ -276,8 +361,8 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
276
361
  }
277
362
  }
278
363
  if (typeof proxyReq.getHeader === 'function' && proxyReq.getHeader('origin') !== `https://${proxyReq.getHeader('host')}`) {
279
- proxyReq.setHeader('origin', `https://www.${_options.baseAmazonPage}`);
280
- _options.logger && _options.logger('Alexa-Cookie: Modify headers: Delete Origin');
364
+ proxyReq.setHeader('origin', `https://${proxyReq.getHeader('host') || defaultAmazonHost}`);
365
+ _options.logger && _options.logger('Alexa-Cookie: Modify headers: Changed Origin');
281
366
  modified = true;
282
367
  }
283
368
 
@@ -310,15 +395,17 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
310
395
  }
311
396
  _options.logger && _options.logger(`Alexa-Cookie: Cookies handled: ${JSON.stringify(proxyCookies)}`);
312
397
 
313
- if (
314
- (proxyRes.socket && proxyRes.socket._host === `www.${_options.baseAmazonPage}` && proxyRes.socket.parser.outgoing && proxyRes.socket.parser.outgoing.method === 'GET' && proxyRes.socket.parser.outgoing.path.startsWith('/ap/maplanding')) ||
315
- (proxyRes.socket && proxyRes.socket.parser.outgoing && proxyRes.socket.parser.outgoing.getHeader('location') && proxyRes.socket.parser.outgoing.getHeader('location').includes('/ap/maplanding?')) ||
316
- (proxyRes.headers.location && (proxyRes.headers.location.includes('/ap/maplanding?') || proxyRes.headers.location.includes('/spa/index.html')))
317
- ) {
398
+ const outgoing = proxyRes.socket && proxyRes.socket.parser && proxyRes.socket.parser.outgoing;
399
+ const successUrl = [
400
+ proxyRes.headers.location,
401
+ outgoing && outgoing.method === 'GET' && outgoing.path,
402
+ outgoing && outgoing.getHeader && outgoing.getHeader('location')
403
+ ].find(isProxySuccessUrl);
404
+
405
+ if (successUrl) {
318
406
  _options.logger && _options.logger('Alexa-Cookie: Proxy detected SUCCESS!!');
319
407
 
320
- const paramStart = proxyRes.headers.location.indexOf('?');
321
- const queryParams = querystring.parse(proxyRes.headers.location.substr(paramStart + 1));
408
+ const queryParams = parseQueryParams(successUrl);
322
409
 
323
410
  proxyRes.statusCode = 302;
324
411
  proxyRes.headers.location = `http://${_options.proxyOwnIp}:${_options.proxyPort}/cookie-success`;
@@ -328,7 +415,7 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
328
415
  _options.logger && _options.logger(`Alexa-Cookie: Proxy catched parameters: ${JSON.stringify(queryParams)}`);
329
416
 
330
417
  callbackCookie && callbackCookie(null, {
331
- 'loginCookie': proxyCookies,
418
+ 'loginCookie': sanitizeAmazonCookie(proxyCookies),
332
419
  'authorization_code': queryParams['openid.oa2.authorization_code'],
333
420
  'frc': initialCookies.frc,
334
421
  'map-md': initialCookies['map-md'],
@@ -342,7 +429,7 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
342
429
  if (proxyRes.headers.location) {
343
430
  _options.logger && _options.logger(`Redirect: Original Location ----> ${proxyRes.headers.location}`);
344
431
  proxyRes.headers.location = replaceHosts(proxyRes.headers.location);
345
- if (reqestHost && proxyRes.headers.location.startsWith('/')) {
432
+ if (reqestHost && amazonProxyHosts.includes(reqestHost) && proxyRes.headers.location.startsWith('/')) {
346
433
  proxyRes.headers.location = `http://${_options.proxyOwnIp}:${_options.proxyPort}/${reqestHost}${proxyRes.headers.location}`;
347
434
  }
348
435
  _options.logger && _options.logger(`Redirect: Final Location ----> ${proxyRes.headers.location}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alexa-cookie2",
3
- "version": "5.0.2",
3
+ "version": "5.0.4",
4
4
  "description": "Generate Cookie and CSRF for Alexa Remote",
5
5
  "author": {
6
6
  "name": "Apollon77",
@@ -22,16 +22,16 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "cookie": "^0.6.0",
25
- "express": "^4.18.2",
26
- "http-proxy-middleware": "^2.0.6",
25
+ "express": "^4.22.2",
26
+ "http-proxy-middleware": "^2.0.10",
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.6.0",
33
- "@alcalzone/release-script-plugin-license": "^3.5.9",
34
- "eslint": "^8.54.0"
32
+ "@alcalzone/release-script": "^5.2.1",
33
+ "@alcalzone/release-script-plugin-license": "^5.2.0",
34
+ "eslint": "^8.57.1"
35
35
  },
36
36
  "repository": {
37
37
  "type": "git",