diff --git a/src/providers/oauth-provider.js b/src/providers/oauth-provider.js index c00b0cb..d9a1e0d 100644 --- a/src/providers/oauth-provider.js +++ b/src/providers/oauth-provider.js @@ -7,6 +7,7 @@ import angular from 'angular'; import queryString from 'query-string'; var defaults = { + authorizePath: '/oauth2/authorize', baseUrl: null, clientId: null, clientSecret: null, @@ -15,6 +16,7 @@ var defaults = { }; var requiredKeys = [ + 'authorizePath', 'baseUrl', 'clientId', 'grantPath', @@ -60,6 +62,11 @@ function OAuthProvider() { config.baseUrl = config.baseUrl.slice(0, -1); } + // Add `authorizePath` facing slash. + if('/' !== config.authorizePath[0]) { + config.authorizePath = `/${config.authorizePath}`; + } + // Add `grantPath` facing slash. if('/' !== config.grantPath[0]) { config.grantPath = `/${config.grantPath}`; @@ -92,6 +99,40 @@ function OAuthProvider() { } } + /** + * Requests a authorization for an application based on clientId, scope and state + * + * @param {string} clientId - Application `clientId` + * @param {string} scope - Scope(s) defined for the application + * @param {string} state - Randomly generated `state` string + * @return {promise} A response promise. + */ + + authorize(clientId, scope, state) { + // Check if `clientId` is defined. + if (!clientId) { + throw new Error('Missing parameter: clientId.'); + } + + const data = { + client_id: clientId, + response_type: 'code' + }; + + if (scope) { + data.scope = scope; + } + + if (state) { + data.state = state; + } + + const qs = queryString.stringify(data); + const url = `${config.baseUrl}${config.authorizePath}?${qs}`; + + return $http.get(url); + } + /** * Verifies if the `user` is authenticated or not based on the `token` * cookie. diff --git a/test/unit/providers/oauth-provider.spec.js b/test/unit/providers/oauth-provider.spec.js index c2af377..1eca760 100644 --- a/test/unit/providers/oauth-provider.spec.js +++ b/test/unit/providers/oauth-provider.spec.js @@ -5,11 +5,13 @@ describe('OAuthProvider', function() { var defaults = { + authorizePath: '/oauth2/authorize', baseUrl: 'https://api.website.com', clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', grantPath: '/oauth2/token', - revokePath: '/oauth2/revoke', - clientSecret: 'CLIENT_SECRET' + redirectUrl: 'https://website.com', + revokePath: '/oauth2/revoke' }; describe('configure()', function() { @@ -48,6 +50,25 @@ describe('OAuthProvider', function() { } }); + it('should throw an error if `authorizePath` param is empty', function() { + try { + provider.configure(_.defaults({ authorizePath: null }, defaults)); + + should.fail(); + } catch(e) { + e.should.be.an.instanceOf(Error); + e.message.should.match(/authorizePath/); + } + }); + + it('should add facing slash from `authorizePath`', function() { + var config = provider.configure(_.defaults({ + authorizePath: 'oauth2/authorize' + }, defaults)); + + config.authorizePath.should.equal('/oauth2/authorize'); + }); + it('should throw an error if `baseUrl` param is empty', function() { try { provider.configure(_.omit(defaults, 'baseUrl')); @@ -137,6 +158,71 @@ describe('OAuthProvider', function() { OAuthToken.removeToken(); })); + describe('authorize()', function() { + var data = { + client_id: defaults.clientId, + response_type: 'code', + scope: 'foo:bar', + state: 'state_hash' + }; + + it('should throw an error if `clientId` is missing', inject(function(OAuth) { + try { + OAuth.authorize(); + + should.fail(); + } catch(e) { + e.should.be.an.instanceOf(Error); + e.message.should.match(/clientId/); + } + })); + + it('should call `queryString.stringify` with default `data` if `state` and `scope` are not provided', inject(function(OAuth) { + sinon.spy(queryString, 'stringify'); + + OAuth.authorize(data.client_id); + + queryString.stringify.callCount.should.equal(1); + queryString.stringify.firstCall.args.should.have.lengthOf(1); + queryString.stringify.firstCall.args[0].should.eql({ + client_id: data.client_id, + response_type: 'code' + }); + + queryString.stringify.restore(); + })); + + it('should call `queryString.stringify` with provided `state` and `scope`', inject(function(OAuth) { + sinon.spy(queryString, 'stringify'); + + OAuth.authorize(data.client_id, data.scope, data.state); + + queryString.stringify.callCount.should.equal(1); + queryString.stringify.firstCall.args.should.have.lengthOf(1); + queryString.stringify.firstCall.args[0].should.eql({ + client_id: data.client_id, + response_type: 'code', + scope: data.scope, + state: data.state + }); + + queryString.stringify.restore(); + })); + + it('should call `$http.get` with url containing the stringified `data`', inject(function($httpBackend, OAuth) { + const qs = queryString.stringify(data); + + $httpBackend.expectGET(`${defaults.baseUrl}${defaults.authorizePath}?${qs}`).respond(200, 'foobar'); + + OAuth.authorize(data.client_id, data.scope, data.state); + + $httpBackend.flush(); + + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + })); + }); + describe('isAuthenticated()', function() { it('should be true when there is a stored `token` cookie', inject(function(OAuth, OAuthToken) { OAuthToken.setToken({ token_type: 'bearer', access_token: 'foo', expires_in: 3600, refresh_token: 'bar' });