@abtnode/blocklet-services 1.16.47-beta-20250730-070104-87a128a5 → 1.16.47-beta-20250801-094129-1ba43aaa

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 (227) hide show
  1. package/api/index.js +16 -3
  2. package/api/libs/image.js +131 -77
  3. package/api/routes/blocklet.js +47 -32
  4. package/api/routes/csp-proxy.js +129 -14
  5. package/api/routes/user.js +6 -2
  6. package/api/services/auth/connect/verify-destroy.js +24 -2
  7. package/api/services/auth/session.js +6 -2
  8. package/api/services/image/index.js +6 -1
  9. package/dist/assets/AdapterDayjs-BN6xrcXw.js +3 -0
  10. package/dist/assets/{Google-kE2qbn1K.js → Google-BfPT9Xh-.js} +1 -1
  11. package/dist/assets/{access-control-FYs8JoGO.js → access-control-DkEAPrH2.js} +1 -1
  12. package/dist/assets/{actions-DvIZNF27.js → actions-CJMVMPKl.js} +1 -1
  13. package/dist/assets/{add-component-core-BB6rCC8n.js → add-component-core-B_I64xA8.js} +1 -1
  14. package/dist/assets/{add-resource-BlKrIrw0.js → add-resource-OtGLfsSC.js} +1 -1
  15. package/dist/assets/{addon-g3Lz4kfk.js → addon-ByRu0wmY.js} +1 -1
  16. package/dist/assets/{advanced-DyVpf_DB.js → advanced-BnXe4cqK.js} +1 -1
  17. package/dist/assets/aigne-CHLpC-y1.js +10 -0
  18. package/dist/assets/api-B6VBwg-Z.js +1 -0
  19. package/dist/assets/{appearance-D0WJqn3Y.js → appearance-DoL2p8uj.js} +1 -1
  20. package/dist/assets/ar-BmxvvhkF.js +7 -0
  21. package/dist/assets/{arrow-down.svg-CH8Xzna_.js → arrow-down.svg-CTyHGzaE.js} +1 -1
  22. package/dist/assets/{audit-logs-BLZ_FXya.js → audit-logs-CYCXwJKF.js} +1 -1
  23. package/dist/assets/authorize-CgriXRp-.js +1 -0
  24. package/dist/assets/{base-chart-Dhrxq8mT.js → base-chart-Bq5ougoU.js} +1 -1
  25. package/dist/assets/{base32-B9yl211y.js → base32-CiFWWnw3.js} +1 -1
  26. package/dist/assets/{branding-B_9Ik1Ul.js → branding-BHOpQPVg.js} +1 -1
  27. package/dist/assets/{branding-CbIhan7S.js → branding-BVhkSHKy.js} +2 -2
  28. package/dist/assets/{bundle-avatar-DzIGEDbC.js → bundle-avatar-EjC4ul9m.js} +1 -1
  29. package/dist/assets/button-DeYwq4G-.js +1 -0
  30. package/dist/assets/{click-to-copy-C43vd7DG.js → click-to-copy-BAVxsRT1.js} +1 -1
  31. package/dist/assets/{cloneDeep-DwV-xVQF.js → cloneDeep-Bp5oPxU3.js} +1 -1
  32. package/dist/assets/{collapse-B0wJmKRl.js → collapse-C3V-lD7M.js} +1 -1
  33. package/dist/assets/{complete-Cx5YFdNr.js → complete-E7j8kDa5.js} +1 -1
  34. package/dist/assets/{component-99-uAXc0.js → component-CDAFdcnx.js} +56 -72
  35. package/dist/assets/{config-Dr0r0k-g.js → config-B6qd-J_H.js} +1 -1
  36. package/dist/assets/{config-B4lheVsX.js → config-Dlwx7WyO.js} +1 -1
  37. package/dist/assets/config-FumbXhpa.js +263 -0
  38. package/dist/assets/{config-navigation-ToPhdOjg.js → config-navigation-Cr1v50S5.js} +3 -3
  39. package/dist/assets/{config-space-CT21iR_2.js → config-space-OVPR-H3Q.js} +1 -1
  40. package/dist/assets/{confirm-5e46DDlz.js → confirm-DSkTNclg.js} +1 -1
  41. package/dist/assets/{connect-hooMfIaO.js → connect-BKQ09vcI.js} +1 -1
  42. package/dist/assets/{connect-BHDDk939.js → connect-VkcmO-ox.js} +1 -1
  43. package/dist/assets/{connect-to-DfxSuVNx.js → connect-to-D4y7gfX6.js} +1 -1
  44. package/dist/assets/{content-layout--aahhgT-.js → content-layout-CT98QJfh.js} +1 -1
  45. package/dist/assets/{createClass-BuE_i6UP.js → createClass-BxvbX2Fy.js} +1 -1
  46. package/dist/assets/dashboard-CCmzTOat.js +106 -0
  47. package/dist/assets/de-7aojY3wG.js +7 -0
  48. package/dist/assets/{delete-confirm-Bim07gVu.js → delete-confirm-CZyKrY8X.js} +1 -1
  49. package/dist/assets/{did-address-DrmEnz9K.js → did-address-D1KZLP2_.js} +1 -1
  50. package/dist/assets/{domain-D_RRXHNv.js → domain-BLaBUwtN.js} +1 -1
  51. package/dist/assets/{domain-action-card-BTosQrln.js → domain-action-card-CtdHon3A.js} +1 -1
  52. package/dist/assets/domains-C4bCGzUa.js +1 -0
  53. package/dist/assets/{dot-BaR-8Dj-.js → dot-BD1gx_6x.js} +1 -1
  54. package/dist/assets/{email-BCDbiSlJ.js → email-DprB1ao7.js} +2 -2
  55. package/dist/assets/{empty-spinner-D6ajz4La.js → empty-spinner-BMuZ6jkU.js} +1 -1
  56. package/dist/assets/engine-BYtxLgE-.js +1 -0
  57. package/dist/assets/es-CEkR13ZB.js +9 -0
  58. package/dist/assets/{exchange-passport-DIp9EJVY.js → exchange-passport-Ba2lo0my.js} +1 -1
  59. package/dist/assets/{form-BVnHvgNl.js → form-bIhrdHBk.js} +1 -1
  60. package/dist/assets/{form-text-input-x3g3qd72.js → form-text-input-DpFFySn2.js} +1 -1
  61. package/dist/assets/{form-wrapper-DimyA4Kf.js → form-wrapper-CpayHdi1.js} +1 -1
  62. package/dist/assets/fr-Blm8Lvo8.js +7 -0
  63. package/dist/assets/{fuel-Fh-ifxfq.js → fuel-DrfkIg-0.js} +1 -1
  64. package/dist/assets/{gen-access-key-BZufQ91-.js → gen-access-key-CM92my-2.js} +1 -1
  65. package/dist/assets/{gen-simple-access-key-DlPE_0y2.js → gen-simple-access-key-BmVdze52.js} +1 -1
  66. package/dist/assets/get-safe-url-CzyoiDvV.js +1 -0
  67. package/dist/assets/hi-D-gLlKa4.js +5 -0
  68. package/dist/assets/{home-B6em2u64.js → home-vNW_uCnj.js} +1 -1
  69. package/dist/assets/id-Dp1w98Mc.js +7 -0
  70. package/dist/assets/{iframe-Dl-5xRyx.js → iframe-H_M17Rw0.js} +1 -1
  71. package/dist/assets/{index-BituGg_i.js → index-5QMAQjkj.js} +1 -1
  72. package/dist/assets/{index-DIeXMLLs.js → index-9LrJ71wP.js} +1 -1
  73. package/dist/assets/index-B-nFCTvl.js +1 -0
  74. package/dist/assets/{index-B_JPa3k-.js → index-B7WwBeaA.js} +1 -1
  75. package/dist/assets/{index-Eagwj9HU.js → index-BCK2cqC6.js} +1 -1
  76. package/dist/assets/{index-D6ZAoKh5.js → index-BRPXjkAq.js} +1 -1
  77. package/dist/assets/{index-DoJKIwOt.js → index-BaofAoCa.js} +1 -1
  78. package/dist/assets/{index-DLDRrQJH.js → index-BeaB_cKh.js} +1 -1
  79. package/dist/assets/{index-DEDOywis.js → index-Bfm7dO_D.js} +1 -1
  80. package/dist/assets/{index-D6YhMyoB.js → index-Bm3aC1-S.js} +63 -63
  81. package/dist/assets/{index-DscHS5BG.js → index-Bp3op3AB.js} +1 -1
  82. package/dist/assets/{index-YhHgNT9f.js → index-BuOGtUh1.js} +1 -1
  83. package/dist/assets/index-ByXiBZvQ.js +1 -0
  84. package/dist/assets/{index-DZFL6rrK.js → index-CN_vv3DD.js} +1 -1
  85. package/dist/assets/{index-B88jr6sm.js → index-CUl7rwhw.js} +1 -1
  86. package/dist/assets/{index-DraljbB7.js → index-CUtbL7Pc.js} +9 -9
  87. package/dist/assets/{index-Cq-4YOTs.js → index-Co2hRThu.js} +1 -1
  88. package/dist/assets/{index-DmgU5YEy.js → index-CzWvmP1s.js} +1 -1
  89. package/dist/assets/{index-ckUPtmoF.js → index-D7O86bEq.js} +2 -2
  90. package/dist/assets/{index-DTuL5S3g.js → index-DKqTApAY.js} +1 -1
  91. package/dist/assets/{index-CkuzlAat.js → index-DZqnYK-R.js} +1 -1
  92. package/dist/assets/{index-1-P2gWGZ.js → index-DefvhIEx.js} +1 -1
  93. package/dist/assets/{index-W7Vsgxmq.js → index-DhpSUbJa.js} +19 -19
  94. package/dist/assets/{index-CC1_0xzr.js → index-Du6WLi38.js} +4 -4
  95. package/dist/assets/{index-B6RoOcNF.js → index-DuiAdMaP.js} +1 -1
  96. package/dist/assets/{index-h4nZrNlX.js → index-IQj51U6i.js} +1 -1
  97. package/dist/assets/{index-BFAR00t8.js → index-Vc0vthSd.js} +1 -1
  98. package/dist/assets/index-ZAHGzjDt.js +124 -0
  99. package/dist/assets/{index-CiFQnBvO.js → index-aJi9Bf-D.js} +1 -1
  100. package/dist/assets/{index-Ca2q4nT6.js → index-j-o7Fhl2.js} +1 -1
  101. package/dist/assets/{index-DiW2Y89J.js → index-six7TSvm.js} +10 -10
  102. package/dist/assets/{index-D8gGAVdB.js → index-vq54lJAL.js} +1 -1
  103. package/dist/assets/{invitation-ygFxiD0p.js → invitation-BsCAwa_S.js} +6 -6
  104. package/dist/assets/invite-BgoFaNhy.js +1 -0
  105. package/dist/assets/{isURL-CTW__dzu.js → isURL-BpO9T7jO.js} +1 -1
  106. package/dist/assets/issue-passport-BukWrnf4.js +1 -0
  107. package/dist/assets/{item-BpwJ2QYy.js → item-lF1pMg0y.js} +1 -1
  108. package/dist/assets/ja-DYfvAc_h.js +9 -0
  109. package/dist/assets/ko-DeeF0knw.js +9 -0
  110. package/dist/assets/{landing-page-Cd61UT7Z.js → landing-page-Bje6A6LW.js} +1 -1
  111. package/dist/assets/{launch-result-message-C7170Bvt.js → launch-result-message-DDqxU5ge.js} +1 -1
  112. package/dist/assets/{layout-Bf7XRYSR.js → layout-ffvsYKlt.js} +1 -1
  113. package/dist/assets/{list-BHhWPWs4.js → list-D1xRxqmd.js} +2 -2
  114. package/dist/assets/{list-A-6ZKqgn.js → list-d3KGpyvC.js} +1 -1
  115. package/dist/assets/{list-header-X0CL-_hO.js → list-header-BDPLDqO-.js} +1 -1
  116. package/dist/assets/localization-D8EUC9hz.js +1 -0
  117. package/dist/assets/{log-ryKQFCdM.js → log-DaUV-KQg.js} +1 -1
  118. package/dist/assets/logger-BkCCiTnd.js +1 -0
  119. package/dist/assets/{login-CYXIFDX8.js → login-C_FzEtDr.js} +1 -1
  120. package/dist/assets/{login-oauth-callback-DAKrg2Ne.js → login-oauth-callback-Br-fBLIM.js} +1 -1
  121. package/dist/assets/{logo-uploader-DDncXmM_.js → logo-uploader-DpVGDA9S.js} +2 -2
  122. package/dist/assets/{lost-passport-DEoDgh2I.js → lost-passport-7dfs_oPd.js} +1 -1
  123. package/dist/assets/{observability-DVi-zwiF.js → observability-C5Y6fafw.js} +1 -1
  124. package/dist/assets/{omit-8QDODHip.js → omit-DDVAqzZq.js} +1 -1
  125. package/dist/assets/{open-window-eYnS4Amh.js → open-window-DZM6BqqN.js} +1 -1
  126. package/dist/assets/{over-due-invoice-payment-CnaSUbBp.js → over-due-invoice-payment-UPFNS992.js} +1 -1
  127. package/dist/assets/{overview-BbhCPjub.js → overview-D-k9QnCr.js} +1 -1
  128. package/dist/assets/{page-header-1pVKKoSa.js → page-header-B9Q5ZOnJ.js} +1 -1
  129. package/dist/assets/{passport-item-CPGYYOeu.js → passport-item-tw8M12dr.js} +1 -1
  130. package/dist/assets/{permission-B0EjzZOA.js → permission-tgoZwbT0.js} +1 -1
  131. package/dist/assets/{playground-CveQRqPB.js → playground-DUuQxqZF.js} +1 -1
  132. package/dist/assets/preferences-BzGoZ4H7.js +1 -0
  133. package/dist/assets/profile-embed-UV7c2kes.js +1 -0
  134. package/dist/assets/pt-B2XwgtkL.js +5 -0
  135. package/dist/assets/publish-resource-DzuGaf5F.js +1 -0
  136. package/dist/assets/{react-beautiful-dnd.esm-CdYP6hjZ.js → react-beautiful-dnd.esm-Bn1B6RSE.js} +1 -1
  137. package/dist/assets/{react-stripe.esm-3alxb6kT.js → react-stripe.esm-DlCFj5cL.js} +1 -1
  138. package/dist/assets/{required-qTx6pgJa.js → required-mwCee7dE.js} +1 -1
  139. package/dist/assets/ru-BQnkoIIE.js +5 -0
  140. package/dist/assets/runtime-tLrFToGI.js +1 -0
  141. package/dist/assets/{sanitize-BSjwjSMl.js → sanitize-d0BswqQy.js} +1 -1
  142. package/dist/assets/sdk-Dli060me.js +1 -0
  143. package/dist/assets/{section-DLNNkzJC.js → section-DkbgrVvQ.js} +1 -1
  144. package/dist/assets/{security-DzpCKO8Z.js → security-Chcv4uGK.js} +3 -3
  145. package/dist/assets/{session-fyUi2EvV.js → session-XtuHaexA.js} +1 -1
  146. package/dist/assets/setup-DT83Wy3z.js +30 -0
  147. package/dist/assets/{shorten-label-C8Cay9FW.js → shorten-label-CzJmma8P.js} +1 -1
  148. package/dist/assets/{simple-select-DCCkM3zE.js → simple-select-WHx-v2hU.js} +1 -1
  149. package/dist/assets/{spaces-Ci_JwPPC.js → spaces-Bz3V0eYU.js} +1 -1
  150. package/dist/assets/{start-CvpuRdlI.js → start-Cvh2uD-h.js} +1 -1
  151. package/dist/assets/{starting-progress-4SearER_.js → starting-progress-DGPdZeyx.js} +1 -1
  152. package/dist/assets/{status-BYn5dcxB.js → status-DRg5cWiY.js} +1 -1
  153. package/dist/assets/{step-actions-vNi6kxQk.js → step-actions-DreWQ64q.js} +1 -1
  154. package/dist/assets/{studio-CU2xUA6B.js → studio-qLDw0ukd.js} +1 -1
  155. package/dist/assets/{switch-control-Cd3JbWWV.js → switch-control-CjKF-gAU.js} +1 -1
  156. package/dist/assets/{table-tips-3_n5uR-X.js → table-tips-Bb8itsxN.js} +1 -1
  157. package/dist/assets/{team-Bxi69lmr.js → team-CDSer28u.js} +5 -5
  158. package/dist/assets/th-x7kvDKZJ.js +5 -0
  159. package/dist/assets/{traffic-5kSM9zFo.js → traffic-CUT8PqO4.js} +1 -1
  160. package/dist/assets/{transfer-1mPm3DHh.js → transfer-YfQr5dnn.js} +1 -1
  161. package/dist/assets/{unsubscribe-BHRGSL9f.js → unsubscribe-Bzl4TSbX.js} +1 -1
  162. package/dist/assets/{use-mobile-B9FM-UBY.js → use-mobile-D0T_V7RQ.js} +1 -1
  163. package/dist/assets/{use-mobile-BC9t6zmM.js → use-mobile-IWOBvgsY.js} +1 -1
  164. package/dist/assets/{use-promise-window-open-DzOXAig9.js → use-promise-window-open-D_ahP8Mz.js} +1 -1
  165. package/dist/assets/{use-server-logo-xLbI9v7G.js → use-server-logo-CUO3ZK3W.js} +1 -1
  166. package/dist/assets/{use-window-close-CleYtwbz.js → use-window-close-Cp670vvd.js} +1 -1
  167. package/dist/assets/{useAsync-BdgUavMa.js → useAsync-CQhKDImp.js} +1 -1
  168. package/dist/assets/{useAsync-DgPr_To1.js → useAsync-VHQLt8EO.js} +1 -1
  169. package/dist/assets/{useLocalStorage-Dgwq5dGM.js → useLocalStorage-DVoRz1Om.js} +1 -1
  170. package/dist/assets/user-center-C1W41jVp.js +126 -0
  171. package/dist/assets/{user-sessions-CFuRw6Kk.js → user-sessions-DeUIUEmh.js} +1 -1
  172. package/dist/assets/{util-CxrGdmDd.js → util-C7XciGgo.js} +1 -1
  173. package/dist/assets/{util-DhFzZqQY.js → util-PB33gwP1.js} +1 -1
  174. package/dist/assets/{vendor-arcblock-BnbwP1kf.js → vendor-arcblock-t44X0l6F.js} +1 -1
  175. package/dist/assets/{vendor-hooks-DTeIFVkk.js → vendor-hooks-B2bT6Nqf.js} +1 -1
  176. package/dist/assets/{vendor-mui-core-CcrDM-5l.js → vendor-mui-core-Bw-IjQ9m.js} +1 -1
  177. package/dist/assets/{vendor-mui-x-BVRWKjdg.js → vendor-mui-x-CuLvhfRb.js} +14 -14
  178. package/dist/assets/{vendor-utils-BOkrEpKO.js → vendor-utils-B4mliPf7.js} +1 -1
  179. package/dist/assets/{vendor-ux-CUBj5GXM.js → vendor-ux-DcEgaaV8.js} +3 -3
  180. package/dist/assets/vi-CT5pkl6Q.js +5 -0
  181. package/dist/assets/{wait-connect-Cht9WKDH.js → wait-connect-DrZC6ybw.js} +1 -1
  182. package/dist/assets/wrap-locale-zQpfFxI2.js +1 -0
  183. package/dist/assets/zh-ATukbAXz.js +10 -0
  184. package/dist/assets/{zh-IccOJN0P.js → zh-Cc55bM2s.js} +1 -1
  185. package/dist/assets/zh-tw-dw7nLLI_.js +9 -0
  186. package/dist/index.html +6 -6
  187. package/dist/service-worker.js +1 -1
  188. package/package.json +35 -34
  189. package/dist/assets/AdapterDayjs-BEkLDdDJ.js +0 -3
  190. package/dist/assets/aigne-B2kmHT_s.js +0 -10
  191. package/dist/assets/api-Rnh95Dt4.js +0 -1
  192. package/dist/assets/ar-7eC8LKj2.js +0 -7
  193. package/dist/assets/authorize-DqbV7Yj5.js +0 -1
  194. package/dist/assets/button-CPgviFPs.js +0 -1
  195. package/dist/assets/config-yxcrU66X.js +0 -26
  196. package/dist/assets/dashboard-DaKd1daE.js +0 -106
  197. package/dist/assets/de-DgwOa_mf.js +0 -7
  198. package/dist/assets/domains-BV1O_rDN.js +0 -1
  199. package/dist/assets/engine-BVHpout7.js +0 -1
  200. package/dist/assets/es-B_gBSKp3.js +0 -9
  201. package/dist/assets/fr-Bm0AhLrO.js +0 -7
  202. package/dist/assets/get-safe-url-D9ZX2qFm.js +0 -1
  203. package/dist/assets/hi-DO31L4at.js +0 -5
  204. package/dist/assets/id-BYCjnoK8.js +0 -7
  205. package/dist/assets/index-467tNKTx.js +0 -124
  206. package/dist/assets/index-7fdKVdEw.js +0 -1
  207. package/dist/assets/index-pvfQRiIz.js +0 -1
  208. package/dist/assets/invite-DMpU1WKR.js +0 -1
  209. package/dist/assets/issue-passport-CH_GIDha.js +0 -1
  210. package/dist/assets/ja-Ck2EVKeO.js +0 -9
  211. package/dist/assets/ko-Nk_6p6Jl.js +0 -9
  212. package/dist/assets/localization-D1S2pks-.js +0 -1
  213. package/dist/assets/logger-MoQC3rmC.js +0 -1
  214. package/dist/assets/preferences-CoP65AxI.js +0 -1
  215. package/dist/assets/profile-embed-CdAkuRfH.js +0 -1
  216. package/dist/assets/pt-C-JzkWqR.js +0 -5
  217. package/dist/assets/publish-resource-O6nixlJ9.js +0 -1
  218. package/dist/assets/ru-DR6is0tr.js +0 -5
  219. package/dist/assets/runtime-lAc0mbn0.js +0 -1
  220. package/dist/assets/sdk-CiRRFKs0.js +0 -1
  221. package/dist/assets/setup-BtNJlN-g.js +0 -30
  222. package/dist/assets/th-Cn3755Vi.js +0 -5
  223. package/dist/assets/user-center-D7U8E_9U.js +0 -126
  224. package/dist/assets/vi-7MIaoDt9.js +0 -5
  225. package/dist/assets/wrap-locale-CnZSJgmv.js +0 -1
  226. package/dist/assets/zh-DswrR4Lx.js +0 -10
  227. package/dist/assets/zh-tw-De_IfliP.js +0 -9
