用工小程序前端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

367 lines
11 KiB

7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
  1. const crypto = require('crypto');
  2. const is = require('is-type-of');
  3. const qs = require('qs');
  4. const { lowercaseKeyHeader } = require('./utils/lowercaseKeyHeader');
  5. const { encodeString } = require('./utils/encodeString');
  6. /**
  7. *
  8. * @param {String} resourcePath
  9. * @param {Object} parameters
  10. * @return
  11. */
  12. exports.buildCanonicalizedResource = function buildCanonicalizedResource(resourcePath, parameters) {
  13. let canonicalizedResource = `${resourcePath}`;
  14. let separatorString = '?';
  15. if (is.string(parameters) && parameters.trim() !== '') {
  16. canonicalizedResource += separatorString + parameters;
  17. } else if (is.array(parameters)) {
  18. parameters.sort();
  19. canonicalizedResource += separatorString + parameters.join('&');
  20. } else if (parameters) {
  21. const processFunc = key => {
  22. canonicalizedResource += separatorString + key;
  23. if (parameters[key] || parameters[key] === 0) {
  24. canonicalizedResource += `=${parameters[key]}`;
  25. }
  26. separatorString = '&';
  27. };
  28. Object.keys(parameters).sort().forEach(processFunc);
  29. }
  30. return canonicalizedResource;
  31. };
  32. /**
  33. * @param {String} method
  34. * @param {String} resourcePath
  35. * @param {Object} request
  36. * @param {String} expires
  37. * @return {String} canonicalString
  38. */
  39. exports.buildCanonicalString = function canonicalString(method, resourcePath, request, expires) {
  40. request = request || {};
  41. const headers = lowercaseKeyHeader(request.headers);
  42. const OSS_PREFIX = 'x-oss-';
  43. const ossHeaders = [];
  44. const headersToSign = {};
  45. let signContent = [
  46. method.toUpperCase(),
  47. headers['content-md5'] || '',
  48. headers['content-type'],
  49. expires || headers['x-oss-date']
  50. ];
  51. Object.keys(headers).forEach(key => {
  52. const lowerKey = key.toLowerCase();
  53. if (lowerKey.indexOf(OSS_PREFIX) === 0) {
  54. headersToSign[lowerKey] = String(headers[key]).trim();
  55. }
  56. });
  57. Object.keys(headersToSign)
  58. .sort()
  59. .forEach(key => {
  60. ossHeaders.push(`${key}:${headersToSign[key]}`);
  61. });
  62. signContent = signContent.concat(ossHeaders);
  63. signContent.push(this.buildCanonicalizedResource(resourcePath, request.parameters));
  64. return signContent.join('\n');
  65. };
  66. /**
  67. * @param {String} accessKeySecret
  68. * @param {String} canonicalString
  69. */
  70. exports.computeSignature = function computeSignature(accessKeySecret, canonicalString, headerEncoding = 'utf-8') {
  71. const signature = crypto.createHmac('sha1', accessKeySecret);
  72. return signature.update(Buffer.from(canonicalString, headerEncoding)).digest('base64');
  73. };
  74. /**
  75. * @param {String} accessKeyId
  76. * @param {String} accessKeySecret
  77. * @param {String} canonicalString
  78. */
  79. exports.authorization = function authorization(accessKeyId, accessKeySecret, canonicalString, headerEncoding) {
  80. return `OSS ${accessKeyId}:${this.computeSignature(accessKeySecret, canonicalString, headerEncoding)}`;
  81. };
  82. /**
  83. * @param {string[]} [additionalHeaders]
  84. * @returns {string[]}
  85. */
  86. exports.fixAdditionalHeaders = additionalHeaders => {
  87. if (!additionalHeaders) {
  88. return [];
  89. }
  90. const OSS_PREFIX = 'x-oss-';
  91. return [...new Set(additionalHeaders.map(v => v.toLowerCase()))]
  92. .filter(v => {
  93. return v !== 'content-type' && v !== 'content-md5' && !v.startsWith(OSS_PREFIX);
  94. })
  95. .sort();
  96. };
  97. /**
  98. * @param {string} method
  99. * @param {Object} request
  100. * @param {Object} request.headers
  101. * @param {Object} [request.queries]
  102. * @param {string} [bucketName]
  103. * @param {string} [objectName]
  104. * @param {string[]} [additionalHeaders] additional headers after deduplication, lowercase and sorting
  105. * @returns {string}
  106. */
  107. exports.getCanonicalRequest = function getCanonicalRequest(method, request, bucketName, objectName, additionalHeaders) {
  108. const headers = lowercaseKeyHeader(request.headers);
  109. const queries = request.queries || {};
  110. const OSS_PREFIX = 'x-oss-';
  111. if (objectName && !bucketName) {
  112. throw Error('Please ensure that bucketName is passed into getCanonicalRequest.');
  113. }
  114. const signContent = [
  115. method.toUpperCase(), // HTTP Verb
  116. encodeString(`/${bucketName ? `${bucketName}/` : ''}${objectName || ''}`).replace(/%2F/g, '/') // Canonical URI
  117. ];
  118. // Canonical Query String
  119. signContent.push(
  120. qs.stringify(queries, {
  121. encoder: encodeString,
  122. sort: (a, b) => a.localeCompare(b),
  123. strictNullHandling: true
  124. })
  125. );
  126. // Canonical Headers
  127. if (additionalHeaders) {
  128. additionalHeaders.forEach(v => {
  129. if (!Object.prototype.hasOwnProperty.call(headers, v)) {
  130. throw Error(`Can't find additional header ${v} in request headers.`);
  131. }
  132. });
  133. }
  134. const tempHeaders = new Set(additionalHeaders);
  135. Object.keys(headers).forEach(v => {
  136. if (v === 'content-type' || v === 'content-md5' || v.startsWith(OSS_PREFIX)) {
  137. tempHeaders.add(v);
  138. }
  139. });
  140. const canonicalHeaders = `${[...tempHeaders]
  141. .sort()
  142. .map(v => `${v}:${is.string(headers[v]) ? headers[v].trim() : headers[v]}\n`)
  143. .join('')}`;
  144. signContent.push(canonicalHeaders);
  145. // Additional Headers
  146. if (additionalHeaders && additionalHeaders.length > 0) {
  147. signContent.push(additionalHeaders.join(';'));
  148. } else {
  149. signContent.push('');
  150. }
  151. // Hashed Payload
  152. signContent.push(headers['x-oss-content-sha256'] || 'UNSIGNED-PAYLOAD');
  153. return signContent.join('\n');
  154. };
  155. /**
  156. * @param {string} date yyyymmdd
  157. * @param {string} region Standard region, e.g. cn-hangzhou
  158. * @param {string} [accessKeyId] Access Key ID
  159. * @param {string} [product] Product name, default is oss
  160. * @returns {string}
  161. */
  162. exports.getCredential = function getCredential(date, region, accessKeyId, product = 'oss') {
  163. const tempCredential = `${date}/${region}/${product}/aliyun_v4_request`;
  164. if (accessKeyId) {
  165. return `${accessKeyId}/${tempCredential}`;
  166. }
  167. return tempCredential;
  168. };
  169. /**
  170. * @param {string} region Standard region, e.g. cn-hangzhou
  171. * @param {string} date ISO8601 UTC:yyyymmdd'T'HHMMss'Z'
  172. * @param {string} canonicalRequest
  173. * @returns {string}
  174. */
  175. exports.getStringToSign = function getStringToSign(region, date, canonicalRequest) {
  176. const stringToSign = [
  177. 'OSS4-HMAC-SHA256',
  178. date, // TimeStamp
  179. this.getCredential(date.split('T')[0], region), // Scope
  180. crypto.createHash('sha256').update(canonicalRequest).digest('hex') // Hashed Canonical Request
  181. ];
  182. return stringToSign.join('\n');
  183. };
  184. /**
  185. * @param {String} accessKeySecret
  186. * @param {string} date yyyymmdd
  187. * @param {string} region Standard region, e.g. cn-hangzhou
  188. * @param {string} stringToSign
  189. * @returns {string}
  190. */
  191. exports.getSignatureV4 = function getSignatureV4(accessKeySecret, date, region, stringToSign) {
  192. const signingDate = crypto.createHmac('sha256', `aliyun_v4${accessKeySecret}`).update(date).digest();
  193. const signingRegion = crypto.createHmac('sha256', signingDate).update(region).digest();
  194. const signingOss = crypto.createHmac('sha256', signingRegion).update('oss').digest();
  195. const signingKey = crypto.createHmac('sha256', signingOss).update('aliyun_v4_request').digest();
  196. const signatureValue = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex');
  197. return signatureValue;
  198. };
  199. /**
  200. * @param {String} accessKeyId
  201. * @param {String} accessKeySecret
  202. * @param {string} region Standard region, e.g. cn-hangzhou
  203. * @param {string} method
  204. * @param {Object} request
  205. * @param {Object} request.headers
  206. * @param {Object} [request.queries]
  207. * @param {string} [bucketName]
  208. * @param {string} [objectName]
  209. * @param {string[]} [additionalHeaders]
  210. * @param {string} [headerEncoding='utf-8']
  211. * @returns {string}
  212. */
  213. exports.authorizationV4 = function authorizationV4(
  214. accessKeyId,
  215. accessKeySecret,
  216. region,
  217. method,
  218. request,
  219. bucketName,
  220. objectName,
  221. additionalHeaders,
  222. headerEncoding = 'utf-8'
  223. ) {
  224. const fixedAdditionalHeaders = this.fixAdditionalHeaders(additionalHeaders);
  225. const fixedHeaders = {};
  226. Object.entries(request.headers).forEach(v => {
  227. fixedHeaders[v[0]] = is.string(v[1]) ? Buffer.from(v[1], headerEncoding).toString() : v[1];
  228. });
  229. const date = fixedHeaders['x-oss-date'] || (request.queries && request.queries['x-oss-date']);
  230. const canonicalRequest = this.getCanonicalRequest(
  231. method,
  232. {
  233. headers: fixedHeaders,
  234. queries: request.queries
  235. },
  236. bucketName,
  237. objectName,
  238. fixedAdditionalHeaders
  239. );
  240. const stringToSign = this.getStringToSign(region, date, canonicalRequest);
  241. const onlyDate = date.split('T')[0];
  242. const signatureValue = this.getSignatureV4(accessKeySecret, onlyDate, region, stringToSign);
  243. const additionalHeadersValue =
  244. fixedAdditionalHeaders.length > 0 ? `AdditionalHeaders=${fixedAdditionalHeaders.join(';')},` : '';
  245. return `OSS4-HMAC-SHA256 Credential=${this.getCredential(onlyDate, region, accessKeyId)},${additionalHeadersValue}Signature=${signatureValue}`;
  246. };
  247. /**
  248. *
  249. * @param {String} accessKeySecret
  250. * @param {Object} options
  251. * @param {String} resource
  252. * @param {Number} expires
  253. */
  254. exports._signatureForURL = function _signatureForURL(accessKeySecret, options = {}, resource, expires, headerEncoding) {
  255. const headers = {};
  256. const { subResource = {} } = options;
  257. if (options.process) {
  258. const processKeyword = 'x-oss-process';
  259. subResource[processKeyword] = options.process;
  260. }
  261. if (options.trafficLimit) {
  262. const trafficLimitKey = 'x-oss-traffic-limit';
  263. subResource[trafficLimitKey] = options.trafficLimit;
  264. }
  265. if (options.response) {
  266. Object.keys(options.response).forEach(k => {
  267. const key = `response-${k.toLowerCase()}`;
  268. subResource[key] = options.response[k];
  269. });
  270. }
  271. Object.keys(options).forEach(key => {
  272. const lowerKey = key.toLowerCase();
  273. const value = options[key];
  274. if (lowerKey.indexOf('x-oss-') === 0) {
  275. headers[lowerKey] = value;
  276. } else if (lowerKey.indexOf('content-md5') === 0) {
  277. headers[key] = value;
  278. } else if (lowerKey.indexOf('content-type') === 0) {
  279. headers[key] = value;
  280. }
  281. });
  282. if (Object.prototype.hasOwnProperty.call(options, 'security-token')) {
  283. subResource['security-token'] = options['security-token'];
  284. }
  285. if (Object.prototype.hasOwnProperty.call(options, 'callback')) {
  286. const json = {
  287. callbackUrl: encodeURI(options.callback.url),
  288. callbackBody: options.callback.body
  289. };
  290. if (options.callback.host) {
  291. json.callbackHost = options.callback.host;
  292. }
  293. if (options.callback.contentType) {
  294. json.callbackBodyType = options.callback.contentType;
  295. }
  296. if (options.callback.callbackSNI) {
  297. json.callbackSNI = options.callback.callbackSNI;
  298. }
  299. subResource.callback = Buffer.from(JSON.stringify(json)).toString('base64');
  300. if (options.callback.customValue) {
  301. const callbackVar = {};
  302. Object.keys(options.callback.customValue).forEach(key => {
  303. callbackVar[`x:${key}`] = options.callback.customValue[key];
  304. });
  305. subResource['callback-var'] = Buffer.from(JSON.stringify(callbackVar)).toString('base64');
  306. }
  307. }
  308. const canonicalString = this.buildCanonicalString(
  309. options.method,
  310. resource,
  311. {
  312. headers,
  313. parameters: subResource
  314. },
  315. expires.toString()
  316. );
  317. return {
  318. Signature: this.computeSignature(accessKeySecret, canonicalString, headerEncoding),
  319. subResource
  320. };
  321. };