Advertisement
minafaw3

sigV4Client

Jan 27th, 2025 (edited)
19
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.08 KB | None | 0 0
  1. import axios from 'axios';
  2. import utils from './utils';
  3.  
  4. const AWS_SHA_256 = 'AWS4-HMAC-SHA256';
  5. const AWS4_REQUEST = 'aws4_request';
  6. const X_AMZ_DATE = 'x-amz-date';
  7. const X_AMZ_SECURITY_TOKEN = 'x-amz-security-token';
  8. const HOST = 'host';
  9. const AUTHORIZATION = 'Authorization';
  10.  
  11. const textEncoder = new TextEncoder();
  12.  
  13. /**
  14. * Converts Uint8Array to a hex string.
  15. */
  16. const toHex = (uint8Array) =>
  17. [...uint8Array].map((b) => b.toString(16).padStart(2, '0')).join('');
  18.  
  19. /**
  20. * Performs SHA-256 hashing.
  21. */
  22. const sha256 = async (str) => {
  23. const hashBuffer = await crypto.subtle.digest('SHA-256', textEncoder.encode(str));
  24. return new Uint8Array(hashBuffer);
  25. };
  26.  
  27. /**
  28. * Generates HMAC-SHA256 signature.
  29. */
  30. const hmacSha256 = async (key, data) => {
  31. const keyBytes = typeof key === 'string' ? textEncoder.encode(key) : key;
  32. const cryptoKey = await crypto.subtle.importKey(
  33. 'raw',
  34. keyBytes,
  35. { name: 'HMAC', hash: 'SHA-256' },
  36. false,
  37. ['sign']
  38. );
  39. return new Uint8Array(await crypto.subtle.sign('HMAC', cryptoKey, textEncoder.encode(data)));
  40. };
  41.  
  42. /**
  43. * Derives AWS SigV4 signing key.
  44. */
  45. const calculateSigningKey = async (secretKey, date, region, service) => {
  46. let kDate = await hmacSha256(`AWS4${secretKey}`, date);
  47. let kRegion = await hmacSha256(kDate, region);
  48. let kService = await hmacSha256(kRegion, service);
  49. return hmacSha256(kService, AWS4_REQUEST);
  50. };
  51.  
  52. /**
  53. * Builds the canonical request for AWS Signature v4.
  54. */
  55. const buildCanonicalRequest = async (method, path, queryParams, headers, payload) => {
  56. const canonicalUri = encodeURI(path);
  57. const canonicalQueryString = new URLSearchParams(queryParams).toString();
  58. const canonicalHeaders = Object.keys(headers)
  59. .sort()
  60. .map((k) => `${k.toLowerCase()}:${headers[k]}`)
  61. .join('\n') + '\n';
  62.  
  63. const signedHeaders = Object.keys(headers)
  64. .map((k) => k.toLowerCase())
  65. .sort()
  66. .join(';');
  67.  
  68. const payloadHashHex = toHex(await sha256(payload || ''));
  69.  
  70. return `${method}\n${canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHashHex}`;
  71. };
  72.  
  73. /**
  74. * Builds the AWS String to Sign.
  75. */
  76. const buildStringToSign = (datetime, credentialScope, hashedCanonicalRequest) =>
  77. `${AWS_SHA_256}\n${datetime}\n${credentialScope}\n${hashedCanonicalRequest}`;
  78.  
  79. /**
  80. * Generates the AWS credential scope.
  81. */
  82. const buildCredentialScope = (datetime, region, service) =>
  83. `${datetime.slice(0, 8)}/${region}/${service}/${AWS4_REQUEST}`;
  84.  
  85. /**
  86. * Constructs the AWS authorization header.
  87. */
  88. const buildAuthorizationHeader = (accessKey, credentialScope, headers, signature) =>
  89. `${AWS_SHA_256} Credential=${accessKey}/${credentialScope}, SignedHeaders=${Object.keys(headers)
  90. .map((k) => k.toLowerCase())
  91. .sort()
  92. .join(';')}, Signature=${signature}`;
  93.  
  94. const sigV4ClientFactory = {
  95. newClient: (config) => {
  96. utils.assertDefined(config.accessKey, 'accessKey');
  97. utils.assertDefined(config.secretKey, 'secretKey');
  98. utils.assertDefined(config.serviceName, 'serviceName');
  99. utils.assertDefined(config.region, 'region');
  100. utils.assertDefined(config.endpoint, 'endpoint');
  101.  
  102. return {
  103. makeRequest: async ({ verb, path, queryParams = {}, headers = {}, body }) => {
  104. headers = utils.copy(headers) || {}; // Ensure headers object exists
  105.  
  106. // Set default headers if missing
  107. headers['Content-Type'] = headers['Content-Type'] || config.defaultContentType || 'application/json';
  108. headers.Accept = headers.Accept || config.defaultAcceptType || 'application/json';
  109.  
  110. if (verb === 'GET') body = '';
  111. else body = body ? JSON.stringify(body) : '';
  112.  
  113. const datetime = new Date().toISOString().replace(/[:-]|\.\d{3}/g, '');
  114. headers[X_AMZ_DATE] = datetime;
  115.  
  116. headers[HOST] = new URL(config.endpoint).hostname;
  117.  
  118. // Build AWS Signature
  119. const canonicalRequest = await buildCanonicalRequest(verb, path, queryParams, headers, body);
  120. const hashedCanonicalRequest = toHex(await sha256(canonicalRequest));
  121.  
  122. const credentialScope = buildCredentialScope(datetime, config.region, config.serviceName);
  123. const stringToSign = buildStringToSign(datetime, credentialScope, hashedCanonicalRequest);
  124. const signingKey = await calculateSigningKey(config.secretKey, datetime.slice(0, 8), config.region, config.serviceName);
  125. const signature = toHex(await hmacSha256(signingKey, stringToSign));
  126.  
  127. // Attach headers
  128. headers[AUTHORIZATION] = buildAuthorizationHeader(config.accessKey, credentialScope, headers, signature);
  129. if (config.sessionToken) headers[X_AMZ_SECURITY_TOKEN] = config.sessionToken;
  130.  
  131. // Construct request URL
  132. let url = `${config.endpoint}${path}`;
  133. const queryString = utils.parseParametersToObject(queryParams, Object.keys(queryParams));
  134. if (queryString) url += `?${new URLSearchParams(queryString).toString()}`;
  135.  
  136. return axios({ method: verb, url, headers, data: body });
  137. },
  138. };
  139. },
  140. };
  141.  
  142. export default sigV4ClientFactory;
  143.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement