@saluzi/saluzi-edu 0.2.9 → 0.2.11

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 (219) hide show
  1. package/dist/cli.js +336 -333
  2. package/dist/rcs-web/assets/Dashboard-BwotBWlJ.js +1 -0
  3. package/dist/rcs-web/assets/Join-4yrksbdX.js +1 -0
  4. package/dist/rcs-web/assets/Login-Cxm_yEeE.js +1 -0
  5. package/dist/rcs-web/assets/SessionDetail-DNdfPOYy.js +50 -0
  6. package/dist/rcs-web/assets/Setup-BiAAxugt.js +1 -0
  7. package/dist/rcs-web/assets/ShareLanding-CP3Z6ckr.js +1 -0
  8. package/dist/rcs-web/assets/TeamCreate-DgdCwRSb.js +1 -0
  9. package/dist/rcs-web/assets/TeamDetail-CkNTFoMB.js +11 -0
  10. package/dist/rcs-web/assets/TeamList-Bf0sQ5RQ.js +1 -0
  11. package/dist/rcs-web/assets/TeamSettings-BaNAfXU3.js +1 -0
  12. package/dist/rcs-web/assets/UserSettings-CsutQjxG.js +1 -0
  13. package/{packages/remote-control-server/web/dist/assets/arc-D90597o-.js → dist/rcs-web/assets/arc-BxxmNQO6.js} +1 -1
  14. package/dist/rcs-web/assets/architectureDiagram-3BPJPVTR-DBjkRdbd.js +36 -0
  15. package/dist/rcs-web/assets/{blockDiagram-GPEHLZMM-DHd8IbOs.js → blockDiagram-GPEHLZMM-BF6COWlM.js} +2 -2
  16. package/dist/rcs-web/assets/{c4Diagram-AAUBKEIU-CjFkQJ9h.js → c4Diagram-AAUBKEIU-DHOf390v.js} +2 -2
  17. package/dist/rcs-web/assets/channel-JsvbMQjW.js +1 -0
  18. package/dist/rcs-web/assets/{chunk-2J33WTMH-C21XWnGZ.js → chunk-2J33WTMH-CeJN8yjM.js} +1 -1
  19. package/dist/rcs-web/assets/{chunk-4BX2VUAB-B3SXiJlt.js → chunk-4BX2VUAB-jP76_P2A.js} +1 -1
  20. package/dist/rcs-web/assets/{chunk-55IACEB6-DjImv45F.js → chunk-55IACEB6-BsFA6emB.js} +1 -1
  21. package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-DtnaqsyD.js → dist/rcs-web/assets/chunk-727SXJPM-BF4HrQjZ.js} +1 -1
  22. package/dist/rcs-web/assets/{chunk-AQP2D5EJ-BVFgH5mS.js → chunk-AQP2D5EJ-DToJ74Kc.js} +1 -1
  23. package/dist/rcs-web/assets/{chunk-FMBD7UC4-Bl8jgb57.js → chunk-FMBD7UC4-DVTARgsa.js} +1 -1
  24. package/dist/rcs-web/assets/{chunk-ND2GUHAM-BpK111zh.js → chunk-ND2GUHAM-C50Gqv9c.js} +1 -1
  25. package/dist/rcs-web/assets/{chunk-QZHKN3VN-ruhIHBim.js → chunk-QZHKN3VN-EsIIh6zV.js} +1 -1
  26. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-BXn8Uc4c.js +1 -0
  27. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-BXn8Uc4c.js +1 -0
  28. package/dist/rcs-web/assets/code-block-IT6T5CEO-BnufHNTT.js +2 -0
  29. package/dist/rcs-web/assets/cose-bilkent-S5V4N54A-M6tNt14I.js +1 -0
  30. package/dist/rcs-web/assets/dagre-BM42HDAG-DaTm29qG.js +4 -0
  31. package/dist/rcs-web/assets/diagram-2AECGRRQ-fqoeOvq2.js +43 -0
  32. package/dist/rcs-web/assets/diagram-5GNKFQAL-6s2CiaGG.js +10 -0
  33. package/dist/rcs-web/assets/{diagram-KO2AKTUF-Uy3PDP5S.js → diagram-KO2AKTUF-C0aw_N95.js} +2 -2
  34. package/dist/rcs-web/assets/diagram-LMA3HP47-C8_Rv3rB.js +24 -0
  35. package/dist/rcs-web/assets/diagram-OG6HWLK6-Dae4u8Uc.js +24 -0
  36. package/{packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-sre_MTeK.js → dist/rcs-web/assets/erDiagram-TEJ5UH35-CosS5VWr.js} +13 -13
  37. package/dist/rcs-web/assets/{flowDiagram-I6XJVG4X-DwrWaLoe.js → flowDiagram-I6XJVG4X-D_Mxs46-.js} +2 -2
  38. package/dist/rcs-web/assets/ganttDiagram-6RSMTGT7-B0jy8Am_.js +292 -0
  39. package/dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-CSdu2HVI.js +106 -0
  40. package/dist/rcs-web/assets/i18n-Dbt-vEQ3.js +1 -0
  41. package/dist/rcs-web/assets/infoDiagram-5YYISTIA-Dcbc9L1S.js +2 -0
  42. package/{packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-SO0nn3--.js → dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-BLMjFE4t.js} +2 -2
  43. package/dist/rcs-web/assets/{journeyDiagram-JHISSGLW-D4igFUd4.js → journeyDiagram-JHISSGLW-Dw4nh1xI.js} +2 -2
  44. package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-Cm_yKsZC.js → kanban-definition-UN3LZRKU-DkU3j_ly.js} +2 -2
  45. package/dist/rcs-web/assets/{linear-4IMGll_E.js → linear-N5Cify7M.js} +1 -1
  46. package/dist/rcs-web/assets/main-30YqUvTd.js +238 -0
  47. package/dist/rcs-web/assets/main-EP6Lac2m.css +1 -0
  48. package/{packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-D_yQ7nAd.js → dist/rcs-web/assets/mermaid-VLURNSYL-gfiJUpl_.js} +5 -5
  49. package/dist/rcs-web/assets/{mermaid.core-B1qOUdiU.js → mermaid.core-B_Xc65Bf.js} +5 -5
  50. package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-yETom5qt.js → mindmap-definition-RKZ34NQL-DUavHfNu.js} +2 -2
  51. package/{packages/remote-control-server/web/dist/assets/pieDiagram-4H26LBE5-KIhqXGT6.js → dist/rcs-web/assets/pieDiagram-4H26LBE5-DSJaToTU.js} +2 -2
  52. package/{packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-DISqmoA6.js → dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-BFp6TnHj.js} +2 -2
  53. package/dist/rcs-web/assets/{requirementDiagram-4Y6WPE33-DsgT4ptk.js → requirementDiagram-4Y6WPE33-eil6F3-O.js} +2 -2
  54. package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-Dn67kbn0.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-DbGhoepK.js} +2 -2
  55. package/{packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-CtWaXZHl.js → dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-B0YrN1ud.js} +14 -14
  56. package/dist/rcs-web/assets/stateDiagram-AJRCARHV-CofFPj3N.js +1 -0
  57. package/dist/rcs-web/assets/stateDiagram-v2-BHNVJYJU-DPlcvPn1.js +1 -0
  58. package/{packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-Dk5WUenC.js → dist/rcs-web/assets/timeline-definition-PNZ67QCA-Bx4dusy8.js} +2 -2
  59. package/dist/rcs-web/assets/useTeams-MDI4djXg.js +1 -0
  60. package/{packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-TY8EiRrC.js → dist/rcs-web/assets/vennDiagram-CIIHVFJN-B_-3l-MF.js} +2 -2
  61. package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-BK3oi47X.js → wardleyDiagram-YWT4CUSO-DNSn-pGp.js} +2 -2
  62. package/dist/rcs-web/assets/workerType-OM09y7Lk.js +1 -0
  63. package/{packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-DhsxqX67.js → dist/rcs-web/assets/xychartDiagram-2RQKCTM6-TqUbNp0j.js} +2 -2
  64. package/dist/rcs-web/index.html +2 -3
  65. package/package.json +2 -1
  66. package/packages/remote-control-server/src/__tests__/acp-ws-handler.test.ts +7 -19
  67. package/packages/remote-control-server/src/__tests__/auth-login.test.ts +561 -0
  68. package/packages/remote-control-server/src/__tests__/auth-security.test.ts +547 -0
  69. package/packages/remote-control-server/src/__tests__/auth.test.ts +7 -18
  70. package/packages/remote-control-server/src/__tests__/bind-replacement.test.ts +251 -0
  71. package/packages/remote-control-server/src/__tests__/cookie.test.ts +155 -0
  72. package/packages/remote-control-server/src/__tests__/data-layer-fixes.test.ts +147 -0
  73. package/packages/remote-control-server/src/__tests__/db.test.ts +582 -0
  74. package/packages/remote-control-server/src/__tests__/design-gaps.test.ts +440 -0
  75. package/packages/remote-control-server/src/__tests__/disconnect-monitor.test.ts +2 -18
  76. package/packages/remote-control-server/src/__tests__/helpers/db.ts +43 -0
  77. package/packages/remote-control-server/src/__tests__/helpers/fixtures.ts +94 -0
  78. package/packages/remote-control-server/src/__tests__/helpers/mock-config.ts +31 -0
  79. package/packages/remote-control-server/src/__tests__/middleware.test.ts +4 -112
  80. package/packages/remote-control-server/src/__tests__/password.test.ts +76 -0
  81. package/packages/remote-control-server/src/__tests__/phase4-cleanup.test.ts +290 -0
  82. package/packages/remote-control-server/src/__tests__/phase4-integration.test.ts +224 -0
  83. package/packages/remote-control-server/src/__tests__/routes.test.ts +292 -207
  84. package/packages/remote-control-server/src/__tests__/services.test.ts +2 -18
  85. package/packages/remote-control-server/src/__tests__/session-visibility.test.ts +452 -0
  86. package/packages/remote-control-server/src/__tests__/session.test.ts +216 -0
  87. package/packages/remote-control-server/src/__tests__/shares.test.ts +1419 -0
  88. package/packages/remote-control-server/src/__tests__/sse-writer.test.ts +2 -18
  89. package/packages/remote-control-server/src/__tests__/teams.test.ts +2089 -0
  90. package/packages/remote-control-server/src/__tests__/work-dispatch.test.ts +2 -18
  91. package/packages/remote-control-server/src/__tests__/worker-token-removal.test.ts +487 -0
  92. package/packages/remote-control-server/src/__tests__/ws-handler.test.ts +2 -18
  93. package/packages/remote-control-server/src/auth/constants.ts +13 -0
  94. package/packages/remote-control-server/src/auth/cookie.ts +75 -0
  95. package/packages/remote-control-server/src/auth/middleware.ts +75 -165
  96. package/packages/remote-control-server/src/auth/password.ts +116 -0
  97. package/packages/remote-control-server/src/auth/session.ts +130 -0
  98. package/packages/remote-control-server/src/db/schema.sql +116 -0
  99. package/packages/remote-control-server/src/db/sqlite.ts +99 -0
  100. package/packages/remote-control-server/src/routes/acp/index.ts +6 -11
  101. package/packages/remote-control-server/src/routes/v1/environments.ts +111 -8
  102. package/packages/remote-control-server/src/routes/web/auth-routes.ts +634 -0
  103. package/packages/remote-control-server/src/routes/web/control.ts +5 -5
  104. package/packages/remote-control-server/src/routes/web/environments.ts +5 -27
  105. package/packages/remote-control-server/src/routes/web/sessions.ts +19 -41
  106. package/packages/remote-control-server/src/routes/web/shares.ts +371 -0
  107. package/packages/remote-control-server/src/routes/web/teams.ts +850 -0
  108. package/packages/remote-control-server/src/services/environment.ts +1 -4
  109. package/packages/remote-control-server/src/services/session-visibility.ts +79 -0
  110. package/packages/remote-control-server/src/services/session.ts +0 -13
  111. package/packages/remote-control-server/src/store.ts +150 -56
  112. package/packages/remote-control-server/src/types/api.ts +2 -8
  113. package/packages/remote-control-server/web/dist/assets/Dashboard-BwotBWlJ.js +1 -0
  114. package/packages/remote-control-server/web/dist/assets/Join-4yrksbdX.js +1 -0
  115. package/packages/remote-control-server/web/dist/assets/Login-Cxm_yEeE.js +1 -0
  116. package/packages/remote-control-server/web/dist/assets/SessionDetail-DNdfPOYy.js +50 -0
  117. package/packages/remote-control-server/web/dist/assets/Setup-BiAAxugt.js +1 -0
  118. package/packages/remote-control-server/web/dist/assets/ShareLanding-CP3Z6ckr.js +1 -0
  119. package/packages/remote-control-server/web/dist/assets/TeamCreate-DgdCwRSb.js +1 -0
  120. package/packages/remote-control-server/web/dist/assets/TeamDetail-CkNTFoMB.js +11 -0
  121. package/packages/remote-control-server/web/dist/assets/TeamList-Bf0sQ5RQ.js +1 -0
  122. package/packages/remote-control-server/web/dist/assets/TeamSettings-BaNAfXU3.js +1 -0
  123. package/packages/remote-control-server/web/dist/assets/UserSettings-CsutQjxG.js +1 -0
  124. package/{dist/rcs-web/assets/arc-D90597o-.js → packages/remote-control-server/web/dist/assets/arc-BxxmNQO6.js} +1 -1
  125. package/packages/remote-control-server/web/dist/assets/architectureDiagram-3BPJPVTR-DBjkRdbd.js +36 -0
  126. package/packages/remote-control-server/web/dist/assets/{blockDiagram-GPEHLZMM-DHd8IbOs.js → blockDiagram-GPEHLZMM-BF6COWlM.js} +2 -2
  127. package/packages/remote-control-server/web/dist/assets/{c4Diagram-AAUBKEIU-CjFkQJ9h.js → c4Diagram-AAUBKEIU-DHOf390v.js} +2 -2
  128. package/packages/remote-control-server/web/dist/assets/channel-JsvbMQjW.js +1 -0
  129. package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-C21XWnGZ.js → chunk-2J33WTMH-CeJN8yjM.js} +1 -1
  130. package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-B3SXiJlt.js → chunk-4BX2VUAB-jP76_P2A.js} +1 -1
  131. package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-DjImv45F.js → chunk-55IACEB6-BsFA6emB.js} +1 -1
  132. package/{dist/rcs-web/assets/chunk-727SXJPM-DtnaqsyD.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-BF4HrQjZ.js} +1 -1
  133. package/packages/remote-control-server/web/dist/assets/{chunk-AQP2D5EJ-BVFgH5mS.js → chunk-AQP2D5EJ-DToJ74Kc.js} +1 -1
  134. package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-Bl8jgb57.js → chunk-FMBD7UC4-DVTARgsa.js} +1 -1
  135. package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-BpK111zh.js → chunk-ND2GUHAM-C50Gqv9c.js} +1 -1
  136. package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-ruhIHBim.js → chunk-QZHKN3VN-EsIIh6zV.js} +1 -1
  137. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-BXn8Uc4c.js +1 -0
  138. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-BXn8Uc4c.js +1 -0
  139. package/packages/remote-control-server/web/dist/assets/code-block-IT6T5CEO-BnufHNTT.js +2 -0
  140. package/packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-M6tNt14I.js +1 -0
  141. package/packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-DaTm29qG.js +4 -0
  142. package/packages/remote-control-server/web/dist/assets/diagram-2AECGRRQ-fqoeOvq2.js +43 -0
  143. package/packages/remote-control-server/web/dist/assets/diagram-5GNKFQAL-6s2CiaGG.js +10 -0
  144. package/packages/remote-control-server/web/dist/assets/{diagram-KO2AKTUF-Uy3PDP5S.js → diagram-KO2AKTUF-C0aw_N95.js} +2 -2
  145. package/packages/remote-control-server/web/dist/assets/diagram-LMA3HP47-C8_Rv3rB.js +24 -0
  146. package/packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-Dae4u8Uc.js +24 -0
  147. package/{dist/rcs-web/assets/erDiagram-TEJ5UH35-sre_MTeK.js → packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-CosS5VWr.js} +13 -13
  148. package/packages/remote-control-server/web/dist/assets/{flowDiagram-I6XJVG4X-DwrWaLoe.js → flowDiagram-I6XJVG4X-D_Mxs46-.js} +2 -2
  149. package/packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-B0jy8Am_.js +292 -0
  150. package/packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-CSdu2HVI.js +106 -0
  151. package/packages/remote-control-server/web/dist/assets/i18n-Dbt-vEQ3.js +1 -0
  152. package/packages/remote-control-server/web/dist/assets/infoDiagram-5YYISTIA-Dcbc9L1S.js +2 -0
  153. package/{dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-SO0nn3--.js → packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-BLMjFE4t.js} +2 -2
  154. package/packages/remote-control-server/web/dist/assets/{journeyDiagram-JHISSGLW-D4igFUd4.js → journeyDiagram-JHISSGLW-Dw4nh1xI.js} +2 -2
  155. package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-Cm_yKsZC.js → kanban-definition-UN3LZRKU-DkU3j_ly.js} +2 -2
  156. package/packages/remote-control-server/web/dist/assets/{linear-4IMGll_E.js → linear-N5Cify7M.js} +1 -1
  157. package/packages/remote-control-server/web/dist/assets/main-30YqUvTd.js +238 -0
  158. package/packages/remote-control-server/web/dist/assets/main-EP6Lac2m.css +1 -0
  159. package/{dist/rcs-web/assets/mermaid-VLURNSYL-D_yQ7nAd.js → packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-gfiJUpl_.js} +5 -5
  160. package/packages/remote-control-server/web/dist/assets/{mermaid.core-B1qOUdiU.js → mermaid.core-B_Xc65Bf.js} +5 -5
  161. package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-yETom5qt.js → mindmap-definition-RKZ34NQL-DUavHfNu.js} +2 -2
  162. package/{dist/rcs-web/assets/pieDiagram-4H26LBE5-KIhqXGT6.js → packages/remote-control-server/web/dist/assets/pieDiagram-4H26LBE5-DSJaToTU.js} +2 -2
  163. package/{dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-DISqmoA6.js → packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-BFp6TnHj.js} +2 -2
  164. package/packages/remote-control-server/web/dist/assets/{requirementDiagram-4Y6WPE33-DsgT4ptk.js → requirementDiagram-4Y6WPE33-eil6F3-O.js} +2 -2
  165. package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-Dn67kbn0.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-DbGhoepK.js} +2 -2
  166. package/{dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-CtWaXZHl.js → packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-B0YrN1ud.js} +14 -14
  167. package/packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-CofFPj3N.js +1 -0
  168. package/packages/remote-control-server/web/dist/assets/stateDiagram-v2-BHNVJYJU-DPlcvPn1.js +1 -0
  169. package/{dist/rcs-web/assets/timeline-definition-PNZ67QCA-Dk5WUenC.js → packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-Bx4dusy8.js} +2 -2
  170. package/packages/remote-control-server/web/dist/assets/useTeams-MDI4djXg.js +1 -0
  171. package/{dist/rcs-web/assets/vennDiagram-CIIHVFJN-TY8EiRrC.js → packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-B_-3l-MF.js} +2 -2
  172. package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-BK3oi47X.js → wardleyDiagram-YWT4CUSO-DNSn-pGp.js} +2 -2
  173. package/packages/remote-control-server/web/dist/assets/workerType-OM09y7Lk.js +1 -0
  174. package/{dist/rcs-web/assets/xychartDiagram-2RQKCTM6-DhsxqX67.js → packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-TqUbNp0j.js} +2 -2
  175. package/packages/remote-control-server/web/dist/index.html +2 -3
  176. package/dist/rcs-web/assets/Dashboard-Cyj-Bb86.js +0 -2
  177. package/dist/rcs-web/assets/SessionDetail-SA9QBkyj.js +0 -51
  178. package/dist/rcs-web/assets/architectureDiagram-3BPJPVTR-CG9q9_bk.js +0 -36
  179. package/dist/rcs-web/assets/channel-CQFGglg-.js +0 -1
  180. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-C5rBdShD.js +0 -1
  181. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-C5rBdShD.js +0 -1
  182. package/dist/rcs-web/assets/code-block-IT6T5CEO-BLGFrBw3.js +0 -2
  183. package/dist/rcs-web/assets/cose-bilkent-S5V4N54A-DBKEXjqM.js +0 -1
  184. package/dist/rcs-web/assets/dagre-BM42HDAG-DB_SWlAe.js +0 -4
  185. package/dist/rcs-web/assets/diagram-2AECGRRQ-Gbm8G3Xz.js +0 -43
  186. package/dist/rcs-web/assets/diagram-5GNKFQAL-BBuWqfDc.js +0 -10
  187. package/dist/rcs-web/assets/diagram-LMA3HP47-DyUwNrlg.js +0 -24
  188. package/dist/rcs-web/assets/diagram-OG6HWLK6-BXpvRwkJ.js +0 -24
  189. package/dist/rcs-web/assets/ganttDiagram-6RSMTGT7-ByDYviDl.js +0 -292
  190. package/dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-BUnOPw74.js +0 -106
  191. package/dist/rcs-web/assets/infoDiagram-5YYISTIA-B1RpwJHG.js +0 -2
  192. package/dist/rcs-web/assets/main-0A4a3FDU.css +0 -1
  193. package/dist/rcs-web/assets/main-Dom16YJk.js +0 -283
  194. package/dist/rcs-web/assets/qr-C0jP9HcD.js +0 -8
  195. package/dist/rcs-web/assets/qr-scanner-worker.min-D85Z9gVD.js +0 -98
  196. package/dist/rcs-web/assets/stateDiagram-AJRCARHV-D6xp64f3.js +0 -1
  197. package/dist/rcs-web/assets/stateDiagram-v2-BHNVJYJU-D76vZCyQ.js +0 -1
  198. package/packages/remote-control-server/web/dist/assets/Dashboard-Cyj-Bb86.js +0 -2
  199. package/packages/remote-control-server/web/dist/assets/SessionDetail-SA9QBkyj.js +0 -51
  200. package/packages/remote-control-server/web/dist/assets/architectureDiagram-3BPJPVTR-CG9q9_bk.js +0 -36
  201. package/packages/remote-control-server/web/dist/assets/channel-CQFGglg-.js +0 -1
  202. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-C5rBdShD.js +0 -1
  203. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-C5rBdShD.js +0 -1
  204. package/packages/remote-control-server/web/dist/assets/code-block-IT6T5CEO-BLGFrBw3.js +0 -2
  205. package/packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-DBKEXjqM.js +0 -1
  206. package/packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-DB_SWlAe.js +0 -4
  207. package/packages/remote-control-server/web/dist/assets/diagram-2AECGRRQ-Gbm8G3Xz.js +0 -43
  208. package/packages/remote-control-server/web/dist/assets/diagram-5GNKFQAL-BBuWqfDc.js +0 -10
  209. package/packages/remote-control-server/web/dist/assets/diagram-LMA3HP47-DyUwNrlg.js +0 -24
  210. package/packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-BXpvRwkJ.js +0 -24
  211. package/packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-ByDYviDl.js +0 -292
  212. package/packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-BUnOPw74.js +0 -106
  213. package/packages/remote-control-server/web/dist/assets/infoDiagram-5YYISTIA-B1RpwJHG.js +0 -2
  214. package/packages/remote-control-server/web/dist/assets/main-0A4a3FDU.css +0 -1
  215. package/packages/remote-control-server/web/dist/assets/main-Dom16YJk.js +0 -283
  216. package/packages/remote-control-server/web/dist/assets/qr-C0jP9HcD.js +0 -8
  217. package/packages/remote-control-server/web/dist/assets/qr-scanner-worker.min-D85Z9gVD.js +0 -98
  218. package/packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-D6xp64f3.js +0 -1
  219. package/packages/remote-control-server/web/dist/assets/stateDiagram-v2-BHNVJYJU-D76vZCyQ.js +0 -1
