Skip to content
This repository has been archived by the owner on Jan 20, 2024. It is now read-only.

Feature client data in body #174

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2752e8b
Bump version. Configure Travis dpl v2 (#143)
postatum May 8, 2020
e430acb
Update deps
postatum Jul 10, 2020
7596124
Bump version;
postatum Jul 10, 2020
767bad0
Merge fresh master
postatum Jul 10, 2020
958f0d9
Merge pull request #147 from mulesoft/update_dependencies
jstoiko Jul 13, 2020
3f1e63e
fix invalid calling object 'btoa' in Edge (Chromium) #148
agentschmitt Jul 16, 2020
159c75a
Merge pull request #150 from agentschmitt/master
postatum Jul 17, 2020
69b10bd
Bump version
postatum Jul 17, 2020
b2f7174
Merge pull request #151 from mulesoft/bump_version
jstoiko Jul 21, 2020
b9567df
Preserve query string params in authorizationUri
postatum Jul 23, 2020
75af020
Merge pull request #153 from mulesoft/i144_auth_url_with_parameters
postatum Jul 27, 2020
a237644
Fixes #98: Do not send empty scopes to an auth server (#154)
postatum Aug 3, 2020
e539384
Update deps. Bump version
postatum Aug 3, 2020
c243223
Change patch version instead of minor
postatum Aug 11, 2020
68d2a71
Merge pull request #156 from mulesoft/release_4.4.0
jstoiko Aug 12, 2020
ed0ce69
Bump version. Configure Travis dpl v2 (#143)
postatum May 8, 2020
8295894
fix invalid calling object 'btoa' in Edge (Chromium) #148
agentschmitt Jul 16, 2020
f5dd947
Bump version
postatum Jul 17, 2020
e695e3f
Preserve query string params in authorizationUri
postatum Jul 23, 2020
d87114e
Fixes #98: Do not send empty scopes to an auth server (#154)
postatum Aug 3, 2020
4a284a3
Update deps. Bump version
postatum Aug 3, 2020
01775ff
Change patch version instead of minor
postatum Aug 11, 2020
55109e8
Add support for providing client_id and client_secret in body instead…
clancy-au Apr 21, 2021
aaf20b2
Merge branch 'feature-client-data-in-body' of https://github.com/digi…
clancy-au Apr 21, 2021
3670d0e
Refresh honours clientCredentialsInBody option
clancy-au Jan 19, 2022
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
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ after_script: npm run test-server-close
node_js:
- "10"
- "12"
- "13"
- "14"
- "node"

cache:
Expand Down
2,732 changes: 1,280 additions & 1,452 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "client-oauth2",
"version": "4.3.0",
"version": "4.3.3",
"description": "Straight-forward execution of OAuth 2.0 flows and authenticated API requests",
"main": "src/client-oauth2.js",
"typings": "index.d.ts",
Expand Down Expand Up @@ -49,15 +49,15 @@
"es6-promise": "^4.2.8",
"express": "^4.17.1",
"is-travis": "^2.0.0",
"karma": "^5.0.3",
"karma": "^5.1.1",
"karma-browserify": "^7.0.0",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^3.1.0",
"karma-cli": "^2.0.0",
"karma-coverage": "^2.0.1",
"karma-coverage": "^2.0.3",
"karma-firefox-launcher": "^1.3.0",
"karma-mocha": "^2.0.1",
"mocha": "^7.1.0",
"mocha": "^8.1.0",
"object-assign": "^4.1.1",
"standard": "^14.3.3",
"watchify": "^3.11.1"
Expand Down
83 changes: 57 additions & 26 deletions src/client-oauth2.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ var btoa
if (typeof Buffer === 'function') {
btoa = btoaBuffer
} else {
btoa = window.btoa
btoa = window.btoa.bind(window)
}

/**
Expand Down Expand Up @@ -161,13 +161,19 @@ function createUri (options, tokenType) {
// Check the required parameters are set.
expects(options, 'clientId', 'authorizationUri')

return options.authorizationUri + '?' + Querystring.stringify(Object.assign({
const qs = {
client_id: options.clientId,
redirect_uri: options.redirectUri,
scope: sanitizeScope(options.scopes),
response_type: tokenType,
state: options.state
}, options.query))
}
if (options.scopes !== undefined) {
qs.scope = sanitizeScope(options.scopes)
}

const sep = options.authorizationUri.includes('?') ? '&' : '?'
return options.authorizationUri + sep + Querystring.stringify(
Object.assign(qs, options.query))
}

/**
Expand Down Expand Up @@ -367,16 +373,22 @@ ClientOAuth2Token.prototype.refresh = function (opts) {
return Promise.reject(new Error('No refresh token'))
}

var body = {
refresh_token: this.refreshToken,
grant_type: 'refresh_token'
}
if (options.clientCredentialsInBody) {
body.client_id = options.clientId
body.client_secret = options.clientSecret
}

return this.client._request(requestOptions({
url: options.accessTokenUri,
method: 'POST',
headers: Object.assign({}, DEFAULT_HEADERS, {
Authorization: auth(options.clientId, options.clientSecret)
}),
body: {
refresh_token: this.refreshToken,
grant_type: 'refresh_token'
}
body: body
}, options))
.then(function (data) {
return self.client.createToken(Object.assign({}, self.data, data))
Expand Down Expand Up @@ -415,18 +427,22 @@ OwnerFlow.prototype.getToken = function (username, password, opts) {
var self = this
var options = Object.assign({}, this.client.options, opts)

const body = {
username: username,
password: password,
grant_type: 'password'
}
if (options.scopes !== undefined) {
body.scope = sanitizeScope(options.scopes)
}

return this.client._request(requestOptions({
url: options.accessTokenUri,
method: 'POST',
headers: Object.assign({}, DEFAULT_HEADERS, {
Authorization: auth(options.clientId, options.clientSecret)
}),
body: {
scope: sanitizeScope(options.scopes),
username: username,
password: password,
grant_type: 'password'
}
body: body
}, options))
.then(function (data) {
return self.client.createToken(data)
Expand Down Expand Up @@ -528,16 +544,21 @@ CredentialsFlow.prototype.getToken = function (opts) {

expects(options, 'clientId', 'clientSecret', 'accessTokenUri')

const body = {
grant_type: 'client_credentials'
}

if (options.scopes !== undefined) {
body.scope = sanitizeScope(options.scopes)
}

return this.client._request(requestOptions({
url: options.accessTokenUri,
method: 'POST',
headers: Object.assign({}, DEFAULT_HEADERS, {
Authorization: auth(options.clientId, options.clientSecret)
}),
body: {
scope: sanitizeScope(options.scopes),
grant_type: 'client_credentials'
}
body: body
}, options))
.then(function (data) {
return self.client.createToken(data)
Expand Down Expand Up @@ -621,10 +642,15 @@ CodeFlow.prototype.getToken = function (uri, opts) {
// `client_id`: REQUIRED, if the client is not authenticating with the
// authorization server as described in Section 3.2.1.
// Reference: https://tools.ietf.org/html/rfc6749#section-3.2.1
if (options.clientSecret) {
headers.Authorization = auth(options.clientId, options.clientSecret)
} else {
if (options.clientCredentialsInBody) {
body.client_id = options.clientId
body.client_secret = options.clientSecret
} else {
if (options.clientSecret) {
headers.Authorization = auth(options.clientId, options.clientSecret)
} else {
body.client_id = options.clientId
}
}

return this.client._request(requestOptions({
Expand Down Expand Up @@ -669,15 +695,20 @@ JwtBearerFlow.prototype.getToken = function (token, opts) {
headers.Authorization = auth(options.clientId, options.clientSecret)
}

const body = {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: token
}

if (options.scopes !== undefined) {
body.scope = sanitizeScope(options.scopes)
}

return this.client._request(requestOptions({
url: options.accessTokenUri,
method: 'POST',
headers: headers,
body: {
scope: sanitizeScope(options.scopes),
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: token
}
body: body
}, options))
.then(function (data) {
return self.client.createToken(data)
Expand Down
81 changes: 79 additions & 2 deletions test/code.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global describe, it */
/* global describe, it, context */
var expect = require('chai').expect
var config = require('./support/config')
var ClientOAuth2 = require('../')
Expand All @@ -21,9 +21,76 @@ describe('code', function () {
expect(githubAuth.code.getUri()).to.equal(
config.authorizationUri + '?client_id=abc&' +
'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' +
'scope=notifications&response_type=code&state='
'response_type=code&state=&scope=notifications'
)
})
context('when scopes are undefined', function () {
it('should not include scope in the uri', function () {
var authWithoutScopes = new ClientOAuth2({
clientId: config.clientId,
clientSecret: config.clientSecret,
accessTokenUri: config.accessTokenUri,
authorizationUri: config.authorizationUri,
authorizationGrants: ['code'],
redirectUri: config.redirectUri
})
expect(authWithoutScopes.code.getUri()).to.equal(
config.authorizationUri + '?client_id=abc&' +
'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' +
'response_type=code&state='
)
})
})
it('should include empty scopes array as an empty string', function () {
var authWithEmptyScopes = new ClientOAuth2({
clientId: config.clientId,
clientSecret: config.clientSecret,
accessTokenUri: config.accessTokenUri,
authorizationUri: config.authorizationUri,
authorizationGrants: ['code'],
redirectUri: config.redirectUri,
scopes: []
})
expect(authWithEmptyScopes.code.getUri()).to.equal(
config.authorizationUri + '?client_id=abc&' +
'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' +
'response_type=code&state=&scope='
)
})
it('should include empty scopes string as an empty string', function () {
var authWithEmptyScopes = new ClientOAuth2({
clientId: config.clientId,
clientSecret: config.clientSecret,
accessTokenUri: config.accessTokenUri,
authorizationUri: config.authorizationUri,
authorizationGrants: ['code'],
redirectUri: config.redirectUri,
scopes: ''
})
expect(authWithEmptyScopes.code.getUri()).to.equal(
config.authorizationUri + '?client_id=abc&' +
'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' +
'response_type=code&state=&scope='
)
})
context('when authorizationUri contains query parameters', function () {
it('should preserve query string parameters', function () {
const authWithParams = new ClientOAuth2({
clientId: config.clientId,
clientSecret: config.clientSecret,
accessTokenUri: config.accessTokenUri,
authorizationUri: config.authorizationUri + '?bar=qux',
authorizationGrants: ['code'],
redirectUri: config.redirectUri,
scopes: 'notifications'
})
expect(authWithParams.code.getUri()).to.equal(
config.authorizationUri + '?bar=qux&client_id=abc&' +
'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' +
'response_type=code&state=&scope=notifications'
)
})
})
})

