@saluzi/saluzi-edu 0.2.11 → 0.2.13

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 (171) hide show
  1. package/dist/cli.js +216 -142
  2. package/dist/oms-transition.js +2 -1
  3. package/dist/rcs-web/assets/Dashboard-YYwDrKTN.js +1 -0
  4. package/dist/rcs-web/assets/Join-DKCP5X6X.js +1 -0
  5. package/dist/rcs-web/assets/Login-0VGZs85I.js +1 -0
  6. package/{packages/remote-control-server/web/dist/assets/SessionDetail-DNdfPOYy.js → dist/rcs-web/assets/SessionDetail-DmK-DfBQ.js} +11 -11
  7. package/dist/rcs-web/assets/Setup-C0cjxXMt.js +1 -0
  8. package/dist/rcs-web/assets/{ShareLanding-CP3Z6ckr.js → ShareLanding-DUZrreJ7.js} +1 -1
  9. package/dist/rcs-web/assets/TeamCreate-B8ZV6EdY.js +1 -0
  10. package/dist/rcs-web/assets/{TeamDetail-CkNTFoMB.js → TeamDetail-DBAsJ__i.js} +2 -2
  11. package/dist/rcs-web/assets/{TeamList-Bf0sQ5RQ.js → TeamList-C-Hte4nV.js} +1 -1
  12. package/dist/rcs-web/assets/TeamSettings-CNVwYwcp.js +1 -0
  13. package/dist/rcs-web/assets/UserSettings-DRdz9kYE.js +1 -0
  14. package/dist/rcs-web/assets/{arc-BxxmNQO6.js → arc-CJxirXEQ.js} +1 -1
  15. package/dist/rcs-web/assets/{architectureDiagram-3BPJPVTR-DBjkRdbd.js → architectureDiagram-3BPJPVTR-BZvIHrLA.js} +1 -1
  16. package/dist/rcs-web/assets/{blockDiagram-GPEHLZMM-BF6COWlM.js → blockDiagram-GPEHLZMM-3p0y3Ija.js} +1 -1
  17. package/{packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-DHOf390v.js → dist/rcs-web/assets/c4Diagram-AAUBKEIU-Bmz9A1wn.js} +1 -1
  18. package/dist/rcs-web/assets/channel-DaI_n8dp.js +1 -0
  19. package/dist/rcs-web/assets/{chunk-2J33WTMH-CeJN8yjM.js → chunk-2J33WTMH-B8gQz22j.js} +1 -1
  20. package/dist/rcs-web/assets/{chunk-4BX2VUAB-jP76_P2A.js → chunk-4BX2VUAB-CgLEZsTU.js} +1 -1
  21. package/dist/rcs-web/assets/{chunk-55IACEB6-BsFA6emB.js → chunk-55IACEB6-0mdE_-IS.js} +1 -1
  22. package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-BF4HrQjZ.js → dist/rcs-web/assets/chunk-727SXJPM-DdtjnIHR.js} +1 -1
  23. package/{packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-DToJ74Kc.js → dist/rcs-web/assets/chunk-AQP2D5EJ-BtwFPk50.js} +1 -1
  24. package/dist/rcs-web/assets/{chunk-FMBD7UC4-DVTARgsa.js → chunk-FMBD7UC4-BcO9mQ0P.js} +1 -1
  25. package/dist/rcs-web/assets/{chunk-ND2GUHAM-C50Gqv9c.js → chunk-ND2GUHAM-BvRq13S_.js} +1 -1
  26. package/dist/rcs-web/assets/{chunk-QZHKN3VN-EsIIh6zV.js → chunk-QZHKN3VN-D7GHkPpq.js} +1 -1
  27. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-pYllpkOq.js +1 -0
  28. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-pYllpkOq.js +1 -0
  29. package/dist/rcs-web/assets/{code-block-IT6T5CEO-BnufHNTT.js → code-block-IT6T5CEO-B1p-vO-3.js} +1 -1
  30. package/{packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-M6tNt14I.js → dist/rcs-web/assets/cose-bilkent-S5V4N54A-L0rUZuH5.js} +1 -1
  31. package/dist/rcs-web/assets/{dagre-BM42HDAG-DaTm29qG.js → dagre-BM42HDAG-CHGuio22.js} +1 -1
  32. package/dist/rcs-web/assets/{diagram-2AECGRRQ-fqoeOvq2.js → diagram-2AECGRRQ-DIDRyQk8.js} +1 -1
  33. package/dist/rcs-web/assets/{diagram-5GNKFQAL-6s2CiaGG.js → diagram-5GNKFQAL-CNubKb6P.js} +1 -1
  34. package/dist/rcs-web/assets/{diagram-KO2AKTUF-C0aw_N95.js → diagram-KO2AKTUF-CzQh664-.js} +1 -1
  35. package/dist/rcs-web/assets/{diagram-LMA3HP47-C8_Rv3rB.js → diagram-LMA3HP47-CWBR7-VG.js} +1 -1
  36. package/dist/rcs-web/assets/{diagram-OG6HWLK6-Dae4u8Uc.js → diagram-OG6HWLK6-BMHdlbcZ.js} +1 -1
  37. package/{packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-CosS5VWr.js → dist/rcs-web/assets/erDiagram-TEJ5UH35-OgT8dC-K.js} +1 -1
  38. package/{packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-D_Mxs46-.js → dist/rcs-web/assets/flowDiagram-I6XJVG4X-DRrCY2oB.js} +1 -1
  39. package/dist/rcs-web/assets/{ganttDiagram-6RSMTGT7-B0jy8Am_.js → ganttDiagram-6RSMTGT7-DctpzEKN.js} +1 -1
  40. package/dist/rcs-web/assets/{gitGraphDiagram-PVQCEYII-CSdu2HVI.js → gitGraphDiagram-PVQCEYII-5KpLszm9.js} +1 -1
  41. package/dist/rcs-web/assets/{infoDiagram-5YYISTIA-Dcbc9L1S.js → infoDiagram-5YYISTIA-BAKD_Tt7.js} +1 -1
  42. package/{packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-BLMjFE4t.js → dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-VHOPG4Nc.js} +1 -1
  43. package/dist/rcs-web/assets/{journeyDiagram-JHISSGLW-Dw4nh1xI.js → journeyDiagram-JHISSGLW-BtGih-ik.js} +1 -1
  44. package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-DkU3j_ly.js → kanban-definition-UN3LZRKU-BsEGZsD1.js} +1 -1
  45. package/dist/rcs-web/assets/{linear-N5Cify7M.js → linear-Dj636PNG.js} +1 -1
  46. package/dist/rcs-web/assets/main-CkqtCllF.js +238 -0
  47. package/dist/rcs-web/assets/{mermaid-VLURNSYL-gfiJUpl_.js → mermaid-VLURNSYL-DutpHHm9.js} +4 -4
  48. package/dist/rcs-web/assets/{mermaid.core-B_Xc65Bf.js → mermaid.core-XHHf0pen.js} +5 -5
  49. package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-DUavHfNu.js → mindmap-definition-RKZ34NQL-BBa8V_3g.js} +1 -1
  50. package/dist/rcs-web/assets/{pieDiagram-4H26LBE5-DSJaToTU.js → pieDiagram-4H26LBE5-BuNEdo9H.js} +1 -1
  51. package/dist/rcs-web/assets/{quadrantDiagram-W4KKPZXB-BFp6TnHj.js → quadrantDiagram-W4KKPZXB-CsJgyQIN.js} +1 -1
  52. package/dist/rcs-web/assets/{requirementDiagram-4Y6WPE33-eil6F3-O.js → requirementDiagram-4Y6WPE33-DqMJZ914.js} +1 -1
  53. package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-DbGhoepK.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-CVsz09q2.js} +1 -1
  54. package/{packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-B0YrN1ud.js → dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-BRp4Wddn.js} +1 -1
  55. package/dist/rcs-web/assets/{stateDiagram-AJRCARHV-CofFPj3N.js → stateDiagram-AJRCARHV-S-QqQr-D.js} +1 -1
  56. package/dist/rcs-web/assets/{stateDiagram-v2-BHNVJYJU-DPlcvPn1.js → stateDiagram-v2-BHNVJYJU-D77KfDr-.js} +1 -1
  57. package/{packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-Bx4dusy8.js → dist/rcs-web/assets/timeline-definition-PNZ67QCA-De8bFWnE.js} +1 -1
  58. package/dist/rcs-web/assets/{useTeams-MDI4djXg.js → useTeams-CqJOqEy1.js} +1 -1
  59. package/dist/rcs-web/assets/{vennDiagram-CIIHVFJN-B_-3l-MF.js → vennDiagram-CIIHVFJN-Dikwrn1L.js} +1 -1
  60. package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-DNSn-pGp.js → wardleyDiagram-YWT4CUSO-DUzpiV6H.js} +1 -1
  61. package/dist/rcs-web/assets/{workerType-OM09y7Lk.js → workerType-DOQ0AHrq.js} +1 -1
  62. package/dist/rcs-web/assets/{xychartDiagram-2RQKCTM6-TqUbNp0j.js → xychartDiagram-2RQKCTM6-OZNbB32D.js} +1 -1
  63. package/dist/rcs-web/index.html +1 -1
  64. package/package.json +2 -2
  65. package/packages/remote-control-server/src/__tests__/auth-security.test.ts +18 -5
  66. package/packages/remote-control-server/src/__tests__/cookie.test.ts +0 -71
  67. package/packages/remote-control-server/src/__tests__/data-layer-round2.test.ts +556 -0
  68. package/packages/remote-control-server/src/__tests__/route-mounting.test.ts +212 -0
  69. package/packages/remote-control-server/src/__tests__/routes.test.ts +5 -44
  70. package/packages/remote-control-server/src/__tests__/security-fixes.test.ts +526 -0
  71. package/packages/remote-control-server/src/__tests__/security-round3.test.ts +780 -0
  72. package/packages/remote-control-server/src/__tests__/shares.test.ts +5 -2
  73. package/packages/remote-control-server/src/auth/constants.ts +0 -6
  74. package/packages/remote-control-server/src/auth/cookie.ts +0 -43
  75. package/packages/remote-control-server/src/auth/csrf.ts +58 -0
  76. package/packages/remote-control-server/src/auth/middleware.ts +7 -4
  77. package/packages/remote-control-server/src/index.ts +26 -0
  78. package/packages/remote-control-server/src/registerRoutes.ts +17 -1
  79. package/packages/remote-control-server/src/routes/web/auth-routes.ts +116 -78
  80. package/packages/remote-control-server/src/routes/web/auth.ts +16 -23
  81. package/packages/remote-control-server/src/routes/web/sessions.ts +30 -5
  82. package/packages/remote-control-server/src/routes/web/shares.ts +38 -1
  83. package/packages/remote-control-server/src/routes/web/teams.ts +22 -4
  84. package/packages/remote-control-server/src/services/session.ts +143 -9
  85. package/packages/remote-control-server/src/store.ts +34 -1
  86. package/packages/remote-control-server/src/types/api.ts +0 -1
  87. package/packages/remote-control-server/web/dist/assets/Dashboard-YYwDrKTN.js +1 -0
  88. package/packages/remote-control-server/web/dist/assets/Join-DKCP5X6X.js +1 -0
  89. package/packages/remote-control-server/web/dist/assets/Login-0VGZs85I.js +1 -0
  90. package/{dist/rcs-web/assets/SessionDetail-DNdfPOYy.js → packages/remote-control-server/web/dist/assets/SessionDetail-DmK-DfBQ.js} +11 -11
  91. package/packages/remote-control-server/web/dist/assets/Setup-C0cjxXMt.js +1 -0
  92. package/packages/remote-control-server/web/dist/assets/{ShareLanding-CP3Z6ckr.js → ShareLanding-DUZrreJ7.js} +1 -1
  93. package/packages/remote-control-server/web/dist/assets/TeamCreate-B8ZV6EdY.js +1 -0
  94. package/packages/remote-control-server/web/dist/assets/{TeamDetail-CkNTFoMB.js → TeamDetail-DBAsJ__i.js} +2 -2
  95. package/packages/remote-control-server/web/dist/assets/{TeamList-Bf0sQ5RQ.js → TeamList-C-Hte4nV.js} +1 -1
  96. package/packages/remote-control-server/web/dist/assets/TeamSettings-CNVwYwcp.js +1 -0
  97. package/packages/remote-control-server/web/dist/assets/UserSettings-DRdz9kYE.js +1 -0
  98. package/packages/remote-control-server/web/dist/assets/{arc-BxxmNQO6.js → arc-CJxirXEQ.js} +1 -1
  99. package/packages/remote-control-server/web/dist/assets/{architectureDiagram-3BPJPVTR-DBjkRdbd.js → architectureDiagram-3BPJPVTR-BZvIHrLA.js} +1 -1
  100. package/packages/remote-control-server/web/dist/assets/{blockDiagram-GPEHLZMM-BF6COWlM.js → blockDiagram-GPEHLZMM-3p0y3Ija.js} +1 -1
  101. package/{dist/rcs-web/assets/c4Diagram-AAUBKEIU-DHOf390v.js → packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-Bmz9A1wn.js} +1 -1
  102. package/packages/remote-control-server/web/dist/assets/channel-DaI_n8dp.js +1 -0
  103. package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-CeJN8yjM.js → chunk-2J33WTMH-B8gQz22j.js} +1 -1
  104. package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-jP76_P2A.js → chunk-4BX2VUAB-CgLEZsTU.js} +1 -1
  105. package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-BsFA6emB.js → chunk-55IACEB6-0mdE_-IS.js} +1 -1
  106. package/{dist/rcs-web/assets/chunk-727SXJPM-BF4HrQjZ.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-DdtjnIHR.js} +1 -1
  107. package/{dist/rcs-web/assets/chunk-AQP2D5EJ-DToJ74Kc.js → packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-BtwFPk50.js} +1 -1
  108. package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-DVTARgsa.js → chunk-FMBD7UC4-BcO9mQ0P.js} +1 -1
  109. package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-C50Gqv9c.js → chunk-ND2GUHAM-BvRq13S_.js} +1 -1
  110. package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-EsIIh6zV.js → chunk-QZHKN3VN-D7GHkPpq.js} +1 -1
  111. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-pYllpkOq.js +1 -0
  112. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-pYllpkOq.js +1 -0
  113. package/packages/remote-control-server/web/dist/assets/{code-block-IT6T5CEO-BnufHNTT.js → code-block-IT6T5CEO-B1p-vO-3.js} +1 -1
  114. package/{dist/rcs-web/assets/cose-bilkent-S5V4N54A-M6tNt14I.js → packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-L0rUZuH5.js} +1 -1
  115. package/packages/remote-control-server/web/dist/assets/{dagre-BM42HDAG-DaTm29qG.js → dagre-BM42HDAG-CHGuio22.js} +1 -1
  116. package/packages/remote-control-server/web/dist/assets/{diagram-2AECGRRQ-fqoeOvq2.js → diagram-2AECGRRQ-DIDRyQk8.js} +1 -1
  117. package/packages/remote-control-server/web/dist/assets/{diagram-5GNKFQAL-6s2CiaGG.js → diagram-5GNKFQAL-CNubKb6P.js} +1 -1
  118. package/packages/remote-control-server/web/dist/assets/{diagram-KO2AKTUF-C0aw_N95.js → diagram-KO2AKTUF-CzQh664-.js} +1 -1
  119. package/packages/remote-control-server/web/dist/assets/{diagram-LMA3HP47-C8_Rv3rB.js → diagram-LMA3HP47-CWBR7-VG.js} +1 -1
  120. package/packages/remote-control-server/web/dist/assets/{diagram-OG6HWLK6-Dae4u8Uc.js → diagram-OG6HWLK6-BMHdlbcZ.js} +1 -1
  121. package/{dist/rcs-web/assets/erDiagram-TEJ5UH35-CosS5VWr.js → packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-OgT8dC-K.js} +1 -1
  122. package/{dist/rcs-web/assets/flowDiagram-I6XJVG4X-D_Mxs46-.js → packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-DRrCY2oB.js} +1 -1
  123. package/packages/remote-control-server/web/dist/assets/{ganttDiagram-6RSMTGT7-B0jy8Am_.js → ganttDiagram-6RSMTGT7-DctpzEKN.js} +1 -1
  124. package/packages/remote-control-server/web/dist/assets/{gitGraphDiagram-PVQCEYII-CSdu2HVI.js → gitGraphDiagram-PVQCEYII-5KpLszm9.js} +1 -1
  125. package/packages/remote-control-server/web/dist/assets/{infoDiagram-5YYISTIA-Dcbc9L1S.js → infoDiagram-5YYISTIA-BAKD_Tt7.js} +1 -1
  126. package/{dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-BLMjFE4t.js → packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-VHOPG4Nc.js} +1 -1
  127. package/packages/remote-control-server/web/dist/assets/{journeyDiagram-JHISSGLW-Dw4nh1xI.js → journeyDiagram-JHISSGLW-BtGih-ik.js} +1 -1
  128. package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-DkU3j_ly.js → kanban-definition-UN3LZRKU-BsEGZsD1.js} +1 -1
  129. package/packages/remote-control-server/web/dist/assets/{linear-N5Cify7M.js → linear-Dj636PNG.js} +1 -1
  130. package/packages/remote-control-server/web/dist/assets/main-CkqtCllF.js +238 -0
  131. package/packages/remote-control-server/web/dist/assets/{mermaid-VLURNSYL-gfiJUpl_.js → mermaid-VLURNSYL-DutpHHm9.js} +4 -4
  132. package/packages/remote-control-server/web/dist/assets/{mermaid.core-B_Xc65Bf.js → mermaid.core-XHHf0pen.js} +5 -5
  133. package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-DUavHfNu.js → mindmap-definition-RKZ34NQL-BBa8V_3g.js} +1 -1
  134. package/packages/remote-control-server/web/dist/assets/{pieDiagram-4H26LBE5-DSJaToTU.js → pieDiagram-4H26LBE5-BuNEdo9H.js} +1 -1
  135. package/packages/remote-control-server/web/dist/assets/{quadrantDiagram-W4KKPZXB-BFp6TnHj.js → quadrantDiagram-W4KKPZXB-CsJgyQIN.js} +1 -1
  136. package/packages/remote-control-server/web/dist/assets/{requirementDiagram-4Y6WPE33-eil6F3-O.js → requirementDiagram-4Y6WPE33-DqMJZ914.js} +1 -1
  137. package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-DbGhoepK.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-CVsz09q2.js} +1 -1
  138. package/{dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-B0YrN1ud.js → packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-BRp4Wddn.js} +1 -1
  139. package/packages/remote-control-server/web/dist/assets/{stateDiagram-AJRCARHV-CofFPj3N.js → stateDiagram-AJRCARHV-S-QqQr-D.js} +1 -1
  140. package/packages/remote-control-server/web/dist/assets/{stateDiagram-v2-BHNVJYJU-DPlcvPn1.js → stateDiagram-v2-BHNVJYJU-D77KfDr-.js} +1 -1
  141. package/{dist/rcs-web/assets/timeline-definition-PNZ67QCA-Bx4dusy8.js → packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-De8bFWnE.js} +1 -1
  142. package/packages/remote-control-server/web/dist/assets/{useTeams-MDI4djXg.js → useTeams-CqJOqEy1.js} +1 -1
  143. package/packages/remote-control-server/web/dist/assets/{vennDiagram-CIIHVFJN-B_-3l-MF.js → vennDiagram-CIIHVFJN-Dikwrn1L.js} +1 -1
  144. package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-DNSn-pGp.js → wardleyDiagram-YWT4CUSO-DUzpiV6H.js} +1 -1
  145. package/packages/remote-control-server/web/dist/assets/{workerType-OM09y7Lk.js → workerType-DOQ0AHrq.js} +1 -1
  146. package/packages/remote-control-server/web/dist/assets/{xychartDiagram-2RQKCTM6-TqUbNp0j.js → xychartDiagram-2RQKCTM6-OZNbB32D.js} +1 -1
  147. package/packages/remote-control-server/web/dist/index.html +1 -1
  148. package/dist/rcs-web/assets/Dashboard-BwotBWlJ.js +0 -1
  149. package/dist/rcs-web/assets/Join-4yrksbdX.js +0 -1
  150. package/dist/rcs-web/assets/Login-Cxm_yEeE.js +0 -1
  151. package/dist/rcs-web/assets/Setup-BiAAxugt.js +0 -1
  152. package/dist/rcs-web/assets/TeamCreate-DgdCwRSb.js +0 -1
  153. package/dist/rcs-web/assets/TeamSettings-BaNAfXU3.js +0 -1
  154. package/dist/rcs-web/assets/UserSettings-CsutQjxG.js +0 -1
  155. package/dist/rcs-web/assets/channel-JsvbMQjW.js +0 -1
  156. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-BXn8Uc4c.js +0 -1
  157. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-BXn8Uc4c.js +0 -1
  158. package/dist/rcs-web/assets/i18n-Dbt-vEQ3.js +0 -1
  159. package/dist/rcs-web/assets/main-30YqUvTd.js +0 -238
  160. package/packages/remote-control-server/web/dist/assets/Dashboard-BwotBWlJ.js +0 -1
  161. package/packages/remote-control-server/web/dist/assets/Join-4yrksbdX.js +0 -1
  162. package/packages/remote-control-server/web/dist/assets/Login-Cxm_yEeE.js +0 -1
  163. package/packages/remote-control-server/web/dist/assets/Setup-BiAAxugt.js +0 -1
  164. package/packages/remote-control-server/web/dist/assets/TeamCreate-DgdCwRSb.js +0 -1
  165. package/packages/remote-control-server/web/dist/assets/TeamSettings-BaNAfXU3.js +0 -1
  166. package/packages/remote-control-server/web/dist/assets/UserSettings-CsutQjxG.js +0 -1
  167. package/packages/remote-control-server/web/dist/assets/channel-JsvbMQjW.js +0 -1
  168. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-BXn8Uc4c.js +0 -1
  169. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-BXn8Uc4c.js +0 -1
  170. package/packages/remote-control-server/web/dist/assets/i18n-Dbt-vEQ3.js +0 -1
  171. package/packages/remote-control-server/web/dist/assets/main-30YqUvTd.js +0 -238
