Skip to content

Commit 36bab0b

Browse files
committed
Add authorize method
1 parent 6519e27 commit 36bab0b

File tree

2 files changed

+157
-2
lines changed

2 files changed

+157
-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: '/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: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55

66
describe('OAuthProvider', function() {
77
var defaults = {
8+
authorizePath: '/oauth2/authorize',
9+
authorizedCode: 'authorized_code',
810
baseUrl: 'https://api.website.com',
911
clientId: 'CLIENT_ID',
12+
clientSecret: 'CLIENT_SECRET',
1013
grantPath: '/oauth2/token',
11-
revokePath: '/oauth2/revoke',
12-
clientSecret: 'CLIENT_SECRET'
14+
redirectUrl: 'https://website.com',
15+
revokePath: '/oauth2/revoke'
1316
};
1417

1518
describe('configure()', function() {
@@ -48,6 +51,25 @@ describe('OAuthProvider', function() {
4851
}
4952
});
5053

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

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

0 commit comments

Comments
 (0)