-
-
Notifications
You must be signed in to change notification settings - Fork 51
Integration test password grant #100
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
Merged
Merged
Changes from 4 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
1a387f4
test example
jorenvandeweyer 4e1d8a8
created db & model factories
jorenvandeweyer f8c2148
added refresh_token grant type test
jorenvandeweyer 4a17212
removed failing test, not implemented feature
jorenvandeweyer 6b38596
add reference to issue
jorenvandeweyer 7380dc7
client authentication test
jorenvandeweyer 380fee9
random client credentials in test
jorenvandeweyer 903f517
replace math.random by crypto.randomBytes
jorenvandeweyer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
/** | ||
* Request | ||
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.2 | ||
* | ||
* grant_type | ||
* REQUIRED. Value MUST be set to "password". | ||
* username | ||
* REQUIRED. The resource owner username. | ||
* password | ||
* REQUIRED. The resource owner password. | ||
* scope | ||
* OPTIONAL. The scope of the access request as described by Section 3.3. | ||
*/ | ||
|
||
/** | ||
* Response | ||
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.1 | ||
* | ||
* access_token | ||
* REQUIRED. The access token issued by the authorization server. | ||
* token_type | ||
* REQUIRED. The type of the token issued as described in | ||
* Section 7.1. Value is case insensitive. | ||
* expires_in | ||
* RECOMMENDED. The lifetime in seconds of the access token. For | ||
* example, the value "3600" denotes that the access token will | ||
* expire in one hour from the time the response was generated. | ||
* If omitted, the authorization server SHOULD provide the | ||
* expiration time via other means or document the default value. | ||
* refresh_token | ||
* OPTIONAL. The refresh token, which can be used to obtain new | ||
* access tokens using the same authorization grant as described | ||
* in Section 6. | ||
* scope | ||
* OPTIONAL, if identical to the scope requested by the client; | ||
* otherwise, REQUIRED. The scope of the access token as | ||
* described by Section 3.3. | ||
*/ | ||
|
||
/** | ||
* Response (error) | ||
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 | ||
* | ||
* error | ||
* REQUIRED. A single ASCII [USASCII] error code from the following: | ||
* invalid_request, invalid_client, invalid_grant | ||
* unauthorized_client, unsupported_grant_type, invalid_scope | ||
* error_description | ||
* OPTIONAL. Human-readable ASCII [USASCII] text providing | ||
* additional information, used to assist the client developer in | ||
* understanding the error that occurred. | ||
* error_uri | ||
* OPTIONAL. A URI identifying a human-readable web page with | ||
* information about the error, used to provide the client | ||
* developer with additional information about the error. | ||
*/ | ||
|
||
const OAuth2Server = require('../..'); | ||
const DB = require('../helpers/db'); | ||
const createModel = require('../helpers/model'); | ||
const createRequest = require('../helpers/request'); | ||
const Response = require('../../lib/response'); | ||
|
||
require('chai').should(); | ||
|
||
const db = new DB(); | ||
|
||
const auth = new OAuth2Server({ | ||
model: createModel(db) | ||
}); | ||
|
||
const user = db.saveUser({ id: 1, username: 'test', password: 'test'}); | ||
const client = db.saveClient({ id: 'a', secret: 'b', grants: ['password'] }); | ||
const scope = 'read write'; | ||
|
||
function createDefaultRequest () { | ||
return createRequest({ | ||
body: { | ||
grant_type: 'password', | ||
client_id: client.id, | ||
client_secret: client.secret, | ||
username: user.username, | ||
password: user.password, | ||
scope | ||
}, | ||
headers: { | ||
'content-type': 'application/x-www-form-urlencoded' | ||
}, | ||
method: 'POST', | ||
}); | ||
} | ||
|
||
describe('PasswordGrantType Compliance', function () { | ||
describe('Authenticate', function () { | ||
it ('Succesfull authorization', async function () { | ||
const request = createDefaultRequest(); | ||
const response = new Response({}); | ||
|
||
const token = await auth.token(request, response, {}); | ||
response.body.token_type.should.equal('Bearer'); | ||
response.body.access_token.should.equal(token.accessToken); | ||
response.body.refresh_token.should.equal(token.refreshToken); | ||
response.body.expires_in.should.be.a('number'); | ||
response.body.scope.should.equal(scope); | ||
|
||
token.accessToken.should.be.a('string'); | ||
token.refreshToken.should.be.a('string'); | ||
token.accessTokenExpiresAt.should.be.a('date'); | ||
token.refreshTokenExpiresAt.should.be.a('date'); | ||
token.scope.should.equal(scope); | ||
|
||
db.accessTokens.has(token.accessToken).should.equal(true); | ||
db.refreshTokens.has(token.refreshToken).should.equal(true); | ||
}); | ||
|
||
it ('Succesfull authorization and authentication', async function () { | ||
const tokenRequest = createDefaultRequest(); | ||
const tokenResponse = new Response({}); | ||
|
||
const token = await auth.token(tokenRequest, tokenResponse, {}); | ||
|
||
const authenticationRequest = createRequest({ | ||
body: {}, | ||
headers: { | ||
'Authorization': `Bearer ${token.accessToken}` | ||
}, | ||
method: 'GET', | ||
query: {} | ||
}); | ||
const authenticationResponse = new Response({}); | ||
|
||
const authenticated = await auth.authenticate( | ||
authenticationRequest, | ||
authenticationResponse, | ||
{}); | ||
|
||
authenticated.scope.should.equal(scope); | ||
authenticated.user.should.be.an('object'); | ||
authenticated.client.should.be.an('object'); | ||
}); | ||
|
||
it ('Username missing', async function () { | ||
const request = createDefaultRequest(); | ||
const response = new Response({}); | ||
|
||
delete request.body.username; | ||
|
||
await auth.token(request, response, {}) | ||
.catch(err => { | ||
err.name.should.equal('invalid_request'); | ||
}); | ||
}); | ||
|
||
it ('Password missing', async function () { | ||
const request = createDefaultRequest(); | ||
const response = new Response({}); | ||
|
||
delete request.body.password; | ||
|
||
await auth.token(request, response, {}) | ||
.catch(err => { | ||
err.name.should.equal('invalid_request'); | ||
}); | ||
}); | ||
|
||
it ('Wrong username', async function () { | ||
const request = createDefaultRequest(); | ||
const response = new Response({}); | ||
|
||
request.body.username = 'wrong'; | ||
|
||
await auth.token(request, response, {}) | ||
.catch(err => { | ||
err.name.should.equal('invalid_grant'); | ||
}); | ||
}); | ||
|
||
it ('Wrong password', async function () { | ||
const request = createDefaultRequest(); | ||
const response = new Response({}); | ||
|
||
request.body.password = 'wrong'; | ||
|
||
await auth.token(request, response, {}) | ||
.catch(err => { | ||
err.name.should.equal('invalid_grant'); | ||
}); | ||
}); | ||
|
||
it ('Client not found', async function () { | ||
const request = createDefaultRequest(); | ||
const response = new Response({}); | ||
|
||
request.body.client_id = 'wrong'; | ||
|
||
await auth.token(request, response, {}) | ||
.catch(err => { | ||
err.name.should.equal('invalid_client'); | ||
}); | ||
}); | ||
|
||
it ('Client secret not required', async function () { | ||
const request = createDefaultRequest(); | ||
const response = new Response({}); | ||
|
||
delete request.body.client_secret; | ||
|
||
const token = await auth.token(request, response, { | ||
requireClientAuthentication: { | ||
password: false | ||
} | ||
}); | ||
|
||
token.accessToken.should.be.a('string'); | ||
}); | ||
|
||
it ('Client secret required', async function () { | ||
const request = createDefaultRequest(); | ||
const response = new Response({}); | ||
|
||
delete request.body.client_secret; | ||
|
||
await auth.token(request, response, { | ||
requireClientAuthentication: { | ||
password: false | ||
} | ||
}) | ||
.catch(err => { | ||
err.name.should.equal('invalid_client'); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.