Skip to content

Commit c1fb9d4

Browse files
authored
tests: add deep integration tests (part I)
Merge pull request #224 from node-oauth/tests-deep-integration-tests
2 parents c6682a6 + c0593ef commit c1fb9d4

19 files changed

+1477
-727
lines changed

lib/grant-types/abstract-grant-type.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ function AbstractGrantType(options) {
3636

3737
AbstractGrantType.prototype.generateAccessToken = async function(client, user, scope) {
3838
if (this.model.generateAccessToken) {
39-
const accessToken = await this.model.generateAccessToken(client, user, scope);
40-
return accessToken || tokenUtil.generateRandomToken();
39+
// We should not fall back to a random accessToken, if the model did not
40+
// return a token, in order to prevent unintended token-issuing.
41+
return this.model.generateAccessToken(client, user, scope);
4142
}
4243

4344
return tokenUtil.generateRandomToken();
@@ -49,8 +50,9 @@ AbstractGrantType.prototype.generateAccessToken = async function(client, user, s
4950

5051
AbstractGrantType.prototype.generateRefreshToken = async function(client, user, scope) {
5152
if (this.model.generateRefreshToken) {
52-
const refreshToken = await this.model.generateRefreshToken(client, user, scope);
53-
return refreshToken || tokenUtil.generateRandomToken();
53+
// We should not fall back to a random refreshToken, if the model did not
54+
// return a token, in order to prevent unintended token-issuing.
55+
return this.model.generateRefreshToken(client, user, scope);
5456
}
5557

5658
return tokenUtil.generateRandomToken();

lib/grant-types/authorization-code-grant-type.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,11 @@ class AuthorizationCodeGrantType extends AbstractGrantType {
195195
const refreshTokenExpiresAt = await this.getRefreshTokenExpiresAt();
196196

197197
const token = {
198-
accessToken: accessToken,
199-
authorizationCode: authorizationCode,
200-
accessTokenExpiresAt: accessTokenExpiresAt,
201-
refreshToken: refreshToken,
202-
refreshTokenExpiresAt: refreshTokenExpiresAt,
198+
accessToken,
199+
authorizationCode,
200+
accessTokenExpiresAt,
201+
refreshToken,
202+
refreshTokenExpiresAt,
203203
scope: validatedScope,
204204
};
205205

lib/grant-types/client-credentials-grant-type.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ class ClientCredentialsGrantType extends AbstractGrantType {
7373
const accessToken = await this.generateAccessToken(client, user, scope);
7474
const accessTokenExpiresAt = await this.getAccessTokenExpiresAt(client, user, scope);
7575
const token = {
76-
accessToken: accessToken,
77-
accessTokenExpiresAt: accessTokenExpiresAt,
76+
accessToken,
77+
accessTokenExpiresAt,
7878
scope: validatedScope,
7979
};
8080

lib/grant-types/password-grant-type.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ class PasswordGrantType extends AbstractGrantType {
9494
const refreshTokenExpiresAt = await this.getRefreshTokenExpiresAt();
9595

9696
const token = {
97-
accessToken: accessToken,
98-
accessTokenExpiresAt: accessTokenExpiresAt,
99-
refreshToken: refreshToken,
100-
refreshTokenExpiresAt: refreshTokenExpiresAt,
97+
accessToken,
98+
accessTokenExpiresAt,
99+
refreshToken,
100+
refreshTokenExpiresAt,
101101
scope: validatedScope,
102102
};
103103

lib/grant-types/refresh-token-grant-type.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ class RefreshTokenGrantType extends AbstractGrantType {
130130
const accessTokenExpiresAt = await this.getAccessTokenExpiresAt();
131131
const refreshTokenExpiresAt = await this.getRefreshTokenExpiresAt();
132132
const token = {
133-
accessToken: accessToken,
134-
accessTokenExpiresAt: accessTokenExpiresAt,
133+
accessToken,
134+
accessTokenExpiresAt,
135135
scope: scope,
136136
};
137137

lib/handlers/authorize-handler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ AuthorizeHandler.prototype.buildErrorRedirectUri = function(redirectUri, error)
347347
error: error.name
348348
};
349349

350+
350351
if (error.message) {
351352
uri.query.error_description = error.message;
352353
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* 4.4. Client Credentials Grant
3+
*
4+
* The client can request an access token using only its client
5+
* credentials (or other supported means of authentication) when the
6+
* client is requesting access to the protected resources under its
7+
* control, or those of another resource owner that have been previously
8+
* arranged with the authorization server (the method of which is beyond
9+
* the scope of this specification).
10+
*
11+
* The client credentials grant type MUST only be used by confidential
12+
* clients.
13+
*
14+
* @see https://www.rfc-editor.org/rfc/rfc6749#section-4.4
15+
*/
16+
17+
const OAuth2Server = require('../..');
18+
const DB = require('../helpers/db');
19+
const createModel = require('../helpers/model');
20+
const createRequest = require('../helpers/request');
21+
const Response = require('../../lib/response');
22+
23+
require('chai').should();
24+
25+
const db = new DB();
26+
// this user represents requests in the name of an external server
27+
// TODO: we should discuss, if we can make user optional for client credential workflows
28+
// as it's not desired to have an extra fake-user representing a server just to pass validation
29+
const userDoc = { id: 'machine2-123456789', name: 'machine2' };
30+
db.saveUser(userDoc);
31+
32+
const oAuth2Server = new OAuth2Server({
33+
model: {
34+
...createModel(db),
35+
getUserFromClient: async function (_client) {
36+
// in a machine2machine setup we might not have a dedicated "user"
37+
// but we need to return a truthy response to
38+
const client = db.findClient(_client.id, _client.secret);
39+
return client && { ...userDoc };
40+
}
41+
}
42+
});
43+
44+
const clientDoc = db.saveClient({
45+
id: 'client-credential-test-client',
46+
secret: 'client-credential-test-secret',
47+
grants: ['client_credentials']
48+
});
49+
50+
const enabledScope = 'read write';
51+
52+
describe('ClientCredentials Workflow Compliance (4.4)', function () {
53+
describe('Access Token Request (4.4.1)', function () {
54+
/**
55+
* 4.4.2. Access Token Request
56+
*
57+
* The client makes a request to the token endpoint by adding the
58+
* following parameters using the "application/x-www-form-urlencoded"
59+
* format per Appendix B with a character encoding of UTF-8 in the HTTP
60+
* request entity-body:
61+
*
62+
* grant_type
63+
* REQUIRED. Value MUST be set to "client_credentials".
64+
*
65+
* scope
66+
* OPTIONAL. The scope of the access request as described by
67+
* Section 3.3.
68+
*
69+
* The client MUST authenticate with the authorization server as
70+
* described in Section 3.2.1.
71+
*/
72+
it('authenticates the client with valid credentials', async function () {
73+
const response = new Response();
74+
const request = createRequest({
75+
body: {
76+
grant_type: 'client_credentials',
77+
scope: enabledScope
78+
},
79+
headers: {
80+
'authorization': 'Basic ' + Buffer.from(clientDoc.id + ':' + clientDoc.secret).toString('base64'),
81+
'content-type': 'application/x-www-form-urlencoded'
82+
},
83+
method: 'POST',
84+
});
85+
86+
const token = await oAuth2Server.token(request, response);
87+
88+
response.status.should.equal(200);
89+
response.headers.should.deep.equal( { 'cache-control': 'no-store', pragma: 'no-cache' });
90+
response.body.token_type.should.equal('Bearer');
91+
response.body.access_token.should.equal(token.accessToken);
92+
response.body.expires_in.should.be.a('number');
93+
response.body.scope.should.equal(enabledScope);
94+
('refresh_token' in response.body).should.equal(false);
95+
96+
token.accessToken.should.be.a('string');
97+
token.accessTokenExpiresAt.should.be.a('date');
98+
('refreshToken' in token).should.equal(false);
99+
('refreshTokenExpiresAt' in token).should.equal(false);
100+
token.scope.should.equal(enabledScope);
101+
102+
db.accessTokens.has(token.accessToken).should.equal(true);
103+
db.refreshTokens.has(token.refreshToken).should.equal(false);
104+
});
105+
106+
/**
107+
* 7. Accessing Protected Resources
108+
*
109+
* The client accesses protected resources by presenting the access
110+
* token to the resource server. The resource server MUST validate the
111+
* access token and ensure that it has not expired and that its scope
112+
* covers the requested resource. The methods used by the resource
113+
* server to validate the access token (as well as any error responses)
114+
* are beyond the scope of this specification but generally involve an
115+
* interaction or coordination between the resource server and the
116+
* authorization server.
117+
*/
118+
it('enables an authenticated request using the access token', async function () {
119+
const [accessToken] = [...db.accessTokens.entries()][0];
120+
const response = new Response();
121+
const request = createRequest({
122+
query: {},
123+
headers: {
124+
'authorization': `Bearer ${accessToken}`
125+
},
126+
method: 'GET',
127+
});
128+
129+
const token = await oAuth2Server.authenticate(request, response);
130+
token.accessToken.should.equal(accessToken);
131+
token.user.should.deep.equal(userDoc);
132+
token.client.should.deep.equal(clientDoc);
133+
token.scope.should.equal(enabledScope);
134+
135+
response.status.should.equal(200);
136+
// there should be no information in the response as it
137+
// should only add information, if permission is denied
138+
response.body.should.deep.equal({});
139+
response.headers.should.deep.equal({});
140+
});
141+
});
142+
});

0 commit comments

Comments
 (0)