lemon-core
Version:
Lemon Serverless Micro-Service Platform
255 lines • 10.6 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.sigV4Client = void 0;
/**
* `sig-v4.ts`
*
* @author Louis <louis@lemoncloud.io>
* @author Claire <claire@lemoncloud.io>
* @date 2024-12-02 initial version
*
* @copyright (C) 2024 LemonCloud Co Ltd. - All Rights Reserved.
*/
const sha256_js_1 = __importDefault(require("crypto-js/sha256.js"));
const hmac_sha256_js_1 = __importDefault(require("crypto-js/hmac-sha256.js"));
const enc_hex_js_1 = __importDefault(require("crypto-js/enc-hex.js"));
const SHA256 = sha256_js_1.default;
const encHex = enc_hex_js_1.default;
const HmacSHA256 = hmac_sha256_js_1.default;
const AWS_SHA_256 = 'AWS4-HMAC-SHA256';
const AWS4_REQUEST = 'aws4_request';
const AWS4 = 'AWS4';
const X_AMZ_DATE = 'x-amz-date';
const X_AMZ_SECURITY_TOKEN = 'x-amz-security-token';
const HOST = 'host';
const AUTHORIZATION = 'Authorization';
const hash = (value) => SHA256(value);
const hexEncode = (value) => value.toString(encHex);
const hmac = (secret, value) => HmacSHA256(value, secret);
const buildCanonicalRequest = (method, path, queryParams, headers, payload) => {
return (method +
'\n' +
buildCanonicalUri(path) +
'\n' +
buildCanonicalQueryString(queryParams) +
'\n' +
buildCanonicalHeaders(headers) +
'\n' +
buildCanonicalSignedHeaders(headers) +
'\n' +
hexEncode(hash(payload)));
};
const hashCanonicalRequest = (request) => hexEncode(hash(request));
const buildCanonicalUri = (uri) => UriEncode(uri);
//* code from: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
//* '*' '+' '!' 같은 특수 문자가 들어갈 경우 인증 에러 발생할 수 있음.
const UriEncode = (input, encodeSlash = false) => {
const result = [];
// const toHexUTF8 = (str: any) => {
// //TOOD - '한글' 인코딩시 utf8 으로 안하는듯.
// str = str || '';
// let hex = '';
// for (let i = 0; i < str.length; i++) {
// hex += '%' + str.charCodeAt(i).toString(16).toUpperCase();
// }
// return hex;
// };
for (let i = 0; i < input.length; i++) {
const ch = input.charAt(i);
if ((ch >= 'A' && ch <= 'Z') ||
(ch >= 'a' && ch <= 'z') ||
(ch >= '0' && ch <= '9') ||
ch === '_' ||
ch === '-' ||
ch === '~' ||
ch === '.') {
result.push(ch);
}
else if (ch == '*') {
//TODO - encodeURIComponent()에서 인코딩이 안되서, 강제로 인코딩됨.
result.push('%2A');
}
else if (ch == '!') {
//TODO - encodeURIComponent()에서 인코딩이 안되서, 강제로 인코딩됨.
result.push('%21');
}
else if (ch == '(') {
//TODO - encodeURIComponent()에서 인코딩이 안되서, 강제로 인코딩됨.
result.push('%28');
}
else if (ch == ')') {
//TODO - encodeURIComponent()에서 인코딩이 안되서, 강제로 인코딩됨.
result.push('%29');
}
else if (ch == '/') {
result.push(encodeSlash ? '%2F' : ch);
}
else {
// result.push(1 ? hexEncode(ch) : toHexUTF8(ch));
result.push(encodeURIComponent(ch));
}
}
// console.log('! encode=',result.join('').toString(), '<-', input);
return result.join('').toString();
};
const buildCanonicalQueryString = (queryParams) => {
if (Object.keys(queryParams).length < 1) {
return '';
}
const sortedQueryParams = [];
for (const property in queryParams) {
if (Object.prototype.hasOwnProperty.call(queryParams, property)) {
sortedQueryParams.push(property);
}
}
sortedQueryParams.sort();
let canonicalQueryString = '';
for (let i = 0; i < sortedQueryParams.length; i++) {
canonicalQueryString +=
sortedQueryParams[i] +
'=' +
//FIX - encoding problem: A-Z, a-z, 0-9, hyphen ( - ), underscore ( _ ), period ( . ), and tilde ( ~ ).
// https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html.
// encodeURIComponent(queryParams[sortedQueryParams[i]]) +
urlencode(queryParams[sortedQueryParams[i]]) +
'&';
}
return canonicalQueryString.substring(0, canonicalQueryString.length - 1);
};
//code from https://www.codeproject.com/Articles/1016044/JavaScript-URL-encode-decode-and-escape
const urlencode = (text) => {
return encodeURIComponent(text)
.replace(/!/g, '%21')
.replace(/'/g, '%27')
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
.replace(/\*/g, '%2A')
.replace(/%20/g, '%20');
};
// function urldecode(text: any) {
// return decodeURIComponent((text + '').replace(/\+/g, '%20'));
// }
const buildCanonicalHeaders = (headers) => {
let canonicalHeaders = '';
const sortedKeys = [];
for (const property in headers) {
if (Object.prototype.hasOwnProperty.call(headers, property)) {
sortedKeys.push(property);
}
}
sortedKeys.sort();
for (let i = 0; i < sortedKeys.length; i++) {
canonicalHeaders += sortedKeys[i].toLowerCase() + ':' + headers[sortedKeys[i]] + '\n';
}
return canonicalHeaders;
};
const buildCanonicalSignedHeaders = (headers) => {
const sortedKeys = [];
for (const property in headers) {
if (Object.prototype.hasOwnProperty.call(headers, property)) {
sortedKeys.push(property.toLowerCase());
}
}
sortedKeys.sort();
return sortedKeys.join(';');
};
const buildStringToSign = (datetime, credentialScope, hashedCanonicalRequest) => {
return AWS_SHA_256 + '\n' + datetime + '\n' + credentialScope + '\n' + hashedCanonicalRequest;
};
const buildCredentialScope = (datetime, region, service) => {
return `${datetime.substr(0, 8)}/${region}/${service}/${AWS4_REQUEST}`;
};
const calculateSigningKey = (secretKey, datetime, region, service) => {
return hmac(hmac(hmac(hmac(`${AWS4}${secretKey}`, datetime.substr(0, 8)), region), service), AWS4_REQUEST);
};
const calculateSignature = (key, stringToSign) => {
return hexEncode(hmac(key, stringToSign));
};
const buildAuthorizationHeader = (accessKey, credentialScope, headers, signature) => {
return `${AWS_SHA_256} Credential=${accessKey}/${credentialScope}, SignedHeaders=${buildCanonicalSignedHeaders(headers)}, Signature=${signature}`;
};
/**
* create the sigV4Client for signed request to AWS
*/
const sigV4Client = (config) => {
const awsSigV4Client = {};
if (!config.accessKey || !config.secretKey) {
return awsSigV4Client;
}
awsSigV4Client.host = config.host;
awsSigV4Client.accessKey = config.accessKey;
awsSigV4Client.secretKey = config.secretKey;
awsSigV4Client.sessionToken = config.sessionToken;
awsSigV4Client.serviceName = config.serviceName || 'execute-api';
awsSigV4Client.region = config.region || 'ap-northeast-2';
awsSigV4Client.defaultAcceptType = config.defaultAcceptType || 'application/json';
awsSigV4Client.defaultContentType = config.defaultContentType || 'application/json';
const invokeUrl = config.endpoint;
const endpoint = /(^https?:\/\/[^/]+)/g.exec(invokeUrl)[1];
const pathComponent = invokeUrl.substring(endpoint.length);
awsSigV4Client.endpoint = endpoint;
awsSigV4Client.pathComponent = pathComponent;
awsSigV4Client.signRequest = (request) => {
const verb = request.method.toUpperCase();
const path = awsSigV4Client.pathComponent + request.path;
const queryParams = request.queryParams || {}; //{ ...request.queryParams };
const headers = request.headers || {}; //{ ...request.headers };
if (!headers['Content-Type']) {
headers['Content-Type'] = awsSigV4Client.defaultContentType;
}
if (!headers['Accept']) {
headers['Accept'] = awsSigV4Client.defaultAcceptType;
}
let body = request.body || {}; //{ ...request.body };
// override request body and set to empty when signing GET requests
if (verb === 'GET' || !request.body) {
body = '';
}
else {
body = body && typeof body === 'object' ? JSON.stringify(body) : body;
}
// If there is no body remove the content-type header so it is not
// included in SigV4 calculation
if (body === '' || body === undefined || body === null) {
delete headers['Content-Type'];
}
const datetime = new Date()
.toISOString()
.replace(/\.\d{3}Z$/, 'Z')
.replace(/[:-]|\.\d{3}/g, '');
headers[X_AMZ_DATE] = datetime;
// let parser = new URL(awsSigV4Client.endpoint);
// headers[HOST] = parser.hostname;
headers[HOST] = awsSigV4Client.host;
const canonicalRequest = buildCanonicalRequest(verb, path, queryParams, headers, body);
const hashedCanonicalRequest = hashCanonicalRequest(canonicalRequest);
const credentialScope = buildCredentialScope(datetime, awsSigV4Client.region, awsSigV4Client.serviceName);
const stringToSign = buildStringToSign(datetime, credentialScope, hashedCanonicalRequest);
const signingKey = calculateSigningKey(awsSigV4Client.secretKey, datetime, awsSigV4Client.region, awsSigV4Client.serviceName);
const signature = calculateSignature(signingKey, stringToSign);
headers[AUTHORIZATION] = buildAuthorizationHeader(awsSigV4Client.accessKey, credentialScope, headers, signature);
if (awsSigV4Client.sessionToken) {
headers[X_AMZ_SECURITY_TOKEN] = awsSigV4Client.sessionToken;
}
delete headers[HOST];
let url = awsSigV4Client.endpoint + path;
const queryString = buildCanonicalQueryString(queryParams);
if (queryString) {
url += `?${queryString}`;
}
if (!headers['Content-Type']) {
headers['Content-Type'] = awsSigV4Client.defaultContentType;
}
return {
headers: headers,
url: url,
$client: awsSigV4Client,
};
};
return awsSigV4Client;
};
exports.sigV4Client = sigV4Client;
//# sourceMappingURL=sig-v4.js.map
;