Skip to content

Commit eaa5f24

Browse files
committed
Add authorize method
1 parent 6519e27 commit eaa5f24

File tree

2 files changed

+156
-2
lines changed

2 files changed

+156
-2
lines changed

src/providers/oauth-provider.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import angular from 'angular';
77
import queryString from 'query-string';
88

99
var defaults = {
10+
authorizePath: '/oauth2/authorize',
1011
baseUrl: null,
1112
clientId: null,
1213
clientSecret: null,
@@ -15,6 +16,7 @@ var defaults = {
1516
};
1617

1718
var requiredKeys = [
19+
'authorizePath',
1820
'baseUrl',
1921
'clientId',
2022
'grantPath',
@@ -60,6 +62,11 @@ function OAuthProvider() {
6062
config.baseUrl = config.baseUrl.slice(0, -1);
6163
}
6264

65+
// Add `authorizePath` facing slash.
66+
if('/' !== config.authorizePath[0]) {
67+
config.authorizePath = `/${config.authorizePath}`;
68+
}
69+
6370
// Add `grantPath` facing slash.
6471
if('/' !== config.grantPath[0]) {
6572
config.grantPath = `/${config.grantPath}`;
@@ -92,6 +99,44 @@ function OAuthProvider() {
9299
}
93100
}
94101

102+
/**
103+
* Requests a authorization for an application based on clientId, scope and state
104+
*
105+
* @param {string} clientId - Application `clientId`
106+
* @param {string} scope - Scope(s) defined for the application
107+
* @param {string} state - Randomly generated `state` string
108+
* @return {promise} A response promise.
109+
*/
110+
111+
authorize(clientId, scope, state) {
112+
// Check if `clientId` is defined.
113+
if (!clientId) {
114+
throw new Error('You must provide an application `clientId`');
115+
}
116+
117+
const data = {
118+
client_id: clientId,
119+
response_type: 'code'
120+
};
121+
122+
if (scope) {
123+
data.scope = scope;
124+
}
125+
126+
if (state) {
127+
data.state = state;
128+
}
129+
130+
const qs = queryString.stringify(data);
131+
const url = `${config.baseUrl}${config.authorizePath}?${qs}`;
132+
133+
const options = {
134+
headers: { 'Content-Type': 'application/json' }
135+
};
136+
137+
return $http.get(url, options);
138+
}
139+
95140
/**
96141
* Verifies if the `user` is authenticated or not based on the `token`
97142
* cookie.

test/unit/providers/oauth-provider.spec.js

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55

66
describe('OAuthProvider', function() {
77
var defaults = {
8+
authorizePath: '/oauth2/authorize',
89
baseUrl: 'https://api.website.com',
910
clientId: 'CLIENT_ID',
11+
clientSecret: 'CLIENT_SECRET',
1012
grantPath: '/oauth2/token',
11-
revokePath: '/oauth2/revoke',
12-
clientSecret: 'CLIENT_SECRET'
13+
redirectUrl: 'https://website.com',
14+
revokePath: '/oauth2/revoke'
1315
};
1416

1517
describe('configure()', function() {
@@ -48,6 +50,25 @@ describe('OAuthProvider', function() {
4850
}
4951
});
5052

53+
it('should throw an error if `authorizePath` param is empty', function() {
54+
try {
55+
provider.configure(_.defaults({ authorizePath: null }, defaults));
56+
57+
should.fail();
58+
} catch(e) {
59+
e.should.be.an.instanceOf(Error);
60+
e.message.should.match(/authorizePath/);
61+
}
62+
});
63+
64+
it('should add facing slash from `authorizePath`', function() {
65+
var config = provider.configure(_.defaults({
66+
authorizePath: 'oauth2/authorize'
67+
}, defaults));
68+
69+
config.authorizePath.should.equal('/oauth2/authorize');
70+
});
71+
5172
it('should throw an error if `baseUrl` param is empty', function() {
5273
try {
5374
provider.configure(_.omit(defaults, 'baseUrl'));
@@ -137,6 +158,94 @@ describe('OAuthProvider', function() {
137158
OAuthToken.removeToken();
138159
}));
139160

161+
describe('authorize()', function() {
162+
var data = {
163+
client_id: defaults.clientId,
164+
response_type: 'code',
165+
scope: 'foo:bar',
166+
state: 'state_hash'
167+
};
168+
169+
it('should throw an error if `clientId` is missing', inject(function(OAuth) {
170+
try {
171+
OAuth.authorize();
172+
173+
should.fail();
174+
} catch(e) {
175+
e.should.be.an.instanceOf(Error);
176+
e.message.should.match(/clientId/);
177+
}
178+
}));
179+
180+
it('should call `queryString.stringify`', inject(function(OAuth) {
181+
sinon.spy(queryString, 'stringify');
182+
183+
OAuth.authorize(data.client_id, data.scope, data.state);
184+
185+
queryString.stringify.callCount.should.equal(1);
186+
queryString.stringify.firstCall.args.should.have.lengthOf(1);
187+
queryString.stringify.firstCall.args[0].should.eql({
188+
client_id: data.client_id,
189+
response_type: 'code',
190+
scope: data.scope,
191+
state: data.state
192+
});
193+
194+
queryString.stringify.restore();
195+
}));
196+
197+
it('should return an error if request response doesn\'t contain a `redirectUri` attribute', inject(function($httpBackend, OAuth) {
198+
$httpBackend.expectGET(`${defaults.baseUrl}${defaults.authorizePath}?${queryString.stringify(data)}`)
199+
.respond(200, { redirectUri: `${defaults.redirectUrl}` });
200+
201+
OAuth.authorize(data.client_id, data.scope, data.state).then(function(response) {
202+
response.data.should.have.property('redirectUri');
203+
});
204+
205+
$httpBackend.flush();
206+
207+
$httpBackend.verifyNoOutstandingExpectation();
208+
$httpBackend.verifyNoOutstandingRequest();
209+
}));
210+
211+
it('should return an `error` and `error_description` parameters if scope is invalid ', inject(function($httpBackend, OAuth) {
212+
$httpBackend.expectGET(`${defaults.baseUrl}${defaults.authorizePath}?${queryString.stringify(data)}`)
213+
.respond(200, { redirectUri: `${defaults.redirectUrl}?error=invalid_scope&error_description=The%20requested%20scope%20is%20invalid` });
214+
215+
OAuth.authorize(data.client_id, data.scope, data.state).then(function(response) {
216+
response.data.should.have.property('redirectUri');
217+
response.data.redirectUri.should.match(/error=/);
218+
response.data.redirectUri.should.match(/error_description=/);
219+
});
220+
221+
$httpBackend.flush();
222+
223+
$httpBackend.verifyNoOutstandingExpectation();
224+
$httpBackend.verifyNoOutstandingRequest();
225+
}));
226+
227+
it('should return an `code` and `state` parameters if scope is valid', inject(function($httpBackend, OAuth) {
228+
var redirectUri = `${defaults.redirectUrl}?code=foo&state=${data.state}`;
229+
_.merge(data, { scope: 'foobar' });
230+
231+
$httpBackend.expectGET(`${defaults.baseUrl}${defaults.authorizePath}?${queryString.stringify(data)}`)
232+
.respond(200, { redirectUri: redirectUri });
233+
234+
OAuth.authorize(data.client_id, data.scope, data.state).then(function(response) {
235+
response.data.should.have.property('redirectUri');
236+
response.data.redirectUri.should.match(/code=/);
237+
response.data.redirectUri.should.match(/state=/);
238+
response.data.redirectUri.should.match(new RegExp(`state=${data.state}`));
239+
});
240+
241+
$httpBackend.flush();
242+
243+
$httpBackend.verifyNoOutstandingExpectation();
244+
$httpBackend.verifyNoOutstandingRequest();
245+
}));
246+
247+
});
248+
140249
describe('isAuthenticated()', function() {
141250
it('should be true when there is a stored `token` cookie', inject(function(OAuth, OAuthToken) {
142251
OAuthToken.setToken({ token_type: 'bearer', access_token: 'foo', expires_in: 3600, refresh_token: 'bar' });

0 commit comments

Comments
 (0)