@contentgrid/fetch-hook-authentication
Version:
98 lines (95 loc) • 3.88 kB
JavaScript
import { FetchHooksError } from '@contentgrid/fetch-hooks';
import MimeType from 'whatwg-mimetype';
import { ValueProviderResolver } from '@contentgrid/fetch-hooks/value-provider';
function createTokenRequestBody(resource) {
const formData = new URLSearchParams();
formData.append("grant_type", "https://contentgrid.cloud/oauth2/grant/extension-token");
formData.append("resource", resource);
return formData;
}
function createAuthenticationToken(responseBody) {
// Verify response validity: https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
if (!("token_type" in responseBody)) {
throw new TokenExchangeProtocolViolationError(`Missing 'token_type' in response`);
}
if (!("access_token" in responseBody)) {
throw new TokenExchangeProtocolViolationError(`Missing 'access_token' in response`);
}
if (String(responseBody["token_type"]).toLowerCase() !== "bearer") {
throw new TokenExchangeProtocolViolationError(`Unsupported token type ${responseBody["token_type"]}`);
}
const expiry = "expires_in" in responseBody ? new Date(Date.now() + responseBody["expires_in"] * 1000) : null;
return {
token: responseBody["access_token"],
expiresAt: expiry
};
}
function isJsonContentType(contentType) {
if (!contentType) {
return false;
}
const mimetype = new MimeType(contentType);
// https://mimesniff.spec.whatwg.org/#json-mime-type
return (mimetype.type === "application" || mimetype.type === "text") && mimetype.subtype === "json" || mimetype.subtype.endsWith("+json");
}
function createOAuth2Error(responseBody) {
if (!("error" in responseBody)) {
return null; // 'error' field is required in OAuth error responses: https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
}
return new OAuth2AuthenticationError(responseBody["error"], responseBody["error_description"]);
}
function createContentgridTokenExchangeTokenSupplier(config) {
const exchangeUrlResolver = ValueProviderResolver.fromValueProvider(config.exchangeUrl);
return async (uri, opts) => {
var _a;
const exchangeUrl = await exchangeUrlResolver.resolve(opts);
const response = await opts.fetch(exchangeUrl, {
signal: (_a = opts === null || opts === void 0 ? void 0 : opts.signal) !== null && _a !== void 0 ? _a : null,
method: "POST",
body: createTokenRequestBody(uri)
});
if (response.ok) {
if (isJsonContentType(response.headers.get("content-type"))) {
return createAuthenticationToken(await response.json());
} else {
throw new TokenExchangeProtocolViolationError(`Content type ${response.headers.get('content-type')} is not JSON`);
}
} else {
if (isJsonContentType(response.headers.get("content-type"))) {
const oauthError = createOAuth2Error(await response.json());
if (oauthError) {
throw oauthError;
}
}
throw new TokenExchangeProtocolViolationError(`Non-OAuth response error: ${response.status} ${response.statusText}`);
}
};
}
/**
* Base class for token exchange errors
*/
class TokenExchangeError extends FetchHooksError {
constructor(message) {
super(`Token exchange failed: ${message}`);
}
}
/**
* Unexpected deviation from the expected token exchange flow
*/
class TokenExchangeProtocolViolationError extends TokenExchangeError {
constructor(message) {
super(`Protocol violation: ${message}`);
}
}
/**
* A potentially-expected OAuth error response
*/
class OAuth2AuthenticationError extends TokenExchangeError {
constructor(code, description) {
super(`OAuth2 error ${code}: ${description}`);
this.code = code;
this.description = description;
}
}
export { OAuth2AuthenticationError, TokenExchangeError, TokenExchangeProtocolViolationError, createContentgridTokenExchangeTokenSupplier as default };
//# sourceMappingURL=contentgridTokenExchange.mjs.map