@saluzi/saluzi-edu 0.3.0 → 0.3.2

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 (161) hide show
  1. package/dist/cli.js +204 -195
  2. package/dist/guide/guide-data.json +17 -5
  3. package/dist/rcs-web/assets/{AdminPanel-DgLYaHiP.js → AdminPanel-Dw_WcoUm.js} +1 -1
  4. package/{packages/remote-control-server/web/dist/assets/ArtifactView-CY2q-o_P.js → dist/rcs-web/assets/ArtifactView-1QwTt1_U.js} +1 -1
  5. package/dist/rcs-web/assets/{ArtifactsGallery-CjZFQhYm.js → ArtifactsGallery-BaqQmrpp.js} +1 -1
  6. package/{packages/remote-control-server/web/dist/assets/Claim-CjEYu8Jg.js → dist/rcs-web/assets/Claim-DbnYUnUa.js} +1 -1
  7. package/dist/rcs-web/assets/{Dashboard-g2VnKR61.js → Dashboard-I2zDQu_C.js} +1 -1
  8. package/dist/rcs-web/assets/{Join-DiEh0bNQ.js → Join-BW_5GOxc.js} +1 -1
  9. package/dist/rcs-web/assets/Login-DfpBv0-E.js +1 -0
  10. package/dist/rcs-web/assets/{Register-CBNGPMR6.js → Register-G3MsHXWX.js} +1 -1
  11. package/dist/rcs-web/assets/{SessionDetail-BkGAqYDS.js → SessionDetail-C1D2E27B.js} +1 -1
  12. package/dist/rcs-web/assets/{SessionVisibilityBadge-DJB8gn6j.js → SessionVisibilityBadge-BKEoqr7V.js} +1 -1
  13. package/dist/rcs-web/assets/{Setup-BqifzvSG.js → Setup-Ba0gdFpo.js} +1 -1
  14. package/dist/rcs-web/assets/{ShareLanding-twz_oOTr.js → ShareLanding-CD0g9hBP.js} +1 -1
  15. package/dist/rcs-web/assets/{TeamCreate-BxQ0djGK.js → TeamCreate-B95jdnoW.js} +1 -1
  16. package/dist/rcs-web/assets/{TeamDetail-BY3nmWNo.js → TeamDetail-B1ez_Vlm.js} +1 -1
  17. package/dist/rcs-web/assets/{TeamList-D1uw5ZQj.js → TeamList-Cfbc_pbK.js} +1 -1
  18. package/dist/rcs-web/assets/{TeamSettings-DqUlB3Gr.js → TeamSettings-CM3C5h_w.js} +1 -1
  19. package/dist/rcs-web/assets/{UserSearchInput-HY3vDxN9.js → UserSearchInput-CXTnk0hA.js} +1 -1
  20. package/dist/rcs-web/assets/{UserSettings-Ca0UvlLs.js → UserSettings-Bnq6nvYc.js} +1 -1
  21. package/dist/rcs-web/assets/{arc-M4eZFMsF.js → arc-BSYwIwEf.js} +1 -1
  22. package/dist/rcs-web/assets/{architectureDiagram-3BPJPVTR-OJqqY_9Z.js → architectureDiagram-3BPJPVTR-D3RCYL_1.js} +1 -1
  23. package/{packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-BiWsXFB4.js → dist/rcs-web/assets/blockDiagram-GPEHLZMM-CHzBQR1p.js} +1 -1
  24. package/{packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-A47kYHom.js → dist/rcs-web/assets/c4Diagram-AAUBKEIU-BJcaDDLG.js} +1 -1
  25. package/dist/rcs-web/assets/channel-CbFNrUsk.js +1 -0
  26. package/dist/rcs-web/assets/{chunk-2J33WTMH-bR31bKjT.js → chunk-2J33WTMH-Bg0Hnyyq.js} +1 -1
  27. package/dist/rcs-web/assets/{chunk-4BX2VUAB-BfujiUUC.js → chunk-4BX2VUAB-DfNc0_mN.js} +1 -1
  28. package/dist/rcs-web/assets/{chunk-55IACEB6-CbFE9tWc.js → chunk-55IACEB6-lSLgeJjv.js} +1 -1
  29. package/dist/rcs-web/assets/{chunk-727SXJPM-B5uYRnS4.js → chunk-727SXJPM-CLJOi1hb.js} +1 -1
  30. package/dist/rcs-web/assets/{chunk-AQP2D5EJ-BpScXOxx.js → chunk-AQP2D5EJ-DTW4EE2i.js} +1 -1
  31. package/dist/rcs-web/assets/{chunk-FMBD7UC4-QOgv52FY.js → chunk-FMBD7UC4-B57Jsd13.js} +1 -1
  32. package/dist/rcs-web/assets/{chunk-ND2GUHAM-Bsk0izyp.js → chunk-ND2GUHAM-B_msF9ch.js} +1 -1
  33. package/dist/rcs-web/assets/{chunk-QZHKN3VN-CqUHgfxZ.js → chunk-QZHKN3VN-BKdDyhR-.js} +1 -1
  34. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-DjCl6Ad4.js +1 -0
  35. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-DjCl6Ad4.js +1 -0
  36. package/dist/rcs-web/assets/{code-block-IT6T5CEO-BdhOgxxC.js → code-block-IT6T5CEO-By1Tu8pm.js} +1 -1
  37. package/dist/rcs-web/assets/{copy-BId_ACIH.js → copy-CIv8ifYN.js} +1 -1
  38. package/{packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-CBemG5Rz.js → dist/rcs-web/assets/cose-bilkent-S5V4N54A--virsnmO.js} +1 -1
  39. package/dist/rcs-web/assets/{dagre-BM42HDAG-DFC7hnTe.js → dagre-BM42HDAG-DWHB77EO.js} +1 -1
  40. package/dist/rcs-web/assets/{diagram-2AECGRRQ--coK1eZ0.js → diagram-2AECGRRQ-Cngu5yzm.js} +1 -1
  41. package/dist/rcs-web/assets/{diagram-5GNKFQAL-DFv2rFe2.js → diagram-5GNKFQAL-KVNmlovi.js} +1 -1
  42. package/dist/rcs-web/assets/{diagram-KO2AKTUF-RJHPBcaH.js → diagram-KO2AKTUF-B7VOvyr-.js} +1 -1
  43. package/dist/rcs-web/assets/{diagram-LMA3HP47-C9WP4Nfe.js → diagram-LMA3HP47-BhGlWLSy.js} +1 -1
  44. package/dist/rcs-web/assets/{diagram-OG6HWLK6-BhsW9xiu.js → diagram-OG6HWLK6-B0nF0JCP.js} +1 -1
  45. package/{packages/remote-control-server/web/dist/assets/dialog-DfUuWIZS.js → dist/rcs-web/assets/dialog-D__YHb1u.js} +1 -1
  46. package/dist/rcs-web/assets/{erDiagram-TEJ5UH35-B5L7aiZD.js → erDiagram-TEJ5UH35-CEl50M4S.js} +1 -1
  47. package/dist/rcs-web/assets/{flowDiagram-I6XJVG4X-ABhMkIfD.js → flowDiagram-I6XJVG4X-fWBjdCNK.js} +1 -1
  48. package/{packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-DlOhmTa2.js → dist/rcs-web/assets/ganttDiagram-6RSMTGT7-BAL8_-XH.js} +1 -1
  49. package/{packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-Bce8oSuR.js → dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-BM_6XPU4.js} +1 -1
  50. package/dist/rcs-web/assets/{infoDiagram-5YYISTIA-Dkb2brY2.js → infoDiagram-5YYISTIA-DtwjUdP-.js} +1 -1
  51. package/dist/rcs-web/assets/{ishikawaDiagram-YF4QCWOH-ChFn0SpW.js → ishikawaDiagram-YF4QCWOH-BwxFByT9.js} +1 -1
  52. package/dist/rcs-web/assets/{journeyDiagram-JHISSGLW-WgDIwjKo.js → journeyDiagram-JHISSGLW-DeWYaHqa.js} +1 -1
  53. package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-CsOBZQC_.js → kanban-definition-UN3LZRKU-DMLiWipt.js} +1 -1
  54. package/dist/rcs-web/assets/{linear-D8M0DKLB.js → linear-DigNGInH.js} +1 -1
  55. package/dist/rcs-web/assets/{main-bilGpQlS.js → main-5C_gChTh.js} +18 -18
  56. package/{packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-B3Kglw26.js → dist/rcs-web/assets/mermaid-VLURNSYL-DiBP7v4h.js} +4 -4
  57. package/dist/rcs-web/assets/{mermaid.core-DCyqolwk.js → mermaid.core-CQwjc9NW.js} +5 -5
  58. package/{packages/remote-control-server/web/dist/assets/mindmap-definition-RKZ34NQL-N5OXbLbx.js → dist/rcs-web/assets/mindmap-definition-RKZ34NQL-CasTiX1-.js} +1 -1
  59. package/dist/rcs-web/assets/{pieDiagram-4H26LBE5-Bwldm4aG.js → pieDiagram-4H26LBE5-D94TX46C.js} +1 -1
  60. package/dist/rcs-web/assets/{quadrantDiagram-W4KKPZXB-ktiotrS5.js → quadrantDiagram-W4KKPZXB-wgKU_sO2.js} +1 -1
  61. package/dist/rcs-web/assets/{requirementDiagram-4Y6WPE33-D3G8oEBo.js → requirementDiagram-4Y6WPE33-DYKKTIT3.js} +1 -1
  62. package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-CKzAXLVh.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-BCovzmUn.js} +1 -1
  63. package/dist/rcs-web/assets/{sequenceDiagram-3UESZ5HK-BJk1-iXO.js → sequenceDiagram-3UESZ5HK-BLtYwGaQ.js} +1 -1
  64. package/dist/rcs-web/assets/{stateDiagram-AJRCARHV-DTJbbn1C.js → stateDiagram-AJRCARHV-jMZaPO--.js} +1 -1
  65. package/dist/rcs-web/assets/{stateDiagram-v2-BHNVJYJU-DTZV1yyE.js → stateDiagram-v2-BHNVJYJU-CoUWNtqD.js} +1 -1
  66. package/{packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-DvSe-Rzm.js → dist/rcs-web/assets/timeline-definition-PNZ67QCA-CbqngX3Z.js} +1 -1
  67. package/{packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-D4sxw7d3.js → dist/rcs-web/assets/vennDiagram-CIIHVFJN-F9m0_HxT.js} +1 -1
  68. package/{packages/remote-control-server/web/dist/assets/wardleyDiagram-YWT4CUSO-DaHUQFiA.js → dist/rcs-web/assets/wardleyDiagram-YWT4CUSO-d_Uua5Ta.js} +1 -1
  69. package/{packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-BS0clo8i.js → dist/rcs-web/assets/xychartDiagram-2RQKCTM6-BDwGBZT6.js} +1 -1
  70. package/dist/rcs-web/index.html +1 -1
  71. package/package.json +1 -1
  72. package/packages/remote-control-server/src/__tests__/helpers/db.ts +6 -0
  73. package/packages/remote-control-server/src/__tests__/helpers/mock-config.ts +2 -0
  74. package/packages/remote-control-server/src/__tests__/otp-route.test.ts +130 -0
  75. package/packages/remote-control-server/src/__tests__/single-user.test.ts +333 -0
  76. package/packages/remote-control-server/src/__tests__/web-login.test.ts +84 -0
  77. package/packages/remote-control-server/src/config.ts +55 -19
  78. package/packages/remote-control-server/src/index.ts +41 -0
  79. package/packages/remote-control-server/src/nodeServer.ts +143 -15
  80. package/packages/remote-control-server/src/registerRoutes.ts +37 -0
  81. package/packages/remote-control-server/src/routes/v1/environments.ts +11 -0
  82. package/packages/remote-control-server/src/routes/web/auth-routes.ts +21 -3
  83. package/packages/remote-control-server/src/services/disconnect-monitor.ts +2 -2
  84. package/packages/remote-control-server/src/services/single-user.ts +123 -0
  85. package/packages/remote-control-server/src/services/web-login.ts +96 -0
  86. package/packages/remote-control-server/web/dist/assets/{AdminPanel-DgLYaHiP.js → AdminPanel-Dw_WcoUm.js} +1 -1
  87. package/{dist/rcs-web/assets/ArtifactView-CY2q-o_P.js → packages/remote-control-server/web/dist/assets/ArtifactView-1QwTt1_U.js} +1 -1
  88. package/packages/remote-control-server/web/dist/assets/{ArtifactsGallery-CjZFQhYm.js → ArtifactsGallery-BaqQmrpp.js} +1 -1
  89. package/{dist/rcs-web/assets/Claim-CjEYu8Jg.js → packages/remote-control-server/web/dist/assets/Claim-DbnYUnUa.js} +1 -1
  90. package/packages/remote-control-server/web/dist/assets/{Dashboard-g2VnKR61.js → Dashboard-I2zDQu_C.js} +1 -1
  91. package/packages/remote-control-server/web/dist/assets/{Join-DiEh0bNQ.js → Join-BW_5GOxc.js} +1 -1
  92. package/packages/remote-control-server/web/dist/assets/Login-DfpBv0-E.js +1 -0
  93. package/packages/remote-control-server/web/dist/assets/{Register-CBNGPMR6.js → Register-G3MsHXWX.js} +1 -1
  94. package/packages/remote-control-server/web/dist/assets/{SessionDetail-BkGAqYDS.js → SessionDetail-C1D2E27B.js} +1 -1
  95. package/packages/remote-control-server/web/dist/assets/{SessionVisibilityBadge-DJB8gn6j.js → SessionVisibilityBadge-BKEoqr7V.js} +1 -1
  96. package/packages/remote-control-server/web/dist/assets/{Setup-BqifzvSG.js → Setup-Ba0gdFpo.js} +1 -1
  97. package/packages/remote-control-server/web/dist/assets/{ShareLanding-twz_oOTr.js → ShareLanding-CD0g9hBP.js} +1 -1
  98. package/packages/remote-control-server/web/dist/assets/{TeamCreate-BxQ0djGK.js → TeamCreate-B95jdnoW.js} +1 -1
  99. package/packages/remote-control-server/web/dist/assets/{TeamDetail-BY3nmWNo.js → TeamDetail-B1ez_Vlm.js} +1 -1
  100. package/packages/remote-control-server/web/dist/assets/{TeamList-D1uw5ZQj.js → TeamList-Cfbc_pbK.js} +1 -1
  101. package/packages/remote-control-server/web/dist/assets/{TeamSettings-DqUlB3Gr.js → TeamSettings-CM3C5h_w.js} +1 -1
  102. package/packages/remote-control-server/web/dist/assets/{UserSearchInput-HY3vDxN9.js → UserSearchInput-CXTnk0hA.js} +1 -1
  103. package/packages/remote-control-server/web/dist/assets/{UserSettings-Ca0UvlLs.js → UserSettings-Bnq6nvYc.js} +1 -1
  104. package/packages/remote-control-server/web/dist/assets/{arc-M4eZFMsF.js → arc-BSYwIwEf.js} +1 -1
  105. package/packages/remote-control-server/web/dist/assets/{architectureDiagram-3BPJPVTR-OJqqY_9Z.js → architectureDiagram-3BPJPVTR-D3RCYL_1.js} +1 -1
  106. package/{dist/rcs-web/assets/blockDiagram-GPEHLZMM-BiWsXFB4.js → packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-CHzBQR1p.js} +1 -1
  107. package/{dist/rcs-web/assets/c4Diagram-AAUBKEIU-A47kYHom.js → packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-BJcaDDLG.js} +1 -1
  108. package/packages/remote-control-server/web/dist/assets/channel-CbFNrUsk.js +1 -0
  109. package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-bR31bKjT.js → chunk-2J33WTMH-Bg0Hnyyq.js} +1 -1
  110. package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-BfujiUUC.js → chunk-4BX2VUAB-DfNc0_mN.js} +1 -1
  111. package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-CbFE9tWc.js → chunk-55IACEB6-lSLgeJjv.js} +1 -1
  112. package/packages/remote-control-server/web/dist/assets/{chunk-727SXJPM-B5uYRnS4.js → chunk-727SXJPM-CLJOi1hb.js} +1 -1
  113. package/packages/remote-control-server/web/dist/assets/{chunk-AQP2D5EJ-BpScXOxx.js → chunk-AQP2D5EJ-DTW4EE2i.js} +1 -1
  114. package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-QOgv52FY.js → chunk-FMBD7UC4-B57Jsd13.js} +1 -1
  115. package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-Bsk0izyp.js → chunk-ND2GUHAM-B_msF9ch.js} +1 -1
  116. package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-CqUHgfxZ.js → chunk-QZHKN3VN-BKdDyhR-.js} +1 -1
  117. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-DjCl6Ad4.js +1 -0
  118. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-DjCl6Ad4.js +1 -0
  119. package/packages/remote-control-server/web/dist/assets/{code-block-IT6T5CEO-BdhOgxxC.js → code-block-IT6T5CEO-By1Tu8pm.js} +1 -1
  120. package/packages/remote-control-server/web/dist/assets/{copy-BId_ACIH.js → copy-CIv8ifYN.js} +1 -1
  121. package/{dist/rcs-web/assets/cose-bilkent-S5V4N54A-CBemG5Rz.js → packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A--virsnmO.js} +1 -1
  122. package/packages/remote-control-server/web/dist/assets/{dagre-BM42HDAG-DFC7hnTe.js → dagre-BM42HDAG-DWHB77EO.js} +1 -1
  123. package/packages/remote-control-server/web/dist/assets/{diagram-2AECGRRQ--coK1eZ0.js → diagram-2AECGRRQ-Cngu5yzm.js} +1 -1
  124. package/packages/remote-control-server/web/dist/assets/{diagram-5GNKFQAL-DFv2rFe2.js → diagram-5GNKFQAL-KVNmlovi.js} +1 -1
  125. package/packages/remote-control-server/web/dist/assets/{diagram-KO2AKTUF-RJHPBcaH.js → diagram-KO2AKTUF-B7VOvyr-.js} +1 -1
  126. package/packages/remote-control-server/web/dist/assets/{diagram-LMA3HP47-C9WP4Nfe.js → diagram-LMA3HP47-BhGlWLSy.js} +1 -1
  127. package/packages/remote-control-server/web/dist/assets/{diagram-OG6HWLK6-BhsW9xiu.js → diagram-OG6HWLK6-B0nF0JCP.js} +1 -1
  128. package/{dist/rcs-web/assets/dialog-DfUuWIZS.js → packages/remote-control-server/web/dist/assets/dialog-D__YHb1u.js} +1 -1
  129. package/packages/remote-control-server/web/dist/assets/{erDiagram-TEJ5UH35-B5L7aiZD.js → erDiagram-TEJ5UH35-CEl50M4S.js} +1 -1
  130. package/packages/remote-control-server/web/dist/assets/{flowDiagram-I6XJVG4X-ABhMkIfD.js → flowDiagram-I6XJVG4X-fWBjdCNK.js} +1 -1
  131. package/{dist/rcs-web/assets/ganttDiagram-6RSMTGT7-DlOhmTa2.js → packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-BAL8_-XH.js} +1 -1
  132. package/{dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-Bce8oSuR.js → packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-BM_6XPU4.js} +1 -1
  133. package/packages/remote-control-server/web/dist/assets/{infoDiagram-5YYISTIA-Dkb2brY2.js → infoDiagram-5YYISTIA-DtwjUdP-.js} +1 -1
  134. package/packages/remote-control-server/web/dist/assets/{ishikawaDiagram-YF4QCWOH-ChFn0SpW.js → ishikawaDiagram-YF4QCWOH-BwxFByT9.js} +1 -1
  135. package/packages/remote-control-server/web/dist/assets/{journeyDiagram-JHISSGLW-WgDIwjKo.js → journeyDiagram-JHISSGLW-DeWYaHqa.js} +1 -1
  136. package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-CsOBZQC_.js → kanban-definition-UN3LZRKU-DMLiWipt.js} +1 -1
  137. package/packages/remote-control-server/web/dist/assets/{linear-D8M0DKLB.js → linear-DigNGInH.js} +1 -1
  138. package/packages/remote-control-server/web/dist/assets/{main-bilGpQlS.js → main-5C_gChTh.js} +18 -18
  139. package/{dist/rcs-web/assets/mermaid-VLURNSYL-B3Kglw26.js → packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-DiBP7v4h.js} +4 -4
  140. package/packages/remote-control-server/web/dist/assets/{mermaid.core-DCyqolwk.js → mermaid.core-CQwjc9NW.js} +5 -5
  141. package/{dist/rcs-web/assets/mindmap-definition-RKZ34NQL-N5OXbLbx.js → packages/remote-control-server/web/dist/assets/mindmap-definition-RKZ34NQL-CasTiX1-.js} +1 -1
  142. package/packages/remote-control-server/web/dist/assets/{pieDiagram-4H26LBE5-Bwldm4aG.js → pieDiagram-4H26LBE5-D94TX46C.js} +1 -1
  143. package/packages/remote-control-server/web/dist/assets/{quadrantDiagram-W4KKPZXB-ktiotrS5.js → quadrantDiagram-W4KKPZXB-wgKU_sO2.js} +1 -1
  144. package/packages/remote-control-server/web/dist/assets/{requirementDiagram-4Y6WPE33-D3G8oEBo.js → requirementDiagram-4Y6WPE33-DYKKTIT3.js} +1 -1
  145. package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-CKzAXLVh.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-BCovzmUn.js} +1 -1
  146. package/packages/remote-control-server/web/dist/assets/{sequenceDiagram-3UESZ5HK-BJk1-iXO.js → sequenceDiagram-3UESZ5HK-BLtYwGaQ.js} +1 -1
  147. package/packages/remote-control-server/web/dist/assets/{stateDiagram-AJRCARHV-DTJbbn1C.js → stateDiagram-AJRCARHV-jMZaPO--.js} +1 -1
  148. package/packages/remote-control-server/web/dist/assets/{stateDiagram-v2-BHNVJYJU-DTZV1yyE.js → stateDiagram-v2-BHNVJYJU-CoUWNtqD.js} +1 -1
  149. package/{dist/rcs-web/assets/timeline-definition-PNZ67QCA-DvSe-Rzm.js → packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-CbqngX3Z.js} +1 -1
  150. package/{dist/rcs-web/assets/vennDiagram-CIIHVFJN-D4sxw7d3.js → packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-F9m0_HxT.js} +1 -1
  151. package/{dist/rcs-web/assets/wardleyDiagram-YWT4CUSO-DaHUQFiA.js → packages/remote-control-server/web/dist/assets/wardleyDiagram-YWT4CUSO-d_Uua5Ta.js} +1 -1
  152. package/{dist/rcs-web/assets/xychartDiagram-2RQKCTM6-BS0clo8i.js → packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-BDwGBZT6.js} +1 -1
  153. package/packages/remote-control-server/web/dist/index.html +1 -1
  154. package/dist/rcs-web/assets/Login-CvYL4i61.js +0 -1
  155. package/dist/rcs-web/assets/channel-CpK1-PDN.js +0 -1
  156. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-BIsjdqJz.js +0 -1
  157. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-BIsjdqJz.js +0 -1
  158. package/packages/remote-control-server/web/dist/assets/Login-CvYL4i61.js +0 -1
  159. package/packages/remote-control-server/web/dist/assets/channel-CpK1-PDN.js +0 -1
  160. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-BIsjdqJz.js +0 -1
  161. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-BIsjdqJz.js +0 -1