package/api/index.js CHANGED
@@ -13,7 +13,8 @@ const httpProxy = require('@arcblock/http-proxy');
13
13
  const { minimatch } = require('minimatch');
14
14
  const helmet = require('helmet');
15
15
  const isUrl = require('is-url');
16
- const { WELLKNOWN_SERVICE_PATH_PREFIX, EVENTS } = require('@abtnode/constant');
16
+ const { WELLKNOWN_SERVICE_PATH_PREFIX, EVENTS, WELLKNOWN_BLOCKLET_ADMIN_PATH } = require('@abtnode/constant');
17
+ const getBlockletComingSoonTemplate = require('@abtnode/router-templates/lib/blocklet-coming-soon');
17
18
  const {
18
19
  BlockletEvents,
19
20
  BlockletInternalEvents,
@@ -147,11 +148,23 @@ module.exports = function createServer(node, serverOptions = {}) {
147
148
  });
148
149
 
149
150
  proxy.safeWeb = (req, res, opts = {}) => {
150
- proxy.web(req, res, opts, (error) => {
151
+ proxy.web(req, res, opts, async (error) => {
151
152
  if (error) {
152
153
  logger.error('http proxy error', { error, raw: req.originalUrl, opts });
153
154
  if (!res.headersSent) {
154
- res.status(502).send(`Can not proxy to upstream target: ${opts.target}`);
155
+ try {
156
+ const [blocklet, nodeInfo] = await Promise.all([req.getBlocklet(), req.getNodeInfo()]);
157
+ res
158
+ .status(502)
159
+ .send(
160
+ blocklet && nodeInfo
161
+ ? getBlockletComingSoonTemplate(blocklet, nodeInfo, WELLKNOWN_BLOCKLET_ADMIN_PATH)
162
+ : `Can not proxy to upstream target: ${opts.target}`
163
+ );
164
+ } catch (err) {
165
+ logger.error('Failed to get blocklet or node info', { err });
166
+ res.status(502).send(`Can not proxy to upstream target: ${opts.target}`);
167
+ }
155
168
  }
156
169
  }
157
170
  });
package/api/libs/image.js CHANGED
@@ -108,7 +108,8 @@ const getCacheFilePath = (dataDir, fileName) => {
108
108
  return result;
109
109
  };
110
110
 
111
- const isImageRequest = (req) => {
111
+ // NOTICE: 如果传入了 fileName,则优先使用 fileName 的 extension,否则使用 req.path extension
112
+ const isImageRequest = (req, fileName) => {
112
113
  if (req.method !== 'GET') {
113
114
  return false;
114
115
  }
@@ -122,7 +123,8 @@ const isImageRequest = (req) => {
122
123
  if (!req.query.imageFilter) {
123
124
  return false;
124
125
  }
125
- const extension = toLower(path.extname(req.path).slice(1));
126
+ const extension = toLower(path.extname(fileName || req.path).slice(1));
127
+
126
128
  if (extension && !FORMATS.includes(EXTENSIONS[extension])) {
127
129
  return false;
128
130
  }
@@ -138,95 +140,136 @@ const isImageRequest = (req) => {
138
140
 
139
141
  const getImageContentType = (extension) => (extension === 'svg' ? 'image/svg+xml' : `image/${extension}`);
140
142
 
141
- const processImage = (src, extension, dest, params) => {
143
+ const processImage = (srcStream, { extension, destPath, srcPath }, filterParams = {}) => {
142
144
  return new Promise((resolve, reject) => {
143
145
  // output stream
144
- const out = fs.createWriteStream(dest);
145
- out.on('close', () => {
146
- resolve(dest);
147
- });
146
+ const out = fs.createWriteStream(destPath);
147
+
148
+ try {
149
+ // 这里是双重保障, /.blocklet/service/blocklet/logo 已经在 isImageRequest 中可以过滤掉了
150
+ // 此处只是为了增加健壮性,一般情况下不会到这里
151
+ if (extension && !FORMATS.includes(EXTENSIONS[extension])) {
152
+ // 如果是不支持的 extension,则将源文件直接写入目标地址
153
+ srcStream.pipe(out);
154
+ return;
155
+ }
148
156
 
149
- out.on('error', (err) => {
150
- reject(err);
151
- });
157
+ out.on('close', () => {
158
+ resolve(destPath);
159
+ });
160
+
161
+ out.on('error', (err) => {
162
+ reject(err);
163
+ });
164
+
165
+ const {
166
+ imageFilter,
167
+ w: width,
168
+ h: height,
169
+ t: top,
170
+ l: left,
171
+ q: quality,
172
+ f: format,
173
+ m: mode,
174
+ r: rotate,
175
+ p: progressive,
176
+ g: greyscale,
177
+ b: blur,
178
+ a: transparency,
179
+ n: negative,
180
+ s: sharpen,
181
+ } = filterParams;
182
+
183
+ const dimensions = { top, left };
184
+ if (width) {
185
+ dimensions.width = width;
186
+ }
187
+ if (height) {
188
+ dimensions.height = height;
189
+ }
152
190
 
153
- const {
154
- imageFilter,
155
- w: width,
156
- h: height,
157
- t: top,
158
- l: left,
159
- q: quality,
160
- f: format,
161
- m: mode,
162
- r: rotate,
163
- p: progressive,
164
- g: greyscale,
165
- b: blur,
166
- a: transparency,
167
- n: negative,
168
- s: sharpen,
169
- } = params;
170
-
171
- const dimensions = { top, left };
172
- if (width) {
173
- dimensions.width = width;
174
- }
175
- if (height) {
176
- dimensions.height = height;
177
- }
191
+ const pipeline = sharp({ animated: true, limitInputPixels: 0 }).timeout({ seconds: 60 });
192
+ if (rotate) {
193
+ pipeline.rotate(rotate);
194
+ }
195
+ if (imageFilter === 'resize') {
196
+ if (!dimensions.width && !dimensions.height) {
197
+ reject(new CustomError(400, 'At least one of `w` or `h` must be provided to resize'));
198
+ return;
199
+ }
200
+ }
201
+ if (dimensions.width || dimensions.height) {
202
+ pipeline.resize({ ...dimensions, fit: mode, withoutEnlargement: true });
203
+ }
204
+ if (imageFilter === 'crop') {
205
+ pipeline.extract(dimensions);
206
+ }
178
207
 
179
- const pipeline = sharp({ animated: true, limitInputPixels: 0 }).timeout({ seconds: 60 });
180
- if (rotate) {
181
- pipeline.rotate(rotate);
182
- }
183
- if (imageFilter === 'resize') {
184
- if (!dimensions.width && !dimensions.height) {
185
- reject(new CustomError(400, 'At least one of `w` or `h` must be provided to resize'));
208
+ if (sharpen) {
209
+ pipeline.sharpen(sharpen);
210
+ }
211
+ if (blur) {
212
+ pipeline.blur(blur);
213
+ }
214
+ if (greyscale) {
215
+ pipeline.greyscale();
216
+ }
217
+ if (transparency) {
218
+ pipeline.ensureAlpha();
219
+ } else {
220
+ pipeline.removeAlpha();
221
+ }
222
+ if (negative) {
223
+ pipeline.negate();
224
+ }
225
+
226
+ const processFn = pipeline[format || EXTENSIONS[extension]];
227
+ // 如果 sharp 实例中不包含目标 extension 的转换方法,则将源文件直接写入目标地址
228
+ if (!processFn || processFn instanceof Function === false) {
229
+ srcStream.pipe(out);
186
230
  return;
187
231
  }
188
- }
189
- if (dimensions.width || dimensions.height) {
190
- pipeline.resize({ ...dimensions, fit: mode, withoutEnlargement: true });
191
- }
192
- if (imageFilter === 'crop') {
193
- pipeline.extract(dimensions);
194
- }
195
232
 
196
- if (sharpen) {
197
- pipeline.sharpen(sharpen);
198
- }
199
- if (blur) {
200
- pipeline.blur(blur);
201
- }
202
- if (greyscale) {
203
- pipeline.greyscale();
204
- }
205
- if (transparency) {
206
- pipeline.ensureAlpha();
207
- } else {
208
- pipeline.removeAlpha();
233
+ // HACK: 这里不能使用 processFn 来替代,会导致图片处理出错(猜测是 this 指针的问题)
234
+ pipeline[format || EXTENSIONS[extension]]({ quality, progressive: !!progressive, dither: 0, force: true });
235
+
236
+ pipeline.on('error', (err) => {
237
+ reject(err);
238
+ });
239
+
240
+ // run the pipeline
241
+ srcStream.pipe(pipeline).pipe(out);
242
+ } catch (err) {
243
+ logger.error('image filter failed', {
244
+ error: err,
245
+ filterParams,
246
+ srcPath,
247
+ destPath,
248
+ extension,
249
+ });
250
+ srcStream.pipe(out);
209
251
  }
210
- if (negative) {
211
- pipeline.negate();
212
- }
213
-
214
- pipeline[format || EXTENSIONS[extension]]({ quality, progressive: !!progressive, dither: 0, force: true });
215
-
216
- pipeline.on('error', (err) => {
217
- reject(err);
218
- });
219
-
220
- // run the pipeline
221
- src.pipe(pipeline).pipe(out);
222
252
  }).catch((err) => {
223
- rmSync(dest, { force: true });
253
+ rmSync(destPath, { force: true });
224
254
  throw err;
225
255
  });
226
256
  };
227
257
 
228
258
  const tasks = {};
229
- const processAndRespond = (req, res, cacheDir, getSrc, ext, sendOptions = { maxAge: '356d', immutable: true }) => {
259
+ const processAndRespond = (
260
+ req,
261
+ res,
262
+ {
263
+ srcPath,
264
+ cacheDir,
265
+ getSrc,
266
+ extension: ext,
267
+ sendOptions = {
268
+ maxAge: '356d',
269
+ immutable: true,
270
+ },
271
+ }
272
+ ) => {
230
273
  if (fs.existsSync(cacheDir) === false) {
231
274
  fs.mkdirSync(cacheDir, { recursive: true });
232
275
  }
@@ -255,6 +298,7 @@ const processAndRespond = (req, res, cacheDir, getSrc, ext, sendOptions = { maxA
255
298
 
256
299
  const cacheKey = md5(stringify({ target: req.target, path: req.originalUrl, params }));
257
300
  const destPath = getCacheFilePath(cacheDir, `${cacheKey}.${params.f || extension}`);
301
+
258
302
  if (fs.existsSync(destPath)) {
259
303
  res.header('Content-Type', getImageContentType(params.f || extension));
260
304
  res.sendFile(destPath, sendOptions);
@@ -263,7 +307,17 @@ const processAndRespond = (req, res, cacheDir, getSrc, ext, sendOptions = { maxA
263
307
 
264
308
  // do the convert
265
309
  tasks[cacheKey] ??= getSrc(req)
266
- .then(([src, _extension]) => processImage(src, toLower(_extension), destPath, params))
310
+ .then(([src, _extension]) =>
311
+ processImage(
312
+ src,
313
+ {
314
+ extension: toLower(_extension),
315
+ destPath,
316
+ srcPath,
317
+ },
318
+ params
319
+ )
320
+ )
267
321
  .finally(() => {
268
322
  setTimeout(() => {
269
323
  delete tasks[cacheKey];
@@ -74,17 +74,16 @@ module.exports = {
74
74
 
75
75
  const attachSendLogoFn = (req, res, next) => {
76
76
  res.sendLogoFile = (fileName, options) => {
77
- if (isImageRequest(req)) {
77
+ if (isImageRequest(req, fileName)) {
78
78
  logger.info('send logo with image service', { fileName, options });
79
79
  const appDir = path.join(req.blocklet.env.cacheDir, '.services', 'image-filter');
80
- processAndRespond(
81
- req,
82
- res,
83
- appDir,
84
- () => Promise.resolve([fs.createReadStream(fileName), path.extname(fileName).slice(1)]),
85
- path.extname(fileName).slice(1),
86
- req.sendOptions
87
- );
80
+ processAndRespond(req, res, {
81
+ srcPath: fileName,
82
+ cacheDir: appDir,
83
+ getSrc: () => Promise.resolve([fs.createReadStream(fileName), path.extname(fileName).slice(1)]),
84
+ extension: path.extname(fileName).slice(1),
85
+ sendOptions: req.sendOptions,
86
+ });
88
87
  } else {
89
88
  logger.info('send logo with file', { fileName, options });
90
89
  res.sendFile(fileName, options);
@@ -165,20 +164,24 @@ module.exports = {
165
164
  }
166
165
  }
167
166
 
168
- const avatarFile = getAvatarFile(dataDir, fileName);
169
- if (!fs.existsSync(avatarFile)) {
167
+ const avatarFilePath = getAvatarFile(dataDir, fileName);
168
+ if (!fs.existsSync(avatarFilePath)) {
170
169
  res.status(404).send('Avatar Not Found');
171
170
  return;
172
171
  }
173
172
 
174
173
  if (isImageAccepted(req) && isImageRequest(req)) {
175
174
  const appDir = path.join(cacheDir, '.services', 'image-filter');
176
- processAndRespond(req, res, appDir, () => {
177
- stream = fs.createReadStream(avatarFile);
178
- return Promise.resolve([stream, path.extname(avatarFile).slice(1)]);
175
+ processAndRespond(req, res, {
176
+ srcPath: avatarFilePath,
177
+ cacheDir: appDir,
178
+ getSrc: () => {
179
+ stream = fs.createReadStream(avatarFilePath);
180
+ return Promise.resolve([stream, path.extname(avatarFilePath).slice(1)]);
181
+ },
179
182
  });
180
183
  } else {
181
- res.sendFile(avatarFile, { maxAge: '365d', immutable: true });
184
+ res.sendFile(avatarFilePath, { maxAge: '365d', immutable: true });
182
185
  }
183
186
  } catch (err) {
184
187
  stream?.destroy();
@@ -561,7 +564,7 @@ module.exports = {
561
564
  info.ogImageHash = encodeURIComponent(ogImage.split('/').slice(-1)[0].slice(0, 7));
562
565
  }
563
566
 
564
- const sourceFile = await getOgImage({
567
+ const sourceFilePath = await getOgImage({
565
568
  input: req.query,
566
569
  info,
567
570
  format,
@@ -570,12 +573,16 @@ module.exports = {
570
573
  });
571
574
  if (format === 'png' && isImageAccepted(req) && isImageRequest(req)) {
572
575
  const appDir = getAppImageCacheDir(blocklet.env.cacheDir);
573
- processAndRespond(req, res, appDir, () => {
574
- stream = fs.createReadStream(sourceFile);
575
- return Promise.resolve([stream, path.extname(sourceFile).slice(1)]);
576
+ processAndRespond(req, res, {
577
+ srcPath: sourceFilePath,
578
+ cacheDir: appDir,
579
+ getSrc: () => {
580
+ stream = fs.createReadStream(sourceFilePath);
581
+ return Promise.resolve([stream, path.extname(sourceFilePath).slice(1)]);
582
+ },
576
583
  });
577
584
  } else {
578
- res.sendFile(sourceFile, cache ? { maxAge: '365d', immutable: true } : { maxAge: 0 });
585
+ res.sendFile(sourceFilePath, cache ? { maxAge: '365d', immutable: true } : { maxAge: 0 });
579
586
  }
580
587
  } catch (err) {
581
588
  stream?.destroy();
@@ -591,17 +598,21 @@ module.exports = {
591
598
  const blocklet = await req.getBlocklet();
592
599
  const appSplash = get(blocklet, `environmentObj.BLOCKLET_APP_SPLASH_${type.toUpperCase()}`);
593
600
  if (appSplash) {
594
- const splashFile = path.join(get(blocklet, 'env.dataDir'), appSplash);
595
- if (fs.existsSync(splashFile)) {
601
+ const splashFilePath = path.join(get(blocklet, 'env.dataDir'), appSplash);
602
+ if (fs.existsSync(splashFilePath)) {
596
603
  if (isImageRequest(req)) {
597
604
  const appDir = getAppImageCacheDir(blocklet.env.cacheDir);
598
- processAndRespond(req, res, appDir, () => {
599
- stream = fs.createReadStream(splashFile);
600
- return Promise.resolve([stream, path.extname(splashFile).slice(1)]);
605
+ processAndRespond(req, res, {
606
+ srcPath: splashFilePath,
607
+ cacheDir: appDir,
608
+ getSrc: () => {
609
+ stream = fs.createReadStream(splashFilePath);
610
+ return Promise.resolve([stream, path.extname(splashFilePath).slice(1)]);
611
+ },
601
612
  });
602
613
  } else {
603
614
  const cache = req.query.nocache !== '1';
604
- res.sendFile(splashFile, cache ? { maxAge: '365d', immutable: true } : { maxAge: 0 });
615
+ res.sendFile(splashFilePath, cache ? { maxAge: '365d', immutable: true } : { maxAge: 0 });
605
616
  }
606
617
  } else {
607
618
  res.sendFile(`/images/splash-${type}.png`, { root: staticDir, maxAge: '1h' });
@@ -622,17 +633,21 @@ module.exports = {
622
633
  const blocklet = await req.getBlocklet();
623
634
  const appOgImage = get(blocklet, 'environmentObj.BLOCKLET_APP_OG_IMAGE');
624
635
  if (appOgImage) {
625
- const ogImageFile = path.join(get(blocklet, 'env.dataDir'), appOgImage);
626
- if (fs.existsSync(ogImageFile)) {
636
+ const ogImageFilePath = path.join(get(blocklet, 'env.dataDir'), appOgImage);
637
+ if (fs.existsSync(ogImageFilePath)) {
627
638
  if (isImageRequest(req)) {
628
639
  const appDir = getAppImageCacheDir(blocklet.env.cacheDir);
629
- processAndRespond(req, res, appDir, () => {
630
- stream = fs.createReadStream(ogImageFile);
631
- return Promise.resolve([stream, path.extname(ogImageFile).slice(1)]);
640
+ processAndRespond(req, res, {
641
+ srcPath: ogImageFilePath,
642
+ cacheDir: appDir,
643
+ getSrc: () => {
644
+ stream = fs.createReadStream(ogImageFilePath);
645
+ return Promise.resolve([stream, path.extname(ogImageFilePath).slice(1)]);
646
+ },
632
647
  });
633
648
  } else {
634
649
  const cache = req.query.nocache !== '1';
635
- res.sendFile(ogImageFile, cache ? { maxAge: '365d', immutable: true } : { maxAge: 0 });
650
+ res.sendFile(ogImageFilePath, cache ? { maxAge: '365d', immutable: true } : { maxAge: 0 });
636
651
  }
637
652
  } else {
638
653
  res.sendFile('/images/og-image.jpg', { root: staticDir, maxAge: '1h' });
@@ -1,52 +1,160 @@
1
- const { getChainClient } = require('@abtnode/util/lib/get-chain-client');
2
- const { MAIN_CHAIN_ENDPOINT } = require('@abtnode/constant');
3
1
  const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
4
2
  const { default: axios } = require('axios');
5
3
  const isUrl = require('is-url');
4
+ const isPrivateIP = require('private-ip');
6
5
 
7
6
  const logger = require('../libs/logger')('blocklet-services:csp-proxy');
8
7
 
9
8
  const PREFIX = WELLKNOWN_SERVICE_PATH_PREFIX;
10
9
 
11
- module.exports = {
12
- init(server) {
13
- const chainClient = getChainClient(MAIN_CHAIN_ENDPOINT);
10
+ // 允许的图片内容类型和 JSON 内容类型
11
+ const ALLOWED_CONTENT_TYPES = [
12
+ 'image/jpeg',
13
+ 'image/png',
14
+ 'image/gif',
15
+ 'image/webp',
16
+ 'image/svg+xml',
17
+ 'image/avif',
18
+ 'image/bmp',
19
+ 'image/x-icon',
20
+ 'application/json',
21
+ ];
22
+
23
+ /**
24
+ * 检查请求来源是否合法
25
+ */
26
+ function checkReferer(req) {
27
+ const referer = req.get('referer');
28
+ if (!referer) {
29
+ return false;
30
+ }
31
+
32
+ try {
33
+ const refererUrl = new URL(referer);
34
+ const currentHost = req.get('host');
14
35
 
15
- if (!chainClient) {
16
- logger.error('get chain client failed');
17
- return;
18
- }
36
+ // 检查referer是否来自当前站点
37
+ return refererUrl.hostname === currentHost || refererUrl.hostname === currentHost.split(':')[0]; // 处理端口号情况
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
19
42
 
43
+ /**
44
+ * 检查内容类型是否为允许的图片类型
45
+ */
46
+ function isAllowedContentType(contentType) {
47
+ if (!contentType) return false;
48
+ const lowerContentType = contentType.toLowerCase();
49
+ return ALLOWED_CONTENT_TYPES.some((type) => lowerContentType.startsWith(type));
50
+ }
51
+
52
+ module.exports = {
53
+ init(server) {
20
54
  server.get(`${PREFIX}/proxy`, async (req, res) => {
21
55
  const { url } = req.query;
22
56
 
23
57
  if (!url) {
24
- res.status(400).send('Missing resource URL');
58
+ res.status(400).send('Invalid parameter');
59
+ return;
25
60
  }
26
61
 
27
62
  if (!isUrl(url)) {
28
- res.status(400).send('Invalid resource URL');
63
+ res.status(400).send('Invalid parameter');
64
+ return;
65
+ }
66
+
67
+ // 检查referer来源
68
+ if (!checkReferer(req)) {
69
+ res.status(403).send('Invalid parameter');
29
70
  return;
30
71
  }
31
72
 
32
73
  try {
33
74
  const tmp = new URL(url);
34
75
  if (tmp.protocol !== 'https:') {
35
- res.status(400).send('Invalid resource URL');
76
+ res.status(400).send('Invalid parameter');
36
77
  return;
37
78
  }
38
79
 
39
- // 使用流式请求和响应
80
+ // 检查是否为内网地址,防止SSRF攻击
81
+ if (isPrivateIP(tmp.hostname)) {
82
+ res.status(400).send('Invalid parameter');
83
+ return;
84
+ }
85
+
86
+ // 配置axios请求,确保不传递用户的cookie等认证信息
87
+ const requestConfig = {
88
+ maxRedirects: 0,
89
+ headers: {
90
+ 'User-Agent': 'BlockletServer-CSP-Proxy/1.0',
91
+ },
92
+ };
93
+
94
+ // 直接发起GET请求,在响应头返回后立即检查content-type
40
95
  const response = await axios.get(url, {
96
+ ...requestConfig,
41
97
  responseType: 'stream',
42
98
  });
43
99
 
100
+ // 立即检查响应的content-type
101
+ const contentType = response.headers['content-type'];
102
+ if (!isAllowedContentType(contentType)) {
103
+ // 立即销毁stream并返回错误,避免继续下载
104
+ response.data.destroy();
105
+ res.status(400).send('Invalid parameter');
106
+ return;
107
+ }
108
+
44
109
  // 设置响应头
45
110
  res.set('Content-Type', response.headers['content-type']);
46
111
  if (response.headers['content-length']) {
47
112
  res.set('Content-Length', response.headers['content-length']);
48
113
  }
49
114
 
115
+ // 关键安全头:防止第三方内容获取本站cookie
116
+ const securityHeaders = {
117
+ // 防止浏览器MIME类型嗅探,严格按content-type处理
118
+ 'X-Content-Type-Options': 'nosniff',
119
+ // 禁止发送referrer信息,保护隐私
120
+ 'Referrer-Policy': 'no-referrer',
121
+ // 防止XSS攻击
122
+ 'X-XSS-Protection': '1; mode=block',
123
+ };
124
+
125
+ // 根据内容类型设置不同的安全策略
126
+ const lowerContentType = contentType.toLowerCase();
127
+
128
+ if (lowerContentType.startsWith('application/json')) {
129
+ // JSON响应:严格禁止执行和嵌入
130
+ securityHeaders['Content-Security-Policy'] = "default-src 'none'; script-src 'none'; object-src 'none';";
131
+ securityHeaders['X-Frame-Options'] = 'DENY';
132
+ } else if (lowerContentType.startsWith('image/svg+xml')) {
133
+ // SVG图片:禁止脚本但允许样式,可能需要嵌入使用
134
+ securityHeaders['Content-Security-Policy'] =
135
+ "default-src 'none'; script-src 'none'; style-src 'unsafe-inline';";
136
+ securityHeaders['X-Frame-Options'] = 'SAMEORIGIN'; // 允许同源嵌入
137
+ } else if (lowerContentType.startsWith('image/')) {
138
+ // 普通图片:最宽松的策略,但仍防止脚本执行
139
+ securityHeaders['Content-Security-Policy'] = "script-src 'none';";
140
+ // 不设置X-Frame-Options,允许嵌入
141
+ }
142
+
143
+ // 仅在HTTPS环境下设置HSTS
144
+ if (req.secure || req.get('x-forwarded-proto') === 'https') {
145
+ securityHeaders['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains';
146
+ }
147
+
148
+ res.set(securityHeaders);
149
+
150
+ // 添加缓存控制头
151
+ res.set('Cache-Control', 'public, max-age=3600'); // 缓存1小时
152
+
153
+ // 确保不传递可能包含敏感信息的响应头
154
+ res.removeHeader('Set-Cookie');
155
+ res.removeHeader('Authorization');
156
+ res.removeHeader('WWW-Authenticate');
157
+
50
158
  // 直接将流管道传输到响应
51
159
  response.data.pipe(res);
52
160
 
@@ -59,7 +167,14 @@ module.exports = {
59
167
  });
60
168
  } catch (error) {
61
169
  logger.error('Error fetching the resource:', { error, url });
62
- res.status(400).send(`Could not fetch the resource: ${error.message}`);
170
+
171
+ if (error.code === 'ECONNABORTED') {
172
+ res.status(408).send('Request timeout');
173
+ } else if (error.response && error.response.status) {
174
+ res.status(error.response.status).send(`Remote server error: ${error.response.status}`);
175
+ } else {
176
+ res.status(400).send('Invalid parameter');
177
+ }
63
178
  }
64
179
  });
65
180
  },
@@ -988,12 +988,16 @@ module.exports = {
988
988
  const { token } = req;
989
989
  if (token) {
990
990
  const sessionTtl = jwtDecode(token).exp * 1000 - Date.now();
991
- await cache.sessionToken.set(md5(token), { block: true }, { ttl: sessionTtl });
991
+ if (sessionTtl > 0) {
992
+ await cache.sessionToken.set(md5(token), { block: true }, { ttl: sessionTtl });
993
+ }
992
994
  }
993
995
  // 兼容旧版本,API 不会携带 refreshToken 的情况
994
996
  if (refreshToken) {
995
997
  const refreshTtl = jwtDecode(refreshToken).exp * 1000 - Date.now();
996
- await cache.refreshToken.set(md5(refreshToken), { block: true }, { ttl: refreshTtl });
998
+ if (refreshTtl > 0) {
999
+ await cache.refreshToken.set(md5(refreshToken), { block: true }, { ttl: refreshTtl });
1000
+ }
997
1001
  }
998
1002
  } catch (err) {
999
1003
  logger.error('Failed to block session token & refresh token', { error: err });