350 lines
14 KiB
JavaScript
350 lines
14 KiB
JavaScript
'use strict';
|
|
|
|
var types = require('@smithy/types');
|
|
var utilMiddleware = require('@smithy/util-middleware');
|
|
var middlewareSerde = require('@smithy/middleware-serde');
|
|
var protocolHttp = require('@smithy/protocol-http');
|
|
var protocols = require('@smithy/core/protocols');
|
|
|
|
const getSmithyContext = (context) => context[types.SMITHY_CONTEXT_KEY] || (context[types.SMITHY_CONTEXT_KEY] = {});
|
|
|
|
const resolveAuthOptions = (candidateAuthOptions, authSchemePreference) => {
|
|
if (!authSchemePreference || authSchemePreference.length === 0) {
|
|
return candidateAuthOptions;
|
|
}
|
|
const preferredAuthOptions = [];
|
|
for (const preferredSchemeName of authSchemePreference) {
|
|
for (const candidateAuthOption of candidateAuthOptions) {
|
|
const candidateAuthSchemeName = candidateAuthOption.schemeId.split("#")[1];
|
|
if (candidateAuthSchemeName === preferredSchemeName) {
|
|
preferredAuthOptions.push(candidateAuthOption);
|
|
}
|
|
}
|
|
}
|
|
for (const candidateAuthOption of candidateAuthOptions) {
|
|
if (!preferredAuthOptions.find(({ schemeId }) => schemeId === candidateAuthOption.schemeId)) {
|
|
preferredAuthOptions.push(candidateAuthOption);
|
|
}
|
|
}
|
|
return preferredAuthOptions;
|
|
};
|
|
|
|
function convertHttpAuthSchemesToMap(httpAuthSchemes) {
|
|
const map = new Map();
|
|
for (const scheme of httpAuthSchemes) {
|
|
map.set(scheme.schemeId, scheme);
|
|
}
|
|
return map;
|
|
}
|
|
const httpAuthSchemeMiddleware = (config, mwOptions) => (next, context) => async (args) => {
|
|
const options = config.httpAuthSchemeProvider(await mwOptions.httpAuthSchemeParametersProvider(config, context, args.input));
|
|
const authSchemePreference = config.authSchemePreference ? await config.authSchemePreference() : [];
|
|
const resolvedOptions = resolveAuthOptions(options, authSchemePreference);
|
|
const authSchemes = convertHttpAuthSchemesToMap(config.httpAuthSchemes);
|
|
const smithyContext = utilMiddleware.getSmithyContext(context);
|
|
const failureReasons = [];
|
|
for (const option of resolvedOptions) {
|
|
const scheme = authSchemes.get(option.schemeId);
|
|
if (!scheme) {
|
|
failureReasons.push(`HttpAuthScheme \`${option.schemeId}\` was not enabled for this service.`);
|
|
continue;
|
|
}
|
|
const identityProvider = scheme.identityProvider(await mwOptions.identityProviderConfigProvider(config));
|
|
if (!identityProvider) {
|
|
failureReasons.push(`HttpAuthScheme \`${option.schemeId}\` did not have an IdentityProvider configured.`);
|
|
continue;
|
|
}
|
|
const { identityProperties = {}, signingProperties = {} } = option.propertiesExtractor?.(config, context) || {};
|
|
option.identityProperties = Object.assign(option.identityProperties || {}, identityProperties);
|
|
option.signingProperties = Object.assign(option.signingProperties || {}, signingProperties);
|
|
smithyContext.selectedHttpAuthScheme = {
|
|
httpAuthOption: option,
|
|
identity: await identityProvider(option.identityProperties),
|
|
signer: scheme.signer,
|
|
};
|
|
break;
|
|
}
|
|
if (!smithyContext.selectedHttpAuthScheme) {
|
|
throw new Error(failureReasons.join("\n"));
|
|
}
|
|
return next(args);
|
|
};
|
|
|
|
const httpAuthSchemeEndpointRuleSetMiddlewareOptions = {
|
|
step: "serialize",
|
|
tags: ["HTTP_AUTH_SCHEME"],
|
|
name: "httpAuthSchemeMiddleware",
|
|
override: true,
|
|
relation: "before",
|
|
toMiddleware: "endpointV2Middleware",
|
|
};
|
|
const getHttpAuthSchemeEndpointRuleSetPlugin = (config, { httpAuthSchemeParametersProvider, identityProviderConfigProvider, }) => ({
|
|
applyToStack: (clientStack) => {
|
|
clientStack.addRelativeTo(httpAuthSchemeMiddleware(config, {
|
|
httpAuthSchemeParametersProvider,
|
|
identityProviderConfigProvider,
|
|
}), httpAuthSchemeEndpointRuleSetMiddlewareOptions);
|
|
},
|
|
});
|
|
|
|
const httpAuthSchemeMiddlewareOptions = {
|
|
step: "serialize",
|
|
tags: ["HTTP_AUTH_SCHEME"],
|
|
name: "httpAuthSchemeMiddleware",
|
|
override: true,
|
|
relation: "before",
|
|
toMiddleware: middlewareSerde.serializerMiddlewareOption.name,
|
|
};
|
|
const getHttpAuthSchemePlugin = (config, { httpAuthSchemeParametersProvider, identityProviderConfigProvider, }) => ({
|
|
applyToStack: (clientStack) => {
|
|
clientStack.addRelativeTo(httpAuthSchemeMiddleware(config, {
|
|
httpAuthSchemeParametersProvider,
|
|
identityProviderConfigProvider,
|
|
}), httpAuthSchemeMiddlewareOptions);
|
|
},
|
|
});
|
|
|
|
const defaultErrorHandler = (signingProperties) => (error) => {
|
|
throw error;
|
|
};
|
|
const defaultSuccessHandler = (httpResponse, signingProperties) => { };
|
|
const httpSigningMiddleware = (config) => (next, context) => async (args) => {
|
|
if (!protocolHttp.HttpRequest.isInstance(args.request)) {
|
|
return next(args);
|
|
}
|
|
const smithyContext = utilMiddleware.getSmithyContext(context);
|
|
const scheme = smithyContext.selectedHttpAuthScheme;
|
|
if (!scheme) {
|
|
throw new Error(`No HttpAuthScheme was selected: unable to sign request`);
|
|
}
|
|
const { httpAuthOption: { signingProperties = {} }, identity, signer, } = scheme;
|
|
const output = await next({
|
|
...args,
|
|
request: await signer.sign(args.request, identity, signingProperties),
|
|
}).catch((signer.errorHandler || defaultErrorHandler)(signingProperties));
|
|
(signer.successHandler || defaultSuccessHandler)(output.response, signingProperties);
|
|
return output;
|
|
};
|
|
|
|
const httpSigningMiddlewareOptions = {
|
|
step: "finalizeRequest",
|
|
tags: ["HTTP_SIGNING"],
|
|
name: "httpSigningMiddleware",
|
|
aliases: ["apiKeyMiddleware", "tokenMiddleware", "awsAuthMiddleware"],
|
|
override: true,
|
|
relation: "after",
|
|
toMiddleware: "retryMiddleware",
|
|
};
|
|
const getHttpSigningPlugin = (config) => ({
|
|
applyToStack: (clientStack) => {
|
|
clientStack.addRelativeTo(httpSigningMiddleware(), httpSigningMiddlewareOptions);
|
|
},
|
|
});
|
|
|
|
const normalizeProvider = (input) => {
|
|
if (typeof input === "function")
|
|
return input;
|
|
const promisified = Promise.resolve(input);
|
|
return () => promisified;
|
|
};
|
|
|
|
const makePagedClientRequest = async (CommandCtor, client, input, withCommand = (_) => _, ...args) => {
|
|
let command = new CommandCtor(input);
|
|
command = withCommand(command) ?? command;
|
|
return await client.send(command, ...args);
|
|
};
|
|
function createPaginator(ClientCtor, CommandCtor, inputTokenName, outputTokenName, pageSizeTokenName) {
|
|
return async function* paginateOperation(config, input, ...additionalArguments) {
|
|
const _input = input;
|
|
let token = config.startingToken ?? _input[inputTokenName];
|
|
let hasNext = true;
|
|
let page;
|
|
while (hasNext) {
|
|
_input[inputTokenName] = token;
|
|
if (pageSizeTokenName) {
|
|
_input[pageSizeTokenName] = _input[pageSizeTokenName] ?? config.pageSize;
|
|
}
|
|
if (config.client instanceof ClientCtor) {
|
|
page = await makePagedClientRequest(CommandCtor, config.client, input, config.withCommand, ...additionalArguments);
|
|
}
|
|
else {
|
|
throw new Error(`Invalid client, expected instance of ${ClientCtor.name}`);
|
|
}
|
|
yield page;
|
|
const prevToken = token;
|
|
token = get(page, outputTokenName);
|
|
hasNext = !!(token && (!config.stopOnSameToken || token !== prevToken));
|
|
}
|
|
return undefined;
|
|
};
|
|
}
|
|
const get = (fromObject, path) => {
|
|
let cursor = fromObject;
|
|
const pathComponents = path.split(".");
|
|
for (const step of pathComponents) {
|
|
if (!cursor || typeof cursor !== "object") {
|
|
return undefined;
|
|
}
|
|
cursor = cursor[step];
|
|
}
|
|
return cursor;
|
|
};
|
|
|
|
function setFeature(context, feature, value) {
|
|
if (!context.__smithy_context) {
|
|
context.__smithy_context = {
|
|
features: {},
|
|
};
|
|
}
|
|
else if (!context.__smithy_context.features) {
|
|
context.__smithy_context.features = {};
|
|
}
|
|
context.__smithy_context.features[feature] = value;
|
|
}
|
|
|
|
class DefaultIdentityProviderConfig {
|
|
authSchemes = new Map();
|
|
constructor(config) {
|
|
for (const [key, value] of Object.entries(config)) {
|
|
if (value !== undefined) {
|
|
this.authSchemes.set(key, value);
|
|
}
|
|
}
|
|
}
|
|
getIdentityProvider(schemeId) {
|
|
return this.authSchemes.get(schemeId);
|
|
}
|
|
}
|
|
|
|
class HttpApiKeyAuthSigner {
|
|
async sign(httpRequest, identity, signingProperties) {
|
|
if (!signingProperties) {
|
|
throw new Error("request could not be signed with `apiKey` since the `name` and `in` signer properties are missing");
|
|
}
|
|
if (!signingProperties.name) {
|
|
throw new Error("request could not be signed with `apiKey` since the `name` signer property is missing");
|
|
}
|
|
if (!signingProperties.in) {
|
|
throw new Error("request could not be signed with `apiKey` since the `in` signer property is missing");
|
|
}
|
|
if (!identity.apiKey) {
|
|
throw new Error("request could not be signed with `apiKey` since the `apiKey` is not defined");
|
|
}
|
|
const clonedRequest = protocolHttp.HttpRequest.clone(httpRequest);
|
|
if (signingProperties.in === types.HttpApiKeyAuthLocation.QUERY) {
|
|
clonedRequest.query[signingProperties.name] = identity.apiKey;
|
|
}
|
|
else if (signingProperties.in === types.HttpApiKeyAuthLocation.HEADER) {
|
|
clonedRequest.headers[signingProperties.name] = signingProperties.scheme
|
|
? `${signingProperties.scheme} ${identity.apiKey}`
|
|
: identity.apiKey;
|
|
}
|
|
else {
|
|
throw new Error("request can only be signed with `apiKey` locations `query` or `header`, " +
|
|
"but found: `" +
|
|
signingProperties.in +
|
|
"`");
|
|
}
|
|
return clonedRequest;
|
|
}
|
|
}
|
|
|
|
class HttpBearerAuthSigner {
|
|
async sign(httpRequest, identity, signingProperties) {
|
|
const clonedRequest = protocolHttp.HttpRequest.clone(httpRequest);
|
|
if (!identity.token) {
|
|
throw new Error("request could not be signed with `token` since the `token` is not defined");
|
|
}
|
|
clonedRequest.headers["Authorization"] = `Bearer ${identity.token}`;
|
|
return clonedRequest;
|
|
}
|
|
}
|
|
|
|
class NoAuthSigner {
|
|
async sign(httpRequest, identity, signingProperties) {
|
|
return httpRequest;
|
|
}
|
|
}
|
|
|
|
const createIsIdentityExpiredFunction = (expirationMs) => function isIdentityExpired(identity) {
|
|
return doesIdentityRequireRefresh(identity) && identity.expiration.getTime() - Date.now() < expirationMs;
|
|
};
|
|
const EXPIRATION_MS = 300_000;
|
|
const isIdentityExpired = createIsIdentityExpiredFunction(EXPIRATION_MS);
|
|
const doesIdentityRequireRefresh = (identity) => identity.expiration !== undefined;
|
|
const memoizeIdentityProvider = (provider, isExpired, requiresRefresh) => {
|
|
if (provider === undefined) {
|
|
return undefined;
|
|
}
|
|
const normalizedProvider = typeof provider !== "function" ? async () => Promise.resolve(provider) : provider;
|
|
let resolved;
|
|
let pending;
|
|
let hasResult;
|
|
let isConstant = false;
|
|
const coalesceProvider = async (options) => {
|
|
if (!pending) {
|
|
pending = normalizedProvider(options);
|
|
}
|
|
try {
|
|
resolved = await pending;
|
|
hasResult = true;
|
|
isConstant = false;
|
|
}
|
|
finally {
|
|
pending = undefined;
|
|
}
|
|
return resolved;
|
|
};
|
|
if (isExpired === undefined) {
|
|
return async (options) => {
|
|
if (!hasResult || options?.forceRefresh) {
|
|
resolved = await coalesceProvider(options);
|
|
}
|
|
return resolved;
|
|
};
|
|
}
|
|
return async (options) => {
|
|
if (!hasResult || options?.forceRefresh) {
|
|
resolved = await coalesceProvider(options);
|
|
}
|
|
if (isConstant) {
|
|
return resolved;
|
|
}
|
|
if (!requiresRefresh(resolved)) {
|
|
isConstant = true;
|
|
return resolved;
|
|
}
|
|
if (isExpired(resolved)) {
|
|
await coalesceProvider(options);
|
|
return resolved;
|
|
}
|
|
return resolved;
|
|
};
|
|
};
|
|
|
|
Object.defineProperty(exports, "requestBuilder", {
|
|
enumerable: true,
|
|
get: function () { return protocols.requestBuilder; }
|
|
});
|
|
exports.DefaultIdentityProviderConfig = DefaultIdentityProviderConfig;
|
|
exports.EXPIRATION_MS = EXPIRATION_MS;
|
|
exports.HttpApiKeyAuthSigner = HttpApiKeyAuthSigner;
|
|
exports.HttpBearerAuthSigner = HttpBearerAuthSigner;
|
|
exports.NoAuthSigner = NoAuthSigner;
|
|
exports.createIsIdentityExpiredFunction = createIsIdentityExpiredFunction;
|
|
exports.createPaginator = createPaginator;
|
|
exports.doesIdentityRequireRefresh = doesIdentityRequireRefresh;
|
|
exports.getHttpAuthSchemeEndpointRuleSetPlugin = getHttpAuthSchemeEndpointRuleSetPlugin;
|
|
exports.getHttpAuthSchemePlugin = getHttpAuthSchemePlugin;
|
|
exports.getHttpSigningPlugin = getHttpSigningPlugin;
|
|
exports.getSmithyContext = getSmithyContext;
|
|
exports.httpAuthSchemeEndpointRuleSetMiddlewareOptions = httpAuthSchemeEndpointRuleSetMiddlewareOptions;
|
|
exports.httpAuthSchemeMiddleware = httpAuthSchemeMiddleware;
|
|
exports.httpAuthSchemeMiddlewareOptions = httpAuthSchemeMiddlewareOptions;
|
|
exports.httpSigningMiddleware = httpSigningMiddleware;
|
|
exports.httpSigningMiddlewareOptions = httpSigningMiddlewareOptions;
|
|
exports.isIdentityExpired = isIdentityExpired;
|
|
exports.memoizeIdentityProvider = memoizeIdentityProvider;
|
|
exports.normalizeProvider = normalizeProvider;
|
|
exports.setFeature = setFeature;
|