Add the redirect flow.
This commit is contained in:
Родитель
a16bb6f2d3
Коммит
79b2ce1ccd
|
@ -4,8 +4,9 @@
|
|||
|
||||
define([
|
||||
'p-promise',
|
||||
'client/auth/lightbox/api'
|
||||
], function (p, LightboxAPI, Options) {
|
||||
'client/auth/lightbox/api',
|
||||
'client/auth/redirect/api'
|
||||
], function (p, LightboxAPI, RedirectAPI, Options) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
|
@ -27,14 +28,22 @@ define([
|
|||
throw new Error('clientId is required');
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate users using the lightbox
|
||||
*
|
||||
* @property auth.lightbox
|
||||
* @type {LightboxAPI}
|
||||
*/
|
||||
this.auth = {
|
||||
lightbox: new LightboxAPI(clientId, options)
|
||||
/**
|
||||
* Authenticate a user using the lightbox
|
||||
*
|
||||
* @property auth.lightbox
|
||||
* @type {LightboxAPI}
|
||||
*/
|
||||
lightbox: new LightboxAPI(clientId, options),
|
||||
|
||||
/**
|
||||
* Authenticate a user using the redirect flow
|
||||
*
|
||||
* @property auth.redirect
|
||||
* @type {RedirectAPI}
|
||||
*/
|
||||
redirect: new RedirectAPI(clientId, options)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
define([
|
||||
'client/lib/constants',
|
||||
'client/lib/options',
|
||||
'client/lib/url'
|
||||
], function (Constants, Options, Url) {
|
||||
|
||||
function getFxaUrl(host, page, clientId, state, scope,
|
||||
redirectUri, email) {
|
||||
var queryParams = {
|
||||
client_id: clientId,
|
||||
state: state,
|
||||
scope: scope,
|
||||
redirect_uri: redirectUri
|
||||
};
|
||||
|
||||
if (email) {
|
||||
queryParams.email = email;
|
||||
}
|
||||
|
||||
return host + '/' + page + Url.objectToQueryString(queryParams);
|
||||
}
|
||||
|
||||
function authenticate(page, config) {
|
||||
var requiredOptions = ['scope', 'state', 'redirect_uri'];
|
||||
Options.checkRequired(requiredOptions, config);
|
||||
|
||||
var self = this;
|
||||
var fxaUrl = getFxaUrl(self._fxaHost, page, self._clientId,
|
||||
config.state, config.scope, config.redirect_uri,
|
||||
config.force_email);
|
||||
|
||||
|
||||
this._window.location.href = fxaUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a user with the redirect flow.
|
||||
*
|
||||
* @class RedirectAPI
|
||||
* @constructor
|
||||
*/
|
||||
function RedirectAPI(clientId, options) {
|
||||
if (! clientId) {
|
||||
throw new Error('clientId is required');
|
||||
}
|
||||
this._clientId = clientId;
|
||||
|
||||
options = options || {};
|
||||
this._fxaHost = options.fxaHost || Constants.DEFAULT_FXA_HOST;
|
||||
this._window = options.window || window;
|
||||
}
|
||||
|
||||
RedirectAPI.prototype = {
|
||||
/**
|
||||
* Sign in an existing user
|
||||
*
|
||||
* @method signIn
|
||||
* @param {Object} config - configuration
|
||||
* @param {String} config.state
|
||||
* CSRF/State token
|
||||
* @param {String} config.redirect_uri
|
||||
* URI to redirect to when complete
|
||||
* @param {String} config.scope
|
||||
* OAuth scope
|
||||
* @param {String} [config.force_email]
|
||||
* Force the user to sign in with the given email
|
||||
*/
|
||||
signIn: function (config) {
|
||||
config = config || {};
|
||||
var page = config.force_email ?
|
||||
Constants.FORCE_EMAIL_ENDPOINT :
|
||||
Constants.SIGNIN_ENDPOINT;
|
||||
return authenticate.call(this, page, config);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign up a new user
|
||||
*
|
||||
* @method signUp
|
||||
* @param {Object} config - configuration
|
||||
* @param {String} config.state
|
||||
* CSRF/State token
|
||||
* @param {String} config.redirect_uri
|
||||
* URI to redirect to when complete
|
||||
* @param {String} config.scope
|
||||
* OAuth scope
|
||||
*/
|
||||
signUp: function (config) {
|
||||
return authenticate.call(this, Constants.SIGNUP_ENDPOINT, config);
|
||||
}
|
||||
};
|
||||
|
||||
return RedirectAPI;
|
||||
});
|
||||
|
|
@ -76,6 +76,10 @@ define([], function () {
|
|||
function WindowMock() {
|
||||
DOMElement.call(this, 'window');
|
||||
this.document = new Document();
|
||||
|
||||
this.location = {
|
||||
href: null
|
||||
};
|
||||
}
|
||||
WindowMock.prototype = new DOMElement();
|
||||
WindowMock.prototype.postMessage = function (message, targetOrigin, origin) {
|
||||
|
|
|
@ -32,6 +32,7 @@ define([
|
|||
});
|
||||
|
||||
assert.ok(client.auth.lightbox);
|
||||
assert.ok(client.auth.redirect);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*global define*/
|
||||
define([
|
||||
'intern!bdd',
|
||||
'intern/chai!assert',
|
||||
'client/auth/redirect/api',
|
||||
'tests/mocks/window',
|
||||
'tests/addons/sinon',
|
||||
'p-promise'
|
||||
],
|
||||
function (bdd, assert, RedirectAPI, WindowMock, sinon, p) {
|
||||
'use strict';
|
||||
|
||||
bdd.describe('auth/redirect/api', function () {
|
||||
|
||||
var windowMock;
|
||||
var redirectAPI;
|
||||
|
||||
bdd.beforeEach(function () {
|
||||
windowMock = new WindowMock();
|
||||
redirectAPI = new RedirectAPI('client_id', {
|
||||
window: windowMock
|
||||
});
|
||||
});
|
||||
|
||||
bdd.afterEach(function () {
|
||||
});
|
||||
|
||||
function testMissingOption(endpoint, optionName) {
|
||||
var options = {
|
||||
state: 'state',
|
||||
scope: 'scope',
|
||||
redirect_uri: 'redirect_uri'
|
||||
};
|
||||
|
||||
delete options[optionName];
|
||||
|
||||
var err;
|
||||
try {
|
||||
redirectAPI[endpoint](options);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
} finally {
|
||||
assert.equal(err.message, optionName + ' is required');
|
||||
}
|
||||
}
|
||||
|
||||
bdd.describe('signIn', function () {
|
||||
bdd.it('should reject if `scope` is not specified', function () {
|
||||
return testMissingOption('signIn', 'scope');
|
||||
});
|
||||
|
||||
bdd.it('should reject if `redirect_uri` is not specified', function () {
|
||||
return testMissingOption('signIn', 'redirect_uri');
|
||||
});
|
||||
|
||||
bdd.it('should reject if `state` is not specified', function () {
|
||||
return testMissingOption('signIn', 'state');
|
||||
});
|
||||
|
||||
bdd.it('should redirect to the /signin page with the expected query parameters', function () {
|
||||
redirectAPI.signIn({
|
||||
state: 'state',
|
||||
scope: 'scope',
|
||||
redirect_uri: 'redirect_uri'
|
||||
});
|
||||
|
||||
var redirectedTo = windowMock.location.href;
|
||||
assert.include(redirectedTo, '/signin');
|
||||
assert.include(redirectedTo, 'state=state');
|
||||
assert.include(redirectedTo, 'scope=scope');
|
||||
assert.include(redirectedTo, 'redirect_uri=redirect_uri');
|
||||
});
|
||||
|
||||
bdd.it('should redirect to the /force_auth page with the expected query parameters if the RP forces authentication as a user', function () {
|
||||
redirectAPI.signIn({
|
||||
state: 'state',
|
||||
scope: 'scope',
|
||||
redirect_uri: 'redirect_uri',
|
||||
force_email: 'testuser@testuser.com'
|
||||
});
|
||||
|
||||
var redirectedTo = windowMock.location.href;
|
||||
assert.include(redirectedTo, '/force_auth');
|
||||
assert.include(redirectedTo, 'state=state');
|
||||
assert.include(redirectedTo, 'scope=scope');
|
||||
assert.include(redirectedTo, 'redirect_uri=redirect_uri');
|
||||
assert.include(redirectedTo, 'email=testuser%40testuser.com');
|
||||
});
|
||||
});
|
||||
|
||||
bdd.describe('signUp', function () {
|
||||
bdd.it('should reject if `scope` is not specified', function () {
|
||||
return testMissingOption('signUp', 'scope');
|
||||
});
|
||||
|
||||
bdd.it('should reject if `redirect_uri` is not specified', function () {
|
||||
return testMissingOption('signUp', 'redirect_uri');
|
||||
});
|
||||
|
||||
bdd.it('should reject if `state` is not specified', function () {
|
||||
return testMissingOption('signUp', 'state');
|
||||
});
|
||||
|
||||
bdd.it('should redirect to the /signup page with the expected query parameters', function () {
|
||||
redirectAPI.signUp({
|
||||
state: 'state',
|
||||
scope: 'scope',
|
||||
redirect_uri: 'redirect_uri'
|
||||
});
|
||||
|
||||
var redirectedTo = windowMock.location.href;
|
||||
assert.include(redirectedTo, '/signup');
|
||||
assert.include(redirectedTo, 'state=state');
|
||||
assert.include(redirectedTo, 'scope=scope');
|
||||
assert.include(redirectedTo, 'redirect_uri=redirect_uri');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
define([
|
||||
'./spec/FxaRelierClient',
|
||||
'./spec/auth/redirect/api',
|
||||
'./spec/auth/lightbox/api',
|
||||
'./spec/auth/lightbox/iframe_channel',
|
||||
'./spec/auth/lightbox/lightbox'
|
||||
|
|
Загрузка…
Ссылка в новой задаче