@okta/okta-sdk-nodejs
Advanced tools
Comparing version 3.2.0 to 3.3.1
# Okta Node SDK Changelog | ||
## 3.3.1 | ||
- [#138](https://github.com/okta/okta-sdk-nodejs/pull/138) Add strategy to handle access token refresh | ||
## 3.2.0 | ||
@@ -4,0 +8,0 @@ |
{ | ||
"name": "@okta/okta-sdk-nodejs", | ||
"version": "3.2.0", | ||
"version": "3.3.1", | ||
"description": "Okta API wrapper for Node.js", | ||
@@ -62,5 +62,5 @@ "engines": { | ||
"okta": { | ||
"commitSha": "cd47a5dbd75cf52671f9f25c82d7c54a32c48468", | ||
"fullVersion": "3.2.0-20200302224712-cd47a5d" | ||
"commitSha": "161205e72a67d63a2c0ed27f3120cc0107a0b698", | ||
"fullVersion": "3.3.1-g161205e" | ||
} | ||
} |
@@ -39,4 +39,28 @@ # okta-sdk-nodejs | ||
### OAuth 2.0 Authentication | ||
Okta allows you to interact with Okta APIs using scoped OAuth 2.0 access tokens. Each access token enables the bearer to perform specific actions on specific Okta endpoints, with that ability controlled by which scopes the access token contains. | ||
This SDK supports this feature only for service-to-service applications. Please read [this guide](https://developer.okta.com/docs/guides/implement-oauth-for-okta/overview/) to learn more about how to register a new service application using a private and public key pair. | ||
When using this approach you won't need an API Token because the SDK will request an access token for you. In order to use OAuth 2.0, construct a client instance by passing the following parameters: | ||
```js | ||
const client = new okta.Client({ | ||
orgUrl: 'https://{yourOktaDomain}/', | ||
client: { | ||
authorizationMode: 'PrivateKey', | ||
clientId: '{oauth application ID}', | ||
scopes: ['okta.users.manage'], | ||
privateKey: '{JWK}' // <-- see notes below | ||
} | ||
}); | ||
``` | ||
The `privateKey` can be passed in the following ways: | ||
- As a JSON encoded string of a JWK object | ||
- A string in PEM format | ||
- As a JSON object, in JWK format | ||
## Table of Contents | ||
@@ -61,2 +85,3 @@ | ||
* [Get Logs](#get-logs) | ||
* [Call other API Endpoints](#call-other-api-endpoints) | ||
* [Collection](#collection) | ||
@@ -363,2 +388,33 @@ * [each](#each) | ||
### Call other API Endpoints | ||
Not every API endpoint is represented by a method in this library. You can call any Okta management API endpoint using this generic syntax: | ||
```javascript | ||
const okta = require('@okta/okta-sdk-nodejs'); | ||
// Assumes configuration is loaded via yaml or environment variables | ||
const client = new okta.Client(); | ||
// https://developer.okta.com/docs/reference/api/apps/#preview-saml-metadata-for-application | ||
const applicationId = '{your custom SAML app id}'; | ||
const url = `${client.baseUrl}/api/v1/apps/${applicationId}/sso/saml/metadata`; | ||
const request = { | ||
method: 'get', | ||
headers: { | ||
'Accept': 'application/xml', | ||
'Content-Type': 'application/json', | ||
} | ||
}; | ||
client.http.http(url, request) | ||
.then(res => res.text()) | ||
.then(text => { | ||
console.log(text); | ||
}) | ||
.catch(err => { | ||
console.error(err); | ||
}); | ||
``` | ||
## Collection | ||
@@ -365,0 +421,0 @@ |
@@ -47,3 +47,3 @@ /*! | ||
if (!parsedConfig.client.token) { | ||
if (!parsedConfig.client.token && parsedConfig.client.authorizationMode !== 'PrivateKey') { | ||
errors.push('Okta API token not provided'); | ||
@@ -50,0 +50,0 @@ } |
@@ -26,4 +26,5 @@ /*! | ||
* @param {Object} Ctor Class of each item in the collection | ||
* @param {Request} [request] Fetch API request object | ||
*/ | ||
constructor(client, uri, factory) { | ||
constructor(client, uri, factory, request) { | ||
this.nextUri = uri; | ||
@@ -33,2 +34,3 @@ this.client = client; | ||
this.currentItems = []; | ||
this.request = request; | ||
} | ||
@@ -69,3 +71,3 @@ | ||
getNextPage() { | ||
return this.client.http.http(this.nextUri, null, {isCollection: true}) | ||
return this.client.http.http(this.nextUri, this.request, {isCollection: true}) | ||
.then(res => { | ||
@@ -72,0 +74,0 @@ const link = res.headers.get('link'); |
111
src/http.js
@@ -24,2 +24,22 @@ /*! | ||
class Http { | ||
static errorFilter(response) { | ||
if (response.status >= 200 && response.status < 300) { | ||
return Promise.resolve(response); | ||
} else { | ||
return response.text() | ||
.then(body => { | ||
let err; | ||
// If the response is JSON, assume it's an Okta API error. Otherwise, assume it's some other HTTP error | ||
try { | ||
err = new OktaApiError(response.url, response.status, JSON.parse(body), response.headers); | ||
} catch (e) { | ||
err = new HttpError(response.url, response.status, body, response.headers); | ||
} | ||
throw err; | ||
}); | ||
} | ||
} | ||
constructor(httpConfig) { | ||
@@ -40,17 +60,4 @@ this.defaultHeaders = {}; | ||
let getToken; | ||
if (this.accessToken) { | ||
getToken = Promise.resolve(this.accessToken); | ||
} else { | ||
getToken = this.oauth.getAccessToken() | ||
.then(this.errorFilter) | ||
.then(res => res.json()) | ||
return this.oauth.getAccessToken() | ||
.then(accessToken => { | ||
this.accessToken = accessToken; | ||
return accessToken; | ||
}); | ||
} | ||
return getToken | ||
.then(accessToken => { | ||
request.headers.Authorization = `Bearer ${accessToken.access_token}`; | ||
@@ -60,22 +67,2 @@ }); | ||
errorFilter(response) { | ||
if (response.status >= 200 && response.status < 300) { | ||
return response; | ||
} else { | ||
return response.text() | ||
.then(body => { | ||
let err; | ||
// If the response is JSON, assume it's an Okta API error. Otherwise, assume it's some other HTTP error | ||
try { | ||
err = new OktaApiError(response.url, response.status, JSON.parse(body), response.headers); | ||
} catch (e) { | ||
err = new HttpError(response.url, response.status, body, response.headers); | ||
} | ||
throw err; | ||
}); | ||
} | ||
} | ||
http(uri, request, context) { | ||
@@ -87,25 +74,41 @@ request = request || {}; | ||
request.method = request.method || 'get'; | ||
if (!this.cacheMiddleware) { | ||
return this.prepareRequest(request) | ||
let retriedOnAuthError = false; | ||
const execute = () => { | ||
const promise = this.prepareRequest(request) | ||
.then(() => this.requestExecutor.fetch(request)) | ||
.then(this.errorFilter); | ||
} | ||
const ctx = { | ||
uri, // TODO: remove unused property. req.url should be the key. | ||
isCollection: context.isCollection, | ||
resources: context.resources, | ||
req: request, | ||
cacheStore: this.cacheStore | ||
}; | ||
return this.cacheMiddleware(ctx, () => { | ||
if (ctx.res) { | ||
return; | ||
.then(Http.errorFilter) | ||
.catch(error => { | ||
// Clear cached token then retry request one more time | ||
if (this.oauth && error && error.status === 401 && !retriedOnAuthError) { | ||
retriedOnAuthError = true; | ||
this.oauth.clearCachedAccessToken(); | ||
return execute(); | ||
} | ||
throw error; | ||
}); | ||
if (!this.cacheMiddleware) { | ||
return promise; | ||
} | ||
return this.prepareRequest(request) | ||
.then(() => this.requestExecutor.fetch(request)) | ||
.then(this.errorFilter) | ||
.then(res => ctx.res = res); | ||
}) | ||
.then(() => ctx.res); | ||
const ctx = { | ||
uri, // TODO: remove unused property. req.url should be the key. | ||
isCollection: context.isCollection, | ||
resources: context.resources, | ||
req: request, | ||
cacheStore: this.cacheStore | ||
}; | ||
return this.cacheMiddleware(ctx, () => { | ||
if (ctx.res) { | ||
return; | ||
} | ||
return promise.then(res => ctx.res = res); | ||
}) | ||
.then(() => ctx.res); | ||
}; | ||
return execute(); | ||
} | ||
@@ -112,0 +115,0 @@ |
@@ -35,3 +35,3 @@ const nJwt = require('njwt'); | ||
function makeJwt(client) { | ||
function makeJwt(client, endpoint) { | ||
const now = Math.floor(new Date().getTime() / 1000); // seconds since epoch | ||
@@ -41,3 +41,3 @@ const plus5Minutes = new Date((now + (5 * 60)) * 1000); // Date object | ||
const claims = { | ||
aud: `${client.baseUrl}/oauth2/v1/token`, | ||
aud: `${client.baseUrl}${endpoint}`, | ||
}; | ||
@@ -44,0 +44,0 @@ return getPemAndJwk(client.privateKey) |
const { makeJwt } = require('./jwt'); | ||
const Http = require('./http'); | ||
@@ -24,7 +25,12 @@ function formatParams(obj) { | ||
this.client = client; | ||
this.jwt = null; | ||
this.accessToken = null; | ||
} | ||
getAccessToken() { | ||
return this.getJwt() | ||
if (this.accessToken) { | ||
return Promise.resolve(this.accessToken); | ||
} | ||
const endpoint = '/oauth2/v1/token'; | ||
return this.getJwt(endpoint) | ||
.then(jwt => { | ||
@@ -38,3 +44,3 @@ const params = formatParams({ | ||
return this.client.requestExecutor.fetch({ | ||
url: `${this.client.baseUrl}/oauth2/v1/token`, | ||
url: `${this.client.baseUrl}${endpoint}`, | ||
method: 'POST', | ||
@@ -47,17 +53,21 @@ body: params, | ||
}); | ||
}) | ||
.then(Http.errorFilter) | ||
.then(res => res.json()) | ||
.then(accessToken => { | ||
this.accessToken = accessToken; | ||
return this.accessToken; | ||
}); | ||
} | ||
getJwt() { | ||
if (!this.jwt) { | ||
return makeJwt(this.client) | ||
.then(jwt => { | ||
this.jwt = jwt.compact(); | ||
return this.jwt; | ||
}); | ||
} | ||
return Promise.resolve(this.jwt); | ||
clearCachedAccessToken() { | ||
this.accessToken = null; | ||
} | ||
getJwt(endpoint) { | ||
return makeJwt(this.client, endpoint) | ||
.then(jwt => jwt.compact()); | ||
} | ||
} | ||
module.exports = OAuth; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
394773
8574
802