@okta-dfuhriman/okta-auth-js
Version:
The Okta Auth SDK
227 lines (200 loc) • 6.74 kB
text/typescript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/*!
* Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*
*/
/* eslint-disable complexity */
import { isString, clone, isAbsoluteUrl, removeNils } from '../util';
import { STATE_TOKEN_KEY_NAME, DEFAULT_CACHE_DURATION } from '../constants';
import {
OktaAuthHttpInterface,
RequestOptions,
FetchOptions,
RequestData,
HttpResponse
} from './types';
import { AuthApiError, OAuthError, AuthSdkError, APIError } from '../errors';
type InsufficientAuthenticationError = {
error: string;
// eslint-disable-next-line camelcase
error_description: string;
// eslint-disable-next-line camelcase
max_age: string;
// eslint-disable-next-line camelcase
acr_values: string;
};
const parseInsufficientAuthenticationError = (
header: string
): InsufficientAuthenticationError => {
if (!header) {
throw new AuthSdkError('Missing header string');
}
return header
.split(',')
.map(part => part.trim())
.map(part => part.split('='))
.reduce((acc, curr) => {
// unwrap quotes from value
acc[curr[0]] = curr[1].replace(/^"(.*)"$/, '$1');
return acc;
}, {}) as InsufficientAuthenticationError;
};
const formatError = (sdk: OktaAuthHttpInterface, resp: HttpResponse): AuthApiError | OAuthError => {
let err: AuthApiError | OAuthError;
let serverErr: Record<string, any> = {};
if (resp.responseText && isString(resp.responseText)) {
try {
serverErr = JSON.parse(resp.responseText);
} catch (e) {
serverErr = {
errorSummary: 'Unknown error'
};
}
}
if (resp.status >= 500) {
serverErr.errorSummary = 'Unknown error';
}
if (sdk.options.transformErrorXHR) {
resp = sdk.options.transformErrorXHR(clone(resp));
}
if (serverErr.error && serverErr.error_description) {
err = new OAuthError(serverErr.error, serverErr.error_description);
} else {
err = new AuthApiError(serverErr as APIError, resp);
}
if (resp?.status === 403 && !!resp?.headers?.['www-authenticate']) {
const {
error,
// eslint-disable-next-line camelcase
error_description,
// eslint-disable-next-line camelcase
max_age,
// eslint-disable-next-line camelcase
acr_values
} = parseInsufficientAuthenticationError(resp?.headers?.['www-authenticate']);
if (error === 'insufficient_authentication_context') {
err = new AuthApiError(
{
errorSummary: error,
// eslint-disable-next-line camelcase
errorCauses: [{ errorSummary: error_description }]
},
resp,
{
// eslint-disable-next-line camelcase
max_age: +max_age,
// eslint-disable-next-line camelcase
...(acr_values && { acr_values })
}
);
}
}
return err;
};
export function httpRequest<T = any>(sdk: OktaAuthHttpInterface, options: RequestOptions): Promise<T> {
options = options || {};
if (sdk.options.httpRequestInterceptors) {
for (const interceptor of sdk.options.httpRequestInterceptors) {
interceptor(options);
}
}
var url = options.url,
method = options.method,
args = options.args,
saveAuthnState = options.saveAuthnState,
accessToken = options.accessToken,
withCredentials = options.withCredentials === true, // default value is false
storageUtil = sdk.options.storageUtil,
storage = storageUtil!.storage,
httpCache = sdk.storageManager.getHttpCache(sdk.options.cookies);
if (options.cacheResponse) {
var cacheContents = httpCache.getStorage();
var cachedResponse = cacheContents[url as string];
if (cachedResponse && Date.now()/1000 < cachedResponse.expiresAt) {
return Promise.resolve(cachedResponse.response);
}
}
var oktaUserAgentHeader = sdk._oktaUserAgent.getHttpHeader();
var headers: HeadersInit = {
'Accept': 'application/json',
'Content-Type': 'application/json',
...oktaUserAgentHeader
};
Object.assign(headers, sdk.options.headers, options.headers);
headers = removeNils(headers) as HeadersInit;
if (accessToken && isString(accessToken)) {
headers['Authorization'] = 'Bearer ' + accessToken;
}
var ajaxOptions: FetchOptions = {
headers,
data: args || undefined,
withCredentials
};
var err, res;
return sdk.options.httpRequestClient!(method!, url!, ajaxOptions)
.then(function(resp) {
res = resp.responseText;
if (res && isString(res)) {
res = JSON.parse(res);
if (res && typeof res === 'object' && !res.headers) {
if (Array.isArray(res)) {
res.forEach(item => {
item.headers = resp.headers;
});
} else {
res.headers = resp.headers;
}
}
}
if (saveAuthnState) {
if (!res.stateToken) {
storage.delete(STATE_TOKEN_KEY_NAME);
}
}
if (res && res.stateToken && res.expiresAt) {
storage.set(STATE_TOKEN_KEY_NAME, res.stateToken, res.expiresAt, sdk.options.cookies!);
}
if (res && options.cacheResponse) {
httpCache.updateStorage(url!, {
expiresAt: Math.floor(Date.now()/1000) + DEFAULT_CACHE_DURATION,
response: res
});
}
return res;
})
.catch(function(resp) {
err = formatError(sdk, resp);
if (err.errorCode === 'E0000011') {
storage.delete(STATE_TOKEN_KEY_NAME);
}
throw err;
});
}
export function get(sdk: OktaAuthHttpInterface, url: string, options?: RequestOptions) {
url = isAbsoluteUrl(url) ? url : sdk.getIssuerOrigin() + url;
var getOptions = {
url: url,
method: 'GET'
};
Object.assign(getOptions, options);
return httpRequest(sdk, getOptions);
}
export function post(sdk: OktaAuthHttpInterface, url: string, args?: RequestData, options?: RequestOptions) {
url = isAbsoluteUrl(url) ? url : sdk.getIssuerOrigin() + url;
var postOptions = {
url: url,
method: 'POST',
args: args,
saveAuthnState: true
};
Object.assign(postOptions, options);
return httpRequest(sdk, postOptions);
}