@@ -0,0 +1,333 @@
1
+ import { describe, test, expect, beforeEach, mock } from 'bun:test'
2
+ import { createMockConfig } from './helpers/mock-config'
3
+
4
+ // Mutable config mock — individual describes flip `singleUser` (and the
5
+ // derived `allowRegistration`) to exercise both modes against the same
6
+ // route modules.
7
+ const configState = createMockConfig()
8
+ mock.module('../config', () => ({
9
+ config: configState,
10
+ getBaseUrl: () => configState.baseUrl,
11
+ }))
12
+
13
+ import { Hono } from 'hono'
14
+ import type { Database } from 'bun:sqlite'
15
+ import { randomUUID } from 'node:crypto'
16
+ import { tmpdir } from 'node:os'
17
+ import { resolve } from 'node:path'
18
+ import v1Environments from '../routes/v1/environments'
19
+ import webAuthRoutes from '../routes/web/auth-routes'
20
+ import { storeReset } from '../store'
21
+ import { initDatabase, resetDbSingleton, getDb } from '../db/sqlite'
22
+ import {
23
+ ensureSingleUserAdmin,
24
+ getSingleUserAdminUuid,
25
+ SINGLE_USER_DEFAULT_PASSWORD,
26
+ SINGLE_USER_USERNAME,
27
+ } from '../services/single-user'
28
+ import { createUserDirectly } from './helpers/fixtures'
29
+
30
+ /**
31
+ * Single-user mode (`slz rcs one`):
32
+ * - First deployment auto-initializes the sole admin account
33
+ * (admin / admin123) — login with those credentials works immediately.
34
+ * - Any worker binding is auto-owned by that account: no X-User-Id, no
35
+ * X-Username, no claim_token in the register response.
36
+ * - Self-registration and invitation joins are refused; register-status
37
+ * reports closed registration so the Web UI hides the sign-up link.
38
+ * - Full mode (singleUser=false) keeps the claim flow.
39
+ */
40
+
41
+ function freshDb(): Database {
42
+ try {
43
+ resetDbSingleton()
44
+ } catch {
45
+ // ignore cleanup errors
46
+ }
47
+ const path = resolve(
48
+ tmpdir(),
49
+ `rcs-single-user-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
50
+ )
51
+ return initDatabase(path)
52
+ }
53
+
54
+ const ADMIN_KEY = {
55
+ Authorization: 'Bearer test-api-key',
56
+ 'Content-Type': 'application/json',
57
+ }
58
+
59
+ function createBridgeApp(): Hono {
60
+ const app = new Hono()
61
+ app.route('/v1/environments', v1Environments)
62
+ return app
63
+ }
64
+
65
+ function createAuthApp(): Hono {
66
+ const app = new Hono()
67
+ app.route('/web/auth', webAuthRoutes)
68
+ return app
69
+ }
70
+
71
+ describe('ensureSingleUserAdmin', () => {
72
+ beforeEach(() => {
73
+ freshDb()
74
+ storeReset()
75
+ configState.singleUser = true
76
+ configState.allowRegistration = false
77
+ })
78
+
79
+ test('creates admin/admin123 on an empty users table (first deployment)', async () => {
80
+ const admin = await ensureSingleUserAdmin()
81
+ expect(admin).not.toBeNull()
82
+ expect(admin!.created).toBe(true)
83
+ expect(admin!.username).toBe(SINGLE_USER_USERNAME)
84
+
85
+ const row = getDb().query('SELECT id, username, role FROM users').get() as {
86
+ id: string
87
+ username: string
88
+ role: string
89
+ }
90
+ expect(row.id).toBe(admin!.userId)
91
+ expect(row.username).toBe(SINGLE_USER_USERNAME)
92
+ expect(row.role).toBe('admin')
93
+
94
+ // The default credential verifies against the stored argon2id hash.
95
+ const { verifyPassword } = await import('../auth/password')
96
+ const hashRow = getDb()
97
+ .query('SELECT password_hash FROM users WHERE username = $u')
98
+ .get({ $u: SINGLE_USER_USERNAME }) as { password_hash: string }
99
+ expect(
100
+ verifyPassword(SINGLE_USER_DEFAULT_PASSWORD, hashRow.password_hash),
101
+ ).toBe(true)
102
+ })
103
+
104
+ test('is idempotent — second call reuses the account, no duplicates', async () => {
105
+ const first = await ensureSingleUserAdmin()
106
+ const second = await ensureSingleUserAdmin()
107
+ expect(second!.created).toBe(false)
108
+ expect(second!.userId).toBe(first!.userId)
109
+
110
+ const count = getDb()
111
+ .query('SELECT COUNT(*) as count FROM users')
112
+ .get() as { count: number }
113
+ expect(count.count).toBe(1)
114
+ })
115
+
116
+ test('reuses an existing admin instead of creating a second account', async () => {
117
+ createUserDirectly(getDb(), { username: 'alice', role: 'admin' })
118
+ const admin = await ensureSingleUserAdmin()
119
+ expect(admin!.created).toBe(false)
120
+ expect(admin!.username).toBe('alice')
121
+
122
+ const count = getDb()
123
+ .query('SELECT COUNT(*) as count FROM users')
124
+ .get() as { count: number }
125
+ expect(count.count).toBe(1)
126
+ })
127
+
128
+ test('returns null when users exist but none is an admin', async () => {
129
+ createUserDirectly(getDb(), { username: 'bob', role: 'member' })
130
+ const admin = await ensureSingleUserAdmin()
131
+ expect(admin).toBeNull()
132
+ })
133
+
134
+ test('getSingleUserAdminUuid resolves the admin id', async () => {
135
+ expect(getSingleUserAdminUuid()).toBeNull() // empty DB
136
+ const admin = await ensureSingleUserAdmin()
137
+ expect(getSingleUserAdminUuid()).toBe(admin!.userId)
138
+ })
139
+ })
140
+
141
+ describe('POST /v1/environments/bridge — single-user auto-ownership', () => {
142
+ let app: Hono
143
+ let db: Database
144
+
145
+ beforeEach(() => {
146
+ db = freshDb()
147
+ storeReset()
148
+ app = createBridgeApp()
149
+ })
150
+
151
+ test('bare API key registration is owned by the single admin, no claim', async () => {
152
+ configState.singleUser = true
153
+ const admin = await ensureSingleUserAdmin()
154
+ expect(admin).not.toBeNull()
155
+
156
+ const res = await app.request('/v1/environments/bridge', {
157
+ method: 'POST',
158
+ headers: ADMIN_KEY,
159
+ body: JSON.stringify({ machine_name: 'local-worker' }),
160
+ })
161
+ expect(res.status).toBe(200)
162
+ const body = (await res.json()) as {
163
+ environment_id: string
164
+ claim_token?: string
165
+ claim_url?: string
166
+ }
167
+ expect(body.environment_id).toMatch(/^env_/)
168
+ // No claim flow in single-user mode — the env already has an owner.
169
+ expect(body.claim_token).toBeUndefined()
170
+ expect(body.claim_url).toBeUndefined()
171
+
172
+ const row = db
173
+ .query('SELECT owner_user_id, claim_token FROM environments')
174
+ .get() as { owner_user_id: string; claim_token: string | null }
175
+ expect(row.owner_user_id).toBe(admin!.userId)
176
+ expect(row.claim_token).toBeNull()
177
+ })
178
+
179
+ test('reattach keeps the env owned by the single admin', async () => {
180
+ configState.singleUser = true
181
+ const admin = await ensureSingleUserAdmin()
182
+
183
+ const first = await app.request('/v1/environments/bridge', {
184
+ method: 'POST',
185
+ headers: ADMIN_KEY,
186
+ body: JSON.stringify({ machine_name: 'w1' }),
187
+ })
188
+ const { environment_id } = (await first.json()) as {
189
+ environment_id: string
190
+ }
191
+
192
+ // Restart with the bridge pointer — reattach must not fork or un-own.
193
+ const second = await app.request('/v1/environments/bridge', {
194
+ method: 'POST',
195
+ headers: ADMIN_KEY,
196
+ body: JSON.stringify({ environment_id, machine_name: 'w1' }),
197
+ })
198
+ expect(second.status).toBe(200)
199
+ const count = db
200
+ .query('SELECT COUNT(*) as count FROM environments')
201
+ .get() as { count: number }
202
+ expect(count.count).toBe(1)
203
+ const row = db.query('SELECT owner_user_id FROM environments').get() as {
204
+ owner_user_id: string
205
+ }
206
+ expect(row.owner_user_id).toBe(admin!.userId)
207
+ })
208
+
209
+ test('ACP worker session is bound to the single admin', async () => {
210
+ configState.singleUser = true
211
+ const admin = await ensureSingleUserAdmin()
212
+
213
+ const res = await app.request('/v1/environments/bridge', {
214
+ method: 'POST',
215
+ headers: ADMIN_KEY,
216
+ body: JSON.stringify({
217
+ machine_name: 'acp-agent',
218
+ worker_type: 'acp',
219
+ bridge_id: 'grp_local',
220
+ }),
221
+ })
222
+ expect(res.status).toBe(200)
223
+ const { session_id } = (await res.json()) as { session_id: string }
224
+ expect(session_id).toBeDefined()
225
+
226
+ const owner = db
227
+ .query(
228
+ `SELECT owner_id FROM session_owners
229
+ WHERE session_id = $s AND owner_type = 'user'`,
230
+ )
231
+ .get({ $s: session_id }) as { owner_id: string }
232
+ expect(owner.owner_id).toBe(admin!.userId)
233
+ })
234
+
235
+ test('full mode (singleUser=false) keeps the claim flow', async () => {
236
+ configState.singleUser = false
237
+ const res = await app.request('/v1/environments/bridge', {
238
+ method: 'POST',
239
+ headers: ADMIN_KEY,
240
+ body: JSON.stringify({ machine_name: 'w1' }),
241
+ })
242
+ expect(res.status).toBe(200)
243
+ const body = (await res.json()) as { claim_token: string }
244
+ expect(body.claim_token).toMatch(/^clm_/)
245
+
246
+ const row = db.query('SELECT owner_user_id FROM environments').get() as {
247
+ owner_user_id: string | null
248
+ }
249
+ expect(row.owner_user_id).toBeNull()
250
+ })
251
+ })
252
+
253
+ describe('web auth routes — single-user mode', () => {
254
+ let app: Hono
255
+
256
+ beforeEach(() => {
257
+ freshDb()
258
+ storeReset()
259
+ app = createAuthApp()
260
+ configState.singleUser = true
261
+ configState.allowRegistration = false
262
+ })
263
+
264
+ test('first deployment: bootstrap → login with admin/admin123 works', async () => {
265
+ const admin = await ensureSingleUserAdmin()
266
+ expect(admin!.created).toBe(true)
267
+
268
+ const res = await app.request('/web/auth/login', {
269
+ method: 'POST',
270
+ headers: { 'Content-Type': 'application/json' },
271
+ body: JSON.stringify({
272
+ username: SINGLE_USER_USERNAME,
273
+ password: SINGLE_USER_DEFAULT_PASSWORD,
274
+ }),
275
+ })
276
+ expect(res.status).toBe(200)
277
+ const body = (await res.json()) as { accessToken: string }
278
+ expect(body.accessToken).toBeDefined()
279
+ })
280
+
281
+ test('register-status reports closed registration', async () => {
282
+ const res = await app.request('/web/auth/register-status')
283
+ expect(res.status).toBe(200)
284
+ const body = (await res.json()) as { allowed: boolean }
285
+ expect(body.allowed).toBe(false)
286
+ })
287
+
288
+ test('self-registration is refused', async () => {
289
+ const res = await app.request('/web/auth/register', {
290
+ method: 'POST',
291
+ headers: { 'Content-Type': 'application/json' },
292
+ body: JSON.stringify({ username: 'intruder', password: 'password123' }),
293
+ })
294
+ expect(res.status).toBe(403)
295
+ const count = getDb()
296
+ .query('SELECT COUNT(*) as count FROM users')
297
+ .get() as { count: number }
298
+ expect(count.count).toBe(0)
299
+ })
300
+
301
+ test('invitation join is refused', async () => {
302
+ const res = await app.request('/web/auth/join', {
303
+ method: 'POST',
304
+ headers: { 'Content-Type': 'application/json' },
305
+ body: JSON.stringify({
306
+ inviteToken: 'inv_whatever',
307
+ username: 'guest',
308
+ password: 'password123',
309
+ }),
310
+ })
311
+ expect(res.status).toBe(403)
312
+ })
313
+
314
+ test('setup-status reports no setup needed and the single-user mode', async () => {
315
+ await ensureSingleUserAdmin()
316
+ const res = await app.request('/web/auth/setup-status')
317
+ expect(res.status).toBe(200)
318
+ const body = (await res.json()) as {
319
+ needsSetup: boolean
320
+ singleUser: boolean
321
+ }
322
+ expect(body.needsSetup).toBe(false)
323
+ expect(body.singleUser).toBe(true)
324
+ })
325
+
326
+ test('full mode keeps registration open (contrast)', async () => {
327
+ configState.singleUser = false
328
+ configState.allowRegistration = true
329
+ const res = await app.request('/web/auth/register-status')
330
+ const body = (await res.json()) as { allowed: boolean }
331
+ expect(body.allowed).toBe(true)
332
+ })
333
+ })
@@ -0,0 +1,84 @@
1
+ import { describe, test, expect, beforeEach } from 'bun:test'
2
+ import {
3
+ MAX_PENDING_CODES,
4
+ consumeWebLoginCode,
5
+ issueWebLoginCode,
6
+ _resetWebLoginCodes,
7
+ } from '../services/web-login'
8
+
9
+ /**
10
+ * One-time web login codes (`slz rcs one` browser auto-open):
11
+ * - single use — the first consume returns the record, the second null
12
+ * - 2-minute TTL — expired codes are rejected and pruned
13
+ * - target paths are clamped to same-origin paths (no '//host' open redirect)
14
+ * - the pending map is bounded
15
+ */
16
+
17
+ describe('web-login codes', () => {
18
+ beforeEach(() => {
19
+ _resetWebLoginCodes()
20
+ })
21
+
22
+ test('consume returns the record exactly once (single use)', () => {
23
+ const issued = issueWebLoginCode('usr_abc', '/code/session_1')
24
+ expect(issued.code).toMatch(/^wlc_[0-9a-f]{32}$/)
25
+ expect(issued.targetPath).toBe('/code/session_1')
26
+ expect(issued.userId).toBe('usr_abc')
27
+
28
+ const consumed = consumeWebLoginCode(issued.code)
29
+ expect(consumed).not.toBeNull()
30
+ expect(consumed?.userId).toBe('usr_abc')
31
+ expect(consumed?.targetPath).toBe('/code/session_1')
32
+
33
+ // Second consume — already used.
34
+ expect(consumeWebLoginCode(issued.code)).toBeNull()
35
+ })
36
+
37
+ test('unknown code returns null', () => {
38
+ expect(
39
+ consumeWebLoginCode('wlc_deadbeefdeadbeefdeadbeefdeadbeef'),
40
+ ).toBeNull()
41
+ })
42
+
43
+ test('targetPath is clamped to a same-origin path', () => {
44
+ expect(issueWebLoginCode('usr_abc', '/code/session_1').targetPath).toBe(
45
+ '/code/session_1',
46
+ )
47
+ // No leading slash → root.
48
+ expect(issueWebLoginCode('usr_abc', 'code/session_1').targetPath).toBe('/')
49
+ // Protocol-relative would be an open redirect — clamp.
50
+ expect(issueWebLoginCode('usr_abc', '//evil.example/x').targetPath).toBe(
51
+ '/',
52
+ )
53
+ // Absolute URL → root.
54
+ expect(
55
+ issueWebLoginCode('usr_abc', 'https://evil.example/x').targetPath,
56
+ ).toBe('/')
57
+ })
58
+
59
+ test('expired code is rejected', () => {
60
+ const issued = issueWebLoginCode('usr_abc', '/code/')
61
+ const realNow = Date.now
62
+ // Jump past the 2-minute TTL.
63
+ Date.now = () => realNow() + 3 * 60 * 1000
64
+ try {
65
+ expect(consumeWebLoginCode(issued.code)).toBeNull()
66
+ } finally {
67
+ Date.now = realNow
68
+ }
69
+ })
70
+
71
+ test('pending map is bounded — oldest entries are evicted', () => {
72
+ const oldest = issueWebLoginCode('usr_abc', '/code/oldest')
73
+ // Fill to exactly the cap without exceeding it.
74
+ let last: ReturnType<typeof issueWebLoginCode> | undefined
75
+ for (let i = 1; i < MAX_PENDING_CODES; i++) {
76
+ last = issueWebLoginCode('usr_abc', `/code/fill-${i}`)
77
+ }
78
+ // One more beyond the cap — the oldest was evicted to make room…
79
+ issueWebLoginCode('usr_abc', '/code/overflow')
80
+ expect(consumeWebLoginCode(oldest.code)).toBeNull()
81
+ // …while recent entries stay intact.
82
+ expect(consumeWebLoginCode(last!.code)).not.toBeNull()
83
+ })
84
+ })
@@ -5,7 +5,49 @@
5
5
  */
6
6
  export const DEFAULT_RCS_API_KEY = 'sk-slz'
7
7
 
8
- export const config = {
8
+ export interface RcsConfig {
9
+ version: string
10
+ port: number
11
+ host: string
12
+ apiKeys: string[]
13
+ baseUrl: string
14
+ pollTimeout: number
15
+ heartbeatInterval: number
16
+ jwtExpiresIn: number
17
+ disconnectTimeout: number
18
+ webCorsOrigins: string[]
19
+ /** Whether the web UI allows open user self-registration (POST /web/auth/register).
20
+ * When false, only admin invitation (/join) can create new users. Default true.
21
+ * Forced false in single-user mode — exactly one account exists. */
22
+ allowRegistration: boolean
23
+ /** Single-user mode (`slz rcs one`): exactly one admin account
24
+ * (auto-created admin/admin123 on first deployment), every worker
25
+ * auto-owned by it, registration/join disabled, host defaults to
26
+ * 127.0.0.1. Local deployments only — convenience over auth.
27
+ * See services/single-user.ts. */
28
+ singleUser: boolean
29
+ /** Bun WebSocket idle timeout (seconds). Bun sends protocol-level pings after
30
+ * this many seconds of no received data. Must be shorter than any reverse
31
+ * proxy's idle timeout (nginx default 60s, Cloudflare 100s). Default 30s. */
32
+ wsIdleTimeout: number
33
+ /** Server→client keep_alive data-frame interval (seconds). Keeps reverse
34
+ * proxies from closing idle connections. Default 20s. */
35
+ wsKeepaliveInterval: number
36
+ /** Server-side client-inactivity timeout (seconds). If the bridge child
37
+ * process sends no data frame within this window, the server closes the
38
+ * WebSocket as dead. Must exceed the child's keep_alive interval
39
+ * (session_keepalive_interval_v2_ms, default 120s) plus jitter, otherwise
40
+ * healthy idle sessions get torn down between keepalives. Default 300s. */
41
+ wsClientInactivityTimeout: number
42
+ /** Whether artifact content (GET /v1/artifacts/:id/content) is served
43
+ * without authentication. Default true — the unguessable artifact id is
44
+ * the secret, same trust model as the upstream cloud-artifacts service.
45
+ * Set RCS_ARTIFACTS_PUBLIC_ACCESS=false to gate reads behind web session
46
+ * or worker credentials. */
47
+ artifactsPublicAccess: boolean
48
+ }
49
+
50
+ export const config: RcsConfig = {
9
51
  version: process.env.RCS_VERSION || '0.1.0',
10
52
  port: parseInt(process.env.RCS_PORT || '3000', 10),
11
53
  host: process.env.RCS_HOST || '0.0.0.0',
@@ -22,34 +64,28 @@ export const config = {
22
64
  .map(origin => origin.trim())
23
65
  .filter(Boolean),
24
66
  /** Whether the web UI allows open user self-registration (POST /web/auth/register).
25
- * When false, only admin invitation (/join) can create new users. Default true. */
26
- allowRegistration: process.env.RCS_ALLOW_REGISTRATION !== 'false',
27
- /** Bun WebSocket idle timeout (seconds). Bun sends protocol-level pings after
28
- * this many seconds of no received data. Must be shorter than any reverse
29
- * proxy's idle timeout (nginx default 60s, Cloudflare 100s). Default 30s. */
67
+ * When false, only admin invitation (/join) can create new users. Default true.
68
+ * Forced false in single-user mode — exactly one account exists. */
69
+ allowRegistration:
70
+ process.env.RCS_SINGLE_USER === 'true'
71
+ ? false
72
+ : process.env.RCS_ALLOW_REGISTRATION !== 'false',
73
+ /** Single-user mode (`slz rcs one`): exactly one admin account
74
+ * (auto-created admin/admin123 on first deployment), every worker
75
+ * auto-owned by it, registration/join disabled, host defaults to
76
+ * 127.0.0.1. Local deployments only — convenience over auth. */
77
+ singleUser: process.env.RCS_SINGLE_USER === 'true',
30
78
  wsIdleTimeout: parseInt(process.env.RCS_WS_IDLE_TIMEOUT || '30', 10),
31
- /** Server→client keep_alive data-frame interval (seconds). Keeps reverse
32
- * proxies from closing idle connections. Default 20s. */
33
79
  wsKeepaliveInterval: parseInt(
34
80
  process.env.RCS_WS_KEEPALIVE_INTERVAL || '20',
35
81
  10,
36
82
  ),
37
- /** Server-side client-inactivity timeout (seconds). If the bridge child
38
- * process sends no data frame within this window, the server closes the
39
- * WebSocket as dead. Must exceed the child's keep_alive interval
40
- * (session_keepalive_interval_v2_ms, default 120s) plus jitter, otherwise
41
- * healthy idle sessions get torn down between keepalives. Default 300s. */
42
83
  wsClientInactivityTimeout: parseInt(
43
84
  process.env.RCS_WS_CLIENT_INACTIVITY_TIMEOUT || '300',
44
85
  10,
45
86
  ),
46
- /** Whether artifact content (GET /v1/artifacts/:id/content) is served
47
- * without authentication. Default true — the unguessable artifact id is
48
- * the secret, same trust model as the upstream cloud-artifacts service.
49
- * Set RCS_ARTIFACTS_PUBLIC_ACCESS=false to gate reads behind web session
50
- * or worker credentials. */
51
87
  artifactsPublicAccess: process.env.RCS_ARTIFACTS_PUBLIC_ACCESS !== 'false',
52
- } as const
88
+ }
53
89
 
54
90
  export function getBaseUrl(): string {
55
91
  const url = config.baseUrl || `http://localhost:${config.port}`
@@ -13,6 +13,11 @@ import { websocket } from './transport/ws-shared'
13
13
  import { getDb } from './db/sqlite'
14
14
  import { reapExpiredSessions } from './auth/session'
15
15
  import { cleanupExpiredArtifacts } from './services/artifacts'
16
+ import {
17
+ ensureSingleUserAdmin,
18
+ SINGLE_USER_DEFAULT_PASSWORD,
19
+ SINGLE_USER_USERNAME,
20
+ } from './services/single-user'
16
21
 
17
22
  console.log('[RCS] In-memory store ready (no SQLite)')
18
23
 
@@ -39,6 +44,12 @@ app.get('/health', c => c.json({ status: 'ok', version: config.version }))
39
44
  // Register all routes with Bun's serveStatic and upgradeWebSocket
40
45
  registerRoutes(app, serveStatic, upgradeWebSocket)
41
46
 
47
+ // Single-user mode: local convenience — bind loopback unless the operator
48
+ // explicitly chose a host via RCS_HOST.
49
+ if (config.singleUser && !process.env.RCS_HOST) {
50
+ config.host = '127.0.0.1'
51
+ }
52
+
42
53
  const port = config.port
43
54
  const host = config.host
44
55
 
@@ -56,6 +67,36 @@ console.log(
56
67
  // Start disconnect monitor
57
68
  startDisconnectMonitor()
58
69
 
70
+ // Single-user mode (`slz rcs one` via bun run rcs one): bootstrap the sole
71
+ // admin account (admin/admin123 on first deployment) before serving
72
+ // requests, so worker registrations always resolve an owner.
73
+ if (config.singleUser) {
74
+ const admin = await ensureSingleUserAdmin()
75
+ if (!admin) {
76
+ console.error(
77
+ '[RCS] Single-user mode requires an empty or admin-bearing users ' +
78
+ 'table, but the existing DB has neither. Run full mode (no `one` ' +
79
+ 'argument) or point RCS_DB_PATH at a fresh database.',
80
+ )
81
+ process.exit(1)
82
+ }
83
+ console.log('[RCS] Single-user mode (one):')
84
+ if (admin.created) {
85
+ console.log(
86
+ `[RCS] admin account initialized — username: ${SINGLE_USER_USERNAME} password: ${SINGLE_USER_DEFAULT_PASSWORD}`,
87
+ )
88
+ console.log(
89
+ '[RCS] (local convenience default — change it after first login)',
90
+ )
91
+ } else {
92
+ console.log(`[RCS] using existing account: ${admin.username}`)
93
+ }
94
+ console.log('[RCS] registration disabled — this is the only account')
95
+ console.log(
96
+ '[RCS] workers bind zero-config and are owned by this account (no claim)',
97
+ )
98
+ }
99
+
59
100
  // Reap expired session tokens at startup and periodically
60
101
  try {
61
102
  const db = getDb()