Skip to content
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

ecmascript modules rewrite #383

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"extends": "airbnb-base",
"parserOptions": {
"sourceType": "script",
"sourceType": "module",
"ecmaVersion": 2022
},
"rules": {
"max-len": ["error", {
"code": 120,
"ignoreComments": true
}],
"strict": ["error", "safe"],
"max-classes-per-file": ["off"],
"lines-between-class-members": ["off"]
"lines-between-class-members": ["off"],
"import/extensions": "off"
}
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Breaking changes
- Drop node 12 support
- Rewrite library using ecmascript modules

### Maintainance
- Use c8 to collect code coverage
Expand Down
184 changes: 72 additions & 112 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ npm install --save simple-oauth2
With a minimal configuration, create a client instance of any supported [grant type](#supported-grant-types).

```javascript
import { ClientCredentials, ResourceOwnerPassword, AuthorizationCode } from 'simple-oauth2';

const config = {
client: {
id: '<client-id>',
Expand All @@ -63,8 +65,6 @@ const config = {
tokenHost: 'https://api.oauth.com'
}
};

const { ClientCredentials, ResourceOwnerPassword, AuthorizationCode } = require('simple-oauth2');
```

For a complete reference of configuration options, see the [API Options](./API.md#options)
Expand All @@ -78,32 +78,28 @@ Depending on your use-case, any of the following supported grant types may be us
The [Authorization Code](https://oauth.net/2/grant-types/authorization-code/) grant type is used by confidential and public clients to exchange an authorization code for an access token. After the user returns to the client via the redirect URL, the application will get the authorization code from the URL and use it to request an access token.

```javascript
async function run() {
const client = new AuthorizationCode(config);
const client = new AuthorizationCode(config);

const authorizationUri = client.authorizeURL({
redirect_uri: 'http://localhost:3000/callback',
scope: '<scope>',
state: '<state>'
});
const authorizationUri = client.authorizeURL({
redirect_uri: 'http://localhost:3000/callback',
scope: '<scope>',
state: '<state>'
});

// Redirect example using Express (see http://expressjs.com/api.html#res.redirect)
res.redirect(authorizationUri);
// Redirect example using Express (see http://expressjs.com/api.html#res.redirect)
res.redirect(authorizationUri);

const tokenParams = {
code: '<code>',
redirect_uri: 'http://localhost:3000/callback',
scope: '<scope>',
};
const tokenParams = {
code: '<code>',
redirect_uri: 'http://localhost:3000/callback',
scope: '<scope>',
};

try {
const accessToken = await client.getToken(tokenParams);
} catch (error) {
console.log('Access Token Error', error.message);
}
try {
const accessToken = await client.getToken(tokenParams);
} catch (error) {
console.log('Access Token Error', error.message);
}

run();
```

See the [API reference](./API.md#new-authorizationcodeoptions) for a complete reference of available options or any of our available examples at the [example folder](./example).
Expand All @@ -113,23 +109,19 @@ See the [API reference](./API.md#new-authorizationcodeoptions) for a complete re
The [Resource Owner Password Credentials](https://oauth.net/2/grant-types/password/) grant type is a way to exchange a user's credentials for an access token. Because the client application has to collect the user's password and send it to the authorization server, it is not recommended that this grant be used at all anymore.

```javascript
async function run() {
const client = new ResourceOwnerPassword(config);
const client = new ResourceOwnerPassword(config);

const tokenParams = {
username: 'username',
password: 'password',
scope: '<scope>',
};
const tokenParams = {
username: 'username',
password: 'password',
scope: '<scope>',
};

try {
const accessToken = await client.getToken(tokenParams);
} catch (error) {
console.log('Access Token Error', error.message);
}
try {
const accessToken = await client.getToken(tokenParams);
} catch (error) {
console.log('Access Token Error', error.message);
}

run();
```

See the [API reference](./API.md#new-resourceownerpasswordoptions) for a complete reference of available options.
Expand All @@ -139,21 +131,17 @@ See the [API reference](./API.md#new-resourceownerpasswordoptions) for a complet
The [Client Credentials](https://oauth.net/2/grant-types/client-credentials/) grant type is used by clients to obtain an access token outside of the context of a user. This is typically used by clients to access resources about themselves rather than to access a user's resources.

```javascript
async function run() {
const client = new ClientCredentials(config);
const client = new ClientCredentials(config);

const tokenParams = {
scope: '<scope>',
};
const tokenParams = {
scope: '<scope>',
};

try {
const accessToken = await client.getToken(tokenParams);
} catch (error) {
console.log('Access Token error', error.message);
}
try {
const accessToken = await client.getToken(tokenParams);
} catch (error) {
console.log('Access Token error', error.message);
}

run();
```

See the [API reference](./API.md#new-clientcredentialsoptions) for a complete reference of available options.
Expand All @@ -168,65 +156,49 @@ On long lived applications, it is often necessary to refresh access tokens. In s


```javascript
async function run() {
const accessTokenJSONString = JSON.stringify(accessToken);
const accessTokenJSONString = JSON.stringify(accessToken);

await persistAccessTokenJSON(accessTokenJSONString);
}

run();
await persistAccessTokenJSON(accessTokenJSONString);
```

By the time we need to refresh the persistent access token, we can get back an [AccessToken](./API.md#accesstoken) instance by using the client's [.createToken](./API.md#createtokentoken--accesstoken) method.

```javascript
async function run() {
const accessTokenJSONString = await getPersistedAccessTokenJSON();

let accessToken = client.createToken(JSON.parse(accessTokenJSONString));
}
const accessTokenJSONString = await getPersistedAccessTokenJSON();

run();
let accessToken = client.createToken(JSON.parse(accessTokenJSONString));
```

Once we have determined the access token needs refreshing with the [.expired()](./API.md#expiredexpirationwindowseconds--boolean) method, we can finally refresh it with a [.refresh()](./API.md#await-refreshparams--accesstoken) method call.

```javascript
async function run() {
if (accessToken.expired()) {
try {
const refreshParams = {
scope: '<scope>',
};

accessToken = await accessToken.refresh(refreshParams);
} catch (error) {
console.log('Error refreshing access token: ', error.message);
}
if (accessToken.expired()) {
try {
const refreshParams = {
scope: '<scope>',
};

accessToken = await accessToken.refresh(refreshParams);
} catch (error) {
console.log('Error refreshing access token: ', error.message);
}
}

run();
```

The [.expired()](./API.md##expiredexpirationwindowseconds--boolean) helper is useful for knowing when a token has definitively expired. However, there is a common race condition when tokens are near expiring. If an OAuth 2.0 token is issued with a `expires_in` property (as opposed to an `expires_at` property), there can be discrepancies between the time the OAuth 2.0 server issues the access token and when it is received.

These come down to factors such as network and processing latency and can be worked around by preemptively refreshing the access token:

```javascript
async function run() {
const EXPIRATION_WINDOW_IN_SECONDS = 300; // Window of time before the actual expiration to refresh the token

if (accessToken.expired(EXPIRATION_WINDOW_IN_SECONDS)) {
try {
accessToken = await accessToken.refresh();
} catch (error) {
console.log('Error refreshing access token: ', error.message);
}
const EXPIRATION_WINDOW_IN_SECONDS = 300; // Window of time before the actual expiration to refresh the token

if (accessToken.expired(EXPIRATION_WINDOW_IN_SECONDS)) {
try {
accessToken = await accessToken.refresh();
} catch (error) {
console.log('Error refreshing access token: ', error.message);
}
}

run();
```

**Warning:** Tokens obtained with the Client Credentials grant may not be refreshed. Fetch a new token when it's expired.
Expand All @@ -238,31 +210,23 @@ See the [API reference](./API.md#accesstoken) for a complete reference of availa
When you've done with the token or you want to log out, you can revoke both access and refresh tokens.

```javascript
async function run() {
try {
await accessToken.revoke('access_token');
await accessToken.revoke('refresh_token');
} catch (error) {
console.log('Error revoking token: ', error.message);
}
try {
await accessToken.revoke('access_token');
await accessToken.revoke('refresh_token');
} catch (error) {
console.log('Error revoking token: ', error.message);
}

run();
```

As a convenience method, you can also revoke both tokens in a single call:

```javascript
async function run() {
try {
// Revokes both tokens, refresh token is only revoked if the access_token is properly revoked
await accessToken.revokeAll();
} catch (error) {
console.log('Error revoking token: ', error.message);
}
try {
// Revokes both tokens, refresh token is only revoked if the access_token is properly revoked
await accessToken.revokeAll();
} catch (error) {
console.log('Error revoking token: ', error.message);
}

run();
```

See the [API reference](./API.md#accesstoken) for a complete reference of available options.
Expand All @@ -272,18 +236,14 @@ See the [API reference](./API.md#accesstoken) for a complete reference of availa
Whenever a client or server error is produced, a [boom](https://github.com/hapijs/boom) error is thrown by the library. As such any [boom error property](https://hapi.dev/module/boom/api) is available, but the exact information may vary according to the type of error.

```javascript
async function run() {
const client = new ClientCredentials(config);
const client = new ClientCredentials(config);

try {
await client.getToken();
} catch(error) {
console.log(error.output);
}
try {
await client.getToken();
} catch(error) {
console.log(error.output);
}

run();

// { statusCode: 401,
// payload:
// { statusCode: 401,
Expand Down
24 changes: 8 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
'use strict';
import Config from './lib/config.js';
import { Client } from './lib/client/index.js';
import AuthorizationCodeGrantType from './lib/authorization-code-grant-type.js';
import ResourceOwnerPasswordGrantType from './lib/resource-owner-password-grant-type.js';
import ClientCredentialsGrantType from './lib/client-credentials-grant-type.js';

const Config = require('./lib/config');
const { Client } = require('./lib/client');
const AuthorizationCodeGrantType = require('./lib/authorization-code-grant-type');
const ResourceOwnerPasswordGrantType = require('./lib/resource-owner-password-grant-type');
const ClientCredentialsGrantType = require('./lib/client-credentials-grant-type');

class AuthorizationCode extends AuthorizationCodeGrantType {
export class AuthorizationCode extends AuthorizationCodeGrantType {
constructor(options) {
const config = Config.apply(options);
const client = new Client(config);
Expand All @@ -15,7 +13,7 @@ class AuthorizationCode extends AuthorizationCodeGrantType {
}
}

class ClientCredentials extends ClientCredentialsGrantType {
export class ClientCredentials extends ClientCredentialsGrantType {
constructor(options) {
const config = Config.apply(options);
const client = new Client(config);
Expand All @@ -24,17 +22,11 @@ class ClientCredentials extends ClientCredentialsGrantType {
}
}

class ResourceOwnerPassword extends ResourceOwnerPasswordGrantType {
export class ResourceOwnerPassword extends ResourceOwnerPasswordGrantType {
constructor(options) {
const config = Config.apply(options);
const client = new Client(config);

super(config, client);
}
}

module.exports = {
ResourceOwnerPassword,
ClientCredentials,
AuthorizationCode,
};
9 changes: 4 additions & 5 deletions lib/access-token-parser.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';
import debugg from 'debug';

const debug = require('debug')('simple-oauth2:access-token');
const debug = debugg('simple-oauth2:access-token');

const EXPIRES_AT_PROPERTY_NAME = 'expires_at';
const EXPIRES_IN_PROPERTY_NAME = 'expires_in';
Expand All @@ -23,7 +23,8 @@ function parseExpirationDate(expirationDate) {
return new Date(expirationDate);
}

function parseToken(token) {
// eslint-disable-next-line import/prefer-default-export
export function parseToken(token) {
const tokenProperties = {};

if (EXPIRES_AT_PROPERTY_NAME in token) {
Expand All @@ -39,5 +40,3 @@ function parseToken(token) {
...tokenProperties,
};
}

module.exports = { parseToken };
Loading