Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add authorize method #73

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/providers/oauth-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import angular from 'angular';
import queryString from 'query-string';

var defaults = {
authorizePath: '/oauth2/authorize',
baseUrl: null,
clientId: null,
clientSecret: null,
Expand All @@ -15,6 +16,7 @@ var defaults = {
};

var requiredKeys = [
'authorizePath',
'baseUrl',
'clientId',
'grantPath',
Expand Down Expand Up @@ -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}`;
Expand Down Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

query-string handles undefined values by not appending it to the stringified component. I would suggest to directly assign it to data such as:

data = angular.extend({
  scope,
  state
}, data);

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.
Expand Down
90 changes: 88 additions & 2 deletions test/unit/providers/oauth-provider.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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'));
Expand Down Expand Up @@ -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' });
Expand Down