alexa-cookie2 5.0.3 → 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 +1 -1
- package/README.md +3 -0
- package/alexa-cookie.js +80 -17
- package/lib/proxy.js +131 -44
- package/package.json +5 -5
- package/test/base-amazon-page-handle.test.js +123 -0
- package/test/proxy-cookie-rewrite.test.js +148 -0
- package/test/proxy-cookie-sanitizing.test.js +186 -0
- package/test/proxy-host-switch.test.js +166 -0
- package/test/proxy-initial-url.test.js +184 -0
- package/test/proxy-success-requires-auth-code.test.js +170 -0
- package/test/refresh-skips-app-registration.test.js +270 -0
- package/test/registration-hardening.test.js +261 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const vm = require('vm');
|
|
6
|
+
|
|
7
|
+
const testName = 'proxy-cookie-rewrite';
|
|
8
|
+
const outputDir = process.env.ALEXA_COOKIE_TEST_OUTPUT_DIR || path.join(__dirname, '..', 'test-output');
|
|
9
|
+
const outputFile = path.join(outputDir, `${testName}.txt`);
|
|
10
|
+
const lines = [];
|
|
11
|
+
|
|
12
|
+
function line(value = '') {
|
|
13
|
+
lines.push(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeOutput() {
|
|
17
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
18
|
+
fs.writeFileSync(outputFile, `${lines.join('\n')}\n`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function recordAssertion(description, fn) {
|
|
22
|
+
try {
|
|
23
|
+
fn();
|
|
24
|
+
line(`${description}: PASS`);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
line(`${description}: FAIL`);
|
|
27
|
+
writeOutput();
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const proxyFile = path.join(__dirname, '..', 'lib', 'proxy.js');
|
|
33
|
+
const source = fs.readFileSync(proxyFile, 'utf8');
|
|
34
|
+
|
|
35
|
+
let capturedProxyOptions;
|
|
36
|
+
|
|
37
|
+
function createExpressStub() {
|
|
38
|
+
return {
|
|
39
|
+
use() {},
|
|
40
|
+
get() {},
|
|
41
|
+
listen() {
|
|
42
|
+
const server = {
|
|
43
|
+
address: () => ({ port: 3456 }),
|
|
44
|
+
on: () => server
|
|
45
|
+
};
|
|
46
|
+
return server;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function loadProxyModule() {
|
|
52
|
+
capturedProxyOptions = undefined;
|
|
53
|
+
const module = { exports: {} };
|
|
54
|
+
const sandbox = {
|
|
55
|
+
Buffer,
|
|
56
|
+
URL,
|
|
57
|
+
__dirname: path.dirname(proxyFile),
|
|
58
|
+
console,
|
|
59
|
+
module,
|
|
60
|
+
exports: module.exports,
|
|
61
|
+
require(name) {
|
|
62
|
+
if (name === 'express') return createExpressStub;
|
|
63
|
+
if (name === 'http-proxy-response-rewrite') return () => {};
|
|
64
|
+
if (name === 'http-proxy-middleware') {
|
|
65
|
+
return {
|
|
66
|
+
createProxyMiddleware(_context, options) {
|
|
67
|
+
capturedProxyOptions = options;
|
|
68
|
+
return function proxyMiddleware() {};
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (name === 'cookie') {
|
|
73
|
+
return { parse: () => ({}) };
|
|
74
|
+
}
|
|
75
|
+
return require(name);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
vm.runInNewContext(source, sandbox, { filename: proxyFile });
|
|
79
|
+
return module.exports;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const proxyModule = loadProxyModule();
|
|
83
|
+
const formerDataStorePath = path.join(os.tmpdir(), `alexa-cookie-proxy-cookie-test-${Date.now()}.json`);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const input = {
|
|
87
|
+
proxyOwnIp: '127.0.0.1',
|
|
88
|
+
proxyPort: 3456,
|
|
89
|
+
proxyListenBind: '0.0.0.0',
|
|
90
|
+
baseAmazonPage: 'amazon.de',
|
|
91
|
+
baseAmazonPageHandle: '_de',
|
|
92
|
+
amazonPageProxyLanguage: 'de_DE',
|
|
93
|
+
acceptLanguage: 'de-DE',
|
|
94
|
+
proxyLogLevel: 'silent',
|
|
95
|
+
formerDataStorePath
|
|
96
|
+
};
|
|
97
|
+
proxyModule.initAmazonProxy(input);
|
|
98
|
+
const cookieDomainRewrite = capturedProxyOptions.cookieDomainRewrite || {};
|
|
99
|
+
const cookiePathRewrite = capturedProxyOptions.cookiePathRewrite || {};
|
|
100
|
+
const cookieDomainRewriteKeys = Object.keys(cookieDomainRewrite);
|
|
101
|
+
const cookiePathRewriteKeys = Object.keys(cookiePathRewrite);
|
|
102
|
+
|
|
103
|
+
line('TEST: proxy cookie rewrite');
|
|
104
|
+
line('');
|
|
105
|
+
line('CODE UNDER TEST:');
|
|
106
|
+
line('- lib/proxy.js: cookieDomainRewrite');
|
|
107
|
+
line('- lib/proxy.js: cookiePathRewrite');
|
|
108
|
+
line('');
|
|
109
|
+
line('INPUT:');
|
|
110
|
+
line(`proxyOwnIp: ${input.proxyOwnIp}`);
|
|
111
|
+
line(`baseAmazonPage: ${input.baseAmazonPage}`);
|
|
112
|
+
line('');
|
|
113
|
+
line('OBSERVED:');
|
|
114
|
+
line(`cookieDomainRewrite keys: ${cookieDomainRewriteKeys.join(', ')}`);
|
|
115
|
+
line(`cookieDomainRewrite["*"]: ${cookieDomainRewrite['*']}`);
|
|
116
|
+
line(`cookiePathRewrite keys: ${cookiePathRewriteKeys.join(', ')}`);
|
|
117
|
+
line(`cookiePathRewrite["*"]: ${cookiePathRewrite['*']}`);
|
|
118
|
+
line(`cookieDomainRewrite contains proxy IP: ${Object.values(cookieDomainRewrite).includes(input.proxyOwnIp)}`);
|
|
119
|
+
line('');
|
|
120
|
+
line('ASSERTIONS:');
|
|
121
|
+
recordAssertion('cookieDomainRewrite only has "*" key', () => {
|
|
122
|
+
assert.deepStrictEqual(cookieDomainRewriteKeys, ['*']);
|
|
123
|
+
});
|
|
124
|
+
recordAssertion('cookieDomainRewrite["*"] === ""', () => {
|
|
125
|
+
assert.strictEqual(cookieDomainRewrite['*'], '');
|
|
126
|
+
});
|
|
127
|
+
recordAssertion('cookieDomainRewrite does not contain proxy IP', () => {
|
|
128
|
+
assert.ok(!Object.values(cookieDomainRewrite).includes(input.proxyOwnIp), 'set-cookie domain must not be rewritten to an IP address');
|
|
129
|
+
});
|
|
130
|
+
recordAssertion('cookiePathRewrite only has "*" key', () => {
|
|
131
|
+
assert.deepStrictEqual(cookiePathRewriteKeys, ['*']);
|
|
132
|
+
});
|
|
133
|
+
recordAssertion('cookiePathRewrite["*"] === "/"', () => {
|
|
134
|
+
assert.strictEqual(cookiePathRewrite['*'], '/');
|
|
135
|
+
});
|
|
136
|
+
line('');
|
|
137
|
+
line('RESULT: PASS');
|
|
138
|
+
writeOutput();
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (!lines.includes('RESULT: PASS')) {
|
|
141
|
+
line('');
|
|
142
|
+
line('RESULT: FAIL');
|
|
143
|
+
writeOutput();
|
|
144
|
+
}
|
|
145
|
+
throw err;
|
|
146
|
+
} finally {
|
|
147
|
+
fs.rmSync(formerDataStorePath, { force: true });
|
|
148
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const vm = require('vm');
|
|
6
|
+
|
|
7
|
+
const testName = 'proxy-cookie-sanitizing';
|
|
8
|
+
const outputDir = process.env.ALEXA_COOKIE_TEST_OUTPUT_DIR || path.join(__dirname, '..', 'test-output');
|
|
9
|
+
const outputFile = path.join(outputDir, `${testName}.txt`);
|
|
10
|
+
const lines = [];
|
|
11
|
+
|
|
12
|
+
function line(value = '') {
|
|
13
|
+
lines.push(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeOutput() {
|
|
17
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
18
|
+
fs.writeFileSync(outputFile, `${lines.join('\n')}\n`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function recordAssertion(description, fn) {
|
|
22
|
+
try {
|
|
23
|
+
fn();
|
|
24
|
+
line(`${description}: PASS`);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
line(`${description}: FAIL`);
|
|
27
|
+
writeOutput();
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const proxyFile = path.join(__dirname, '..', 'lib', 'proxy.js');
|
|
33
|
+
const source = fs.readFileSync(proxyFile, 'utf8');
|
|
34
|
+
|
|
35
|
+
let capturedProxyOptions;
|
|
36
|
+
|
|
37
|
+
function createExpressStub() {
|
|
38
|
+
return {
|
|
39
|
+
use() {},
|
|
40
|
+
get() {},
|
|
41
|
+
listen() {
|
|
42
|
+
const server = {
|
|
43
|
+
address: () => ({ port: 3456 }),
|
|
44
|
+
on: () => server
|
|
45
|
+
};
|
|
46
|
+
return server;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function loadProxyModule() {
|
|
52
|
+
capturedProxyOptions = undefined;
|
|
53
|
+
const module = { exports: {} };
|
|
54
|
+
const sandbox = {
|
|
55
|
+
Buffer,
|
|
56
|
+
URL,
|
|
57
|
+
__dirname: path.dirname(proxyFile),
|
|
58
|
+
console,
|
|
59
|
+
module,
|
|
60
|
+
exports: module.exports,
|
|
61
|
+
require(name) {
|
|
62
|
+
if (name === 'express') return createExpressStub;
|
|
63
|
+
if (name === 'http-proxy-response-rewrite') return () => {};
|
|
64
|
+
if (name === 'http-proxy-middleware') {
|
|
65
|
+
return {
|
|
66
|
+
createProxyMiddleware(_context, options) {
|
|
67
|
+
capturedProxyOptions = options;
|
|
68
|
+
return function proxyMiddleware() {};
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return require(name);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
vm.runInNewContext(source, sandbox, { filename: proxyFile });
|
|
76
|
+
return module.exports;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function createProxyRequest(cookie) {
|
|
80
|
+
const headers = {
|
|
81
|
+
cookie,
|
|
82
|
+
host: 'www.amazon.de',
|
|
83
|
+
origin: 'https://www.amazon.de'
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
getHeader(name) {
|
|
87
|
+
return headers[name.toLowerCase()];
|
|
88
|
+
},
|
|
89
|
+
setHeader(name, value) {
|
|
90
|
+
headers[name.toLowerCase()] = value;
|
|
91
|
+
},
|
|
92
|
+
getHeaders() {
|
|
93
|
+
return headers;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createProxyResponse(location) {
|
|
99
|
+
return {
|
|
100
|
+
statusCode: 302,
|
|
101
|
+
headers: {
|
|
102
|
+
location
|
|
103
|
+
},
|
|
104
|
+
socket: {
|
|
105
|
+
_host: 'www.amazon.de',
|
|
106
|
+
parser: {
|
|
107
|
+
outgoing: {
|
|
108
|
+
method: 'GET',
|
|
109
|
+
path: '/ap/maplanding?openid.oa2.authorization_code=AUTH_CODE_FROM_PROXY',
|
|
110
|
+
getHeader() {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const proxyModule = loadProxyModule();
|
|
120
|
+
const formerDataStorePath = path.join(os.tmpdir(), `alexa-cookie-proxy-cookie-sanitizing-test-${Date.now()}.json`);
|
|
121
|
+
let callbackData;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const input = {
|
|
125
|
+
proxyOwnIp: '127.0.0.1',
|
|
126
|
+
proxyPort: 3456,
|
|
127
|
+
proxyListenBind: '0.0.0.0',
|
|
128
|
+
baseAmazonPage: 'amazon.de',
|
|
129
|
+
baseAmazonPageHandle: '_de',
|
|
130
|
+
amazonPageProxyLanguage: 'de_DE',
|
|
131
|
+
acceptLanguage: 'de-DE',
|
|
132
|
+
proxyLogLevel: 'silent',
|
|
133
|
+
formerDataStorePath
|
|
134
|
+
};
|
|
135
|
+
proxyModule.initAmazonProxy(input, (_err, data) => {
|
|
136
|
+
callbackData = data;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const proxyReq = createProxyRequest('connect.sid=LOCAL_BROWSER; session-id=SID_BROWSER; ubid-acbde=UBID_BROWSER');
|
|
140
|
+
const req = {
|
|
141
|
+
method: 'GET',
|
|
142
|
+
url: '/www.amazon.de/ap/maplanding?openid.oa2.authorization_code=AUTH_CODE_FROM_PROXY',
|
|
143
|
+
originalUrl: '/www.amazon.de/ap/maplanding?openid.oa2.authorization_code=AUTH_CODE_FROM_PROXY',
|
|
144
|
+
headers: {
|
|
145
|
+
host: '127.0.0.1:3456'
|
|
146
|
+
},
|
|
147
|
+
on() {}
|
|
148
|
+
};
|
|
149
|
+
capturedProxyOptions.onProxyReq(proxyReq, req);
|
|
150
|
+
capturedProxyOptions.onProxyRes(createProxyResponse('https://www.amazon.de/ap/maplanding?openid.oa2.authorization_code=AUTH_CODE_FROM_PROXY'), req, {});
|
|
151
|
+
|
|
152
|
+
line('TEST: proxy callback sanitizes login cookie');
|
|
153
|
+
line('');
|
|
154
|
+
line('CODE UNDER TEST:');
|
|
155
|
+
line('- lib/proxy.js: onProxyReq() proxy cookie capture');
|
|
156
|
+
line('- lib/proxy.js: callbackCookie loginCookie value');
|
|
157
|
+
line('');
|
|
158
|
+
line('OBSERVED:');
|
|
159
|
+
line(`loginCookie: ${callbackData && callbackData.loginCookie}`);
|
|
160
|
+
line('');
|
|
161
|
+
line('ASSERTIONS:');
|
|
162
|
+
recordAssertion('callback data is present', () => {
|
|
163
|
+
assert.ok(callbackData);
|
|
164
|
+
});
|
|
165
|
+
recordAssertion('loginCookie keeps Amazon session-id', () => {
|
|
166
|
+
assert.ok(callbackData.loginCookie.includes('session-id=SID_BROWSER'));
|
|
167
|
+
});
|
|
168
|
+
recordAssertion('loginCookie keeps Amazon ubid cookie', () => {
|
|
169
|
+
assert.ok(callbackData.loginCookie.includes('ubid-acbde=UBID_BROWSER'));
|
|
170
|
+
});
|
|
171
|
+
recordAssertion('loginCookie removes unrelated local browser cookie', () => {
|
|
172
|
+
assert.ok(!callbackData.loginCookie.includes('connect.sid=LOCAL_BROWSER'));
|
|
173
|
+
});
|
|
174
|
+
line('');
|
|
175
|
+
line('RESULT: PASS');
|
|
176
|
+
writeOutput();
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (!lines.includes('RESULT: PASS')) {
|
|
179
|
+
line('');
|
|
180
|
+
line('RESULT: FAIL');
|
|
181
|
+
writeOutput();
|
|
182
|
+
}
|
|
183
|
+
throw err;
|
|
184
|
+
} finally {
|
|
185
|
+
fs.rmSync(formerDataStorePath, { force: true });
|
|
186
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const vm = require('vm');
|
|
6
|
+
|
|
7
|
+
const testName = 'proxy-host-switch';
|
|
8
|
+
const outputDir = process.env.ALEXA_COOKIE_TEST_OUTPUT_DIR || path.join(__dirname, '..', 'test-output');
|
|
9
|
+
const outputFile = path.join(outputDir, `${testName}.txt`);
|
|
10
|
+
const lines = [];
|
|
11
|
+
|
|
12
|
+
function line(value = '') {
|
|
13
|
+
lines.push(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeOutput() {
|
|
17
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
18
|
+
fs.writeFileSync(outputFile, `${lines.join('\n')}\n`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function recordAssertion(description, fn) {
|
|
22
|
+
try {
|
|
23
|
+
fn();
|
|
24
|
+
line(`${description}: PASS`);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
line(`${description}: FAIL`);
|
|
27
|
+
writeOutput();
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const proxyFile = path.join(__dirname, '..', 'lib', 'proxy.js');
|
|
33
|
+
const source = fs.readFileSync(proxyFile, 'utf8');
|
|
34
|
+
|
|
35
|
+
let capturedProxyOptions;
|
|
36
|
+
|
|
37
|
+
function createExpressStub() {
|
|
38
|
+
return {
|
|
39
|
+
use() {},
|
|
40
|
+
get() {},
|
|
41
|
+
listen() {
|
|
42
|
+
const server = {
|
|
43
|
+
address: () => ({ port: 3456 }),
|
|
44
|
+
on: () => server
|
|
45
|
+
};
|
|
46
|
+
return server;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function loadProxyModule() {
|
|
52
|
+
capturedProxyOptions = undefined;
|
|
53
|
+
const module = { exports: {} };
|
|
54
|
+
const sandbox = {
|
|
55
|
+
Buffer,
|
|
56
|
+
URL,
|
|
57
|
+
__dirname: path.dirname(proxyFile),
|
|
58
|
+
console,
|
|
59
|
+
module,
|
|
60
|
+
exports: module.exports,
|
|
61
|
+
require(name) {
|
|
62
|
+
if (name === 'express') return createExpressStub;
|
|
63
|
+
if (name === 'http-proxy-response-rewrite') return () => {};
|
|
64
|
+
if (name === 'http-proxy-middleware') {
|
|
65
|
+
return {
|
|
66
|
+
createProxyMiddleware(_context, options) {
|
|
67
|
+
capturedProxyOptions = options;
|
|
68
|
+
return function proxyMiddleware() {};
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (name === 'cookie') {
|
|
73
|
+
return { parse: () => ({}) };
|
|
74
|
+
}
|
|
75
|
+
return require(name);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
vm.runInNewContext(source, sandbox, { filename: proxyFile });
|
|
79
|
+
return module.exports;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function applyPathRewrite(pathname, req) {
|
|
83
|
+
const rewrite = capturedProxyOptions.pathRewrite;
|
|
84
|
+
if (typeof rewrite === 'function') return rewrite(pathname, req);
|
|
85
|
+
for (const pattern of Object.keys(rewrite)) {
|
|
86
|
+
const regex = new RegExp(pattern);
|
|
87
|
+
if (regex.test(pathname)) return pathname.replace(regex, rewrite[pattern]);
|
|
88
|
+
}
|
|
89
|
+
return pathname;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const proxyModule = loadProxyModule();
|
|
93
|
+
const formerDataStorePath = path.join(os.tmpdir(), `alexa-cookie-proxy-host-test-${Date.now()}.json`);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const input = {
|
|
97
|
+
proxyOwnIp: '127.0.0.1',
|
|
98
|
+
proxyPort: 3456,
|
|
99
|
+
proxyListenBind: '0.0.0.0',
|
|
100
|
+
baseAmazonPage: 'amazon.de',
|
|
101
|
+
baseAmazonPageHandle: '_de',
|
|
102
|
+
amazonPageProxyLanguage: 'de_DE',
|
|
103
|
+
acceptLanguage: 'de-DE',
|
|
104
|
+
proxyLogLevel: 'silent',
|
|
105
|
+
formerDataStorePath
|
|
106
|
+
};
|
|
107
|
+
proxyModule.initAmazonProxy(input);
|
|
108
|
+
|
|
109
|
+
const directReq = {
|
|
110
|
+
method: 'GET',
|
|
111
|
+
url: '/www.amazon.com/ap/signin',
|
|
112
|
+
headers: { host: '127.0.0.1:3456' }
|
|
113
|
+
};
|
|
114
|
+
const refererReq = {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
url: '/ap/cvf/verify',
|
|
117
|
+
headers: {
|
|
118
|
+
host: '127.0.0.1:3456',
|
|
119
|
+
referer: 'http://127.0.0.1:3456/www.amazon.com/ap/signin'
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const directTarget = capturedProxyOptions.router(directReq);
|
|
124
|
+
const directRewrittenPath = applyPathRewrite(directReq.url, directReq);
|
|
125
|
+
const refererTarget = capturedProxyOptions.router(refererReq);
|
|
126
|
+
|
|
127
|
+
line('TEST: proxy host switch');
|
|
128
|
+
line('');
|
|
129
|
+
line('CODE UNDER TEST:');
|
|
130
|
+
line('- lib/proxy.js: router()');
|
|
131
|
+
line('- lib/proxy.js: rewriteProxyPath()');
|
|
132
|
+
line('');
|
|
133
|
+
line('INPUT:');
|
|
134
|
+
line(`baseAmazonPage: ${input.baseAmazonPage}`);
|
|
135
|
+
line(`direct request url: ${directReq.url}`);
|
|
136
|
+
line(`referer request url: ${refererReq.url}`);
|
|
137
|
+
line(`referer header: ${refererReq.headers.referer}`);
|
|
138
|
+
line('');
|
|
139
|
+
line('OBSERVED:');
|
|
140
|
+
line(`direct router target: ${directTarget}`);
|
|
141
|
+
line(`direct rewritten path: ${directRewrittenPath}`);
|
|
142
|
+
line(`referer router target: ${refererTarget}`);
|
|
143
|
+
line('');
|
|
144
|
+
line('ASSERTIONS:');
|
|
145
|
+
recordAssertion('direct router target === "https://www.amazon.com"', () => {
|
|
146
|
+
assert.strictEqual(directTarget, 'https://www.amazon.com');
|
|
147
|
+
});
|
|
148
|
+
recordAssertion('direct rewritten path === "/ap/signin"', () => {
|
|
149
|
+
assert.strictEqual(directRewrittenPath, '/ap/signin');
|
|
150
|
+
});
|
|
151
|
+
recordAssertion('referer router target === "https://www.amazon.com"', () => {
|
|
152
|
+
assert.strictEqual(refererTarget, 'https://www.amazon.com');
|
|
153
|
+
});
|
|
154
|
+
line('');
|
|
155
|
+
line('RESULT: PASS');
|
|
156
|
+
writeOutput();
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (!lines.includes('RESULT: PASS')) {
|
|
159
|
+
line('');
|
|
160
|
+
line('RESULT: FAIL');
|
|
161
|
+
writeOutput();
|
|
162
|
+
}
|
|
163
|
+
throw err;
|
|
164
|
+
} finally {
|
|
165
|
+
fs.rmSync(formerDataStorePath, { force: true });
|
|
166
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const vm = require('vm');
|
|
6
|
+
|
|
7
|
+
const testName = 'proxy-initial-url';
|
|
8
|
+
const outputDir = process.env.ALEXA_COOKIE_TEST_OUTPUT_DIR || path.join(__dirname, '..', 'test-output');
|
|
9
|
+
const outputFile = path.join(outputDir, `${testName}.txt`);
|
|
10
|
+
const lines = [];
|
|
11
|
+
|
|
12
|
+
function line(value = '') {
|
|
13
|
+
lines.push(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeOutput() {
|
|
17
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
18
|
+
fs.writeFileSync(outputFile, `${lines.join('\n')}\n`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function recordAssertion(description, fn) {
|
|
22
|
+
try {
|
|
23
|
+
fn();
|
|
24
|
+
line(`${description}: PASS`);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
line(`${description}: FAIL`);
|
|
27
|
+
writeOutput();
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const proxyFile = path.join(__dirname, '..', 'lib', 'proxy.js');
|
|
33
|
+
const source = fs.readFileSync(proxyFile, 'utf8');
|
|
34
|
+
|
|
35
|
+
let capturedProxyOptions;
|
|
36
|
+
|
|
37
|
+
function createExpressStub() {
|
|
38
|
+
return {
|
|
39
|
+
use() {},
|
|
40
|
+
get() {},
|
|
41
|
+
listen() {
|
|
42
|
+
const server = {
|
|
43
|
+
address: () => ({ port: 3456 }),
|
|
44
|
+
on: () => server
|
|
45
|
+
};
|
|
46
|
+
return server;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function loadProxyModule() {
|
|
52
|
+
const module = { exports: {} };
|
|
53
|
+
const sandbox = {
|
|
54
|
+
Buffer,
|
|
55
|
+
URL,
|
|
56
|
+
__dirname: path.dirname(proxyFile),
|
|
57
|
+
console,
|
|
58
|
+
module,
|
|
59
|
+
exports: module.exports,
|
|
60
|
+
require(name) {
|
|
61
|
+
if (name === 'express') return createExpressStub;
|
|
62
|
+
if (name === 'http-proxy-response-rewrite') return () => {};
|
|
63
|
+
if (name === 'http-proxy-middleware') {
|
|
64
|
+
return {
|
|
65
|
+
createProxyMiddleware(_context, options) {
|
|
66
|
+
capturedProxyOptions = options;
|
|
67
|
+
return function proxyMiddleware() {};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (name === 'cookie') {
|
|
72
|
+
return { parse: () => ({}) };
|
|
73
|
+
}
|
|
74
|
+
return require(name);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
vm.runInNewContext(source, sandbox, { filename: proxyFile });
|
|
78
|
+
return module.exports;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function applyPathRewrite(pathname, req) {
|
|
82
|
+
const rewrite = capturedProxyOptions.pathRewrite;
|
|
83
|
+
if (typeof rewrite === 'function') return rewrite(pathname, req);
|
|
84
|
+
for (const pattern of Object.keys(rewrite)) {
|
|
85
|
+
const regex = new RegExp(pattern);
|
|
86
|
+
if (regex.test(pathname)) return pathname.replace(regex, rewrite[pattern]);
|
|
87
|
+
}
|
|
88
|
+
return pathname;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const proxyModule = loadProxyModule();
|
|
92
|
+
const formerDataStorePath = path.join(os.tmpdir(), `alexa-cookie-proxy-test-${Date.now()}.json`);
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const input = {
|
|
96
|
+
proxyOwnIp: '127.0.0.1',
|
|
97
|
+
proxyPort: 3456,
|
|
98
|
+
proxyListenBind: '0.0.0.0',
|
|
99
|
+
baseAmazonPage: 'amazon.co.uk',
|
|
100
|
+
baseAmazonPageHandle: '_uk',
|
|
101
|
+
amazonPageProxyLanguage: 'en_GB',
|
|
102
|
+
acceptLanguage: 'en-GB',
|
|
103
|
+
proxyLogLevel: 'silent',
|
|
104
|
+
formerDataStorePath
|
|
105
|
+
};
|
|
106
|
+
proxyModule.initAmazonProxy(input);
|
|
107
|
+
|
|
108
|
+
const req = {
|
|
109
|
+
method: 'GET',
|
|
110
|
+
url: '/',
|
|
111
|
+
headers: {
|
|
112
|
+
host: '127.0.0.1:3456'
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const target = capturedProxyOptions.router(req);
|
|
117
|
+
const rewrittenPath = applyPathRewrite(req.url, req);
|
|
118
|
+
const targetUrl = new URL(target);
|
|
119
|
+
const rewrittenUrl = new URL(rewrittenPath, target);
|
|
120
|
+
|
|
121
|
+
line('TEST: proxy initial URL');
|
|
122
|
+
line('');
|
|
123
|
+
line('CODE UNDER TEST:');
|
|
124
|
+
line('- lib/proxy.js: router()');
|
|
125
|
+
line('- lib/proxy.js: pathRewrite / rewriteProxyPath()');
|
|
126
|
+
line('- lib/proxy.js: buildInitialUrl()');
|
|
127
|
+
line('');
|
|
128
|
+
line('INPUT:');
|
|
129
|
+
line(`baseAmazonPage: ${input.baseAmazonPage}`);
|
|
130
|
+
line(`baseAmazonPageHandle: ${input.baseAmazonPageHandle}`);
|
|
131
|
+
line(`proxy host header: ${req.headers.host}`);
|
|
132
|
+
line(`request url: ${req.url}`);
|
|
133
|
+
line('');
|
|
134
|
+
line('OBSERVED:');
|
|
135
|
+
line(`router target host: ${targetUrl.host}`);
|
|
136
|
+
line(`router target search: ${targetUrl.search}`);
|
|
137
|
+
line(`rewritten pathname: ${rewrittenUrl.pathname}`);
|
|
138
|
+
line(`rewritten openid.assoc_handle: ${rewrittenUrl.searchParams.get('openid.assoc_handle')}`);
|
|
139
|
+
line(`rewritten pageId: ${rewrittenUrl.searchParams.get('pageId')}`);
|
|
140
|
+
line(`rewritten openid.return_to: ${rewrittenUrl.searchParams.get('openid.return_to')}`);
|
|
141
|
+
line(`rewritten openid.ns.oa2: ${rewrittenUrl.searchParams.get('openid.ns.oa2')}`);
|
|
142
|
+
line(`rewritten path starts with /ap/signin?: ${rewrittenPath.startsWith('/ap/signin?')}`);
|
|
143
|
+
line(`rewritten path contains openid.return_to: ${rewrittenPath.includes('openid.return_to=')}`);
|
|
144
|
+
line(`rewritten path contains regional handle: ${rewrittenPath.includes('amzn_dp_project_dee_ios_uk')}`);
|
|
145
|
+
line(`rewritten path contains code challenge: ${rewrittenPath.includes('openid.oa2.code_challenge=')}`);
|
|
146
|
+
line('');
|
|
147
|
+
line('ASSERTIONS:');
|
|
148
|
+
recordAssertion('router target host === "www.amazon.co.uk"', () => {
|
|
149
|
+
assert.strictEqual(targetUrl.host, 'www.amazon.co.uk');
|
|
150
|
+
});
|
|
151
|
+
recordAssertion('router target search === ""', () => {
|
|
152
|
+
assert.ok(targetUrl.search === '', 'router target search must be empty');
|
|
153
|
+
});
|
|
154
|
+
recordAssertion('rewritten path starts with "/ap/signin?"', () => {
|
|
155
|
+
assert.ok(rewrittenPath.startsWith('/ap/signin?'), `expected signin path, got ${rewrittenPath}`);
|
|
156
|
+
});
|
|
157
|
+
recordAssertion('rewritten assoc handle === "amzn_dp_project_dee_ios_uk"', () => {
|
|
158
|
+
assert.strictEqual(rewrittenUrl.searchParams.get('openid.assoc_handle'), 'amzn_dp_project_dee_ios_uk');
|
|
159
|
+
});
|
|
160
|
+
recordAssertion('rewritten pageId === "amzn_dp_project_dee_ios_uk"', () => {
|
|
161
|
+
assert.strictEqual(rewrittenUrl.searchParams.get('pageId'), 'amzn_dp_project_dee_ios_uk');
|
|
162
|
+
});
|
|
163
|
+
recordAssertion('rewritten path contains "openid.return_to="', () => {
|
|
164
|
+
assert.ok(rewrittenPath.includes('openid.return_to='));
|
|
165
|
+
});
|
|
166
|
+
recordAssertion('rewritten path contains regional "_uk" handle', () => {
|
|
167
|
+
assert.ok(rewrittenPath.includes('amzn_dp_project_dee_ios_uk'));
|
|
168
|
+
});
|
|
169
|
+
recordAssertion('rewritten path contains "openid.oa2.code_challenge="', () => {
|
|
170
|
+
assert.ok(rewrittenPath.includes('openid.oa2.code_challenge='));
|
|
171
|
+
});
|
|
172
|
+
line('');
|
|
173
|
+
line('RESULT: PASS');
|
|
174
|
+
writeOutput();
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if (!lines.includes('RESULT: PASS')) {
|
|
177
|
+
line('');
|
|
178
|
+
line('RESULT: FAIL');
|
|
179
|
+
writeOutput();
|
|
180
|
+
}
|
|
181
|
+
throw err;
|
|
182
|
+
} finally {
|
|
183
|
+
fs.rmSync(formerDataStorePath, { force: true });
|
|
184
|
+
}
|