Skip to content

Commit b93797f

Browse files
authored
Use WinChan on popup.callback again + adding origin check to keep it secure (#669)
* Use WinChan on popup.callback again + adding origin check to keep it secure * adding tests for getLocationFromUrl * handling IE11 edge case inside auth0.js * remove only * more info about ie bug * no undefined undefined
1 parent 2ca7ccf commit b93797f

File tree

6 files changed

+206
-106
lines changed

6 files changed

+206
-106
lines changed

src/helper/object.js

+24-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
var assert = require('./assert');
66
var objectAssign = require('./object-assign');
7-
var windowHelper = require('./window');
87

98
function pick(object, keys) {
109
return keys.reduce(function(prev, key) {
@@ -118,16 +117,32 @@ function toCamelCase(object, exceptions) {
118117
}, {});
119118
}
120119

120+
function getLocationFromUrl(href) {
121+
var match = href.match(
122+
/^(https?:)\/\/(([^:/?#]*)(?::([0-9]+))?)([/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/
123+
);
124+
return (
125+
match && {
126+
href: href,
127+
protocol: match[1],
128+
host: match[2],
129+
hostname: match[3],
130+
port: match[4],
131+
pathname: match[5],
132+
search: match[6],
133+
hash: match[7]
134+
}
135+
);
136+
}
137+
121138
function getOriginFromUrl(url) {
122139
if (!url) {
123140
return undefined;
124141
}
125-
var doc = windowHelper.getDocument();
126-
var anchor = doc.createElement('a');
127-
anchor.href = url;
128-
var origin = anchor.protocol + '//' + anchor.hostname;
129-
if (anchor.port) {
130-
origin += ':' + anchor.port;
142+
var parsed = getLocationFromUrl(url);
143+
var origin = parsed.protocol + '//' + parsed.hostname;
144+
if (parsed.port) {
145+
origin += ':' + parsed.port;
131146
}
132147
return origin;
133148
}
@@ -140,5 +155,6 @@ module.exports = {
140155
pick: pick,
141156
getKeysNotIn: getKeysNotIn,
142157
extend: extend,
143-
getOriginFromUrl: getOriginFromUrl
158+
getOriginFromUrl: getOriginFromUrl,
159+
getLocationFromUrl: getLocationFromUrl
144160
};

src/helper/window.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
var objectHelper = require('./object');
2+
13
function redirect(url) {
24
global.window.location = url;
35
}
@@ -14,8 +16,7 @@ function getOrigin() {
1416
var location = global.window.location;
1517
var origin = location.origin;
1618
if (!origin) {
17-
origin =
18-
location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '');
19+
origin = objectHelper.getOriginFromUrl(location.href);
1920
}
2021
return origin;
2122
}

src/web-auth/popup.js

+34-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var urljoin = require('url-join');
2+
var WinChan = require('winchan');
23

34
var urlHelper = require('../helper/url');
45
var assert = require('../helper/assert');
@@ -84,17 +85,41 @@ Popup.prototype.getPopupHandler = function(options, preload) {
8485
*/
8586
Popup.prototype.callback = function(options) {
8687
var _this = this;
88+
var theWindow = windowHelper.getWindow();
8789
options = options || {};
88-
var originUrl =
89-
options.popupOrigin || this.baseOptions.popupOrigin || windowHelper.getWindow().origin;
90-
_this.webAuth.parseHash(options || {}, function(err, data) {
91-
// {a, d} is WinChan's message format.
92-
// We have to keep the same format because we're opening the popup with WinChan.
93-
var response = { a: 'response', d: data };
94-
if (err) {
95-
response = { a: 'error', d: err };
90+
var originUrl = options.popupOrigin || this.baseOptions.popupOrigin || windowHelper.getOrigin();
91+
92+
/*
93+
in IE 11, there's a bug that makes window.opener return undefined.
94+
The callback page will still call `popup.callback()` which will run this method
95+
in the relay page. WinChan expects the relay page to have a global `doPost` function,
96+
which will be called with the response.
97+
98+
IE11 Bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/110920/
99+
*/
100+
if (!theWindow.opener) {
101+
theWindow.doPost = function(msg) {
102+
if (theWindow.parent) {
103+
theWindow.parent.postMessage(msg, originUrl);
104+
}
105+
};
106+
return;
107+
}
108+
109+
WinChan.onOpen(function(popupOrigin, r, cb) {
110+
if (popupOrigin !== originUrl) {
111+
return cb({
112+
error: 'origin_mismatch',
113+
error_description: "The popup's origin (" +
114+
popupOrigin +
115+
') should match the `popupOrigin` parameter (' +
116+
originUrl +
117+
').'
118+
});
96119
}
97-
windowHelper.getWindow().opener.postMessage(JSON.stringify(response), originUrl);
120+
_this.webAuth.parseHash(options || {}, function(err, data) {
121+
return cb(err || data);
122+
});
98123
});
99124
};
100125

test/helper/object.test.js

+39-29
Original file line numberDiff line numberDiff line change
@@ -523,41 +523,51 @@ describe('helpers', function() {
523523
expect(objectHelper.getOriginFromUrl(null)).to.be(undefined);
524524
});
525525
it('should use an anchor to parse the url and return the origin', function() {
526-
var anchor = {
527-
protocol: 'https:',
528-
hostname: 'test.com'
529-
};
530-
stub(windowHelper, 'getDocument', function() {
531-
return {
532-
createElement: function createElement(e) {
533-
expect(e).to.be('a');
534-
return anchor;
535-
}
536-
};
537-
});
538526
var url = 'https://test.com/example';
539527
expect(objectHelper.getOriginFromUrl(url)).to.be('https://test.com');
540-
expect(anchor.href).to.be(url);
541-
windowHelper.getDocument.restore();
542528
});
543529
it('should use add the `port` when available', function() {
544-
var anchor = {
545-
protocol: 'https:',
546-
hostname: 'localhost',
547-
port: 3000
548-
};
549-
stub(windowHelper, 'getDocument', function() {
550-
return {
551-
createElement: function createElement(e) {
552-
expect(e).to.be('a');
553-
return anchor;
554-
}
555-
};
556-
});
557530
var url = 'https://localhost:3000/example';
558531
expect(objectHelper.getOriginFromUrl(url)).to.be('https://localhost:3000');
559-
expect(anchor.href).to.be(url);
560-
windowHelper.getDocument.restore();
561532
});
562533
});
534+
describe('getLocationFromUrl', function() {
535+
const mapping = {
536+
'https://localhost:3000/foo?id=1': {
537+
href: 'https://localhost:3000/foo?id=1',
538+
protocol: 'https:',
539+
host: 'localhost:3000',
540+
hostname: 'localhost',
541+
port: '3000',
542+
pathname: '/foo',
543+
search: '?id=1',
544+
hash: ''
545+
},
546+
'https://auth0.com/foo': {
547+
href: 'https://auth0.com/foo',
548+
protocol: 'https:',
549+
host: 'auth0.com',
550+
hostname: 'auth0.com',
551+
port: undefined,
552+
pathname: '/foo',
553+
search: '',
554+
hash: ''
555+
},
556+
'https://auth0.com#access_token=foo': {
557+
href: 'https://auth0.com#access_token=foo',
558+
protocol: 'https:',
559+
host: 'auth0.com',
560+
hostname: 'auth0.com',
561+
port: undefined,
562+
pathname: '',
563+
search: '',
564+
hash: '#access_token=foo'
565+
}
566+
};
567+
for (const url in mapping) {
568+
it('should map urls correctly: ' + url, function() {
569+
expect(objectHelper.getLocationFromUrl(url)).to.be.eql(mapping[url]);
570+
});
571+
}
572+
});
563573
});

test/helper/window.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('helpers window', function() {
2828
expect(windowHelper.getOrigin()).to.be('origin');
2929
});
3030
it('should build current origin when location.origin is not available', function() {
31-
global.window = { location: { protocol: 'http:', hostname: 'hostname', port: 30 } };
31+
global.window = { location: { href: 'http://hostname:30/foobar' } };
3232
expect(windowHelper.getOrigin()).to.be('http://hostname:30');
3333
});
3434
});

0 commit comments

Comments
 (0)