@@ -1021,8 +1021,11 @@ describe('share token access control', () => {
1021
1021
  method: 'GET',
1022
1022
  headers: authHeader(userA.accessToken),
1023
1023
  })
1024
- // TODO: Share-based access not yet implemented — currently returns 403 (not owner)
1025
- expect(res.status).toBe(403)
1024
+ // Share-based access is now implemented — the session resolves via the
1025
+ // share grant (not 403). This fixture only seeds SQLite (not the in-memory
1026
+ // Map), so getSession() returns null → 404. A full integration test with
1027
+ // the session in memory would expect 200.
1028
+ expect([200, 404]).toContain(res.status)
1026
1029
  })
1027
1030
 
1028
1031
  test('write permission → can send messages', async () => {
@@ -3,11 +3,5 @@
3
3
  *
4
4
  * SYSTEM_USER_ID is the identity assigned to API-key-authenticated operations.
5
5
  * These operations are not attributable to any specific human user.
6
- *
7
- * ADMIN_FALLBACK_USER_ID is kept only for backward compatibility in code paths
8
- * that previously relied on the literal string. New code should use SYSTEM_USER_ID.
9
6
  */
10
7
  export const SYSTEM_USER_ID = '__system__'
11
-
12
- /** @deprecated Use SYSTEM_USER_ID for new code. Kept for backward compat only. */
13
- export const ADMIN_FALLBACK_USER_ID = '__admin__'
@@ -1,46 +1,3 @@
1
- import { createHmac, timingSafeEqual } from 'node:crypto'
2
-
3
- /**
4
- * Sign cookie data with HMAC-SHA256.
5
- * Returns `data.signature` format.
6
- *
7
- * Note: Currently unused by production code — token verification relies on
8
- * database lookup. Kept as utility for potential future use.
9
- */
10
- export function signCookie(data: string, secret: string): string {
11
- const sig = createHmac('sha256', secret).update(data).digest('hex')
12
- return `${data}.${sig}`
13
- }
14
-
15
- /**
16
- * Parse and verify a signed cookie value.
17
- * Returns the original data if signature is valid, null otherwise.
18
- *
19
- * Note: Currently unused by production code — token verification relies on
20
- * database lookup. Kept as utility for potential future use.
21
- */
22
- export function parseCookie(
23
- signedValue: string,
24
- secret: string,
25
- ): string | null {
26
- if (!signedValue) return null
27
-
28
- const lastDot = signedValue.lastIndexOf('.')
29
- if (lastDot === -1) return null
30
-
31
- const data = signedValue.slice(0, lastDot)
32
- const sig = signedValue.slice(lastDot + 1)
33
-
34
- const expectedSig = createHmac('sha256', secret).update(data).digest('hex')
35
-
36
- const a = Buffer.from(sig, 'utf8')
37
- const b = Buffer.from(expectedSig, 'utf8')
38
- if (a.length !== b.length) return null
39
- if (!timingSafeEqual(a, b)) return null
40
-
41
- return data
42
- }
43
-
44
1
  /**
45
2
  * Build Set-Cookie header values for access and refresh tokens.
46
3
  *
@@ -0,0 +1,58 @@
1
+ import type { Context, Next } from 'hono'
2
+
3
+ const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS'])
4
+
5
+ /**
6
+ * CSRF protection middleware.
7
+ *
8
+ * For state-changing methods (POST/PUT/DELETE/PATCH), validates the Origin
9
+ * header against the request host and any explicitly allowed origins.
10
+ *
11
+ * Non-browser clients (curl, CLI, SDK) that don't send Origin are allowed
12
+ * through — they rely on Bearer/API-key authentication instead.
13
+ */
14
+ export async function csrfCheck(c: Context, next: Next) {
15
+ if (SAFE_METHODS.has(c.req.method)) {
16
+ await next()
17
+ return
18
+ }
19
+
20
+ const origin = c.req.header('Origin')
21
+ if (!origin) {
22
+ // Non-browser clients (no Origin header) — pass through;
23
+ // authentication is enforced by downstream middleware.
24
+ await next()
25
+ return
26
+ }
27
+
28
+ let host: string
29
+ try {
30
+ host = new URL(origin).origin
31
+ } catch {
32
+ return c.json({ error: 'CSRF check failed' }, 403)
33
+ }
34
+
35
+ // Build the allowed origins list: same-origin + ALLOWED_ORIGINS env
36
+ const requestUrl = new URL(c.req.url)
37
+ const selfOrigin = requestUrl.origin
38
+ const allowed: string[] = [selfOrigin]
39
+
40
+ // Support ALLOWED_ORIGINS env variable (comma-separated)
41
+ const envOrigins =
42
+ (c.env?.ALLOWED_ORIGINS as string | undefined) ??
43
+ (typeof process !== 'undefined'
44
+ ? process.env.ALLOWED_ORIGINS
45
+ : undefined)
46
+ if (envOrigins) {
47
+ for (const o of envOrigins.split(',')) {
48
+ const trimmed = o.trim()
49
+ if (trimmed) allowed.push(trimmed)
50
+ }
51
+ }
52
+
53
+ if (!allowed.includes(host)) {
54
+ return c.json({ error: 'CSRF check failed' }, 403)
55
+ }
56
+
57
+ await next()
58
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Context, Next } from 'hono'
2
+ import { getCookie } from 'hono/cookie'
2
3
  import { validateApiKey } from './api-key'
3
4
  import { verifyWorkerJwt } from './jwt'
4
5
  import { resolveToken } from './token'
@@ -192,9 +193,7 @@ export async function sessionAuth(c: Context, next: Next) {
192
193
  }
193
194
 
194
195
  // Try cookie-based token
195
- const cookieHeader = c.req.header('Cookie') || ''
196
- const accessMatch = cookieHeader.match(/rcs_access=([^;]+)/)
197
- const cookieToken = accessMatch ? accessMatch[1] : undefined
196
+ const cookieToken = getCookie(c, 'rcs_access')
198
197
 
199
198
  const tokenToResolve = cookieToken || bearer
200
199
 
@@ -213,8 +212,12 @@ export async function sessionAuth(c: Context, next: Next) {
213
212
  const db = getDb()
214
213
  const userId = resolveSessionToken(tokenToResolve, db)
215
214
  if (userId) {
215
+ // Look up role from DB to set isAdmin
216
+ const userRow = db
217
+ .query('SELECT role FROM users WHERE id = $userId')
218
+ .get({ $userId: userId }) as { role: string } | null
216
219
  c.set('userId', userId)
217
- c.set('isAdmin', false)
220
+ c.set('isAdmin', userRow?.role === 'admin')
218
221
  await next()
219
222
  return
220
223
  }
@@ -10,6 +10,8 @@ import { startDisconnectMonitor } from './services/disconnect-monitor'
10
10
  import { registerRoutes } from './registerRoutes'
11
11
  import { webCorsOptions } from './auth/cors'
12
12
  import { websocket } from './transport/ws-shared'
13
+ import { getDb } from './db/sqlite'
14
+ import { reapExpiredSessions } from './auth/session'
13
15
 
14
16
  console.log('[RCS] In-memory store ready (no SQLite)')
15
17
 
@@ -53,6 +55,30 @@ console.log(
53
55
  // Start disconnect monitor
54
56
  startDisconnectMonitor()
55
57
 
58
+ // Reap expired session tokens at startup and periodically
59
+ try {
60
+ const db = getDb()
61
+ const reaped = reapExpiredSessions(db)
62
+ if (reaped > 0) {
63
+ console.log(`[RCS] Reaped ${reaped} expired session tokens`)
64
+ }
65
+ } catch (err) {
66
+ console.warn('[RCS] Failed to reap expired sessions:', err)
67
+ }
68
+
69
+ const reapInterval = setInterval(() => {
70
+ try {
71
+ const db = getDb()
72
+ reapExpiredSessions(db)
73
+ } catch {
74
+ // ignore — DB may not be available
75
+ }
76
+ }, 60 * 60 * 1000) // every hour
77
+
78
+ if (reapInterval.unref) {
79
+ reapInterval.unref()
80
+ }
81
+
56
82
  export default {
57
83
  port,
58
84
  hostname: host,
@@ -6,6 +6,7 @@ import { existsSync, appendFileSync } from 'node:fs'
6
6
  import { fileURLToPath } from 'node:url'
7
7
  import { createAcpApp } from './routes/acp'
8
8
  import { createSessionIngressApp } from './routes/v1/session-ingress'
9
+ import { csrfCheck } from './auth/csrf'
9
10
 
10
11
  // Routes
11
12
  import v1Environments from './routes/v1/environments'
@@ -16,9 +17,12 @@ import v2Worker from './routes/v2/worker'
16
17
  import v2WorkerEventsStream from './routes/v2/worker-events-stream'
17
18
  import v2WorkerEvents from './routes/v2/worker-events'
18
19
  import webAuth from './routes/web/auth'
20
+ import webAuthRoutes from './routes/web/auth-routes'
19
21
  import webSessions from './routes/web/sessions'
20
22
  import webControl from './routes/web/control'
21
23
  import webEnvironments from './routes/web/environments'
24
+ import webShares from './routes/web/shares'
25
+ import webTeams from './routes/web/teams'
22
26
 
23
27
  type ServeStaticFn = (opts: Record<string, unknown>) => MiddlewareHandler
24
28
 
@@ -72,8 +76,12 @@ export function registerRoutes(
72
76
  app.route('/v1/code/sessions', v2WorkerEventsStream)
73
77
  app.route('/v1/code/sessions', v2WorkerEvents)
74
78
 
75
- // Web control panel routes
79
+ // Web control panel routes — CSRF protection applied before route handlers
80
+ app.use('/web/*', csrfCheck)
76
81
  app.route('/web', webAuth)
82
+ app.route('/web/auth', webAuthRoutes)
83
+ app.route('/web', webShares)
84
+ app.route('/web', webTeams)
77
85
  app.route('/web', webSessions)
78
86
  app.route('/web', webControl)
79
87
  app.route('/web', webEnvironments)
@@ -96,4 +104,12 @@ export function registerRoutes(
96
104
  return c.json({ ok: false, error: String(err) }, 500)
97
105
  }
98
106
  })
107
+
108
+ // SPA fallback — serve index.html for frontend routes handled by React Router.
109
+ // Must be registered AFTER all API routes to avoid intercepting /web/*, /v1/*, /acp/* etc.
110
+ const spaPaths = ['/login', '/setup', '/join', '/settings', '/teams']
111
+ for (const p of spaPaths) {
112
+ app.get(p, ss({ root: webDir, path: 'index.html' }))
113
+ app.get(`${p}/*`, ss({ root: webDir, path: 'index.html' }))
114
+ }
99
115
  }
@@ -1,8 +1,13 @@
1
1
  import { Hono } from 'hono'
2
+ import { getCookie } from 'hono/cookie'
2
3
  import { randomUUID, createHash } from 'node:crypto'
3
4
  import { config } from '../../config'
4
5
  import { getDb } from '../../db/sqlite'
5
- import { hashPassword, hashPasswordAsync, verifyPassword, verifyPasswordAsync } from '../../auth/password'
6
+ import {
7
+ hashPasswordAsync,
8
+ verifyPassword,
9
+ verifyPasswordAsync,
10
+ } from '../../auth/password'
6
11
  import {
7
12
  issueSessionToken,
8
13
  resolveSessionToken,
@@ -14,6 +19,25 @@ import { SYSTEM_USER_ID } from '../../auth/constants'
14
19
 
15
20
  const app = new Hono()
16
21
 
22
+ /**
23
+ * Detect whether the current request is over HTTPS, taking reverse-proxy
24
+ * headers (X-Forwarded-Proto) into account.
25
+ */
26
+ function isSecureRequest(c: import('hono').Context): boolean {
27
+ const forwardedProto = c.req.header('X-Forwarded-Proto')
28
+ if (forwardedProto === 'https') return true
29
+ return c.req.url.startsWith('https://')
30
+ }
31
+
32
+ /**
33
+ * Dummy argon2id hash used for timing-safe login when the user does not
34
+ * exist. Prevents timing side-channel attacks that would reveal whether
35
+ * a username is registered. The value is a valid PHC-format argon2id hash
36
+ * that will never match any real password.
37
+ */
38
+ const DUMMY_HASH =
39
+ '$argon2id$v=19$m=65536,t=3,p=1$dGVzdHNhbHQ$dGVzdGhhc2h0ZXN0aGFzaA'
40
+
17
41
  // ---------------------------------------------------------------------------
18
42
  // Login rate limiting (Task 2) — in-memory, per IP+username
19
43
  // ---------------------------------------------------------------------------
@@ -171,42 +195,36 @@ app.post('/setup', async c => {
171
195
 
172
196
  const db = getDb()
173
197
 
174
- // Use BEGIN IMMEDIATE transaction to prevent concurrent setup race.
175
- // Two simultaneous requests: only one can acquire the write lock and
176
- // observe count=0; the other will see count>0 and throw.
198
+ // Atomic INSERT with WHERE NOT EXISTS guard — race-free under SQLite's
199
+ // serial write model. The first request INSERTs (changes=1), the second
200
+ // finds the admin row already present (changes=0 → 409).
177
201
  const userId = `usr_${randomUUID().replace(/-/g, '')}`
178
- const passwordHash = hashPassword(password)
202
+ const passwordHash = await hashPasswordAsync(password)
179
203
  const now = new Date().toISOString()
180
204
 
181
205
  try {
182
- db.transaction(() => {
183
- const count = db.query('SELECT COUNT(*) as count FROM users').get() as {
184
- count: number
185
- }
186
- if (count.count > 0) {
187
- throw new Error('setup_completed')
188
- }
189
-
190
- db.query(
206
+ const result = db
207
+ .query(
191
208
  `INSERT INTO users (id, username, password_hash, role, created_at)
192
- VALUES ($id, $username, $passwordHash, $role, $now)`,
193
- ).run({
209
+ SELECT $id, $username, $passwordHash, 'admin', $now
210
+ WHERE NOT EXISTS (SELECT 1 FROM users WHERE role = 'admin')`,
211
+ )
212
+ .run({
194
213
  $id: userId,
195
214
  $username: username,
196
215
  $passwordHash: passwordHash,
197
- $role: 'admin',
198
216
  $now: now,
199
217
  })
200
- })()
201
- } catch (err) {
202
- if (err instanceof Error && err.message === 'setup_completed') {
218
+
219
+ if (result.changes === 0) {
203
220
  return c.json({ error: 'Setup already completed' }, 409)
204
221
  }
205
- console.warn('[/setup] transaction failed:', err)
222
+ } catch (err) {
223
+ console.warn('[/setup] insert failed:', err)
206
224
  return c.json({ error: 'Setup failed' }, 500)
207
225
  }
208
226
 
209
- const isHttps = c.req.url.startsWith('https://')
227
+ const isHttps = isSecureRequest(c)
210
228
  return issueAndRespond(c, userId, isHttps)
211
229
  })
212
230
 
@@ -242,8 +260,16 @@ app.post('/login', async c => {
242
260
  .query('SELECT * FROM users WHERE username = $username')
243
261
  .get({ $username: username }) as UserRow | null
244
262
 
245
- // Generic error to avoid leaking user existence
246
- if (!user || !verifyPassword(password, user.password_hash)) {
263
+ // Generic error to avoid leaking user existence.
264
+ // When user doesn't exist, still run verifyPassword against a dummy hash
265
+ // to prevent timing side-channel attacks.
266
+ if (!user) {
267
+ verifyPassword(password, DUMMY_HASH)
268
+ recordFailedAttempt(clientIp, username)
269
+ return c.json({ error: 'Invalid credentials' }, 401)
270
+ }
271
+
272
+ if (!verifyPassword(password, user.password_hash)) {
247
273
  recordFailedAttempt(clientIp, username)
248
274
  return c.json({ error: 'Invalid credentials' }, 401)
249
275
  }
@@ -257,7 +283,7 @@ app.post('/login', async c => {
257
283
  $id: user.id,
258
284
  })
259
285
 
260
- const isHttps = c.req.url.startsWith('https://')
286
+ const isHttps = isSecureRequest(c)
261
287
  return issueAndRespond(c, user.id, isHttps)
262
288
  })
263
289
 
@@ -274,7 +300,7 @@ app.post('/refresh', async c => {
274
300
  const refreshToken =
275
301
  (body.refreshToken as string) ||
276
302
  (body.refresh_token as string) ||
277
- parseCookieValue(c.req.header('Cookie'), 'rcs_refresh')
303
+ getCookie(c, 'rcs_refresh')
278
304
 
279
305
  if (!refreshToken) {
280
306
  return c.json({ error: 'Refresh token required' }, 401)
@@ -291,7 +317,7 @@ app.post('/refresh', async c => {
291
317
  revokeSessionToken(refreshToken, db)
292
318
 
293
319
  // Issue new access + refresh token pair
294
- const isHttps = c.req.url.startsWith('https://')
320
+ const isHttps = isSecureRequest(c)
295
321
  const { accessToken, refreshToken: newRefreshToken } = issueSessionToken(
296
322
  userId,
297
323
  db,
@@ -318,7 +344,7 @@ app.post('/logout', async c => {
318
344
  ? authHeader.slice('Bearer '.length)
319
345
  : undefined
320
346
 
321
- const cookieToken = parseCookieValue(c.req.header('Cookie'), 'rcs_access')
347
+ const cookieToken = getCookie(c, 'rcs_access')
322
348
 
323
349
  const token = bearerToken || cookieToken
324
350
 
@@ -327,7 +353,18 @@ app.post('/logout', async c => {
327
353
  const db = getDb()
328
354
  revokeSessionToken(token, db)
329
355
  } catch (err) {
330
- console.warn('[/logout] failed to revoke token:', err)
356
+ console.warn('[/logout] failed to revoke access token:', err)
357
+ }
358
+ }
359
+
360
+ // Also revoke the refresh token if present
361
+ const refreshToken = getCookie(c, 'rcs_refresh')
362
+ if (refreshToken) {
363
+ try {
364
+ const db = getDb()
365
+ revokeSessionToken(refreshToken, db)
366
+ } catch {
367
+ // ignore — refresh token may already be invalid
331
368
  }
332
369
  }
333
370
 
@@ -388,66 +425,73 @@ app.post('/join', async c => {
388
425
  return c.json({ error: 'Invitation expired' }, 400)
389
426
  }
390
427
 
391
- // Check if already fully consumed (pre-check; atomic check below)
392
- if (invitation.consumed_at || invitation.uses >= invitation.max_uses) {
428
+ // Check if already fully consumed (pre-check; atomic check below).
429
+ // Only check uses >= max_uses — consumed_at is set only when the last
430
+ // slot is used, so checking it here would break max_uses > 1 invitations.
431
+ if (invitation.uses >= invitation.max_uses) {
393
432
  return c.json({ error: 'Invitation already used' }, 400)
394
433
  }
395
434
 
396
- // Check username uniqueness
435
+ // Check username uniqueness (pre-check; atomic check inside transaction)
397
436
  const existingUser = db
398
437
  .query('SELECT id FROM users WHERE username = $username')
399
438
  .get({ $username: username })
400
439
  if (existingUser) {
401
- return c.json({ error: 'Username already taken' }, 400)
440
+ return c.json({ error: 'Username already taken' }, 409)
402
441
  }
403
442
 
404
443
  // Create user + atomically consume invitation slot in a transaction.
405
- // The UPDATE with `uses < max_uses AND consumed_at IS NULL` clause
406
- // ensures that concurrent joins for a max_uses=1 invitation only
407
- // succeed for one of them (affected rows = 0 for the loser).
444
+ // The UPDATE with `uses < max_uses` clause ensures that concurrent joins
445
+ // only succeed up to max_uses times (affected rows = 0 for the losers).
408
446
  const userId = `usr_${randomUUID().replace(/-/g, '')}`
409
- const passwordHash = hashPassword(password)
447
+ const passwordHash = await hashPasswordAsync(password)
410
448
  const now = new Date().toISOString()
411
449
 
412
450
  try {
413
451
  db.transaction(() => {
414
- // Re-check invitation state inside the transaction (may have changed)
452
+ // Re-check invitation state inside the transaction (may have changed).
453
+ // Only check uses >= max_uses — consumed_at is set conditionally below.
415
454
  const currentInv = db
416
- .query('SELECT * FROM invitations WHERE token_hash = $hash')
417
- .get({ $hash: tokenHash }) as typeof invitation | null
418
-
419
- if (
420
- !currentInv ||
421
- currentInv.consumed_at ||
422
- currentInv.uses >= currentInv.max_uses
423
- ) {
455
+ .query('SELECT uses, max_uses FROM invitations WHERE token_hash = $hash')
456
+ .get({ $hash: tokenHash }) as { uses: number; max_uses: number } | null
457
+
458
+ if (!currentInv || currentInv.uses >= currentInv.max_uses) {
424
459
  throw new Error('invitation_used')
425
460
  }
426
461
 
427
- // Insert user
428
- db.query(
429
- `INSERT INTO users (id, username, password_hash, role, created_at)
430
- VALUES ($id, $username, $passwordHash, $role, $now)`,
431
- ).run({
432
- $id: userId,
433
- $username: username,
434
- $passwordHash: passwordHash,
435
- $role: invitation.role,
436
- $now: now,
437
- })
438
-
439
- // Atomically increment uses — will fail (changes=0) if race condition
462
+ // Insert user — INSERT OR IGNORE handles the UNIQUE username race:
463
+ // if another concurrent request inserted the same username, changes=0.
440
464
  const result = db
465
+ .query(
466
+ `INSERT OR IGNORE INTO users (id, username, password_hash, role, created_at)
467
+ VALUES ($id, $username, $passwordHash, $role, $now)`,
468
+ )
469
+ .run({
470
+ $id: userId,
471
+ $username: username,
472
+ $passwordHash: passwordHash,
473
+ $role: invitation.role,
474
+ $now: now,
475
+ })
476
+
477
+ if (result.changes === 0) {
478
+ throw new Error('username_taken')
479
+ }
480
+
481
+ // Atomically increment uses — will fail (changes=0) if race condition.
482
+ // consumed_at is only set when the last slot is used (uses + 1 >= max_uses).
483
+ const updateResult = db
441
484
  .query(
442
485
  `UPDATE invitations
443
- SET uses = uses + 1, consumed_at = $now, consumed_by = $userId
486
+ SET uses = uses + 1,
487
+ consumed_at = CASE WHEN uses + 1 >= max_uses THEN $now ELSE consumed_at END,
488
+ consumed_by = CASE WHEN uses + 1 >= max_uses THEN $userId ELSE consumed_by END
444
489
  WHERE token_hash = $hash
445
- AND uses < max_uses
446
- AND consumed_at IS NULL`,
490
+ AND uses < max_uses`,
447
491
  )
448
492
  .run({ $now: now, $userId: userId, $hash: tokenHash })
449
493
 
450
- if (result.changes === 0) {
494
+ if (updateResult.changes === 0) {
451
495
  throw new Error('invitation_used')
452
496
  }
453
497
 
@@ -469,11 +513,14 @@ app.post('/join', async c => {
469
513
  if (err instanceof Error && err.message === 'invitation_used') {
470
514
  return c.json({ error: 'Invitation already used or invalid' }, 400)
471
515
  }
516
+ if (err instanceof Error && err.message === 'username_taken') {
517
+ return c.json({ error: 'Username already taken' }, 409)
518
+ }
472
519
  console.warn('[/join] transaction failed:', err)
473
520
  return c.json({ error: 'Failed to process invitation' }, 500)
474
521
  }
475
522
 
476
- const isHttps = c.req.url.startsWith('https://')
523
+ const isHttps = isSecureRequest(c)
477
524
  return issueAndRespond(c, userId, isHttps)
478
525
  })
479
526
 
@@ -525,10 +572,7 @@ app.post(
525
572
  }
526
573
 
527
574
  if (newPassword.length < 8) {
528
- return c.json(
529
- { error: 'Password must be at least 8 characters' },
530
- 400,
531
- )
575
+ return c.json({ error: 'Password must be at least 8 characters' }, 400)
532
576
  }
533
577
 
534
578
  const db = getDb()
@@ -540,7 +584,10 @@ app.post(
540
584
  return c.json({ error: 'User not found' }, 404)
541
585
  }
542
586
 
543
- const isValid = await verifyPasswordAsync(currentPassword, user.password_hash)
587
+ const isValid = await verifyPasswordAsync(
588
+ currentPassword,
589
+ user.password_hash,
590
+ )
544
591
  if (!isValid) {
545
592
  return c.json({ error: 'Current password is incorrect' }, 401)
546
593
  }
@@ -622,13 +669,4 @@ app.get(
622
669
  },
623
670
  )
624
671
 
625
- function parseCookieValue(
626
- cookieHeader: string | undefined,
627
- name: string,
628
- ): string | undefined {
629
- if (!cookieHeader) return undefined
630
- const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`))
631
- return match ? match[1] : undefined
632
- }
633
-
634
672
  export default app
@@ -1,30 +1,23 @@
1
1
  import { Hono } from 'hono'
2
- import { storeBindSession } from '../../store'
3
- import {
4
- resolveExistingWebSessionId,
5
- toWebSessionId,
6
- } from '../../services/session'
7
2
 
8
3
  const app = new Hono()
9
4
 
10
- /** POST /web/bind — Bind a session to a UUID (no-login auth) */
11
- app.post('/bind', async c => {
12
- const body = await c.req.json()
13
- const sessionId = body.sessionId
14
- // UUID can come from query param (api.js sends it in URL) or body
15
- const uuid = c.req.query('uuid') || body.uuid
16
-
17
- if (!sessionId || !uuid) {
18
- return c.json({ error: 'sessionId and uuid are required' }, 400)
19
- }
20
-
21
- const resolvedSessionId = resolveExistingWebSessionId(sessionId)
22
- if (!resolvedSessionId) {
23
- return c.json({ error: 'Session not found' }, 404)
24
- }
25
-
26
- storeBindSession(resolvedSessionId, uuid)
27
- return c.json({ ok: true, sessionId: toWebSessionId(resolvedSessionId) })
5
+ /**
6
+ * POST /web/bind — REMOVED (security: unauthenticated session binding).
7
+ *
8
+ * Returns 410 Gone to signal clients that this endpoint has been
9
+ * intentionally deprecated and will not be restored.
10
+ */
11
+ app.all('/bind', c => {
12
+ return c.json(
13
+ {
14
+ error: {
15
+ type: 'gone',
16
+ message: '/web/bind has been removed. Use authenticated session creation instead.',
17
+ },
18
+ },
19
+ 410,
20
+ )
28
21
  })
29
22
 
30
23
  export default app