describe('#getToken', function () {
Expand All @@ -36,6 +103,16 @@ describe('code', function () {
})
})

it('should request the token with body credentials', function () {
const opts = { clientCredentialsInBody: true }
return githubAuth.code.getToken(uri, opts)
.then(function (user) {
expect(user).to.an.instanceOf(ClientOAuth2.Token)
expect(user.accessToken).to.equal(config.accessToken)
expect(user.tokenType).to.equal('bearer')
})
})

it('should reject with auth errors', function () {
var errored = false

Expand Down
56 changes: 55 additions & 1 deletion test/credentials.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global describe, it */
/* global describe, it, context */
var expect = require('chai').expect
var config = require('./support/config')
var ClientOAuth2 = require('../')
Expand All @@ -19,8 +19,62 @@ describe('credentials', function () {
expect(user).to.an.instanceOf(ClientOAuth2.Token)
expect(user.accessToken).to.equal(config.accessToken)
expect(user.tokenType).to.equal('bearer')
expect(user.data.scope).to.equal('notifications')
})
})
context('when scopes are undefined', function () {
it('should not send scopes to an auth server', function () {
var authWithoutScopes = new ClientOAuth2({
clientId: config.clientId,
clientSecret: config.clientSecret,
accessTokenUri: config.accessTokenUri,
authorizationGrants: ['credentials']
})
return authWithoutScopes.credentials.getToken()
.then(function (user) {
expect(user).to.an.instanceOf(ClientOAuth2.Token)
expect(user.accessToken).to.equal(config.accessToken)
expect(user.tokenType).to.equal('bearer')
expect(user.data.scope).to.equal(undefined)
})
})
})
context('when scopes is an empty array', function () {
it('should send empty scope string to an auth server', function () {
var authWithoutScopes = new ClientOAuth2({
clientId: config.clientId,
clientSecret: config.clientSecret,
accessTokenUri: config.accessTokenUri,
authorizationGrants: ['credentials'],
scopes: []
})
return authWithoutScopes.credentials.getToken()
.then(function (user) {
expect(user).to.an.instanceOf(ClientOAuth2.Token)
expect(user.accessToken).to.equal(config.accessToken)
expect(user.tokenType).to.equal('bearer')
expect(user.data.scope).to.equal('')
})
})
})
context('when scopes is an empty string', function () {
it('should send empty scope string to an auth server', function () {
var authWithoutScopes = new ClientOAuth2({
clientId: config.clientId,
clientSecret: config.clientSecret,
accessTokenUri: config.accessTokenUri,
authorizationGrants: ['credentials'],
scopes: ''
})
return authWithoutScopes.credentials.getToken()
.then(function (user) {
expect(user).to.an.instanceOf(ClientOAuth2.Token)
expect(user.accessToken).to.equal(config.accessToken)
expect(user.tokenType).to.equal('bearer')
expect(user.data.scope).to.equal('')
})
})
})

describe('#sign', function () {
it('should be able to sign a standard request object', function () {
Expand Down
Loading