New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@advanced-rest-client/oauth-authorization

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@advanced-rest-client/oauth-authorization - npm Package Compare versions

Comparing version 3.0.2 to 4.0.0

index.d.ts

41

CHANGELOG.md

@@ -433,1 +433,42 @@ <a name="2.0.2"></a>

<a name="4.0.0"></a>
# [4.0.0](https://github.com/advanced-rest-client/oauth-authorization/compare/3.0.1...4.0.0) (2020-03-04)
## Build
* bumping version [839f35f](https://github.com/advanced-rest-client/oauth-authorization/commit/839f35f1ec3a7632a9f268f8a30f25ff600839cf) by Pawel Psztyc
## Continuous integration
* updating SL configuration [f4ebfee](https://github.com/advanced-rest-client/oauth-authorization/commit/f4ebfee0050e5bb861b3b36cc88744fbba93ad0e) by Pawel Psztyc
* updating Travis configuration [d3a48f4](https://github.com/advanced-rest-client/oauth-authorization/commit/d3a48f4495427c12fd5a040939ed3ee2a805b7be) by Pawel Psztyc
## Update
* adding types [48c7017](https://github.com/advanced-rest-client/oauth-authorization/commit/48c70179c3f5b093869ec752212e31b2c276f08f) by Pawel Psztyc
* adding exception handling for OAuth 1 [4f3be8d](https://github.com/advanced-rest-client/oauth-authorization/commit/4f3be8d9ca27e48120f78346e07bb5c6b1de98c6) by Pawel Psztyc
* bumping major version [42a2238](https://github.com/advanced-rest-client/oauth-authorization/commit/42a2238b307e9c0c7e228ce13696a43f253189f4) by Pawel Psztyc
* removing unused commands from `lint-starged` cnf [b1c8e47](https://github.com/advanced-rest-client/oauth-authorization/commit/b1c8e47bf562498d2e329e055494994903cc44c9) by Pawel Psztyc
* [ci skip] automated merge master->stage. syncing main branches [32b86fd](https://github.com/advanced-rest-client/oauth-authorization/commit/32b86fde6967e653563a60bde92e5128bc77301c) by Ci agent
* upgrding dependencies [838bc1e](https://github.com/advanced-rest-client/oauth-authorization/commit/838bc1e268f211aa0b91ff1c5e74f0de80f55b91) by Pawel Psztyc
* upgrading dependencies [4429e94](https://github.com/advanced-rest-client/oauth-authorization/commit/4429e94c3ca1731b3c3c1c1fb47bcb34852c8f29) by Pawel
## Features
* adding `signRequest()` method [a682a65](https://github.com/advanced-rest-client/oauth-authorization/commit/a682a657cc4c7ae6e9c93424e4eb5bf255ad1c50) by Pawel Psztyc
## Refactor
* move sources to src and add events-target-mixin support [d37b847](https://github.com/advanced-rest-client/oauth-authorization/commit/d37b8478b703297584913eef89c9202fe052925a) by Pawel Psztyc
## Testing
* temporaily removing Safari-1 [36e502d](https://github.com/advanced-rest-client/oauth-authorization/commit/36e502df5b6dda8eeaba4f75e7f497eb382db5a9) by Pawel Psztyc
* updating test commands [e0800ff](https://github.com/advanced-rest-client/oauth-authorization/commit/e0800ffab5988e67e38f3351ab0997f413d489fb) by Pawel Psztyc

446

oauth1-authorization.d.ts

@@ -13,449 +13,5 @@ /**

// tslint:disable:variable-name Describing an API that's defined elsewhere.
// tslint:disable:no-any describes the API as best we are able today
import {LitElement} from 'lit-element';
import {OAuth1Authorization} from './src/OAuth1Authorization.js';
import {HeadersParserMixin} from '@advanced-rest-client/headers-parser-mixin/headers-parser-mixin.js';
export {OAuth1Authorization};
declare class OAuth1Authorization {
/**
* Latest valid token exchanged with the authorization endpoint.
*/
lastIssuedToken: object|null|undefined;
/**
* Returns a list of characters that can be used to buid nonce.
*/
readonly nonceChars: Array<String|null>|null;
/**
* If set, requests made by this element to authorization endpoint will be
* prefixed with the proxy value.
*/
proxy: string|null|undefined;
/**
* OAuth 1 token authorization endpoint.
*/
requestTokenUri: string|null|undefined;
/**
* Oauth 1 token exchange endpoint
*/
accessTokenUri: string|null|undefined;
/**
* Oauth 1 consumer key to use with auth request
*/
consumerKey: string|null|undefined;
/**
* Oauth 1 consumer secret to be used to generate the signature.
*/
consumerSecret: string|null|undefined;
/**
* A signature generation method.
* Once of: `PLAINTEXT`, `HMAC-SHA1` or `RSA-SHA1`
*/
signatureMethod: string|null|undefined;
/**
* Location of the OAuth authorization parameters.
* It can be either `authorization` meaning as a header and
* `querystring` to put OAuth parameters to the URL.
*/
authParamsLocation: string|null|undefined;
_caseMap: object|null|undefined;
_camelRegex: object|null|undefined;
/**
* Returns `application/x-www-form-urlencoded` content type value.
*/
urlEncodedType: string|null|undefined;
connectedCallback(): void;
disconnectedCallback(): void;
/**
* The `before-request` handler. Creates an authorization header if needed.
* Normally `before-request` expects to set a promise on the `detail.promises`
* object. But because this taks is sync it skips the promise and manipulate
* request object directly.
*/
_handleRequest(e: CustomEvent|null): void;
/**
* Applies OAuth1 authorization header with generated signature for this
* request.
*
* This method expects the `auth` object to be set on the request. The object
* is full configuration for the OAuth1 authorization as described in
* `auth-methods/oauth1.html` element.
*
* @param request ARC request object
* @param auth Token request auth object
*/
_applyBeforeRequestSignature(request: object|null, auth: String|null): void;
/**
* A handler for the `oauth1-token-requested` event.
* Performs OAuth1 authorization for given settings.
*
* The detail object of the event contains OAuth1 configuration as described
* in `auth-methods/oauth1.html`element.
*/
_tokenRequestedHandler(e: CustomEvent|null): void;
/**
* Performs a request to authorization server.
*
* @param settings Oauth1 configuration. See description for more
* details or `auth-methods/oauth1.html` element that collectes configuration
* from the user.
*/
authorize(settings: object|null): void;
/**
* Sets a configuration properties on this element from passed settings.
*
* @param params See description for more
* details or `auth-methods/oauth1.html` element that collectes configuration
* from the user.
*/
_prepareOauth(params: object|null): void;
/**
* List of default headers to send with auth request.
*
* @returns Map of default headers.
*/
_defaultHeaders(): object|null;
/**
* Returns current timestamp.
*
* @returns Current timestamp
*/
getTimestamp(): Number|null;
/**
* URL encodes the string.
*
* @param toEncode A string to encode.
* @returns Encoded string
*/
encodeData(toEncode: String|null): String|null;
/**
* Normalizes url encoded values as defined in the OAuth 1 spec.
*
* @param url URI encoded params.
* @returns Normalized params.
*/
_finishEncodeParams(url: String|null): String|null;
/**
* URL decodes data.
* Also replaces `+` with ` ` (space).
*
* @param toDecode String to decode.
* @returns Decoded string
*/
decodeData(toDecode: String|null): String|null;
/**
* Computes signature for the request.
*
* @param signatureMethod Method to use to generate the signature.
* Supported are: `PLAINTEXT`, `HMAC-SHA1`, `RSA-SHA1`. It throws an error if
* value of this property is other than listed here.
* @param requestMethod Request HTTP method.
* @param url Request full URL.
* @param oauthParameters Map of oauth parameters.
* @param tokenSecret Optional, token secret.
* @param body Body used with the request. Note: this parameter
* can only be set if the request's content-type header equals
* `application/x-www-form-urlencoded`.
* @returns Generated OAuth1 signature for given `signatureMethod`
*/
getSignature(signatureMethod: String|null, requestMethod: String|null, url: String|null, oauthParameters: object|null, tokenSecret: String|null, body: String|null): String|null;
/**
* Normalizes URL to base string URI as described in
* https://tools.ietf.org/html/rfc5849#section-3.4.1.2
*
* @param url Request full URL.
* @returns Base String URI
*/
_normalizeUrl(url: String|null): String|null;
/**
* @param parameter Parameter name (key).
* @returns True if the `parameter` is an OAuth 1 parameter.
*/
_isParameterNameAnOAuthParameter(parameter: String|null): Boolean|null;
/**
* Creates an Authorization header value to trasmit OAuth params in headers
* as described in https://tools.ietf.org/html/rfc5849#section-3.5.1
*
* @param orderedParameters Oauth parameters that are already
* ordered.
* @returns The Authorization header value
*/
_buildAuthorizationHeaders(orderedParameters: any[]|null): String|null;
/**
* Creates a body for www-urlencoded content type to transmit OAuth params
* in request body as described in
* https://tools.ietf.org/html/rfc5849#section-3.5.2
*
* @param orderedParameters Oauth parameters that are already
* ordered.
* @returns The body to send
*/
_buildFormDataParameters(orderedParameters: any[]|null): String|null;
/**
* Adds query paramteres with OAuth 1 parameters to the URL
* as described in https://tools.ietf.org/html/rfc5849#section-3.5.3
*
* @param orderedParameters Oauth parameters that are already
* ordered.
* @returns URL to use with the request
*/
_buildAuthorizationQueryStirng(url: String|null, orderedParameters: any[]|null): String|null;
/**
* of argument/value pairs.
*/
_makeArrayOfArgumentsHash(argumentsHash: any): any;
/**
* Sorts the encoded key value pairs by encoded name, then encoded value
*/
_sortRequestParams(argumentPairs: any): any;
/**
* Sort function to sort parameters as described in
* https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
*/
_sortParamsFunction(a: String|null, b: String|null): Number|null;
/**
* Normalizes request parameters as described in
* https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
*
* @param args List of parameters to normalize. It must contain
* a list of array items where first element of the array is parameter name
* and second is parameter value.
* @returns Normalized parameters to string.
*/
_normaliseRequestParams(args: any[]|null): String|null;
/**
* Computes array of parameters from the request URL.
*
* @param url Full request URL
* @returns Array of parameters where each item is an array with
* first element as a name of the parameter and second element as a value.
*/
_listQueryParameters(url: String|null): any[]|null;
/**
* Computes array of parameters from the entity body.
* The body must be `application/x-www-form-urlencoded`.
*
* @param body Entity body of `application/x-www-form-urlencoded`
* request
* @returns Array of parameters where each item is an array with
* first element as a name of the parameter and second element as a value.
* Keys and values are percent decoded. Additionally each `+` is replaced
* with space character.
*/
_formUrlEncodedToParams(body: String|null): any[]|null;
/**
* Creates a signature base as defined in
* https://tools.ietf.org/html/rfc5849#section-3.4.1
*
* @param method HTTP method used with the request
* @param url Full URL of the request
* @param oauthParams Key - value pairs of OAuth parameters
* @param body Body used with the request. Note: this parameter
* can only be set if the request's content-type header equals
* `application/x-www-form-urlencoded`.
* @returns A base string to be used to generate signature.
*/
createSignatureBase(method: String|null, url: String|null, oauthParams: object|null, body: String|null): String|null;
/**
* Creates a signature key to compute the signature as described in
* https://tools.ietf.org/html/rfc5849#section-3.4.2
*
* @param clientSecret Client secret (consumer secret).
* @param tokenSecret Optional, token secret
* @returns A key to be used to generate the signature.
*/
createSignatureKey(clientSecret: String|null, tokenSecret: String|null): String|null;
/**
* Found at http://jsfiddle.net/ARTsinn/6XaUL/
*
* @param h Hexadecimal input
* @returns Result of transforming value to string.
*/
hex2b64(h: String|null): String|null;
/**
* Creates a signature for the PLAINTEXT method.
*
* In this case the signature is the key.
*
* @param key Computed signature key.
* @returns Computed OAuth1 signature.
*/
_createSignaturePlainText(key: String|null): String|null;
/**
* Creates a signature for the RSA-SHA1 method.
*
* @param baseText Computed signature base text.
* @param privateKey Client private key.
* @returns Computed OAuth1 signature.
*/
_createSignatureRsaSha1(baseText: String|null, privateKey: String|null): String|null;
/**
* Creates a signature for the HMAC-SHA1 method.
*
* @param baseText Computed signature base text.
* @param key Computed signature key.
* @returns Computed OAuth1 signature.
*/
_createSignatureHamacSha1(baseText: String|null, key: String|null): String|null;
_getNonce(nonceSize: any): any;
_prepareParameters(token: any, tokenSecret: any, method: any, url: any, extraParams: any, body: any): any;
/**
* Encodes parameters in the map.
*/
encodeUriParams(params: any): any;
/**
* Creates OAuth1 signature for a `request` object.
* The request object must contain:
* - `url` - String
* - `method` - String
* - `headers` - String
* It also may contain the `body` property.
*
* It alters the request object by applying OAuth1 parameters to a set
* location (qurey parameters, authorization header, body). This is
* controlled by `this.authParamsLocation` property. By default the
* parameters are applied to authorization header.
*
* @param request ARC request object.
* @param token OAuth token to use to generate the signature.
* If not set, then it will use a value from `this.lastIssuedToken`.
* @param tokenSecret OAuth token secret to use to generate the
* signature. If not set, then it will use a value from
* `this.lastIssuedToken`.
* @returns The same object with applied OAuth 1 parameters.
*/
signRequestObject(request: object|null, token: String|null, tokenSecret: String|null): object|null;
_performRequest(token: any, tokenSecret: any, method: any, url: any, extraParams: any, body: any, contentType: any): any;
/**
* Exchanges temporary authorization token for authorized token.
* When ready this function fires `oauth1-token-response`
*/
getOAuthAccessToken(token: String|null, secret: String|null, verifier: String|null): Promise<any>|null;
/**
* Clears variables set for current request after signature has been
* generated and token obtained.
*/
clearRequestVariables(): void;
/**
* Requests the authorization server for temporarty authorization token.
* This token should be passed to `authorizationUri` as a `oauth_token`
* parameter.
*
* @param extraParams List of extra parameters to include in the
* request.
* @returns A promise resolved to a map of OAuth 1 parameters:
* `oauth_token`, `oauth_token_secret`, `oauth_verifier` and
* `oauth_callback_confirmed` (for 1.0a version).
*/
getOAuthRequestToken(extraParams: object|null): Promise<any>|null;
/**
* Makes a HTTP request.
* Before making the request it sends `auth-request-proxy` custom event
* with the URL and init object in event's detail object.
* If the event is cancelled then it will use detail's `result` value to
* return from this function. The `result` must be a Promise that will
* resolve to a `Response` object.
* Otherwise it will use internall `fetch` implementation.
*
* @param url An URL to call
* @param init Init object that will be passed to a `Request`
* object.
* @returns A promise that resolves to a `Response` object.
*/
request(url: String|null, init: object|null): Promise<any>|null;
/**
* Performs a HTTP request.
* If `proxy` is set or `iron-meta` with a key `auth-proxy` is set then
* it will prefix the URL with the value of proxy.
*
* @param url An URL to call
* @param init Init object that will be passed to a `Request`
* object.
* @returns A promise that resolves to a `Response` object.
*/
_fetch(url: String|null, init: object|null): Promise<any>|null;
_listenPopup(e: any): void;
/**
* Observer if the popup has been closed befor the data has been received.
*/
_observePopupState(): void;
_beforePopupUnloadHandler(): void;
/**
* Dispatches an error event that propagates through the DOM.
*/
_dispatchError(message: String|null, code: String|null): void;
/**
* Adds camel case keys to a map of parameters.
* It adds new keys to the object tranformed from `oauth_token`
* to `oauthToken`
*/
parseMapKeys(obj: object|null): object|null;
/**
* Parses a query parameter object to produce camel case map of parameters.
* This sets values to the `settings` object which is passed by reference.
* No need to return value.
*
* @param param Key in the `settings` object.
* @param settings Parameters.
*/
_parseParameter(param: String|null, settings: object|null): object|null;
_getCaseParam(param: any): any;
}
declare global {
interface HTMLElementTagNameMap {
"oauth1-authorization": OAuth1Authorization;
}
}

@@ -14,1290 +14,4 @@ /**

*/
import { LitElement } from 'lit-element';
import '@polymer/iron-meta/iron-meta.js';
import { HeadersParserMixin } from '@advanced-rest-client/headers-parser-mixin/headers-parser-mixin.js';
function noop() {}
/**
* @typedef AuthSettings
* @property {Boolean} valid
* @property {String} type
* @property {Object} settings
*/
/**
An element to perform OAuth1 authorization and to sign auth requests.
Note that the OAuth1 authorization wasn't designed for browser. Most existing
OAuth1 implementation deisallow browsers to perform the authorization by
not allowing POST requests to authorization server. Therefore receiving token
may not be possible without using browser extensions to alter HTTP request to
enable CORS.
If the server disallow obtaining authorization token and secret from clients
then your application has to listen for `oauth1-token-requested` custom event
and perform authorization on the server side.
When auth token and secret is available and the user is to perform a HTTP request,
the request panel sends `before-request` cutom event. This element handles the event
and apllies authorization header with generated signature to the request.
## OAuth 1 configuration object
Both authorization or request signing requires detailed configuration object.
This is handled by the request panel. It sets OAuth1 configuration in the `request.auth`
property.
| Property | Type | Description |
| ----------------|-------------|---------- |
| `signatureMethod` | `String` | One of `PLAINTEXT`, `HMAC-SHA1`, `RSA-SHA1` |
| `requestTokenUri` | `String` | Token request URI. Optional for before request. Required for authorization |
| `accessTokenUri` | `String` | Access token request URI. Optional for before request. Required for authorization |
| `authorizationUri` | `String` | User dialog URL. |
| `consumerKey` | `String` | Consumer key to be used to generate the signature. Optional for before request. |
| `consumerSecret` | `String` | Consumer secret to be used to generate the signature. Optional for before request. |
| `redirectUri` | `String` | Redirect URI for the authorization. Optional for before request. |
| `authParamsLocation` | `String` | Location of the authorization parameters. Default to `authorization` header |
| `authTokenMethod` | `String` | Token request HTTP method. Default to `POST`. Optional for before request. |
| `version` | `String` | Oauth1 protocol version. Default to `1.0` |
| `nonceSize` | `Number` | Size of the nonce word to generate. Default to 32. Unused if `nonce` is set. |
| `nonce` | `String` | Nonce to be used to generate signature. |
| `timestamp` | `Number` | Request timestamp. If not set it sets current timestamp |
| `customHeaders` | `Object` | Map of custom headers to set with authorization request |
| `type` | `String` | Must be set to `oauth1` or during before-request this object will be ignored. |
| `token` | `String` | Required for signing requests. Received OAuth token |
| `tokenSecret` | `String` | Required for signing requests. Received OAuth token secret |
## Error codes
- `params-error` Oauth1 parameters are invalid
- `oauth1-error` OAuth popup is blocked.
- `token-request-error` HTTP request to the authorization server failed
- `no-response` No response recorded.
## Acknowledgements
- This element uses [jsrsasign](https://github.com/kjur/jsrsasign) library distributed
under MIT licence.
- This element uses [crypto-js](https://code.google.com/archive/p/crypto-js/) library
distributed under BSD license.
## Required dependencies
The `CryptoJS` and `RSAKey` libraries are not included into the element sources.
If your project do not use this libraries already include it into your project.
This component also uses `URLSearchParams` so provide a polyfill for `URL` and `URLSearchParams`.
```
npm i cryptojslib jsrsasign
```
```html
<script src="../cryptojslib/components/core.js"></script>
<script src="../cryptojslib/rollups/sha1.js"></script>
<script src="../cryptojslib/components/enc-base64-min.js"></script>
<script src="../cryptojslib/rollups/md5.js"></script>
<script src="../cryptojslib/rollups/hmac-sha1.js"></script>
<script src="../jsrsasign/lib/jsrsasign-rsa-min.js"></script>
```
@customElement
@memberof LogicElements
@appliesMixin HeadersParserMixin
*/
if (window) {
window.forceJURL = true;
}
export class OAuth1Authorization extends HeadersParserMixin(LitElement) {
get lastIssuedToken() {
return this._lastIssuedToken;
}
set lastIssuedToken(value) {
const old = this._lastIssuedToken;
if (old === value) {
return;
}
this._lastIssuedToken = value;
this.dispatchEvent(
new CustomEvent('last-issued-token-changed', {
detail: {
value
}
})
);
}
static get properties() {
return {
/**
* If set, requests made by this element to authorization endpoint will be
* prefixed with the proxy value.
*/
proxy: { type: String },
/**
* Latest valid token exchanged with the authorization endpoint.
*/
lastIssuedToken: { type: Object },
/**
* OAuth 1 token authorization endpoint.
*/
requestTokenUri: { type: String },
/**
* Oauth 1 token exchange endpoint
*/
accessTokenUri: { type: String },
/**
* Oauth 1 consumer key to use with auth request
*/
consumerKey: { type: String },
/**
* Oauth 1 consumer secret to be used to generate the signature.
*/
consumerSecret: { type: String },
/**
* A signature generation method.
* Once of: `PLAINTEXT`, `HMAC-SHA1` or `RSA-SHA1`
*/
signatureMethod: { type: String },
/**
* Location of the OAuth authorization parameters.
* It can be either `authorization` meaning as a header and
* `querystring` to put OAuth parameters to the URL.
*/
authParamsLocation: { type: String },
_caseMap: { type: Object },
_camelRegex: { type: Object },
/**
* Returns `application/x-www-form-urlencoded` content type value.
*/
urlEncodedType: { type: String }
};
}
constructor() {
super();
this._tokenRequestedHandler = this._tokenRequestedHandler.bind(this);
this._listenPopup = this._listenPopup.bind(this);
this._handleRequest = this._handleRequest.bind(this);
this.signatureMethod = 'HMAC-SHA1';
this.authParamsLocation = 'authorization';
this._caseMap = {};
this._camelRegex = /([A-Z])/g;
this.urlEncodedType = 'application/x-www-form-urlencoded';
}
connectedCallback() {
super.connectedCallback();
window.addEventListener('oauth1-token-requested', this._tokenRequestedHandler);
window.addEventListener('message', this._listenPopup);
window.addEventListener('before-request', this._handleRequest);
this.setAttribute('aria-hidden', 'true');
}
disconnectedCallback() {
super.disconnectedCallback();
window.removeEventListener('oauth1-token-requested', this._tokenRequestedHandler);
window.removeEventListener('message', this._listenPopup);
window.removeEventListener('before-request', this._handleRequest);
}
/**
* The `before-request` handler. Creates an authorization header if needed.
* Normally `before-request` expects to set a promise on the `detail.promises`
* object. But because this taks is sync it skips the promise and manipulate
* request object directly.
* @param {CustomEvent} e
*/
_handleRequest(e) {
const request = e.detail;
if (!request.auth || request.auth.type !== 'oauth1') {
return;
}
this._applyBeforeRequestSignature(request, request.auth);
}
/**
* This is similar to `signRequestObject()` fut it accepts the request object
* and authorization settings separately and it uses OAuth configuration
* from the auth object.
*
* @param {Object} request ARC/API Console request object
* @param {AuthSettings|Array<AuthSettings>} auth Authorization object
* @return {Object} Signed request object.
*/
signRequest(request, auth) {
if (!auth) {
return request;
}
const authInfo = Array.isArray(auth)
? auth.find((item) => item.type === 'oauth 1')
: auth.type === 'oauth1' || auth.type === 'oauth 1'
? auth
: undefined;
if (!authInfo) {
return request;
}
const authSettings = authInfo.settings || {};
const { token, tokenSecret } = authSettings;
if (!token || !tokenSecret) {
return request;
}
this._applyBeforeRequestSignature(request, authSettings);
return request;
}
/**
* Applies OAuth1 authorization header with generated signature for this
* request.
*
* This method expects the `auth` object to be set on the request. The object
* is full configuration for the OAuth1 authorization as described in
* `auth-methods/oauth1.html` element.
*
* @param {Object} request ARC request object
* @param {String} auth Token request auth object
*/
_applyBeforeRequestSignature(request, auth) {
if (!request || !request.method || !request.url) {
return;
}
try {
this._prepareOauth(auth);
} catch (_) {
return;
}
const token = auth.token || this.lastIssuedToken.oauth_token;
const tokenSecret = auth.tokenSecret || this.lastIssuedToken.oauth_token_secret;
let method = request.method || 'GET';
method = method.toUpperCase();
const withPayload = ['GET', 'HEAD'].indexOf(request.method) === -1;
let body;
if (withPayload && request.headers && request.body) {
let contentType;
try {
contentType = this.getContentType(request.headers);
} catch (e) {
// ...
}
if (contentType && contentType.indexOf(this.urlEncodedType) === 0) {
body = request.body;
}
}
const orderedParameters = this._prepareParameters(token, tokenSecret, method, request.url, {}, body);
if (this.authParamsLocation === 'authorization') {
const authorization = this._buildAuthorizationHeaders(orderedParameters);
try {
request.headers = this.replaceHeaderValue(request.headers, 'authorization', authorization);
} catch (_) {
noop();
}
} else {
request.url = this._buildAuthorizationQueryStirng(request.url, orderedParameters);
}
}
/**
* A handler for the `oauth1-token-requested` event.
* Performs OAuth1 authorization for given settings.
*
* The detail object of the event contains OAuth1 configuration as described
* in `auth-methods/oauth1.html`element.
*
* @param {CustomEvent} e
*/
_tokenRequestedHandler(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
this.authorize(e.detail);
}
/**
* Performs a request to authorization server.
*
* @param {Object} settings Oauth1 configuration. See description for more
* details or `auth-methods/oauth1.html` element that collectes configuration
* from the user.
*/
authorize(settings) {
try {
this._prepareOauth(settings);
} catch (e) {
noop();
this._dispatchError('Unable to authorize: ' + e.message, 'params-error');
return;
}
this.getOAuthRequestToken()
.then((temporaryCredentials) => {
this.temporaryCredentials = temporaryCredentials;
const authorizationUri = settings.authorizationUri + '?oauth_token=' + temporaryCredentials.oauth_token;
this.popupClosedProperly = undefined;
this._popup = window.open(authorizationUri, 'api-console-oauth1');
if (!this._popup) {
// popup blocked.
this._dispatchError('Authorization popup is blocked', 'popup-blocked');
return;
}
this._next = 'exchange-token';
this._popup.window.focus();
this._observePopupState();
})
.catch((e) => {
const msg = e.message || 'Unknown error when getting the token';
this._dispatchError(msg, 'token-request-error');
});
}
/**
* Sets a configuration properties on this element from passed settings.
*
* @param {Object} params See description for more
* details or `auth-methods/oauth1.html` element that collectes configuration
* from the user.
*/
_prepareOauth(params) {
if (params.signatureMethod) {
const signMethod = params.signatureMethod;
if (['PLAINTEXT', 'HMAC-SHA1', 'RSA-SHA1'].indexOf(signMethod) === -1) {
throw new Error('Unsupported signature method: ' + signMethod);
}
if (signMethod === 'RSA-SHA1') {
this._privateKey = params.consumerSecret;
}
this.signatureMethod = signMethod;
}
if (params.requestTokenUri) {
this.requestTokenUri = params.requestTokenUri;
}
if (params.accessTokenUri) {
this.accessTokenUri = params.accessTokenUri;
}
if (params.consumerKey) {
this.consumerKey = params.consumerKey;
}
if (params.consumerSecret) {
this.consumerSecret = params.consumerSecret;
}
if (params.redirectUri) {
this._authorizeCallback = params.redirectUri;
}
if (params.authParamsLocation) {
this.authParamsLocation = params.authParamsLocation;
} else {
this.authParamsLocation = 'authorization';
}
if (params.authTokenMethod) {
this.authTokenMethod = params.authTokenMethod;
} else {
this.authTokenMethod = 'POST';
}
this._version = params.version || '1.0';
this._nonceSize = params.nonceSize || 32;
this._nonce = params.nonce;
this._timestamp = params.timestamp;
this._headers = params.customHeaders || this._defaultHeaders();
this._oauthParameterSeperator = ',';
}
/**
* List of default headers to send with auth request.
*
* @return {Object} Map of default headers.
*/
_defaultHeaders() {
return {
'Accept': '*/*',
'Connection': 'close',
'User-Agent': 'Advanced REST Client authorization'
};
}
/**
* Returns current timestamp.
*
* @return {Number} Current timestamp
*/
getTimestamp() {
return Math.floor(new Date().getTime() / 1000);
}
/**
* URL encodes the string.
*
* @param {String} toEncode A string to encode.
* @return {String} Encoded string
*/
encodeData(toEncode) {
if (!toEncode) {
return '';
}
const result = encodeURIComponent(toEncode);
return this._finishEncodeParams(result);
}
/**
* Normalizes url encoded values as defined in the OAuth 1 spec.
*
* @param {String} url URI encoded params.
* @return {String} Normalized params.
*/
_finishEncodeParams(url) {
return url
.replace(/!/g, '%21')
.replace(/'/g, '%27')
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
.replace(/\*/g, '%2A');
}
/**
* URL decodes data.
* Also replaces `+` with ` ` (space).
*
* @param {String} toDecode String to decode.
* @return {String} Decoded string
*/
decodeData(toDecode) {
if (!toDecode) {
return '';
}
toDecode = toDecode.replace(/\+/g, ' ');
return decodeURIComponent(toDecode);
}
/**
* Computes signature for the request.
*
* @param {String} signatureMethod Method to use to generate the signature.
* Supported are: `PLAINTEXT`, `HMAC-SHA1`, `RSA-SHA1`. It throws an error if
* value of this property is other than listed here.
* @param {String} requestMethod Request HTTP method.
* @param {String} url Request full URL.
* @param {Object} oauthParameters Map of oauth parameters.
* @param {?String} tokenSecret Optional, token secret.
* @return {String} Generated OAuth1 signature for given `signatureMethod`
* @param {?String} body Body used with the request. Note: this parameter
* can only be set if the request's content-type header equals
* `application/x-www-form-urlencoded`.
* @throws Error when `signatureMethod` is not one of listed here.
*/
getSignature(signatureMethod, requestMethod, url, oauthParameters, tokenSecret, body) {
let signatureBase;
let key;
if (signatureMethod !== 'PLAINTEXT') {
signatureBase = this.createSignatureBase(requestMethod, url, oauthParameters, body);
}
if (signatureMethod !== 'RSA-SHA1') {
key = this.createSignatureKey(this.consumerSecret, tokenSecret);
}
switch (signatureMethod) {
case 'PLAINTEXT':
return this._createSignaturePlainText(key);
case 'RSA-SHA1':
return this._createSignatureRsaSha1(signatureBase, this._privateKey);
case 'HMAC-SHA1':
return this._createSignatureHamacSha1(signatureBase, key);
default:
throw new Error('Unknown signature method');
}
}
/**
* Normalizes URL to base string URI as described in
* https://tools.ietf.org/html/rfc5849#section-3.4.1.2
*
* @param {String} url Request full URL.
* @return {String} Base String URI
*/
_normalizeUrl(url) {
const parsedUrl = new URL(url);
let port = '';
if (parsedUrl.port) {
if (
(parsedUrl.protocol === 'http:' && parsedUrl.port !== '80') ||
(parsedUrl.protocol === 'https:' && parsedUrl.port !== '443')
) {
port = ':' + parsedUrl.port;
}
}
if (!parsedUrl.pathname || parsedUrl.pathname === '') {
parsedUrl.pathname = '/';
}
return parsedUrl.protocol + '//' + parsedUrl.hostname + port + parsedUrl.pathname;
}
/**
* @param {String} parameter Parameter name (key).
* @return {Boolean} True if the `parameter` is an OAuth 1 parameter.
*/
_isParameterNameAnOAuthParameter(parameter) {
return !!(parameter && parameter.indexOf('oauth_') === 0);
}
/**
* Creates an Authorization header value to trasmit OAuth params in headers
* as described in https://tools.ietf.org/html/rfc5849#section-3.5.1
*
* @param {Array} orderedParameters Oauth parameters that are already
* ordered.
* @return {String} The Authorization header value
*/
_buildAuthorizationHeaders(orderedParameters) {
let authHeader = 'OAuth ';
const params = [];
orderedParameters.forEach((item) => {
if (!this._isParameterNameAnOAuthParameter(item[0])) {
return;
}
params.push(this.encodeData(item[0]) + '="' + this.encodeData(item[1]) + '"');
});
authHeader += params.join(this._oauthParameterSeperator + ' ');
return authHeader;
}
/**
* Creates a body for www-urlencoded content type to transmit OAuth params
* in request body as described in
* https://tools.ietf.org/html/rfc5849#section-3.5.2
*
* @param {Array} orderedParameters Oauth parameters that are already
* ordered.
* @return {String} The body to send
*/
_buildFormDataParameters(orderedParameters) {
const result = [];
orderedParameters.forEach((item) => {
if (!this._isParameterNameAnOAuthParameter(item[0])) {
return;
}
result.push(this.encodeData(item[0]) + '=' + this.encodeData(item[1]));
});
return result.join('&');
}
/**
* Adds query paramteres with OAuth 1 parameters to the URL
* as described in https://tools.ietf.org/html/rfc5849#section-3.5.3
*
* @param {String} url
* @param {Array} orderedParameters Oauth parameters that are already
* ordered.
* @return {String} URL to use with the request
*/
_buildAuthorizationQueryStirng(url, orderedParameters) {
const parser = new URL(url);
orderedParameters.forEach((item) => {
parser.searchParams.append(item[0], item[1]);
});
return parser.toString();
}
// Takes an object literal that represents the arguments, and returns an array
// of argument/value pairs.
_makeArrayOfArgumentsHash(argumentsHash) {
const argumentPairs = [];
Object.keys(argumentsHash).forEach(function(key) {
const value = argumentsHash[key];
if (Array.isArray(value)) {
for (let i = 0, len = value.length; i < len; i++) {
argumentPairs[argumentPairs.length] = [key, value[i]];
}
} else {
argumentPairs[argumentPairs.length] = [key, value];
}
});
return argumentPairs;
}
// Sorts the encoded key value pairs by encoded name, then encoded value
_sortRequestParams(argumentPairs) {
// Sort by name, then value.
argumentPairs.sort(function(a, b) {
if (a[0] === b[0]) {
return a[1] < b[1] ? -1 : 1;
} else {
return a[0] < b[0] ? -1 : 1;
}
});
return argumentPairs;
}
/**
* Sort function to sort parameters as described in
* https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
* @param {String} a
* @param {String} b
* @return {Number}
*/
_sortParamsFunction(a, b) {
if (a[0] === b[0]) {
return String(a[1]).localeCompare(String(b[1]));
}
return String(a[0]).localeCompare(String(b[0]));
}
/**
* Normalizes request parameters as described in
* https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
*
* @param {Array} args List of parameters to normalize. It must contain
* a list of array items where first element of the array is parameter name
* and second is parameter value.
* @return {String} Normalized parameters to string.
*/
_normaliseRequestParams(args) {
const len = args.length;
let i = 0;
// First encode them #3.4.1.3.2 .1
for (; i < len; i++) {
args[i][0] = this.encodeData(args[i][0]);
args[i][1] = this.encodeData(args[i][1]);
}
// Then sort them #3.4.1.3.2 .2
args.sort(this._sortParamsFunction);
// Then concatenate together #3.4.1.3.2 .3 & .4
const result = [];
args.forEach((pair) => {
if (pair[0] === 'oauth_signature') {
return;
}
result.push(pair[0] + '=' + String(pair[1]));
});
return result.join('&');
}
/**
* Computes array of parameters from the request URL.
*
* @param {String} url Full request URL
* @return {Array} Array of parameters where each item is an array with
* first element as a name of the parameter and second element as a value.
*/
_listQueryParameters(url) {
const parsedUrl = new URL(url);
const result = [];
parsedUrl.searchParams.forEach((value, key) => {
result[result.length] = [this.decodeData(key), this.decodeData(value)];
});
return result;
}
/**
* Computes array of parameters from the entity body.
* The body must be `application/x-www-form-urlencoded`.
*
* @param {String} body Entity body of `application/x-www-form-urlencoded`
* request
* @return {Array} Array of parameters where each item is an array with
* first element as a name of the parameter and second element as a value.
* Keys and values are percent decoded. Additionally each `+` is replaced
* with space character.
*/
_formUrlEncodedToParams(body) {
if (!body) {
return [];
}
const parts = body.split('&').map((part) => {
const pair = part.split('=');
const key = this.decodeData(pair[0]);
let value = '';
if (pair[1]) {
value = this.decodeData(pair[1]);
}
return [key, value];
});
return parts;
}
/**
* Creates a signature base as defined in
* https://tools.ietf.org/html/rfc5849#section-3.4.1
*
* @param {String} method HTTP method used with the request
* @param {String} url Full URL of the request
* @param {Object} oauthParams Key - value pairs of OAuth parameters
* @param {?String} body Body used with the request. Note: this parameter
* can only be set if the request's content-type header equals
* `application/x-www-form-urlencoded`.
* @return {String} A base string to be used to generate signature.
*/
createSignatureBase(method, url, oauthParams, body) {
let allParameter = [];
const uriParameters = this._listQueryParameters(url);
oauthParams = this._makeArrayOfArgumentsHash(oauthParams);
allParameter = uriParameters.concat(oauthParams);
if (body) {
body = this._formUrlEncodedToParams(body);
allParameter = allParameter.concat(body);
}
allParameter = this._normaliseRequestParams(allParameter);
allParameter = this.encodeData(allParameter);
url = this.encodeData(this._normalizeUrl(url));
return [method.toUpperCase(), url, allParameter].join('&');
}
/**
* Creates a signature key to compute the signature as described in
* https://tools.ietf.org/html/rfc5849#section-3.4.2
*
* @param {String} clientSecret Client secret (consumer secret).
* @param {?String} tokenSecret Optional, token secret
* @return {String} A key to be used to generate the signature.
*/
createSignatureKey(clientSecret, tokenSecret) {
if (!tokenSecret) {
tokenSecret = '';
} else {
tokenSecret = this.encodeData(tokenSecret);
}
clientSecret = this.encodeData(clientSecret);
return clientSecret + '&' + tokenSecret;
}
/**
* Found at http://jsfiddle.net/ARTsinn/6XaUL/
*
* @param {String} h Hexadecimal input
* @return {String} Result of transforming value to string.
*/
hex2b64(h) {
const b64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const b64pad = '=';
let i;
let c;
let ret = '';
for (i = 0; i + 3 <= h.length; i += 3) {
c = parseInt(h.substring(i, i + 3), 16);
ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63);
}
if (i + 1 === h.length) {
c = parseInt(h.substring(i, i + 1), 16);
ret += b64map.charAt(c << 2);
} else if (i + 2 === h.length) {
c = parseInt(h.substring(i, i + 2), 16);
ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4);
}
while ((ret.length & 3) > 0) {
ret += b64pad;
}
return ret;
}
/**
* Creates a signature for the PLAINTEXT method.
*
* In this case the signature is the key.
*
* @param {String} key Computed signature key.
* @return {String} Computed OAuth1 signature.
*/
_createSignaturePlainText(key) {
return key;
}
/**
* Creates a signature for the RSA-SHA1 method.
*
* @param {String} baseText Computed signature base text.
* @param {String} privateKey Client private key.
* @return {String} Computed OAuth1 signature.
*/
_createSignatureRsaSha1(baseText, privateKey) {
/* global RSAKey */
const rsa = new RSAKey();
rsa.readPrivateKeyFromPEMString(privateKey);
const hSig = rsa.sign(baseText, 'sha1');
return this.hex2b64(hSig);
}
/**
* Creates a signature for the HMAC-SHA1 method.
*
* @param {String} baseText Computed signature base text.
* @param {String} key Computed signature key.
* @return {String} Computed OAuth1 signature.
*/
_createSignatureHamacSha1(baseText, key) {
/* global CryptoJS */
const hash = CryptoJS.HmacSHA1(baseText, key);
return hash.toString(CryptoJS.enc.Base64);
}
/**
* Returns a list of characters that can be used to buid nonce.
*
* @return {Array<String>}
*/
get nonceChars() {
return [
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9'
];
}
_getNonce(nonceSize) {
const result = [];
const chars = this.nonceChars;
let charPos;
const nonceCharsLength = chars.length;
for (let i = 0; i < nonceSize; i++) {
charPos = Math.floor(Math.random() * nonceCharsLength);
result[i] = chars[charPos];
}
return result.join('');
}
_prepareParameters(token, tokenSecret, method, url, extraParams, body) {
const oauthParameters = {
oauth_timestamp: this._timestamp || this.getTimestamp(),
oauth_nonce: this._nonce || this._getNonce(this._nonceSize),
oauth_version: this._version,
oauth_signature_method: this.signatureMethod,
oauth_consumer_key: this.consumerKey
};
if (token) {
oauthParameters.oauth_token = token;
}
let sig;
if (this._isEcho) {
sig = this.getSignature(this.signatureMethod, 'GET', this._verifyCredentials, oauthParameters, tokenSecret, body);
} else {
if (extraParams) {
Object.keys(extraParams).forEach((key) => {
oauthParameters[key] = extraParams[key];
});
}
sig = this.getSignature(this.signatureMethod, method, url, oauthParameters, tokenSecret, body);
}
const orderedParameters = this._sortRequestParams(this._makeArrayOfArgumentsHash(oauthParameters));
orderedParameters[orderedParameters.length] = ['oauth_signature', sig];
return orderedParameters;
}
// Encodes parameters in the map.
encodeUriParams(params) {
const result = Object.keys(params).map((key) => {
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
});
return result.join('&');
}
/**
* Creates OAuth1 signature for a `request` object.
* The request object must contain:
* - `url` - String
* - `method` - String
* - `headers` - String
* It also may contain the `body` property.
*
* It alters the request object by applying OAuth1 parameters to a set
* location (qurey parameters, authorization header, body). This is
* controlled by `this.authParamsLocation` property. By default the
* parameters are applied to authorization header.
*
* @param {Object} request ARC request object.
* @param {?String} token OAuth token to use to generate the signature.
* If not set, then it will use a value from `this.lastIssuedToken`.
* @param {?String} tokenSecret OAuth token secret to use to generate the
* signature. If not set, then it will use a value from
* `this.lastIssuedToken`.
* @return {Object} The same object with applied OAuth 1 parameters.
*/
signRequestObject(request, token, tokenSecret) {
if (!request || !request.method || !request.url) {
return request;
}
token = token || this.lastIssuedToken.oauth_token;
tokenSecret = tokenSecret || this.lastIssuedToken.oauth_token_secret;
let method = request.method || 'GET';
method = method.toUpperCase();
const withPayload = ['GET', 'HEAD'].indexOf(request.method) === -1;
let body;
if (withPayload && request.headers && request.body) {
let contentType;
try {
contentType = this.getContentType(request.headers);
} catch (_) {
noop();
}
if (contentType && contentType.indexOf(this.urlEncodedType) === 0) {
body = request.body;
}
}
const orderedParameters = this._prepareParameters(token, tokenSecret, method, request.url, {}, body);
if (this.authParamsLocation === 'authorization') {
const authorization = this._buildAuthorizationHeaders(orderedParameters);
try {
request.headers = this.replaceHeaderValue(request.headers, 'authorization', authorization);
} catch (_) {
noop();
}
} else {
request.url = this._buildAuthorizationQueryStirng(request.url, orderedParameters);
}
this.clearRequestVariables();
return request;
}
_performRequest(token, tokenSecret, method, url, extraParams, body, contentType) {
const withPayload = ['POST', 'PUT'].indexOf(method) !== -1;
const orderedParameters = this._prepareParameters(token, tokenSecret, method, url, extraParams);
if (withPayload && !contentType) {
contentType = this.urlEncodedType;
}
const headers = {};
if (this.authParamsLocation === 'authorization') {
const authorization = this._buildAuthorizationHeaders(orderedParameters);
if (this._isEcho) {
headers['X-Verify-Credentials-Authorization'] = authorization;
} else {
headers.authorization = authorization;
}
} else {
url = this._buildAuthorizationQueryStirng(url, orderedParameters);
}
if (this._headers) {
Object.keys(this._headers).forEach((key) => {
headers[key] = this._headers[key];
});
}
if (extraParams) {
Object.keys(extraParams).forEach((key) => {
if (this._isParameterNameAnOAuthParameter(key)) {
delete extraParams[key];
}
});
}
if (withPayload && extraParams && !body && ['POST', 'PUT'].indexOf(method) !== -1) {
body = this.encodeUriParams(extraParams);
body = this._finishEncodeParams(body);
}
if (withPayload && !body) {
headers['Content-length'] = '0';
}
const init = {
method: method,
headers: headers
};
if (withPayload && body) {
init.body = body;
}
let responseHeaders;
return this.request(url, init)
.then((response) => {
if (!response.ok) {
throw new Error('Token request error ended with status ' + response.status);
}
responseHeaders = response.headers;
return response.text();
})
.then((text) => {
return {
response: text,
headers: responseHeaders
};
});
}
/**
* Exchanges temporary authorization token for authorized token.
* When ready this function fires `oauth1-token-response`
*
* @param {String} token
* @param {String} secret
* @param {String} verifier
* @return {Promise}
*/
getOAuthAccessToken(token, secret, verifier) {
const extraParams = {};
if (verifier) {
extraParams.oauth_verifier = verifier;
}
const method = this.authTokenMethod;
return this._performRequest(token, secret, method, this.accessTokenUri, extraParams)
.then((response) => {
if (!response.response) {
let message = "Couldn't exchange token. ";
message += 'Authorization server may be down or CORS is disabled.';
throw new Error(message);
}
const params = {};
this._formUrlEncodedToParams(response.response).forEach((pair) => {
params[pair[0]] = pair[1];
});
return params;
})
.then((tokenInfo) => {
this.clearRequestVariables();
this.lastIssuedToken = tokenInfo;
const e = new CustomEvent('oauth1-token-response', {
bubbles: true,
composed: true,
cancelable: false,
detail: tokenInfo
});
this.dispatchEvent(e);
});
}
/**
* Clears variables set for current request after signature has been
* generated and token obtained.
*/
clearRequestVariables() {
this.temporaryCredentials = undefined;
this._timestamp = undefined;
this._nonce = undefined;
}
/**
* Requests the authorization server for temporarty authorization token.
* This token should be passed to `authorizationUri` as a `oauth_token`
* parameter.
*
* @param {Object} extraParams List of extra parameters to include in the
* request.
* @return {Promise} A promise resolved to a map of OAuth 1 parameters:
* `oauth_token`, `oauth_token_secret`, `oauth_verifier` and
* `oauth_callback_confirmed` (for 1.0a version).
*/
getOAuthRequestToken(extraParams) {
extraParams = extraParams || {};
if (this._authorizeCallback) {
extraParams.oauth_callback = this._authorizeCallback;
}
const method = this.authTokenMethod;
return this._performRequest(null, null, method, this.requestTokenUri, extraParams).then((response) => {
if (!response.response) {
let message = "Couldn't request for authorization token. ";
message += 'Authorization server may be down or CORS is disabled.';
throw new Error(message);
}
const params = {};
this._formUrlEncodedToParams(response.response).forEach((pair) => {
params[pair[0]] = pair[1];
});
return params;
});
}
/**
* Makes a HTTP request.
* Before making the request it sends `auth-request-proxy` custom event
* with the URL and init object in event's detail object.
* If the event is cancelled then it will use detail's `result` value to
* return from this function. The `result` must be a Promise that will
* resolve to a `Response` object.
* Otherwise it will use internall `fetch` implementation.
*
* @param {String} url An URL to call
* @param {Object} init Init object that will be passed to a `Request`
* object.
* @return {Promise} A promise that resolves to a `Response` object.
*/
request(url, init) {
const e = new CustomEvent('auth-request-proxy', {
bubbles: true,
composed: true,
cancelable: true,
detail: {
url: url,
init: init
}
});
this.dispatchEvent(e);
return e.defaultPrevented ? e.detail.result : this._fetch(url, init);
}
/**
* Performs a HTTP request.
* If `proxy` is set or `iron-meta` with a key `auth-proxy` is set then
* it will prefix the URL with the value of proxy.
*
* @param {String} url An URL to call
* @param {Object} init Init object that will be passed to a `Request`
* object.
* @return {Promise} A promise that resolves to a `Response` object.
*/
_fetch(url, init) {
let proxy;
if (this.proxy) {
proxy = this.proxy;
} else {
proxy = document.createElement('iron-meta').byKey('auth-proxy');
}
if (proxy) {
url = proxy + url;
}
init.mode = 'cors';
return fetch(url, init);
}
_listenPopup(e) {
if (
!location ||
!e.source ||
!this._popup ||
e.origin !== location.origin ||
e.source.location.href !== this._popup.location.href
) {
return;
}
const tokenInfo = e.data;
this.popupClosedProperly = true;
switch (this._next) {
case 'exchange-token':
this.getOAuthAccessToken(
tokenInfo.oauthToken,
this.temporaryCredentials.oauth_token_secret,
tokenInfo.oauthVerifier
);
break;
}
this._popup.close();
}
// Observer if the popup has been closed befor the data has been received.
_observePopupState() {
const popupCheckInterval = setInterval(() => {
if (!this._popup || this._popup.closed) {
clearInterval(popupCheckInterval);
this._beforePopupUnloadHandler();
}
}, 500);
}
_beforePopupUnloadHandler() {
if (this.popupClosedProperly) {
return;
}
this._popup = undefined;
this._dispatchError('No response has been recorded.', 'no-response');
}
/**
* Dispatches an error event that propagates through the DOM.
*
* @param {String} message
* @param {String} code
*/
_dispatchError(message, code) {
const e = new CustomEvent('oauth1-error', {
bubbles: true,
composed: true,
detail: {
message: message,
code: code
}
});
this.dispatchEvent(e);
}
/**
* Adds camel case keys to a map of parameters.
* It adds new keys to the object tranformed from `oauth_token`
* to `oauthToken`
*
* @param {Object} obj
* @return {Object}
*/
parseMapKeys(obj) {
Object.keys(obj).forEach((key) => this._parseParameter(key, obj));
return obj;
}
/**
* Parses a query parameter object to produce camel case map of parameters.
* This sets values to the `settings` object which is passed by reference.
* No need to return value.
*
* @param {String} param Key in the `settings` object.
* @param {Object} settings Parameters.
* @return {Object}
*/
_parseParameter(param, settings) {
if (!(param in settings)) {
return settings;
}
const value = settings[param];
let oauthParam;
if (this._caseMap[param]) {
oauthParam = this._caseMap[param];
} else {
oauthParam = this._getCaseParam(param);
}
settings[oauthParam] = value;
}
_getCaseParam(param) {
return 'oauth_' + param.replace(this._camelRegex, '_$1').toLowerCase();
}
/**
* Fired when authorization is unsuccessful
*
* @event oauth1-error
* @param {String} message Human readable error message
* @param {String} code Error code associated with the error. See description
* of the element fo code mening.
*/
/**
* Fired when the authorization is successful and token and secret are ready.
*
* @event oauth1-token-response
* @param {String} oauth_token Received OAuth1 token
* @param {String} oauth_token_secret Received OAuth1 token secret
*/
/**
* Dispatched when the component requests to proxy authorization request
* through proxy. If the application decide to proxy the request it must
* cancel the events.
*
* The handler must set `event.detail.result` property to be a `Promise`
* with call result that will be reported to the application.
*
* It can be used to proxy CORS requests if the application can support this
* case.
*
* @event auth-request-proxy
* @param {String} url The request URL
* @param {Object} init The same `init` object as the one used to initialize
* `Request` object for fetch API.
*/
}
import { OAuth1Authorization } from './src/OAuth1Authorization.js';
export { OAuth1Authorization };
window.customElements.define('oauth1-authorization', OAuth1Authorization);

@@ -13,348 +13,5 @@ /**

// tslint:disable:variable-name Describing an API that's defined elsewhere.
// tslint:disable:no-any describes the API as best we are able today
import {OAuth2Authorization} from './src/OAuth2Authorization.js';
export {OAuth2Authorization};
declare namespace LogicElements {
/**
* The `<outh2-authorization>` performs an OAuth2 requests to get a token for given settings.
*/
class OAuth2Authorization extends HTMLElement {
readonly tokenInfo: object|null;
ontokenerror: Function|null;
ontokenresponse: Function|null;
connectedCallback(): void;
disconnectedCallback(): void;
/**
* Clears the state of the element.
*/
clear(): void;
/**
* Clean up popup reference and closes the window if not yet closed.
*/
_cleanupPopup(): void;
/**
* Handler for the `oauth2-token-requested` custom event.
*/
_tokenRequestedHandler(e: CustomEvent|null): void;
/**
* Authorize the user using provided settings.
*
* @param settings Map of authorization settings.
* - type {String} Authorization grant type. Can be `implicit`,
* `authorization_code`, `client_credentials`, `password` or custom value
* as OAuth 2.0 allows extensions to grant type.
*/
authorize(settings: {[key: String|null]: String|null}): void;
/**
* Checks if basic configuration of the OAuth 2 request is valid an can proceed
* with authentication.
*
* @param settings authorization settings
*/
_sanityCheck(settings: object|null): void;
/**
* Checks if the URL has valid scheme for OAuth flow.
*
* @param url The url value to test
*/
_checkUrl(url: String|null): void;
/**
* Authorizes the user in the OAuth authorization endpoint.
* By default it authorizes the user using a popup that displays
* authorization screen. When `interactive` property is set to `false`
* on the `settings` object then it will quietly create an iframe
* and try to receive the token.
*
* @param authUrl Complete authorization url
* @param settings Passed user settings
*/
_authorize(authUrl: String|null, settings: object|null): void;
/**
* Creates and opens auth popup.
*
* @param url Complete authorization url
*/
_authorizePopup(url: String|null): void;
/**
* Tries to Authorize the user in a non interactive way.
* This method always result in a success response. When there's an error or
* user is not logged in then the response won't contain auth token info.
*
* @param url Complete authorization url
*/
_authorizeTokenNonInteractive(url: String|null): void;
/**
* Removes the frame and any event listeners attached to it.
*/
_cleanupFrame(): void;
/**
* Handler for `error` event dispatched by oauth iframe.
*/
_frameLoadErrorHandler(): void;
/**
* Handler for iframe `load` event.
*/
_frameLoadHandler(): void;
/**
* Observer if the popup has been closed befor the data has been received.
*/
_observePopupState(): void;
/**
* Function called in the interval.
* Observer popup state and calls `_beforePopupUnloadHandler()`
* when popup is no longer opened.
*/
_popupObserver(): void;
/**
* Browser or server flow: open the initial popup.
*
* @param settings Settings passed to the authorize function.
* @param type `token` or `code`
* @returns Full URL for the endpoint.
*/
_constructPopupUrl(settings: object|null, type: String|null): String|null;
/**
* Computes `scope` URL parameter from scopes array.
*
* @param scopes List of scopes to use with the request.
* @returns Computed scope value.
*/
_computeScope(scopes: Array<String|null>|null): String|null;
/**
* Listens for a message from the popup.
*/
_popupMessageHandler(e: Event|null): void;
_processPopupData(e: any): void;
_clearIframeTimeout(): void;
/**
* http://stackoverflow.com/a/10727155/1127848
*/
randomString(len: any): any;
/**
* Popup is closed by this element so if data is not yet set it means that the
* user closed the window - probably some error.
* The UI state is reset if needed.
*/
_beforePopupUnloadHandler(): void;
/**
* Exchange code for token.
* One note here. This element is intened to use with applications that test endpoints.
* It asks user to provide `client_secret` parameter and it is not a security concern to him.
* However, this method **can't be used in regular web applications** because it is a
* security risk and whole OAuth token exchange can be compromised. Secrets should never be
* present on client side.
*
* @param code Returned code from the authorization endpoint.
* @returns Promise with token information.
*/
_exchangeCode(code: String|null): Promise<any>|null;
/**
* Returns a body value for the code exchange request.
*
* @param settings Initial settings object.
* @param code Authorization code value returned by the authorization
* server.
* @returns Request body.
*/
_getCodeEchangeBody(settings: object|null, code: String|null): String|null;
/**
* Requests for token from the authorization server for `code`, `password`,
* `client_credentials` and custom grant types.
*
* @param url Base URI of the endpoint. Custom properties will be
* applied to the final URL.
* @param body Generated body for given type. Custom properties will
* be applied to the final body.
* @param settings Settings object passed to the `authorize()` function
* @returns Promise resolved to the response string.
*/
_requestToken(url: String|null, body: String|null, settings: object|null): Promise<any>|null;
/**
* Handler for the code request load event.
* Processes the response and either rejects the promise with an error
* or resolves it to token info object.
*
* @param e XHR load event.
* @param resolve Resolve function
* @param reject Reject function
*/
_processTokenResponseHandler(e: Event|null, resolve: Function|null, reject: Function|null): void;
/**
* Handler for the code request error event.
* Rejects the promise with error description.
*
* @param e XHR error event
* @param reject Promise's reject function.
*/
_processTokenResponseErrorHandler(e: Event|null, reject: Function|null): void;
/**
* Processes token request body and produces map of values.
*
* @param body Body received in the response.
* @param contentType Response content type.
* @returns Response as an object.
*/
_processCodeResponse(body: String|null, contentType: String|null): object|null;
/**
* Processes token info object when it's ready.
* Sets `tokenInfo` property, notifies listeners about the response
* and cleans up.
*
* @param tokenInfo Token info returned from the server.
* @returns The same tokenInfo, used for Promise return value.
*/
_handleTokenInfo(tokenInfo: object|null): object|null;
/**
* Handler fore an error that happened during code exchange.
*/
_handleTokenCodeError(e: Error|null): void;
/**
* Replaces `-` or `_` with camel case.
*
* @param name The string to process
* @returns Camel cased string or `undefined` if not
* transformed.
*/
_camel(name: String|null): String|null|undefined;
/**
* Requests a token for `password` request type.
*
* @param settings The same settings as passed to `authorize()`
* function.
* @returns Promise resolved to token info.
*/
authorizePassword(settings: object|null): Promise<any>|null;
/**
* Generates a payload message for password authorization.
*
* @param settings Settings object passed to the `authorize()`
* function
* @returns Message body as defined in OAuth2 spec.
*/
_getPasswordBody(settings: object|null): String|null;
/**
* Requests a token for `client_credentials` request type.
*
* @param settings The same settings as passed to `authorize()`
* function.
* @returns Promise resolved to a token info object.
*/
authorizeClientCredentials(settings: object|null): Promise<any>|null;
/**
* Generates a payload message for client credentials.
*
* @param settings Settings object passed to the `authorize()`
* function
* @returns Message body as defined in OAuth2 spec.
*/
_getClientCredentialsBody(settings: object|null): String|null;
/**
* Performs authorization on custom grant type.
* This extension is described in OAuth 2.0 spec.
*
* @param settings Settings object as for `authorize()` function.
* @returns Promise resolved to a token info object.
*/
authorizeCustomGrant(settings: object|null): Promise<any>|null;
/**
* Creates a body for custom gran type.
* It does not assume any parameter to be required.
* It applies all known OAuth 2.0 parameters and then custom parameters
*
* @returns Request body.
*/
_getCustomGrantBody(settings: object|null): String|null;
/**
* Applies custom properties defined in the OAuth settings object to the URL.
*
* @param url Generated URL for an endpoint.
* @param data `customData.[type]` property from the settings object.
* The type is either `auth` or `token`.
*/
_applyCustomSettingsQuery(url: String|null, data: object|null): String|null;
/**
* Applies custom headers from the settings object
*
* @param xhr Instance of the request object.
* @param data Value of settings' `customData` property
*/
_applyCustomSettingsHeaders(xhr: XMLHttpRequest|null, data: object|null): void;
/**
* Applies custom body properties from the settings to the body value.
*
* @param body Already computed body for OAuth request. Custom
* properties are appended at the end of OAuth string.
* @param data Value of settings' `customData` property
* @returns Request body
*/
_applyCustomSettingsBody(body: String|null, data: object|null): String|null;
/**
* Dispatches an error event that propagates through the DOM.
*
* @param detail The detail object.
*/
_dispatchError(detail: object|null): void;
/**
* Dispatches an error event that propagates through the DOM.
*
* @param detail The detail object.
*/
_dispatchResponse(detail: object|null): void;
/**
* Registers an event handler for given type
*
* @param eventType Event type (name)
* @param value The handler to register
*/
_registerCallback(eventType: String|null, value: Function|null): void;
}
}
declare global {
interface HTMLElementTagNameMap {
"oauth2-authorization": LogicElements.OAuth2Authorization;
}
}

@@ -14,986 +14,4 @@ /**

*/
function noop() {}
/**
The `<outh2-authorization>` performs an OAuth2 requests to get a token for given settings.
@customElement
@memberof LogicElements
*/
export class OAuth2Authorization extends HTMLElement {
/**
* @return {Object} A full data returned by the authorization endpoint.
*/
get tokenInfo() {
return this._tokenInfo;
}
constructor() {
super();
this._frameLoadErrorHandler = this._frameLoadErrorHandler.bind(this);
this._frameLoadHandler = this._frameLoadHandler.bind(this);
this._tokenRequestedHandler = this._tokenRequestedHandler.bind(this);
this._popupMessageHandler = this._popupMessageHandler.bind(this);
this._popupObserver = this._popupObserver.bind(this);
}
connectedCallback() {
window.addEventListener('oauth2-token-requested', this._tokenRequestedHandler);
window.addEventListener('message', this._popupMessageHandler);
this.setAttribute('aria-hidden', 'true');
}
disconnectedCallback() {
window.removeEventListener('oauth2-token-requested', this._tokenRequestedHandler);
window.removeEventListener('message', this._popupMessageHandler);
}
/**
* Clears the state of the element.
*/
clear() {
this._state = undefined;
this._settings = undefined;
this._cleanupFrame();
this._cleanupPopup();
}
/**
* Clean up popup reference and closes the window if not yet closed.
*/
_cleanupPopup() {
if (this._popup) {
if (!this._popup.closed) {
this._popup.close();
}
this._popup = undefined;
}
}
/**
* Handler for the `oauth2-token-requested` custom event.
*
* @param {CustomEvent} e
*/
_tokenRequestedHandler(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
this.authorize(e.detail);
}
/**
* Authorize the user using provided settings.
*
* @param {Object<String, String>} settings Map of authorization settings.
* - type {String} Authorization grant type. Can be `implicit`,
* `authorization_code`, `client_credentials`, `password` or custom value
* as OAuth 2.0 allows extensions to grant type.
*
* NOTE:
* For authorization_code and any other grant type that may receive a code
* and exchange it for an access token, the settings object may have a property
* "overrideExchangeCodeFlow" with a boolean value (true/false).
*
* The "overrideExchangeCodeFlow" property is a flag indicating that the developer wants to handle
* exchanging the code for the token instead of having the module do it.
*
* If "overrideExchangeCodeFlow" is set to true for the authorization_code grant type,
* we dispatch an "oauth2-code-response" event with the auth code.
*
* The user of this module should listen for this event and exchange the token for an access token on their end.
*
* This allows client-side apps to exchange the auth code with their backend/server for an access token
* since CORS isn't enabled for the /token endpoint.
*/
authorize(settings) {
this._tokenInfo = undefined;
this._type = settings.type;
this._state = settings.state || this.randomString(6);
this._settings = settings;
this._errored = false;
this._overrideExchangeCodeFlow = settings.overrideExchangeCodeFlow;
try {
this._sanityCheck(settings);
} catch (e) {
this._dispatchError({
message: e.message,
code: 'oauth_error',
state: this._state,
interactive: settings.interactive
});
throw e;
}
switch (settings.type) {
case 'implicit':
this._authorize(this._constructPopupUrl(settings, 'token'), settings);
break;
case 'authorization_code':
this._authorize(this._constructPopupUrl(settings, 'code'), settings);
break;
case 'client_credentials':
this.authorizeClientCredentials(settings).catch(() => {});
break;
case 'password':
this.authorizePassword(settings).catch(() => {});
break;
default:
this.authorizeCustomGrant(settings).catch(() => {});
}
}
/**
* Checks if basic configuration of the OAuth 2 request is valid an can proceed
* with authentication.
* @param {Object} settings authorization settings
* @throws {Error} When setttings are not valid
*/
_sanityCheck(settings) {
if (settings.type === 'implicit' || settings.type === 'authorization_code') {
try {
this._checkUrl(settings.authorizationUri);
} catch (e) {
throw new Error(`authorizationUri: ${e.message}`);
}
if (settings.accessTokenUri) {
try {
this._checkUrl(settings.accessTokenUri);
} catch (e) {
throw new Error(`accessTokenUri: ${e.message}`);
}
}
} else if (settings.accessTokenUri) {
if (settings.accessTokenUri) {
try {
this._checkUrl(settings.accessTokenUri);
} catch (e) {
throw new Error(`accessTokenUri: ${e.message}`);
}
}
}
}
/**
* Checks if the URL has valid scheme for OAuth flow.
* @param {String} url The url value to test
* @throws {TypeError} When passed value is not set, empty, or not a string
* @throws {Error} When passed value is not a valid URL for OAuth 2 flow
*/
_checkUrl(url) {
if (!url) {
throw new TypeError('the value is missing');
}
if (typeof url !== 'string') {
throw new TypeError('the value is not a string');
}
if (url.indexOf('http://') === -1 && url.indexOf('https://') === -1) {
throw new Error('the value has invalid scheme');
}
}
/**
* Authorizes the user in the OAuth authorization endpoint.
* By default it authorizes the user using a popup that displays
* authorization screen. When `interactive` property is set to `false`
* on the `settings` object then it will quietly create an iframe
* and try to receive the token.
*
* @param {String} authUrl Complete authorization url
* @param {Object} settings Passed user settings
*/
_authorize(authUrl, settings) {
this._settings = settings;
this._errored = false;
if (settings.interactive === false) {
this._authorizeTokenNonInteractive(authUrl);
} else {
this._authorizePopup(authUrl);
}
}
/**
* Creates and opens auth popup.
*
* @param {String} url Complete authorization url
*/
_authorizePopup(url) {
const op = 'menubar=no,location=no,resizable=yes,scrollbars=yes,status=no,width=800,height=600';
this._popup = window.open(url, 'oauth-window', op);
if (!this._popup) {
// popup blocked.
this._dispatchError({
message: 'Authorization popup is being blocked.',
code: 'popup_blocked',
state: this._state,
interactive: this._settings.interactive
});
return;
}
this._popup.window.focus();
this._observePopupState();
}
/**
* Tries to Authorize the user in a non interactive way.
* This method always result in a success response. When there's an error or
* user is not logged in then the response won't contain auth token info.
*
* @param {String} url Complete authorization url
*/
_authorizeTokenNonInteractive(url) {
const iframe = document.createElement('iframe');
iframe.style.border = '0';
iframe.style.width = '0';
iframe.style.height = '0';
iframe.style.overflow = 'hidden';
iframe.addEventListener('error', this._frameLoadErrorHandler);
iframe.addEventListener('load', this._frameLoadHandler);
iframe.id = 'oauth2-authorization-frame';
iframe.setAttribute('data-owner', 'arc-oauth-authorization');
document.body.appendChild(iframe);
iframe.src = url;
this._iframe = iframe;
}
/**
* Removes the frame and any event listeners attached to it.
*/
_cleanupFrame() {
if (!this._iframe) {
return;
}
this._iframe.removeEventListener('error', this._frameLoadErrorHandler);
this._iframe.removeEventListener('load', this._frameLoadHandler);
try {
document.body.removeChild(this._iframe);
} catch (_) {
noop();
}
this._iframe = undefined;
}
/**
* Handler for `error` event dispatched by oauth iframe.
*/
_frameLoadErrorHandler() {
if (this._errored) {
return;
}
this._dispatchResponse({
interactive: false,
code: 'iframe_load_error',
state: this._state
});
this.clear();
}
/**
* Handler for iframe `load` event.
*/
_frameLoadHandler() {
if (this.__frameLoadInfo) {
return;
}
this.__frameLoadInfo = true;
this.__frameLoadTimeout = setTimeout(() => {
if (!this.tokenInfo && !this._errored) {
this._dispatchResponse({
interactive: false,
code: 'not_authorized',
state: this._state
});
}
this.clear();
this.__frameLoadInfo = false;
}, 700);
}
// Observer if the popup has been closed befor the data has been received.
_observePopupState() {
this.__popupCheckInterval = setInterval(this._popupObserver, 250);
}
/**
* Function called in the interval.
* Observer popup state and calls `_beforePopupUnloadHandler()`
* when popup is no longer opened.
*/
_popupObserver() {
if (!this._popup || this._popup.closed) {
clearInterval(this.__popupCheckInterval);
this.__popupCheckInterval = undefined;
this._beforePopupUnloadHandler();
}
}
/**
* Browser or server flow: open the initial popup.
* @param {Object} settings Settings passed to the authorize function.
* @param {String} type `token` or `code`
* @return {String} Full URL for the endpoint.
*/
_constructPopupUrl(settings, type) {
let url = settings.authorizationUri;
if (url.indexOf('?') === -1) {
url += '?';
} else {
url += '&';
}
url += 'response_type=' + type;
url += '&client_id=' + encodeURIComponent(settings.clientId || '');
if (settings.redirectUri) {
url += '&redirect_uri=' + encodeURIComponent(settings.redirectUri);
}
if (settings.scopes && settings.scopes.length) {
url += '&scope=' + this._computeScope(settings.scopes);
}
url += '&state=' + encodeURIComponent(this._state);
if (settings.includeGrantedScopes) {
url += '&include_granted_scopes=true';
}
if (settings.loginHint) {
url += '&login_hint=' + encodeURIComponent(settings.loginHint);
}
if (settings.interactive === false) {
url += '&prompt=none';
}
// custom query parameters
if (settings.customData) {
const key = type === 'token' ? 'auth' : 'token';
const cs = settings.customData[key];
if (cs) {
url = this._applyCustomSettingsQuery(url, cs);
}
}
return url;
}
/**
* Computes `scope` URL parameter from scopes array.
*
* @param {Array<String>} scopes List of scopes to use with the request.
* @return {String} Computed scope value.
*/
_computeScope(scopes) {
if (!scopes) {
return '';
}
const scope = scopes.join(' ');
return encodeURIComponent(scope);
}
/**
* Listens for a message from the popup.
* @param {Event} e
*/
_popupMessageHandler(e) {
if (!this._popup && !this._iframe) {
return;
}
this._processPopupData(e);
}
_processPopupData(e) {
const tokenInfo = e.data;
const dontProcess = !this._overrideExchangeCodeFlow && (!tokenInfo || !tokenInfo.oauth2response);
if (dontProcess) {
// Possibly a message in the authorization info, not the popup.
return;
}
if (!this._settings) {
this._settings = {};
}
if (tokenInfo.state !== this._state) {
this._dispatchError({
message: 'Invalid state returned by the OAuth server.',
code: 'invalid_state',
state: this._state,
serverState: tokenInfo.state,
interactive: this._settings.interactive
});
this._errored = true;
this._clearIframeTimeout();
this.clear();
} else if ('error' in tokenInfo) {
this._dispatchError({
message: tokenInfo.errorDescription || 'The request is invalid.',
code: tokenInfo.error || 'oauth_error',
state: this._state,
interactive: this._settings.interactive
});
this._errored = true;
this._clearIframeTimeout();
this.clear();
} else if (this._type === 'implicit') {
this._handleTokenInfo(tokenInfo);
this.clear();
} else if (this._type === 'authorization_code') {
/**
* For the authorization_code flow, the developer (user of the oauth2-authorization lib)
* can pass a setting to override the code exchange flow. In this scenario,
* we dispatch an event with the auth code instead of exchanging the code for an access token.
* See {@link authorize()} comment for more details.
*/
if (this._overrideExchangeCodeFlow) {
this._dispatchCodeResponse(tokenInfo);
} else {
this._exchangeCodeValue = tokenInfo.code;
this._exchangeCode(tokenInfo.code).catch(() => {});
this._clearIframeTimeout();
}
}
}
_clearIframeTimeout() {
if (this.__frameLoadTimeout) {
clearTimeout(this.__frameLoadTimeout);
this.__frameLoadTimeout = undefined;
}
}
// http://stackoverflow.com/a/10727155/1127848
randomString(len) {
return Math.round(Math.pow(36, len + 1) - Math.random() * Math.pow(36, len))
.toString(36)
.slice(1);
}
/**
* Popup is closed by this element so if data is not yet set it means that the
* user closed the window - probably some error.
* The UI state is reset if needed.
*/
_beforePopupUnloadHandler() {
if (this.tokenInfo || (this._type === 'authorization_code' && this._exchangeCodeValue)) {
return;
}
const settings = this._settings || {};
this._dispatchError({
message: 'No response has been recorded.',
code: 'no_response',
state: this._state,
interactive: settings.interactive
});
this.clear();
}
/**
* Exchange code for token.
* One note here. This element is intened to use with applications that test endpoints.
* It asks user to provide `client_secret` parameter and it is not a security concern to him.
* However, this method **can't be used in regular web applications** because it is a
* security risk and whole OAuth token exchange can be compromised. Secrets should never be
* present on client side.
*
* @param {String} code Returned code from the authorization endpoint.
* @return {Promise} Promise with token information.
*/
async _exchangeCode(code) {
const url = this._settings.accessTokenUri;
const body = this._getCodeEchangeBody(this._settings, code);
try {
const tokenInfo = await this._requestToken(url, body, this._settings);
const result = this._handleTokenInfo(tokenInfo);
this.clear();
return result;
} catch (cause) {
this._handleTokenCodeError(cause);
}
}
/**
* Returns a body value for the code exchange request.
* @param {Object} settings Initial settings object.
* @param {String} code Authorization code value returned by the authorization
* server.
* @return {String} Request body.
*/
_getCodeEchangeBody(settings, code) {
let url = 'grant_type=authorization_code';
url += '&client_id=' + encodeURIComponent(settings.clientId);
if (settings.redirectUri) {
url += '&redirect_uri=' + encodeURIComponent(settings.redirectUri);
}
url += '&code=' + encodeURIComponent(code);
if (settings.clientSecret) {
url += '&client_secret=' + encodeURIComponent(settings.clientSecret);
} else {
url += '&client_secret=';
}
return url;
}
/**
* Requests for token from the authorization server for `code`, `password`,
* `client_credentials` and custom grant types.
*
* @param {String} url Base URI of the endpoint. Custom properties will be
* applied to the final URL.
* @param {String} body Generated body for given type. Custom properties will
* be applied to the final body.
* @param {Object} settings Settings object passed to the `authorize()` function
* @return {Promise} Promise resolved to the response string.
*/
_requestToken(url, body, settings) {
if (settings.customData) {
const cs = settings.customData.token;
if (cs) {
url = this._applyCustomSettingsQuery(url, cs);
}
body = this._applyCustomSettingsBody(body, settings.customData);
}
/* global Promise */
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', (e) => this._processTokenResponseHandler(e, resolve, reject));
xhr.addEventListener('error', (e) => this._processTokenResponseErrorHandler(e, reject));
xhr.open('POST', url);
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
if (settings.customData) {
this._applyCustomSettingsHeaders(xhr, settings.customData);
}
try {
xhr.send(body);
} catch (e) {
reject(new Error('Client request error: ' + e.message));
}
});
}
/**
* Handler for the code request load event.
* Processes the response and either rejects the promise with an error
* or resolves it to token info object.
*
* @param {Event} e XHR load event.
* @param {Function} resolve Resolve function
* @param {Function} reject Reject function
*/
_processTokenResponseHandler(e, resolve, reject) {
const status = e.target.status;
const srvResponse = e.target.response;
if (status === 404) {
const message = 'Authorization URI is invalid. Received status 404.';
reject(new Error(message));
return;
} else if (status >= 400 && status < 500) {
const message = 'Client error: ' + srvResponse;
reject(new Error(message));
return;
} else if (status >= 500) {
const message = 'Authorization server error. Response code is ' + status;
reject(new Error(message));
return;
}
let tokenInfo;
try {
tokenInfo = this._processCodeResponse(srvResponse, e.target.getResponseHeader('content-type'));
} catch (e) {
reject(new Error(e.message));
return;
}
resolve(tokenInfo);
}
/**
* Handler for the code request error event.
* Rejects the promise with error description.
*
* @param {Event} e XHR error event
* @param {Function} reject Promise's reject function.
*/
_processTokenResponseErrorHandler(e, reject) {
const status = e.target.status;
let message = 'The request to the authorization server failed.';
if (status) {
message += ' Response code is: ' + status;
}
reject(new Error(message));
}
/**
* Processes token request body and produces map of values.
*
* @param {String} body Body received in the response.
* @param {String} contentType Response content type.
* @return {Object} Response as an object.
* @throws {Error} Exception when body is invalid.
*/
_processCodeResponse(body, contentType) {
if (!body) {
throw new Error('Code response body is empty.');
}
let tokenInfo;
if (contentType.indexOf('json') !== -1) {
tokenInfo = JSON.parse(body);
Object.keys(tokenInfo).forEach((name) => {
const camelName = this._camel(name);
if (camelName) {
tokenInfo[camelName] = tokenInfo[name];
}
});
} else {
tokenInfo = {};
body.split('&').forEach((p) => {
const item = p.split('=');
const name = item[0];
const camelName = this._camel(name);
const value = decodeURIComponent(item[1]);
tokenInfo[name] = value;
tokenInfo[camelName] = value;
});
}
return tokenInfo;
}
/**
* Processes token info object when it's ready.
* Sets `tokenInfo` property, notifies listeners about the response
* and cleans up.
*
* @param {Object} tokenInfo Token info returned from the server.
* @return {Object} The same tokenInfo, used for Promise return value.
*/
_handleTokenInfo(tokenInfo) {
this._tokenInfo = tokenInfo;
tokenInfo.interactive = this._settings.interactive;
if ('error' in tokenInfo) {
this._dispatchError({
message: tokenInfo.errorDescription || 'The request is invalid.',
code: tokenInfo.error,
state: this._state,
interactive: this._settings.interactive
});
} else {
this._dispatchResponse(tokenInfo);
}
if (this.__frameLoadTimeout) {
clearTimeout(this.__frameLoadTimeout);
this.__frameLoadTimeout = undefined;
}
this._settings = undefined;
this._exchangeCodeValue = undefined;
return tokenInfo;
}
/**
* Handler fore an error that happened during code exchange.
* @param {Error} e
*/
_handleTokenCodeError(e) {
this._dispatchError({
message: "Couldn't connect to the server. " + e.message,
code: 'request_error',
state: this._state,
interactive: this._settings.interactive
});
this.clear();
throw e;
}
/**
* Replaces `-` or `_` with camel case.
* @param {String} name The string to process
* @return {String|undefined} Camel cased string or `undefined` if not
* transformed.
*/
_camel(name) {
let i = 0;
let l;
let changed = false;
while ((l = name[i])) {
if ((l === '_' || l === '-') && i + 1 < name.length) {
name = name.substr(0, i) + name[i + 1].toUpperCase() + name.substr(i + 2);
changed = true;
}
i++;
}
return changed ? name : undefined;
}
/**
* Requests a token for `password` request type.
*
* @param {Object} settings The same settings as passed to `authorize()`
* function.
* @return {Promise} Promise resolved to token info.
*/
async authorizePassword(settings) {
this._settings = settings;
const url = settings.accessTokenUri;
const body = this._getPasswordBody(settings);
try {
const tokenInfo = await this._requestToken(url, body, settings);
const result = this._handleTokenInfo(tokenInfo);
this.clear();
return result;
} catch (cause) {
this._handleTokenCodeError(cause);
}
}
/**
* Generates a payload message for password authorization.
*
* @param {Object} settings Settings object passed to the `authorize()`
* function
* @return {String} Message body as defined in OAuth2 spec.
*/
_getPasswordBody(settings) {
let url = 'grant_type=password';
url += '&username=' + encodeURIComponent(settings.username);
url += '&password=' + encodeURIComponent(settings.password);
if (settings.clientId) {
url += '&client_id=' + encodeURIComponent(settings.clientId);
}
if (settings.scopes && settings.scopes.length) {
url += '&scope=' + encodeURIComponent(settings.scopes.join(' '));
}
return url;
}
/**
* Requests a token for `client_credentials` request type.
*
* @param {Object} settings The same settings as passed to `authorize()`
* function.
* @return {Promise} Promise resolved to a token info object.
*/
async authorizeClientCredentials(settings) {
this._settings = settings;
const url = settings.accessTokenUri;
const body = this._getClientCredentialsBody(settings);
try {
const tokenInfo = await this._requestToken(url, body, settings);
const result = this._handleTokenInfo(tokenInfo);
this.clear();
return result;
} catch (cause) {
this._handleTokenCodeError(cause);
}
}
/**
* Generates a payload message for client credentials.
*
* @param {Object} settings Settings object passed to the `authorize()`
* function
* @return {String} Message body as defined in OAuth2 spec.
*/
_getClientCredentialsBody(settings) {
let url = 'grant_type=client_credentials';
if (settings.clientId) {
url += '&client_id=' + encodeURIComponent(settings.clientId);
}
if (settings.clientSecret) {
url += '&client_secret=' + encodeURIComponent(settings.clientSecret);
}
if (settings.scopes && settings.scopes.length) {
url += '&scope=' + this._computeScope(settings.scopes);
}
return url;
}
/**
* Performs authorization on custom grant type.
* This extension is described in OAuth 2.0 spec.
*
* @param {Object} settings Settings object as for `authorize()` function.
* @return {Promise} Promise resolved to a token info object.
*/
async authorizeCustomGrant(settings) {
this._settings = settings;
const url = settings.accessTokenUri;
const body = this._getCustomGrantBody(settings);
try {
const tokenInfo = await this._requestToken(url, body, settings);
const result = this._handleTokenInfo(tokenInfo);
this.clear();
return result;
} catch (cause) {
this._handleTokenCodeError(cause);
}
}
/**
* Creates a body for custom gran type.
* It does not assume any parameter to be required.
* It applies all known OAuth 2.0 parameters and then custom parameters
*
* @param {Object} settings
* @return {String} Request body.
*/
_getCustomGrantBody(settings) {
const parts = ['grant_type=' + encodeURIComponent(settings.type)];
if (settings.clientId) {
parts[parts.length] = 'client_id=' + encodeURIComponent(settings.clientId);
}
if (settings.clientSecret) {
parts[parts.length] = 'client_secret=' + encodeURIComponent(settings.clientSecret);
}
if (settings.scopes && settings.scopes.length) {
parts[parts.length] = 'scope=' + this._computeScope(settings.scopes);
}
if (settings.redirectUri) {
parts[parts.length] = 'redirect_uri=' + encodeURIComponent(settings.redirectUri);
}
if (settings.username) {
parts[parts.length] = 'username=' + encodeURIComponent(settings.username);
}
if (settings.password) {
parts[parts.length] = 'password=' + encodeURIComponent(settings.password);
}
return parts.join('&');
}
/**
* Applies custom properties defined in the OAuth settings object to the URL.
*
* @param {String} url Generated URL for an endpoint.
* @param {?Object} data `customData.[type]` property from the settings object.
* The type is either `auth` or `token`.
* @return {String}
*/
_applyCustomSettingsQuery(url, data) {
if (!data || !data.parameters) {
return url;
}
url += url.indexOf('?') === -1 ? '?' : '&';
url += data.parameters
.map((item) => {
let value = item.value;
if (value) {
value = encodeURIComponent(value);
}
return encodeURIComponent(item.name) + '=' + value;
})
.join('&');
return url;
}
/**
* Applies custom headers from the settings object
*
* @param {XMLHttpRequest} xhr Instance of the request object.
* @param {Object} data Value of settings' `customData` property
*/
_applyCustomSettingsHeaders(xhr, data) {
if (!data || !data.token || !data.token.headers) {
return;
}
data.token.headers.forEach((item) => {
try {
xhr.setRequestHeader(item.name, item.value);
} catch (e) {
noop();
}
});
}
/**
* Applies custom body properties from the settings to the body value.
*
* @param {String} body Already computed body for OAuth request. Custom
* properties are appended at the end of OAuth string.
* @param {Object} data Value of settings' `customData` property
* @return {String} Request body
*/
_applyCustomSettingsBody(body, data) {
if (!data || !data.token || !data.token.body) {
return body;
}
body +=
'&' +
data.token.body
.map(function(item) {
let value = item.value;
if (value) {
value = encodeURIComponent(value);
}
return encodeURIComponent(item.name) + '=' + value;
})
.join('&');
return body;
}
/**
* Dispatches an error event that propagates through the DOM.
*
* @param {Object} detail The detail object.
*/
_dispatchError(detail) {
const e = new CustomEvent('oauth2-error', {
bubbles: true,
composed: true,
detail
});
this.dispatchEvent(e);
}
/**
* Dispatches an event with the authorization code that propagates through the DOM.
* Closes the popup once the authorization code has been dispatched.
*
* @param {Object} detail The detail object.
*/
_dispatchCodeResponse(detail) {
const e = new CustomEvent('oauth2-code-response', {
bubbles: true,
composed: true,
detail
});
this.dispatchEvent(e);
this.clear();
}
/**
* Dispatches an event with the token (e.g. access token) that propagates through the DOM.
*
* @param {Object} detail The detail object.
*/
_dispatchResponse(detail) {
const e = new CustomEvent('oauth2-token-response', {
bubbles: true,
composed: true,
detail
});
this.dispatchEvent(e);
}
/**
* @return {Function} Previously registered handler for `oauth2-error` event
*/
get ontokenerror() {
return this['_onoauth2-error'];
}
/**
* Registers a callback function for `oauth2-error` event
* @param {Function} value A callback to register. Pass `null` or `undefined`
* to clear the listener.
*/
set ontokenerror(value) {
this._registerCallback('oauth2-error', value);
}
/**
* @return {Function} Previously registered handler for `oauth2-token-response` event
*/
get ontokenresponse() {
return this['_onoauth2-token-response'];
}
/**
* Registers a callback function for `oauth2-token-response` event
* @param {Function} value A callback to register. Pass `null` or `undefined`
* to clear the listener.
*/
set ontokenresponse(value) {
this._registerCallback('oauth2-token-response', value);
}
/**
* Registers an event handler for given type
* @param {String} eventType Event type (name)
* @param {Function} value The handler to register
*/
_registerCallback(eventType, value) {
const key = `_on${eventType}`;
if (this[key]) {
this.removeEventListener(eventType, this[key]);
}
if (typeof value !== 'function') {
this[key] = null;
return;
}
this[key] = value;
this.addEventListener(eventType, value);
}
/**
* Fired when OAuth2 token has been received.
* Properties of the `detail` object will contain the response from the authentication server.
* It will contain the original parameteres but also camel case of the parameters.
*
* So for example 'implicit' will be in the response as well as `accessToken` with the same
* value. The puropse of this is to support JS application that has strict formatting rules
* and disallow using '_' in property names. Like ARC.
*
* @event oauth2-token-response
*/
/**
* Fired wne error occurred.
* An error may occure when `state` parameter of the OAuth2 response is different from
* the requested one. Another example is when the popup window has been closed before it passed
* response token. It may happen when the OAuth request was invalid.
*
* @event oauth2-error
* @param {String} message A message that can be displayed to the user.
* @param {String} code A message code: `invalid_state` - when `state` parameter is different;
* `no_response` when the popup was closed before sendin token data; `response_parse` - when
* the response from the code exchange can't be parsed; `request_error` when the request
* errored by the transport library. Other status codes are defined in
* [rfc6749](https://tools.ietf.org/html/rfc6749).
* @param {String} state The `state` parameter either generated by this element
* when requesting the token or passed to the element from other element.
*/
}
import { OAuth2Authorization } from './src/OAuth2Authorization.js';
export { OAuth2Authorization };
window.customElements.define('oauth2-authorization', OAuth2Authorization);
{
"name": "@advanced-rest-client/oauth-authorization",
"description": "A set of elements that perform oauth authorization",
"version": "3.0.2",
"version": "4.0.0",
"license": "Apache-2.0",

@@ -30,2 +30,3 @@ "main": "outh-authorization.js",

"dependencies": {
"@advanced-rest-client/events-target-mixin": "^3.0.0",
"@advanced-rest-client/headers-parser-mixin": "^3.0.0",

@@ -73,7 +74,5 @@ "@polymer/iron-meta": "^3.0.0",

"*.js": [
"eslint --fix",
"prettier --write",
"git add"
"eslint --fix"
]
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc