sts.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // 临时密钥服务例子
  2. var bodyParser = require('body-parser');
  3. var STS = require('qcloud-cos-sts');
  4. var express = require('express');
  5. var crypto = require('crypto');
  6. var pathLib = require('path');
  7. var fs = require('fs');
  8. // 配置参数
  9. var config = {
  10. secretId: process.env.SecretId,
  11. secretKey: process.env.SecretKey,
  12. proxy: process.env.Proxy,
  13. durationSeconds: 1800,
  14. bucket: process.env.Bucket,
  15. region: process.env.Region,
  16. // 允许操作(上传)的对象前缀,可以根据自己网站的用户登录态判断允许上传的目录,例子: user1/* 或者 * 或者a.jpg
  17. // 请注意当使用 * 时,可能存在安全风险,详情请参阅:https://cloud.tencent.com/document/product/436/40265
  18. allowPrefix: '_ALLOW_DIR_/*',
  19. // 密钥的权限列表
  20. allowActions: [
  21. // 所有 action 请看文档 https://cloud.tencent.com/document/product/436/31923
  22. // 简单上传
  23. 'name/cos:PutObject',
  24. 'name/cos:PostObject',
  25. // 分片上传
  26. 'name/cos:InitiateMultipartUpload',
  27. 'name/cos:ListMultipartUploads',
  28. 'name/cos:ListParts',
  29. 'name/cos:UploadPart',
  30. 'name/cos:CompleteMultipartUpload'
  31. ],
  32. };
  33. // 创建临时密钥服务和用于调试的静态服务
  34. var app = express();
  35. var replaceBucketRegion = (filePath) => {
  36. return (req, res, next) => {
  37. var content = fs.readFileSync(filePath).toString()
  38. .replace(/(var config = {\r?\n *Bucket: ')test-1250000000(',\r?\n *Region: ')ap-guangzhou(',?\r?\n};?)/,
  39. '$1' + config.bucket + '$2' + config.region +'$3');
  40. content = content.replace("config.Uin = '10001';", "config.Uin = '" + process.env.Uin + "'");
  41. res.header('Content-Type', 'application/javascript');
  42. res.send(content);
  43. };
  44. };
  45. app.use('/demo/demo.js', replaceBucketRegion(pathLib.resolve(__dirname, '../demo/demo.js')));
  46. app.use('/test/test.js', replaceBucketRegion(pathLib.resolve(__dirname, '../test/test.js')));
  47. app.use('/dist/', express.static(pathLib.resolve(__dirname, '../dist')));
  48. app.use('/demo/', express.static(pathLib.resolve(__dirname, '../demo')));
  49. app.use('/test/', express.static(pathLib.resolve(__dirname, '../test')));
  50. app.all('/', (req, res, next) => res.redirect('/demo/'));
  51. app.use(bodyParser.json());
  52. // 格式一:临时密钥接口
  53. app.all('/sts', function (req, res, next) {
  54. // TODO 这里根据自己业务需要做好放行判断
  55. if (config.allowPrefix === '_ALLOW_DIR_/*') {
  56. res.send({error: '请修改 allowPrefix 配置项,指定允许上传的路径前缀'});
  57. return;
  58. }
  59. // 获取临时密钥
  60. var LongBucketName = config.bucket;
  61. var ShortBucketName = LongBucketName.substr(0, LongBucketName.lastIndexOf('-'));
  62. var AppId = LongBucketName.substr(LongBucketName.lastIndexOf('-') + 1);
  63. // 数据万象DescribeMediaBuckets接口需要resource为*,参考 https://cloud.tencent.com/document/product/460/41741
  64. var policy = {
  65. 'version': '2.0',
  66. 'statement': [{
  67. 'action': config.allowActions,
  68. 'effect': 'allow',
  69. 'resource': [
  70. 'qcs::cos:' + config.region + ':uid/' + AppId + ':prefix//' + AppId + '/' + ShortBucketName + '/' + config.allowPrefix,
  71. ],
  72. }],
  73. };
  74. var startTime = Math.round(Date.now() / 1000);
  75. STS.getCredential({
  76. secretId: config.secretId,
  77. secretKey: config.secretKey,
  78. proxy: config.proxy,
  79. region: config.region,
  80. durationSeconds: config.durationSeconds,
  81. policy: policy,
  82. }, function (err, tempKeys) {
  83. if (tempKeys) tempKeys.startTime = startTime;
  84. res.send(err || tempKeys);
  85. });
  86. });
  87. // // 格式二:临时密钥接口,支持细粒度权限控制
  88. // // 判断是否允许获取密钥
  89. // var allowScope = function (scope) {
  90. // var allow = (scope || []).every(function (item) {
  91. // return config.allowActions.includes(item.action) &&
  92. // item.bucket === config.bucket &&
  93. // item.region === config.region &&
  94. // (item.prefix || '').startsWith(config.allowPrefix);
  95. // });
  96. // return allow;
  97. // };
  98. // app.all('/sts-scope', function (req, res, next) {
  99. // var scope = req.body;
  100. //
  101. // // TODO 这里根据自己业务需要做好放行判断
  102. // if (config.allowPrefix === '_ALLOW_DIR_/*') {
  103. // res.send({error: '请修改 allowPrefix 配置项,指定允许上传的路径前缀'});
  104. // return;
  105. // }
  106. // // TODO 这里可以判断 scope 细粒度控制权限
  107. // if (!scope || !scope.length || !allowScope(scope)) return res.send({error: 'deny'});
  108. //
  109. // // 获取临时密钥
  110. // var policy = STS.getPolicy(scope);
  111. // var startTime = Math.round(Date.now() / 1000);
  112. // STS.getCredential({
  113. // secretId: config.secretId,
  114. // secretKey: config.secretKey,
  115. // proxy: config.proxy,
  116. // durationSeconds: config.durationSeconds,
  117. // policy: policy,
  118. // }, function (err, tempKeys) {
  119. // if (tempKeys) tempKeys.startTime = startTime;
  120. // res.send(err || tempKeys);
  121. // });
  122. // });
  123. //
  124. // // 用于 PostObject 签名保护
  125. // app.all('/post-policy', function (req, res, next) {
  126. // var query = req.query;
  127. // var now = Math.round(Date.now() / 1000);
  128. // var exp = now + 900;
  129. // var qKeyTime = now + ';' + exp;
  130. // var qSignAlgorithm = 'sha1';
  131. // var policy = JSON.stringify({
  132. // 'expiration': new Date(exp * 1000).toISOString(),
  133. // 'conditions': [
  134. // // {'acl': query.ACL},
  135. // // ['starts-with', '$Content-Type', 'image/'],
  136. // // ['starts-with', '$success_action_redirect', redirectUrl],
  137. // // ['eq', '$x-cos-server-side-encryption', 'AES256'],
  138. // {'q-sign-algorithm': qSignAlgorithm},
  139. // {'q-ak': config.secretId},
  140. // {'q-sign-time': qKeyTime},
  141. // {'bucket': config.bucket},
  142. // {'key': query.key},
  143. // ]
  144. // });
  145. //
  146. // // 签名算法说明文档:https://www.qcloud.com/document/product/436/7778
  147. // // 步骤一:生成 SignKey
  148. // var signKey = crypto.createHmac('sha1', config.secretKey).update(qKeyTime).digest('hex');
  149. //
  150. // // 步骤二:生成 StringToSign
  151. // var stringToSign = crypto.createHash('sha1').update(policy).digest('hex');
  152. //
  153. // // 步骤三:生成 Signature
  154. // var qSignature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex');
  155. //
  156. // console.log(policy);
  157. // res.send({
  158. // policyObj: JSON.parse(policy),
  159. // policy: Buffer.from(policy).toString('base64'),
  160. // qSignAlgorithm: qSignAlgorithm,
  161. // qAk: config.secretId,
  162. // qKeyTime: qKeyTime,
  163. // qSignature: qSignature,
  164. // // securityToken: securityToken, // 如果使用临时密钥,要返回在这个资源 sessionToken 的值
  165. // });
  166. // });
  167. //
  168. // // 上传限制 Content-Type 示例,对应示例 demo/mime-limit.html
  169. // var COS = require('cos-nodejs-sdk-v5');
  170. // var cos = new COS({
  171. // SecretId: config.secretId,
  172. // SecretKey: config.secretKey,
  173. // });
  174. // app.post('/uploadSign', function (req, res, next) {
  175. //
  176. // var T = function (x, n) {
  177. // return ('0000' + x).slice(-(n || 2));
  178. // }
  179. // var guid = function () {
  180. // var S4 = () => (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
  181. // return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
  182. // }
  183. //
  184. // // 后端来决定文件名,安全性更高
  185. // var filename = req.query.filename; // 前端传文件原名,后端来决定上传路径
  186. // var ext = pathLib.extname(filename);
  187. // var d = new Date();
  188. // var key = `images/${d.getFullYear()}/${T(d.getMonth() + 1)}/${T(d.getDate())}/${guid()}${ext}`;
  189. //
  190. // // 计算前端可能会用到的多个签名,x-cos-mime-limit: text/plain;img/jpg;img/*
  191. // var signMap = {};
  192. // var expires = 7200;
  193. // var mimeLimit = 'image/*';
  194. // var host = `${config.bucket}.cos.${config.region}.myqcloud.com`;
  195. // // 1. ListMultipartUploads 签名
  196. // signMap.ListMultipartUploads = cos.getAuth({
  197. // Method: 'GET',
  198. // Key: '',
  199. // Expires: expires,
  200. // Query: { uploads: '', prefix: key },
  201. // Headers: { host: host },
  202. // });
  203. // // 2. ListParts 签名
  204. // signMap.ListParts = cos.getAuth({
  205. // Method: 'GET',
  206. // Key: key,
  207. // Expires: expires,
  208. // Headers: { host: host },
  209. // });
  210. // // 3. InitiateMultipartUpload 签名
  211. // signMap.InitiateMultipartUpload = cos.getAuth({
  212. // Method: 'POST',
  213. // Key: key,
  214. // Expires: expires,
  215. // Query: { uploads: '' },
  216. // Headers: { host: host },
  217. // });
  218. // // 4. UploadPart 签名
  219. // signMap.UploadPart = cos.getAuth({
  220. // Method: 'PUT',
  221. // Key: key,
  222. // Expires: expires,
  223. // Headers: { host: host, 'x-cos-mime-limit': mimeLimit },
  224. // });
  225. // // 5. CompleteMultipartUpload 签名
  226. // signMap.CompleteMultipartUpload = cos.getAuth({
  227. // Method: 'POST',
  228. // Key: key,
  229. // Expires: expires,
  230. // Headers: { host: host },
  231. // });
  232. // // 6. PutObject 签名
  233. // signMap.PutObject = cos.getAuth({
  234. // Method: 'PUT',
  235. // Key: key,
  236. // Expires: expires,
  237. // Headers: { host: host, 'x-cos-mime-limit': mimeLimit },
  238. // });
  239. // res.send({
  240. // code: 0,
  241. // host,
  242. // signMap,
  243. // bucket: config.bucket,
  244. // region: config.region,
  245. // key,
  246. // mimeLimit,
  247. // });
  248. // });
  249. app.all('*', function (req, res, next) {
  250. res.send({code: -1, message: '404 Not Found'});
  251. });
  252. // 启动签名服务
  253. app.listen(3000);
  254. console.log('app is listening at http://127.0.0.1:3000');