free-be-account 0.0.26 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +182 -2
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -451,6 +451,16 @@ module.exports = (app) => ({
451
451
  Read: { type: 'Boolean', default: false },
452
452
  Category: { type: 'String' },
453
453
  },
454
+
455
+ // 静态资源访问权限控制
456
+ staticResourcePermissionControl: {
457
+ User: { type: 'String', refer: 'account', required: true }, // 创建用户
458
+ ResourcePath: { type: 'String', index: true, required: true, unique: true }, // 资源路径
459
+
460
+ Users: { type: 'String' }, // 逗号分割的字符串,每一个代表一个允许访问的用户ID, self代表资源创建者本人
461
+ Referers: { type: 'String' }, // 逗号分割的字符串,每一个代表一个允许访问的来源
462
+ Permissions: { type: 'String' }, // 逗号分割的字符串,每一个代表一个api路径
463
+ },
454
464
  },
455
465
  utils: {
456
466
  verify_api_permission,
@@ -1031,10 +1041,33 @@ module.exports = (app) => ({
1031
1041
  const uuid = uuidv1();
1032
1042
 
1033
1043
  res.app.cache.put(`captcha_${uuid}`, captcha.text, m.config.captcha.cache || 300000);
1034
- const matches = captcha.data.match(/d="([^"]*)"/g);
1044
+
1045
+ const pathAndStrokeList = [];
1046
+ // 1. 先把每个 <path .../> 整体拎出来
1047
+ const pathRE = /<path\b[^>]*\/>/gi;
1048
+
1049
+ // 2. 对每个标签再抽属性(顺序无关,可缺)
1050
+ const attrRE = /\b(d|stroke|fill)=["']([^"']*)["']/gi;
1051
+
1052
+ let pathMatch;
1053
+
1054
+ while ((pathMatch = pathRE.exec(captcha.data)) !== null) {
1055
+ const obj = { p: undefined, s: undefined, f: undefined };
1056
+ let attrMatch;
1057
+ attrRE.lastIndex = 0; // 重置内层正则
1058
+ while ((attrMatch = attrRE.exec(pathMatch[0])) !== null) {
1059
+ const [, key, val] = attrMatch;
1060
+ if (key === 'd') obj.p = val;
1061
+ if (key === 'stroke') obj.s = val;
1062
+ if (key === 'fill') obj.f = val;
1063
+ }
1064
+
1065
+ pathAndStrokeList.push(obj);
1066
+ }
1035
1067
 
1036
1068
  res.endWithData({
1037
- captcha: matches.map((m) => m.replace('d="', '').replace('"', '')),
1069
+ captcha: pathAndStrokeList,
1070
+ f: m.config.captcha.options.background,
1038
1071
  id: uuid,
1039
1072
  });
1040
1073
  }
@@ -1560,6 +1593,153 @@ module.exports = (app) => ({
1560
1593
  }
1561
1594
  },
1562
1595
  onRoutersReady: async (app, m) => {
1596
+ // file uploads
1597
+ app.post(
1598
+ `${app.config.baseUrl}/upload`,
1599
+ async (req, res, next) => {
1600
+ if (!res.locals.data?.id) return next();
1601
+ if (!req.body?.perms && !req.body?.refs && !req.body?.users) return next();
1602
+
1603
+ // save the permission control info
1604
+ const assetsFilePath = res.locals.data?.id.split(/[\\|/]/g).slice(-2).join('/');
1605
+ await app.models.staticResourcePermissionControl.create({
1606
+ User: req.user?.id,
1607
+ ResourcePath: assetsFilePath,
1608
+
1609
+ Users: req.body?.users || '',
1610
+ Permissions: req.body?.perms || '',
1611
+ Referers: req.body?.refs || '',
1612
+ })
1613
+
1614
+ return next();
1615
+ }
1616
+ );
1617
+ // multiple files upload
1618
+ app.post(
1619
+ `${app.config.baseUrl}/uploads`,
1620
+ async (req, res, next) => {
1621
+ if (!res.locals.data?.length) return next();
1622
+ if (!req.body?.perms && !req.body?.refs && !req.body?.users) return next();
1623
+
1624
+ // save the permission control info
1625
+ for (let i = 0; i < res.loals.data.length; i += 1) {
1626
+ const resi = res.locals.data[i];
1627
+ if (!resi?.id) continue;
1628
+
1629
+ const assetsFilePath = resi?.id.split(/[\\|/]/g).slice(-2).join('/');
1630
+ await app.models.staticResourcePermissionControl.create({
1631
+ User: req.user?.id,
1632
+ ResourcePath: assetsFilePath,
1633
+
1634
+ Users: req.body?.users || '',
1635
+ Permissions: req.body?.perms || '',
1636
+ Referers: req.body?.refs || '',
1637
+ })
1638
+ }
1639
+
1640
+ return next();
1641
+ }
1642
+ );
1643
+
1644
+ // 静态资源身份验证
1645
+ async function checkPerm (req) {
1646
+ // 验证是静态资源请求
1647
+ const assetsPath = req.originalUrl;
1648
+ if (!assetsPath || !assetsPath.startsWith(app.config.assetsUrlPrefix)) {
1649
+ return false;
1650
+ }
1651
+
1652
+ // 获取当前资源的权限控制
1653
+ const assetsFilePath = assetsPath.replace(app.config.assetsUrlPrefix, '').split(/[\\|/]/g).slice(-2).join('/');
1654
+ const assetsPerm = await app.models.staticResourcePermissionControl.findOne({ ResourcePath: assetsFilePath }).lean();
1655
+
1656
+ // 如果没有保存对应的权限控制,说明是公开资源
1657
+ if (!assetsPerm || (!assetsPerm.Users && !assetsPerm.Permissions && !assetsPerm.Referers)) {
1658
+ return true;
1659
+ }
1660
+
1661
+ // 验证referer。并不完全可靠,但能防一部分无脑盗链
1662
+ // 配置可能太复杂,所以先不需要
1663
+ const neededReferer = assetsPerm.Referers || '';
1664
+
1665
+ // check referrer
1666
+ const referer = req.get('Referer') || '';
1667
+ let refererPath = '/';
1668
+ try {
1669
+ const urlObj = new URL(referer);
1670
+ refererPath = urlObj.pathname;
1671
+ } catch (ex) {
1672
+ refererPath = '/';
1673
+ }
1674
+
1675
+ if (!refererPath || refererPath === '/') {
1676
+ return false;
1677
+ }
1678
+
1679
+ const refererMatched = /^\/pdfjs(_.+)?\/web\/viewer.html$/.test(refererPath) || neededReferer.split(/,|,/).map((nr) => new RegExp(nr)).some((reg) => reg.test(refererPath));
1680
+
1681
+ if (!refererMatched) {
1682
+ return false;
1683
+ }
1684
+
1685
+ // 验证host。并不完全可靠,但能防一部分无脑盗链
1686
+ const host = req.get('Host') || '';
1687
+
1688
+ if (!host || (app.config.host !== host && app.config.host !== `https://${host}` && app.config.host !== `http://${host}`)) {
1689
+ return false;
1690
+ }
1691
+
1692
+ // 验证用户
1693
+ const neededUsers = assetsPerm.Users || '';
1694
+ if (neededUsers) {
1695
+ if (!req.user?.id) return false;
1696
+
1697
+ const userList = neededUsers.split(/,|,/) || [];
1698
+ let userMatched = false;
1699
+ for (let i = 0; i < userList.length; i += 1) {
1700
+ const u = userList[i];
1701
+ if ((u.toLowerCase() === 'self' && req.user.id === assetsPerm.User.toString()) || req.user.id === u) {
1702
+ userMatched = true;
1703
+ break;
1704
+ }
1705
+ }
1706
+
1707
+ if (!userMatched) {
1708
+ return false;
1709
+ }
1710
+ }
1711
+
1712
+ // 验证权限
1713
+ const neededPerms = assetsPerm.Permissions || '';
1714
+ const permList = neededPerms.split(/,|,/) || [];
1715
+ if (permList.length <= 0) {
1716
+ return true;
1717
+ }
1718
+
1719
+ // 验证权限列表
1720
+ if (!req.user?.id) {
1721
+ return false;
1722
+ }
1723
+
1724
+ let hasPerm = false;
1725
+ for (let i = 0; i < permList.length; i += 1) {
1726
+ const p = permList[i];
1727
+ if (verify_api_permission(req.app, m, req.user, p)) {
1728
+ hasPerm = true;
1729
+ break;
1730
+ }
1731
+ }
1732
+
1733
+ return hasPerm;
1734
+ }
1735
+ app.use(`/assets`, async (req, res, next) => {
1736
+ if (await checkPerm(req)) {
1737
+ return next()
1738
+ } else {
1739
+ res.status(404).end();
1740
+ }
1741
+ });
1742
+
1563
1743
  // create default user if it's an empty db
1564
1744
  if (await m.models['account'].countDocuments({}) <= 0) {
1565
1745
  // let perms = app.ctx.serviceList()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-be-account",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "main": "index.js",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {