alexa-cookie2 5.0.3 → 5.0.5

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-2025 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.5 (2026-07-06)
59
+ * (@Apollon77) Fix Proxy URL when no static port is provided
60
+
61
+ ### 5.0.4 (2026-07-05)
62
+ * (@fkhr79, @blabond) Fix Amazon login proxy auth flow
63
+
58
64
  ### 5.0.3 (2025-07-13)
59
65
  * (Apollon77) Update some version details to sync with Alexa App
60
66
 
package/alexa-cookie.js CHANGED
@@ -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) {
@@ -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;
@@ -721,6 +747,42 @@ function AlexaCookie() {
721
747
  });
722
748
  };
723
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
+
724
786
  this.refreshAlexaCookie = (__options, callback) => {
725
787
  if (!__options || !__options.formerRegistrationData || !__options.formerRegistrationData.loginCookie || !__options.formerRegistrationData.refreshToken) {
726
788
  callback && callback(new Error('No former registration data provided for Cookie Refresh'), null);
@@ -798,6 +860,7 @@ function AlexaCookie() {
798
860
  getLocalCookies(_options.baseAmazonPage, _options.formerRegistrationData.refreshToken, (err, comCookie) => {
799
861
  if (err) {
800
862
  callback && callback(err, null);
863
+ return;
801
864
  }
802
865
 
803
866
  // Restore frc and map-md
@@ -806,8 +869,8 @@ function AlexaCookie() {
806
869
  newCookie += `map-md=${initCookies['map-md']}; `;
807
870
  newCookie += comCookie;
808
871
 
809
- _options.formerRegistrationData.loginCookie = newCookie;
810
- handleTokenRegistration(_options, _options.formerRegistrationData, callback);
872
+ _options.formerRegistrationData.loginCookie = sanitizeAmazonCookie(newCookie);
873
+ finishCookieRefresh(_options.formerRegistrationData, callback);
811
874
  });
812
875
  });
813
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,43 @@ 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
+ const base = proxyBase();
297
+ for (const host of amazonProxyHosts) {
298
+ const hostRegex = new RegExp(`${escapeRegex(base)}${escapeRegex(host)}/`, 'g');
299
+ data = data.replace(hostRegex, `https://${host}/`);
300
+ }
301
+ if (data === base) {
232
302
  data = returnedInitUrl;
233
303
  }
304
+ else if (data.startsWith(base)) {
305
+ data = `https://${defaultAmazonHost}/${data.slice(base.length)}`;
306
+ }
234
307
  return data;
235
308
  }
236
309
 
310
+ function parseQueryParams(data) {
311
+ const paramStart = data && data.indexOf('?');
312
+ if (paramStart === -1 || paramStart === undefined) return {};
313
+ return querystring.parse(data.substr(paramStart + 1));
314
+ }
315
+
316
+ function isProxySuccessUrl(data) {
317
+ if (!data) return false;
318
+ if (data.includes('/spa/index.html')) return true;
319
+ if (!data.includes('/ap/maplanding?')) return false;
320
+ return !!parseQueryParams(data)['openid.oa2.authorization_code'];
321
+ }
322
+
237
323
  function onProxyReq(proxyReq, req/*, _res*/) {
238
324
  const url = req.originalUrl || req.url;
239
325
  if (url.endsWith('.ico') || url.endsWith('.js') || url.endsWith('.ttf') || url.endsWith('.svg') || url.endsWith('.png') || url.endsWith('.appcache')) return;
@@ -276,8 +362,8 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
276
362
  }
277
363
  }
278
364
  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');
365
+ proxyReq.setHeader('origin', `https://${proxyReq.getHeader('host') || defaultAmazonHost}`);
366
+ _options.logger && _options.logger('Alexa-Cookie: Modify headers: Changed Origin');
281
367
  modified = true;
282
368
  }
283
369
 
@@ -310,15 +396,17 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
310
396
  }
311
397
  _options.logger && _options.logger(`Alexa-Cookie: Cookies handled: ${JSON.stringify(proxyCookies)}`);
