UNPKG

@contentgrid/fetch-hook-authentication

Version:
98 lines (95 loc) 3.88 kB
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