@@ -0,0 +1,634 @@
1
+ import { Hono } from 'hono'
2
+ import { randomUUID, createHash } from 'node:crypto'
3
+ import { config } from '../../config'
4
+ import { getDb } from '../../db/sqlite'
5
+ import { hashPassword, hashPasswordAsync, verifyPassword, verifyPasswordAsync } from '../../auth/password'
6
+ import {
7
+ issueSessionToken,
8
+ resolveSessionToken,
9
+ revokeSessionToken,
10
+ } from '../../auth/session'
11
+ import { buildSessionCookies, buildClearCookies } from '../../auth/cookie'
12
+ import { sessionAuth } from '../../auth/middleware'
13
+ import { SYSTEM_USER_ID } from '../../auth/constants'
14
+
15
+ const app = new Hono()
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Login rate limiting (Task 2) — in-memory, per IP+username
19
+ // ---------------------------------------------------------------------------
20
+ const RATE_LIMIT_MAX_ATTEMPTS = 5
21
+ const RATE_LIMIT_LOCKOUT_MS = 5 * 60 * 1000 // 5 minutes
22
+ const RATE_LIMIT_CLEANUP_INTERVAL_MS = 10 * 60 * 1000 // 10 minutes
23
+
24
+ interface RateLimitEntry {
25
+ count: number
26
+ lockedUntil: number
27
+ }
28
+
29
+ const loginAttempts = new Map<string, RateLimitEntry>()
30
+
31
+ /** Reset the rate limit map (exported for testing). */
32
+ export function _resetRateLimits(): void {
33
+ loginAttempts.clear()
34
+ }
35
+
36
+ // Periodically clean up stale entries to prevent memory leaks
37
+ const rateLimitCleanupTimer = setInterval(() => {
38
+ const now = Date.now()
39
+ for (const [key, entry] of loginAttempts) {
40
+ if (entry.lockedUntil < now && entry.count === 0) {
41
+ loginAttempts.delete(key)
42
+ }
43
+ }
44
+ }, RATE_LIMIT_CLEANUP_INTERVAL_MS)
45
+
46
+ // Allow Node/Bun to exit even if timer is active
47
+ if (rateLimitCleanupTimer.unref) {
48
+ rateLimitCleanupTimer.unref()
49
+ }
50
+
51
+ function getRateLimitKey(ip: string, username: string): string {
52
+ return `${ip}:${username.toLowerCase()}`
53
+ }
54
+
55
+ /**
56
+ * Returns true if the request is allowed, false if rate-limited.
57
+ */
58
+ export function checkRateLimit(ip: string, username: string): boolean {
59
+ const key = getRateLimitKey(ip, username)
60
+ const entry = loginAttempts.get(key)
61
+ if (!entry) return true
62
+ if (entry.lockedUntil > Date.now()) return false
63
+ return true
64
+ }
65
+
66
+ /**
67
+ * Record a failed login attempt. Returns true if now locked out.
68
+ */
69
+ export function recordFailedAttempt(ip: string, username: string): boolean {
70
+ const key = getRateLimitKey(ip, username)
71
+ const now = Date.now()
72
+ const entry = loginAttempts.get(key) || { count: 0, lockedUntil: 0 }
73
+
74
+ // If a previous lockout has expired, reset counter for a fresh window.
75
+ // Only reset when lockedUntil was previously set (> 0) and has now expired.
76
+ if (entry.lockedUntil > 0 && entry.lockedUntil < now) {
77
+ entry.count = 0
78
+ entry.lockedUntil = 0
79
+ }
80
+
81
+ entry.count += 1
82
+ if (entry.count >= RATE_LIMIT_MAX_ATTEMPTS) {
83
+ entry.lockedUntil = now + RATE_LIMIT_LOCKOUT_MS
84
+ }
85
+ loginAttempts.set(key, entry)
86
+ return entry.lockedUntil > now
87
+ }
88
+
89
+ /**
90
+ * Clear rate limit on successful login.
91
+ */
92
+ function clearRateLimit(ip: string, username: string): void {
93
+ const key = getRateLimitKey(ip, username)
94
+ loginAttempts.delete(key)
95
+ }
96
+
97
+ /**
98
+ * Extract client IP from request (respects X-Forwarded-For).
99
+ */
100
+ function getClientIp(c: import('hono').Context): string {
101
+ const forwarded = c.req.header('X-Forwarded-For')
102
+ if (forwarded) {
103
+ const first = forwarded.split(',')[0]?.trim()
104
+ if (first) return first
105
+ }
106
+ return c.req.header('X-Real-IP') || 'unknown'
107
+ }
108
+
109
+ interface UserRow {
110
+ id: string
111
+ username: string
112
+ password_hash: string
113
+ role: string
114
+ created_at: string
115
+ }
116
+
117
+ /**
118
+ * Helper: issue tokens, set cookies, return JSON response.
119
+ */
120
+ function issueAndRespond(
121
+ c: import('hono').Context,
122
+ userId: string,
123
+ isHttps: boolean,
124
+ ) {
125
+ const db = getDb()
126
+ const { accessToken, refreshToken } = issueSessionToken(userId, db)
127
+ const cookies = buildSessionCookies(accessToken, refreshToken, {
128
+ secure: isHttps,
129
+ })
130
+ for (const cookie of cookies) {
131
+ c.header('Set-Cookie', cookie, { append: true })
132
+ }
133
+ return c.json({
134
+ accessToken,
135
+ refreshToken,
136
+ })
137
+ }
138
+
139
+ // POST /setup — First-deploy admin bootstrap
140
+ app.post('/setup', async c => {
141
+ let body: Record<string, unknown>
142
+ try {
143
+ body = await c.req.json()
144
+ } catch {
145
+ return c.json({ error: 'Invalid JSON' }, 400)
146
+ }
147
+
148
+ const { apiKey, username, password } = body as {
149
+ apiKey?: string
150
+ username?: string
151
+ password?: string
152
+ }
153
+
154
+ // Validate API key
155
+ if (!apiKey || !config.apiKeys.includes(apiKey)) {
156
+ return c.json({ error: 'Invalid API key' }, 401)
157
+ }
158
+
159
+ // Validate username
160
+ if (!username || typeof username !== 'string') {
161
+ return c.json({ error: 'Username is required' }, 400)
162
+ }
163
+
164
+ // Validate password
165
+ if (!password || typeof password !== 'string') {
166
+ return c.json({ error: 'Password is required' }, 400)
167
+ }
168
+ if (password.length < 8) {
169
+ return c.json({ error: 'Password must be at least 8 characters' }, 400)
170
+ }
171
+
172
+ const db = getDb()
173
+
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.
177
+ const userId = `usr_${randomUUID().replace(/-/g, '')}`
178
+ const passwordHash = hashPassword(password)
179
+ const now = new Date().toISOString()
180
+
181
+ 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(
191
+ `INSERT INTO users (id, username, password_hash, role, created_at)
192
+ VALUES ($id, $username, $passwordHash, $role, $now)`,
193
+ ).run({
194
+ $id: userId,
195
+ $username: username,
196
+ $passwordHash: passwordHash,
197
+ $role: 'admin',
198
+ $now: now,
199
+ })
200
+ })()
201
+ } catch (err) {
202
+ if (err instanceof Error && err.message === 'setup_completed') {
203
+ return c.json({ error: 'Setup already completed' }, 409)
204
+ }
205
+ console.warn('[/setup] transaction failed:', err)
206
+ return c.json({ error: 'Setup failed' }, 500)
207
+ }
208
+
209
+ const isHttps = c.req.url.startsWith('https://')
210
+ return issueAndRespond(c, userId, isHttps)
211
+ })
212
+
213
+ // POST /login
214
+ app.post('/login', async c => {
215
+ let body: Record<string, unknown>
216
+ try {
217
+ body = await c.req.json()
218
+ } catch {
219
+ return c.json({ error: 'Invalid JSON' }, 400)
220
+ }
221
+
222
+ const { username, password } = body as {
223
+ username?: string
224
+ password?: string
225
+ }
226
+
227
+ if (!username || !password) {
228
+ return c.json({ error: 'Username and password are required' }, 400)
229
+ }
230
+
231
+ // Rate limit by IP + username
232
+ const clientIp = getClientIp(c)
233
+ if (!checkRateLimit(clientIp, username)) {
234
+ return c.json(
235
+ { error: 'Too many failed login attempts. Try again later.' },
236
+ 429,
237
+ )
238
+ }
239
+
240
+ const db = getDb()
241
+ const user = db
242
+ .query('SELECT * FROM users WHERE username = $username')
243
+ .get({ $username: username }) as UserRow | null
244
+
245
+ // Generic error to avoid leaking user existence
246
+ if (!user || !verifyPassword(password, user.password_hash)) {
247
+ recordFailedAttempt(clientIp, username)
248
+ return c.json({ error: 'Invalid credentials' }, 401)
249
+ }
250
+
251
+ // Successful login — clear rate limit
252
+ clearRateLimit(clientIp, username)
253
+
254
+ // Update last_login_at
255
+ db.query('UPDATE users SET last_login_at = $now WHERE id = $id').run({
256
+ $now: new Date().toISOString(),
257
+ $id: user.id,
258
+ })
259
+
260
+ const isHttps = c.req.url.startsWith('https://')
261
+ return issueAndRespond(c, user.id, isHttps)
262
+ })
263
+
264
+ // POST /refresh — rotate both access and refresh tokens (Task 4)
265
+ app.post('/refresh', async c => {
266
+ let body: Record<string, unknown> = {}
267
+ try {
268
+ body = await c.req.json()
269
+ } catch {
270
+ // body may be empty
271
+ }
272
+
273
+ // Get refresh token from body or cookie
274
+ const refreshToken =
275
+ (body.refreshToken as string) ||
276
+ (body.refresh_token as string) ||
277
+ parseCookieValue(c.req.header('Cookie'), 'rcs_refresh')
278
+
279
+ if (!refreshToken) {
280
+ return c.json({ error: 'Refresh token required' }, 401)
281
+ }
282
+
283
+ const db = getDb()
284
+ const userId = resolveSessionToken(refreshToken, db, 'refresh')
285
+
286
+ if (!userId) {
287
+ return c.json({ error: 'Invalid or expired refresh token' }, 401)
288
+ }
289
+
290
+ // Revoke the old refresh token (rotation)
291
+ revokeSessionToken(refreshToken, db)
292
+
293
+ // Issue new access + refresh token pair
294
+ const isHttps = c.req.url.startsWith('https://')
295
+ const { accessToken, refreshToken: newRefreshToken } = issueSessionToken(
296
+ userId,
297
+ db,
298
+ )
299
+
300
+ const cookies = buildSessionCookies(accessToken, newRefreshToken, {
301
+ secure: isHttps,
302
+ })
303
+ for (const cookie of cookies) {
304
+ c.header('Set-Cookie', cookie, { append: true })
305
+ }
306
+
307
+ return c.json({
308
+ accessToken,
309
+ refreshToken: newRefreshToken,
310
+ })
311
+ })
312
+
313
+ // POST /logout
314
+ app.post('/logout', async c => {
315
+ // Try to get the token for revocation
316
+ const authHeader = c.req.header('Authorization')
317
+ const bearerToken = authHeader?.startsWith('Bearer ')
318
+ ? authHeader.slice('Bearer '.length)
319
+ : undefined
320
+
321
+ const cookieToken = parseCookieValue(c.req.header('Cookie'), 'rcs_access')
322
+
323
+ const token = bearerToken || cookieToken
324
+
325
+ if (token) {
326
+ try {
327
+ const db = getDb()
328
+ revokeSessionToken(token, db)
329
+ } catch (err) {
330
+ console.warn('[/logout] failed to revoke token:', err)
331
+ }
332
+ }
333
+
334
+ // Always clear cookies (idempotent)
335
+ const clearCookies = buildClearCookies()
336
+ for (const cookie of clearCookies) {
337
+ c.header('Set-Cookie', cookie, { append: true })
338
+ }
339
+
340
+ return c.json({ ok: true })
341
+ })
342
+
343
+ // POST /join — Join via invitation token
344
+ app.post('/join', async c => {
345
+ let body: Record<string, unknown>
346
+ try {
347
+ body = await c.req.json()
348
+ } catch {
349
+ return c.json({ error: 'Invalid JSON' }, 400)
350
+ }
351
+
352
+ const { inviteToken, username, password } = body as {
353
+ inviteToken?: string
354
+ username?: string
355
+ password?: string
356
+ }
357
+
358
+ if (!inviteToken || !username || !password) {
359
+ return c.json({ error: 'All fields are required' }, 400)
360
+ }
361
+
362
+ if (password.length < 8) {
363
+ return c.json({ error: 'Password must be at least 8 characters' }, 400)
364
+ }
365
+
366
+ const db = getDb()
367
+ const tokenHash = createHash('sha256').update(inviteToken).digest('hex')
368
+
369
+ // Look up invitation (read-only, for validation)
370
+ const invitation = db
371
+ .query('SELECT * FROM invitations WHERE token_hash = $hash')
372
+ .get({ $hash: tokenHash }) as {
373
+ token_hash: string
374
+ team_id: string | null
375
+ role: string
376
+ expires_at: string
377
+ max_uses: number
378
+ uses: number
379
+ consumed_at: string | null
380
+ } | null
381
+
382
+ if (!invitation) {
383
+ return c.json({ error: 'Invalid invitation' }, 400)
384
+ }
385
+
386
+ // Check expiry
387
+ if (new Date(invitation.expires_at).getTime() < Date.now()) {
388
+ return c.json({ error: 'Invitation expired' }, 400)
389
+ }
390
+
391
+ // Check if already fully consumed (pre-check; atomic check below)
392
+ if (invitation.consumed_at || invitation.uses >= invitation.max_uses) {
393
+ return c.json({ error: 'Invitation already used' }, 400)
394
+ }
395
+
396
+ // Check username uniqueness
397
+ const existingUser = db
398
+ .query('SELECT id FROM users WHERE username = $username')
399
+ .get({ $username: username })
400
+ if (existingUser) {
401
+ return c.json({ error: 'Username already taken' }, 400)
402
+ }
403
+
404
+ // 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).
408
+ const userId = `usr_${randomUUID().replace(/-/g, '')}`
409
+ const passwordHash = hashPassword(password)
410
+ const now = new Date().toISOString()
411
+
412
+ try {
413
+ db.transaction(() => {
414
+ // Re-check invitation state inside the transaction (may have changed)
415
+ 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
+ ) {
424
+ throw new Error('invitation_used')
425
+ }
426
+
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
440
+ const result = db
441
+ .query(
442
+ `UPDATE invitations
443
+ SET uses = uses + 1, consumed_at = $now, consumed_by = $userId
444
+ WHERE token_hash = $hash
445
+ AND uses < max_uses
446
+ AND consumed_at IS NULL`,
447
+ )
448
+ .run({ $now: now, $userId: userId, $hash: tokenHash })
449
+
450
+ if (result.changes === 0) {
451
+ throw new Error('invitation_used')
452
+ }
453
+
454
+ // Phase 2: If invitation is linked to a team, auto-add user to team
455
+ if (invitation.team_id) {
456
+ db.query(
457
+ `INSERT OR IGNORE INTO team_members (team_id, user_id, role, added_at, added_by)
458
+ VALUES ($teamId, $userId, $role, $now, $addedBy)`,
459
+ ).run({
460
+ $teamId: invitation.team_id,
461
+ $userId: userId,
462
+ $role: invitation.role,
463
+ $now: now,
464
+ $addedBy: userId,
465
+ })
466
+ }
467
+ })()
468
+ } catch (err) {
469
+ if (err instanceof Error && err.message === 'invitation_used') {
470
+ return c.json({ error: 'Invitation already used or invalid' }, 400)
471
+ }
472
+ console.warn('[/join] transaction failed:', err)
473
+ return c.json({ error: 'Failed to process invitation' }, 500)
474
+ }
475
+
476
+ const isHttps = c.req.url.startsWith('https://')
477
+ return issueAndRespond(c, userId, isHttps)
478
+ })
479
+
480
+ // GET /setup-status — Check if initial setup is needed
481
+ app.get('/setup-status', c => {
482
+ try {
483
+ const db = getDb()
484
+ const count = db.query('SELECT COUNT(*) as count FROM users').get() as {
485
+ count: number
486
+ }
487
+ return c.json({ needsSetup: count.count === 0 })
488
+ } catch (err) {
489
+ console.warn('[/setup-status] DB check failed:', err)
490
+ return c.json({ needsSetup: true })
491
+ }
492
+ })
493
+
494
+ // POST /change-password — Change current user's password
495
+ app.post(
496
+ '/change-password',
497
+ async (c, next) => {
498
+ const result = await sessionAuth(c, next)
499
+ if (result) return result
500
+ },
501
+ async c => {
502
+ const userId = c.get('userId') as string
503
+
504
+ if (userId === SYSTEM_USER_ID) {
505
+ return c.json({ error: 'Cannot change system user password' }, 403)
506
+ }
507
+
508
+ let body: Record<string, unknown>
509
+ try {
510
+ body = await c.req.json()
511
+ } catch {
512
+ return c.json({ error: 'Invalid JSON' }, 400)
513
+ }
514
+
515
+ const { currentPassword, newPassword } = body as {
516
+ currentPassword?: string
517
+ newPassword?: string
518
+ }
519
+
520
+ if (!currentPassword || !newPassword) {
521
+ return c.json(
522
+ { error: 'Current password and new password are required' },
523
+ 400,
524
+ )
525
+ }
526
+
527
+ if (newPassword.length < 8) {
528
+ return c.json(
529
+ { error: 'Password must be at least 8 characters' },
530
+ 400,
531
+ )
532
+ }
533
+
534
+ const db = getDb()
535
+ const user = db
536
+ .query('SELECT password_hash FROM users WHERE id = $id')
537
+ .get({ $id: userId }) as { password_hash: string } | null
538
+
539
+ if (!user) {
540
+ return c.json({ error: 'User not found' }, 404)
541
+ }
542
+
543
+ const isValid = await verifyPasswordAsync(currentPassword, user.password_hash)
544
+ if (!isValid) {
545
+ return c.json({ error: 'Current password is incorrect' }, 401)
546
+ }
547
+
548
+ const newHash = await hashPasswordAsync(newPassword)
549
+ db.query('UPDATE users SET password_hash = $hash WHERE id = $id').run({
550
+ $hash: newHash,
551
+ $id: userId,
552
+ })
553
+
554
+ return c.json({ ok: true })
555
+ },
556
+ )
557
+
558
+ // POST /logout-all — Revoke all refresh tokens for the current user
559
+ app.post(
560
+ '/logout-all',
561
+ async (c, next) => {
562
+ const result = await sessionAuth(c, next)
563
+ if (result) return result
564
+ },
565
+ c => {
566
+ const userId = c.get('userId') as string
567
+
568
+ const db = getDb()
569
+ const now = new Date().toISOString()
570
+ db.query(
571
+ `UPDATE session_tokens SET revoked_at = $now WHERE user_id = $userId AND kind = 'refresh'`,
572
+ ).run({ $now: now, $userId: userId })
573
+
574
+ // Also clear cookies
575
+ const clearCookies = buildClearCookies()
576
+ for (const cookie of clearCookies) {
577
+ c.header('Set-Cookie', cookie, { append: true })
578
+ }
579
+
580
+ return c.json({ ok: true })
581
+ },
582
+ )
583
+
584
+ // GET /me — Current user info (requires sessionAuth)
585
+ app.get(
586
+ '/me',
587
+ async (c, next) => {
588
+ const result = await sessionAuth(c, next)
589
+ if (result) return result
590
+ },
591
+ c => {
592
+ const userId = c.get('userId') as string
593
+
594
+ if (userId === SYSTEM_USER_ID) {
595
+ return c.json({
596
+ userId: SYSTEM_USER_ID,
597
+ username: 'system',
598
+ role: 'admin',
599
+ isAdmin: true,
600
+ })
601
+ }
602
+
603
+ const db = getDb()
604
+ const user = db
605
+ .query('SELECT id, username, role FROM users WHERE id = $id')
606
+ .get({ $id: userId }) as {
607
+ id: string
608
+ username: string
609
+ role: string
610
+ } | null
611
+
612
+ if (!user) {
613
+ return c.json({ error: 'User not found' }, 404)
614
+ }
615
+
616
+ return c.json({
617
+ userId: user.id,
618
+ username: user.username,
619
+ role: user.role,
620
+ isAdmin: user.role === 'admin',
621
+ })
622
+ },
623
+ )
624
+
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
+ export default app
@@ -1,6 +1,6 @@
1
1
  import { log, error as logError } from '../../logger'