312
398
 
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
- ) {
399
+ const outgoing = proxyRes.socket && proxyRes.socket.parser && proxyRes.socket.parser.outgoing;
400
+ const successUrl = [
401
+ proxyRes.headers.location,
402
+ outgoing && outgoing.method === 'GET' && outgoing.path,
403
+ outgoing && outgoing.getHeader && outgoing.getHeader('location')
404
+ ].find(isProxySuccessUrl);
405
+
406
+ if (successUrl) {
318
407
  _options.logger && _options.logger('Alexa-Cookie: Proxy detected SUCCESS!!');
319
408
 
320
- const paramStart = proxyRes.headers.location.indexOf('?');
321
- const queryParams = querystring.parse(proxyRes.headers.location.substr(paramStart + 1));
409
+ const queryParams = parseQueryParams(successUrl);
322
410
 
323
411
  proxyRes.statusCode = 302;
324
412
  proxyRes.headers.location = `http://${_options.proxyOwnIp}:${_options.proxyPort}/cookie-success`;
@@ -328,7 +416,7 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
328
416
  _options.logger && _options.logger(`Alexa-Cookie: Proxy catched parameters: ${JSON.stringify(queryParams)}`);
329
417
 
330
418
  callbackCookie && callbackCookie(null, {
331
- 'loginCookie': proxyCookies,
419
+ 'loginCookie': sanitizeAmazonCookie(proxyCookies),
332
420
  'authorization_code': queryParams['openid.oa2.authorization_code'],
333
421
  'frc': initialCookies.frc,
334
422
  'map-md': initialCookies['map-md'],
@@ -342,7 +430,7 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
342
430
  if (proxyRes.headers.location) {
343
431
  _options.logger && _options.logger(`Redirect: Original Location ----> ${proxyRes.headers.location}`);
344
432
  proxyRes.headers.location = replaceHosts(proxyRes.headers.location);
345
- if (reqestHost && proxyRes.headers.location.startsWith('/')) {
433
+ if (reqestHost && amazonProxyHosts.includes(reqestHost) && proxyRes.headers.location.startsWith('/')) {
346
434
  proxyRes.headers.location = `http://${_options.proxyOwnIp}:${_options.proxyPort}/${reqestHost}${proxyRes.headers.location}`;
347
435
  }
348
436
  _options.logger && _options.logger(`Redirect: Final Location ----> ${proxyRes.headers.location}`);
@@ -379,7 +467,8 @@ function initAmazonProxy(_options, callbackCookie, callbackListening) {
379
467
  _options.proxyPort = undefined;
380
468
  }
381
469
  const server = app.listen(_options.proxyPort, _options.proxyListenBind, function() {
382
- _options.logger && _options.logger(`Alexa-Cookie: Proxy-Server listening on port ${server.address().port}`);
470
+ _options.proxyPort = this.address().port;
471
+ _options.logger && _options.logger(`Alexa-Cookie: Proxy-Server listening on port ${_options.proxyPort}`);
383
472
  callbackListening && callbackListening(server);
384
473
  callbackListening = null;
385
474
  }).on('error', err => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alexa-cookie2",
3
- "version": "5.0.3",
3
+ "version": "5.0.5",
4
4
  "description": "Generate Cookie and CSRF for Alexa Remote",
5
5
  "author": {
6
6
  "name": "Apollon77",
@@ -22,15 +22,15 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "cookie": "^0.6.0",
25
- "express": "^4.21.2",
26
- "http-proxy-middleware": "^2.0.9",
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.8.0",
33
- "@alcalzone/release-script-plugin-license": "^3.7.0",
32
+ "@alcalzone/release-script": "^5.2.1",
33
+ "@alcalzone/release-script-plugin-license": "^5.2.0",
34
34
  "eslint": "^8.57.1"
35
35
  },
36
36
  "repository": {
@@ -0,0 +1,123 @@
1
+ const assert = require('assert');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const vm = require('vm');
5
+
6
+ const testName = 'base-amazon-page-handle';
7
+ const outputDir = process.env.ALEXA_COOKIE_TEST_OUTPUT_DIR || path.join(__dirname, '..', 'test-output');
8
+ const outputFile = path.join(outputDir, `${testName}.txt`);
9
+ const lines = [];
10
+
11
+ function line(value = '') {
12
+ lines.push(value);
13
+ }
14
+
15
+ function writeOutput() {
16
+ fs.mkdirSync(outputDir, { recursive: true });
17
+ fs.writeFileSync(outputFile, `${lines.join('\n')}\n`);
18
+ }
19
+
20
+ function recordAssertion(description, fn) {
21
+ try {
22
+ fn();
23
+ line(`${description}: PASS`);
24
+ } catch (err) {
25
+ line(`${description}: FAIL`);
26
+ writeOutput();
27
+ throw err;
28
+ }
29
+ }
30
+
31
+ const cookieFile = path.join(__dirname, '..', 'alexa-cookie.js');
32
+ const source = fs.readFileSync(cookieFile, 'utf8');
33
+
34
+ let capturedProxyOptions;
35
+
36
+ function loadCookieModule() {
37
+ capturedProxyOptions = undefined;
38
+ const module = { exports: {} };
39
+ const sandbox = {
40
+ __dirname: path.dirname(cookieFile),
41
+ console,
42
+ module,
43
+ exports: module.exports,
44
+ require(name) {
45
+ if (name === './lib/proxy.js') {
46
+ return {
47
+ initAmazonProxy(options, _callbackCookie, callbackListening) {
48
+ capturedProxyOptions = options;
49
+ callbackListening && callbackListening({
50
+ address: () => ({ port: options.proxyPort || 3456 }),
51
+ close: (callback) => callback && callback()
52
+ });
53
+ }
54
+ };
55
+ }
56
+ if (name === 'cookie') {
57
+ return { parse: () => ({}) };
58
+ }
59
+ return require(name);
60
+ }
61
+ };
62
+ vm.runInNewContext(source, sandbox, { filename: cookieFile });
63
+ return module.exports;
64
+ }
65
+
66
+ function runProxyOnlyConfig(baseAmazonPage) {
67
+ const cookieModule = loadCookieModule();
68
+ const input = {
69
+ proxyOwnIp: '127.0.0.1',
70
+ proxyPort: 3456,
71
+ baseAmazonPage,
72
+ amazonPage: baseAmazonPage,
73
+ logger: () => {}
74
+ };
75
+ cookieModule.generateAlexaCookie('', '', input, () => {});
76
+ return { input, output: capturedProxyOptions };
77
+ }
78
+
79
+ try {
80
+ const de = runProxyOnlyConfig('amazon.de');
81
+ const uk = runProxyOnlyConfig('amazon.co.uk');
82
+ const com = runProxyOnlyConfig('amazon.com');
83
+
84
+ line('TEST: base Amazon page handle');
85
+ line('');
86
+ line('CODE UNDER TEST:');
87
+ line('- alexa-cookie.js: generateAlexaCookie option initialization');
88
+ line('- option passed to lib/proxy.js: baseAmazonPageHandle');
89
+ line('');
90
+ line('INPUT:');
91
+ line(`DE baseAmazonPage: ${de.input.baseAmazonPage}`);
92
+ line(`DE amazonPage: ${de.input.amazonPage}`);
93
+ line(`UK baseAmazonPage: ${uk.input.baseAmazonPage}`);
94
+ line(`UK amazonPage: ${uk.input.amazonPage}`);
95
+ line(`COM baseAmazonPage: ${com.input.baseAmazonPage}`);
96
+ line(`COM amazonPage: ${com.input.amazonPage}`);
97
+ line('');
98
+ line('OBSERVED:');
99
+ line(`DE baseAmazonPageHandle: ${de.output.baseAmazonPageHandle}`);
100
+ line(`UK baseAmazonPageHandle: ${uk.output.baseAmazonPageHandle}`);
101
+ line(`COM baseAmazonPageHandle: ${com.output.baseAmazonPageHandle}`);
102
+ line('');
103
+ line('ASSERTIONS:');
104
+ recordAssertion('DE baseAmazonPageHandle === "_de"', () => {
105
+ assert.strictEqual(de.output.baseAmazonPageHandle, '_de');
106
+ });
107
+ recordAssertion('UK baseAmazonPageHandle === "_uk"', () => {
108
+ assert.strictEqual(uk.output.baseAmazonPageHandle, '_uk');
109
+ });
110
+ recordAssertion('COM baseAmazonPageHandle === ""', () => {
111
+ assert.strictEqual(com.output.baseAmazonPageHandle, '');
112
+ });
113
+ line('');
114
+ line('RESULT: PASS');
115
+ writeOutput();
116
+ } catch (err) {
117
+ if (!lines.includes('RESULT: PASS')) {
118
+ line('');
119
+ line('RESULT: FAIL');
120
+ writeOutput();
121
+ }
122
+ throw err;
123
+ }