This commit is contained in:
Shane Tomlinson 2014-11-26 11:36:53 +00:00
Родитель a16bb6f2d3
Коммит 79b2ce1ccd
6 изменённых файлов: 248 добавлений и 9 удалений

Просмотреть файл

@ -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'