2
2
  import { Hono } from 'hono'
3
- import { uuidAuth, requireWorkerToken } from '../../auth/middleware'
3
+ import { sessionAuth } from '../../auth/middleware'
4
4
  import {
5
5
  getSession,
6
6
  isSessionClosedStatus,
@@ -26,7 +26,7 @@ function checkOwnership(
26
26
  c: { get: (key: string) => string | undefined },
27
27
  sessionId: string,
28
28
  ): OwnershipCheckResult {
29
- const uuid = c.get('uuid')!
29
+ const uuid = c.get('userId')!
30
30
  const resolvedSessionId = resolveOwnedWebSessionId(sessionId, uuid)
31
31
  if (!resolvedSessionId) {
32
32
  return { error: true }
@@ -46,7 +46,7 @@ function closedSessionResponse(message: string) {
46
46
  }
47
47
 
48
48
  /** POST /web/sessions/:id/events — Send user message to session */
49
- app.post('/sessions/:id/events', uuidAuth, requireWorkerToken, async c => {
49
+ app.post('/sessions/:id/events', sessionAuth, async c => {
50
50
  const requestedSessionId = c.req.param('id')!
51
51
  const ownership = checkOwnership(c, requestedSessionId)
52
52
  if (ownership.error) {
@@ -85,7 +85,7 @@ app.post('/sessions/:id/events', uuidAuth, requireWorkerToken, async c => {
85
85
  })
86
86
 
87
87
  /** POST /web/sessions/:id/control — Send control request (permission approval etc) */
88
- app.post('/sessions/:id/control', uuidAuth, requireWorkerToken, async c => {
88
+ app.post('/sessions/:id/control', sessionAuth, async c => {
89
89
  const requestedSessionId = c.req.param('id')!
90
90
  const ownership = checkOwnership(c, requestedSessionId)
91
91
  if (ownership.error) {
@@ -112,7 +112,7 @@ app.post('/sessions/:id/control', uuidAuth, requireWorkerToken, async c => {
112
112
  })
113
113
 
114
114
  /** POST /web/sessions/:id/interrupt — Interrupt session */
115
- app.post('/sessions/:id/interrupt', uuidAuth, requireWorkerToken, async c => {
115
+ app.post('/sessions/:id/interrupt', sessionAuth, async c => {
116
116
  const requestedSessionId = c.req.param('id')!
117
117
  const ownership = checkOwnership(c, requestedSessionId)
118
118
  if (ownership.error) {