80 lines
3.0 KiB
JavaScript
80 lines
3.0 KiB
JavaScript
import { toHex } from "@smithy/util-hex-encoding";
|
|
import { normalizeProvider } from "@smithy/util-middleware";
|
|
import { escapeUri } from "@smithy/util-uri-escape";
|
|
import { toUint8Array } from "@smithy/util-utf8";
|
|
import { getCanonicalQuery } from "./getCanonicalQuery";
|
|
import { iso8601 } from "./utilDate";
|
|
export class SignatureV4Base {
|
|
service;
|
|
regionProvider;
|
|
credentialProvider;
|
|
sha256;
|
|
uriEscapePath;
|
|
applyChecksum;
|
|
constructor({ applyChecksum, credentials, region, service, sha256, uriEscapePath = true, }) {
|
|
this.service = service;
|
|
this.sha256 = sha256;
|
|
this.uriEscapePath = uriEscapePath;
|
|
this.applyChecksum = typeof applyChecksum === "boolean" ? applyChecksum : true;
|
|
this.regionProvider = normalizeProvider(region);
|
|
this.credentialProvider = normalizeProvider(credentials);
|
|
}
|
|
createCanonicalRequest(request, canonicalHeaders, payloadHash) {
|
|
const sortedHeaders = Object.keys(canonicalHeaders).sort();
|
|
return `${request.method}
|
|
${this.getCanonicalPath(request)}
|
|
${getCanonicalQuery(request)}
|
|
${sortedHeaders.map((name) => `${name}:${canonicalHeaders[name]}`).join("\n")}
|
|
|
|
${sortedHeaders.join(";")}
|
|
${payloadHash}`;
|
|
}
|
|
async createStringToSign(longDate, credentialScope, canonicalRequest, algorithmIdentifier) {
|
|
const hash = new this.sha256();
|
|
hash.update(toUint8Array(canonicalRequest));
|
|
const hashedRequest = await hash.digest();
|
|
return `${algorithmIdentifier}
|
|
${longDate}
|
|
${credentialScope}
|
|
${toHex(hashedRequest)}`;
|
|
}
|
|
getCanonicalPath({ path }) {
|
|
if (this.uriEscapePath) {
|
|
const normalizedPathSegments = [];
|
|
for (const pathSegment of path.split("/")) {
|
|
if (pathSegment?.length === 0)
|
|
continue;
|
|
if (pathSegment === ".")
|
|
continue;
|
|
if (pathSegment === "..") {
|
|
normalizedPathSegments.pop();
|
|
}
|
|
else {
|
|
normalizedPathSegments.push(pathSegment);
|
|
}
|
|
}
|
|
const normalizedPath = `${path?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path?.endsWith("/") ? "/" : ""}`;
|
|
const doubleEncoded = escapeUri(normalizedPath);
|
|
return doubleEncoded.replace(/%2F/g, "/");
|
|
}
|
|
return path;
|
|
}
|
|
validateResolvedCredentials(credentials) {
|
|
if (typeof credentials !== "object" ||
|
|
typeof credentials.accessKeyId !== "string" ||
|
|
typeof credentials.secretAccessKey !== "string") {
|
|
throw new Error("Resolved credential object is not valid");
|
|
}
|
|
}
|
|
formatDate(now) {
|
|
const longDate = iso8601(now).replace(/[\-:]/g, "");
|
|
return {
|
|
longDate,
|
|
shortDate: longDate.slice(0, 8),
|
|
};
|
|
}
|
|
getCanonicalHeaderList(headers) {
|
|
return Object.keys(headers).sort().join(";");
|
|
}
|
|
}
|