@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.
- package/dist/cli.js +204 -195
- package/dist/guide/guide-data.json +17 -5
- package/dist/rcs-web/assets/{AdminPanel-DgLYaHiP.js → AdminPanel-Dw_WcoUm.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/ArtifactView-CY2q-o_P.js → dist/rcs-web/assets/ArtifactView-1QwTt1_U.js} +1 -1
- package/dist/rcs-web/assets/{ArtifactsGallery-CjZFQhYm.js → ArtifactsGallery-BaqQmrpp.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/Claim-CjEYu8Jg.js → dist/rcs-web/assets/Claim-DbnYUnUa.js} +1 -1
- package/dist/rcs-web/assets/{Dashboard-g2VnKR61.js → Dashboard-I2zDQu_C.js} +1 -1
- package/dist/rcs-web/assets/{Join-DiEh0bNQ.js → Join-BW_5GOxc.js} +1 -1
- package/dist/rcs-web/assets/Login-DfpBv0-E.js +1 -0
- package/dist/rcs-web/assets/{Register-CBNGPMR6.js → Register-G3MsHXWX.js} +1 -1
- package/dist/rcs-web/assets/{SessionDetail-BkGAqYDS.js → SessionDetail-C1D2E27B.js} +1 -1
- package/dist/rcs-web/assets/{SessionVisibilityBadge-DJB8gn6j.js → SessionVisibilityBadge-BKEoqr7V.js} +1 -1
- package/dist/rcs-web/assets/{Setup-BqifzvSG.js → Setup-Ba0gdFpo.js} +1 -1
- package/dist/rcs-web/assets/{ShareLanding-twz_oOTr.js → ShareLanding-CD0g9hBP.js} +1 -1
- package/dist/rcs-web/assets/{TeamCreate-BxQ0djGK.js → TeamCreate-B95jdnoW.js} +1 -1
- package/dist/rcs-web/assets/{TeamDetail-BY3nmWNo.js → TeamDetail-B1ez_Vlm.js} +1 -1
- package/dist/rcs-web/assets/{TeamList-D1uw5ZQj.js → TeamList-Cfbc_pbK.js} +1 -1
- package/dist/rcs-web/assets/{TeamSettings-DqUlB3Gr.js → TeamSettings-CM3C5h_w.js} +1 -1
- package/dist/rcs-web/assets/{UserSearchInput-HY3vDxN9.js → UserSearchInput-CXTnk0hA.js} +1 -1
- package/dist/rcs-web/assets/{UserSettings-Ca0UvlLs.js → UserSettings-Bnq6nvYc.js} +1 -1
- package/dist/rcs-web/assets/{arc-M4eZFMsF.js → arc-BSYwIwEf.js} +1 -1
- package/dist/rcs-web/assets/{architectureDiagram-3BPJPVTR-OJqqY_9Z.js → architectureDiagram-3BPJPVTR-D3RCYL_1.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-BiWsXFB4.js → dist/rcs-web/assets/blockDiagram-GPEHLZMM-CHzBQR1p.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-A47kYHom.js → dist/rcs-web/assets/c4Diagram-AAUBKEIU-BJcaDDLG.js} +1 -1
- package/dist/rcs-web/assets/channel-CbFNrUsk.js +1 -0
- package/dist/rcs-web/assets/{chunk-2J33WTMH-bR31bKjT.js → chunk-2J33WTMH-Bg0Hnyyq.js} +1 -1
- package/dist/rcs-web/assets/{chunk-4BX2VUAB-BfujiUUC.js → chunk-4BX2VUAB-DfNc0_mN.js} +1 -1
- package/dist/rcs-web/assets/{chunk-55IACEB6-CbFE9tWc.js → chunk-55IACEB6-lSLgeJjv.js} +1 -1
- package/dist/rcs-web/assets/{chunk-727SXJPM-B5uYRnS4.js → chunk-727SXJPM-CLJOi1hb.js} +1 -1
- package/dist/rcs-web/assets/{chunk-AQP2D5EJ-BpScXOxx.js → chunk-AQP2D5EJ-DTW4EE2i.js} +1 -1
- package/dist/rcs-web/assets/{chunk-FMBD7UC4-QOgv52FY.js → chunk-FMBD7UC4-B57Jsd13.js} +1 -1
- package/dist/rcs-web/assets/{chunk-ND2GUHAM-Bsk0izyp.js → chunk-ND2GUHAM-B_msF9ch.js} +1 -1
- package/dist/rcs-web/assets/{chunk-QZHKN3VN-CqUHgfxZ.js → chunk-QZHKN3VN-BKdDyhR-.js} +1 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-DjCl6Ad4.js +1 -0
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-DjCl6Ad4.js +1 -0
- package/dist/rcs-web/assets/{code-block-IT6T5CEO-BdhOgxxC.js → code-block-IT6T5CEO-By1Tu8pm.js} +1 -1
- package/dist/rcs-web/assets/{copy-BId_ACIH.js → copy-CIv8ifYN.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-CBemG5Rz.js → dist/rcs-web/assets/cose-bilkent-S5V4N54A--virsnmO.js} +1 -1
- package/dist/rcs-web/assets/{dagre-BM42HDAG-DFC7hnTe.js → dagre-BM42HDAG-DWHB77EO.js} +1 -1
- package/dist/rcs-web/assets/{diagram-2AECGRRQ--coK1eZ0.js → diagram-2AECGRRQ-Cngu5yzm.js} +1 -1
- package/dist/rcs-web/assets/{diagram-5GNKFQAL-DFv2rFe2.js → diagram-5GNKFQAL-KVNmlovi.js} +1 -1
- package/dist/rcs-web/assets/{diagram-KO2AKTUF-RJHPBcaH.js → diagram-KO2AKTUF-B7VOvyr-.js} +1 -1
- package/dist/rcs-web/assets/{diagram-LMA3HP47-C9WP4Nfe.js → diagram-LMA3HP47-BhGlWLSy.js} +1 -1
- package/dist/rcs-web/assets/{diagram-OG6HWLK6-BhsW9xiu.js → diagram-OG6HWLK6-B0nF0JCP.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/dialog-DfUuWIZS.js → dist/rcs-web/assets/dialog-D__YHb1u.js} +1 -1
- package/dist/rcs-web/assets/{erDiagram-TEJ5UH35-B5L7aiZD.js → erDiagram-TEJ5UH35-CEl50M4S.js} +1 -1
- package/dist/rcs-web/assets/{flowDiagram-I6XJVG4X-ABhMkIfD.js → flowDiagram-I6XJVG4X-fWBjdCNK.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-DlOhmTa2.js → dist/rcs-web/assets/ganttDiagram-6RSMTGT7-BAL8_-XH.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-Bce8oSuR.js → dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-BM_6XPU4.js} +1 -1
- package/dist/rcs-web/assets/{infoDiagram-5YYISTIA-Dkb2brY2.js → infoDiagram-5YYISTIA-DtwjUdP-.js} +1 -1
- package/dist/rcs-web/assets/{ishikawaDiagram-YF4QCWOH-ChFn0SpW.js → ishikawaDiagram-YF4QCWOH-BwxFByT9.js} +1 -1
- package/dist/rcs-web/assets/{journeyDiagram-JHISSGLW-WgDIwjKo.js → journeyDiagram-JHISSGLW-DeWYaHqa.js} +1 -1
- package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-CsOBZQC_.js → kanban-definition-UN3LZRKU-DMLiWipt.js} +1 -1
- package/dist/rcs-web/assets/{linear-D8M0DKLB.js → linear-DigNGInH.js} +1 -1
- package/dist/rcs-web/assets/{main-bilGpQlS.js → main-5C_gChTh.js} +18 -18
- package/{packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-B3Kglw26.js → dist/rcs-web/assets/mermaid-VLURNSYL-DiBP7v4h.js} +4 -4
- package/dist/rcs-web/assets/{mermaid.core-DCyqolwk.js → mermaid.core-CQwjc9NW.js} +5 -5
- package/{packages/remote-control-server/web/dist/assets/mindmap-definition-RKZ34NQL-N5OXbLbx.js → dist/rcs-web/assets/mindmap-definition-RKZ34NQL-CasTiX1-.js} +1 -1
- package/dist/rcs-web/assets/{pieDiagram-4H26LBE5-Bwldm4aG.js → pieDiagram-4H26LBE5-D94TX46C.js} +1 -1
- package/dist/rcs-web/assets/{quadrantDiagram-W4KKPZXB-ktiotrS5.js → quadrantDiagram-W4KKPZXB-wgKU_sO2.js} +1 -1
- package/dist/rcs-web/assets/{requirementDiagram-4Y6WPE33-D3G8oEBo.js → requirementDiagram-4Y6WPE33-DYKKTIT3.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-CKzAXLVh.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-BCovzmUn.js} +1 -1
- package/dist/rcs-web/assets/{sequenceDiagram-3UESZ5HK-BJk1-iXO.js → sequenceDiagram-3UESZ5HK-BLtYwGaQ.js} +1 -1
- package/dist/rcs-web/assets/{stateDiagram-AJRCARHV-DTJbbn1C.js → stateDiagram-AJRCARHV-jMZaPO--.js} +1 -1
- package/dist/rcs-web/assets/{stateDiagram-v2-BHNVJYJU-DTZV1yyE.js → stateDiagram-v2-BHNVJYJU-CoUWNtqD.js} +1 -1
- 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
- package/{packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-D4sxw7d3.js → dist/rcs-web/assets/vennDiagram-CIIHVFJN-F9m0_HxT.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/wardleyDiagram-YWT4CUSO-DaHUQFiA.js → dist/rcs-web/assets/wardleyDiagram-YWT4CUSO-d_Uua5Ta.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-BS0clo8i.js → dist/rcs-web/assets/xychartDiagram-2RQKCTM6-BDwGBZT6.js} +1 -1
- package/dist/rcs-web/index.html +1 -1
- package/package.json +1 -1
- package/packages/remote-control-server/src/__tests__/helpers/db.ts +6 -0
- package/packages/remote-control-server/src/__tests__/helpers/mock-config.ts +2 -0
- package/packages/remote-control-server/src/__tests__/otp-route.test.ts +130 -0
- package/packages/remote-control-server/src/__tests__/single-user.test.ts +333 -0
- package/packages/remote-control-server/src/__tests__/web-login.test.ts +84 -0
- package/packages/remote-control-server/src/config.ts +55 -19
- package/packages/remote-control-server/src/index.ts +41 -0
- package/packages/remote-control-server/src/nodeServer.ts +143 -15
- package/packages/remote-control-server/src/registerRoutes.ts +37 -0
- package/packages/remote-control-server/src/routes/v1/environments.ts +11 -0
- package/packages/remote-control-server/src/routes/web/auth-routes.ts +21 -3
- package/packages/remote-control-server/src/services/disconnect-monitor.ts +2 -2
- package/packages/remote-control-server/src/services/single-user.ts +123 -0
- package/packages/remote-control-server/src/services/web-login.ts +96 -0
- package/packages/remote-control-server/web/dist/assets/{AdminPanel-DgLYaHiP.js → AdminPanel-Dw_WcoUm.js} +1 -1
- package/{dist/rcs-web/assets/ArtifactView-CY2q-o_P.js → packages/remote-control-server/web/dist/assets/ArtifactView-1QwTt1_U.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ArtifactsGallery-CjZFQhYm.js → ArtifactsGallery-BaqQmrpp.js} +1 -1
- package/{dist/rcs-web/assets/Claim-CjEYu8Jg.js → packages/remote-control-server/web/dist/assets/Claim-DbnYUnUa.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{Dashboard-g2VnKR61.js → Dashboard-I2zDQu_C.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{Join-DiEh0bNQ.js → Join-BW_5GOxc.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/Login-DfpBv0-E.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{Register-CBNGPMR6.js → Register-G3MsHXWX.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{SessionDetail-BkGAqYDS.js → SessionDetail-C1D2E27B.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{SessionVisibilityBadge-DJB8gn6j.js → SessionVisibilityBadge-BKEoqr7V.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{Setup-BqifzvSG.js → Setup-Ba0gdFpo.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ShareLanding-twz_oOTr.js → ShareLanding-CD0g9hBP.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{TeamCreate-BxQ0djGK.js → TeamCreate-B95jdnoW.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{TeamDetail-BY3nmWNo.js → TeamDetail-B1ez_Vlm.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{TeamList-D1uw5ZQj.js → TeamList-Cfbc_pbK.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{TeamSettings-DqUlB3Gr.js → TeamSettings-CM3C5h_w.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{UserSearchInput-HY3vDxN9.js → UserSearchInput-CXTnk0hA.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{UserSettings-Ca0UvlLs.js → UserSettings-Bnq6nvYc.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{arc-M4eZFMsF.js → arc-BSYwIwEf.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{architectureDiagram-3BPJPVTR-OJqqY_9Z.js → architectureDiagram-3BPJPVTR-D3RCYL_1.js} +1 -1
- package/{dist/rcs-web/assets/blockDiagram-GPEHLZMM-BiWsXFB4.js → packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-CHzBQR1p.js} +1 -1
- package/{dist/rcs-web/assets/c4Diagram-AAUBKEIU-A47kYHom.js → packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-BJcaDDLG.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/channel-CbFNrUsk.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-bR31bKjT.js → chunk-2J33WTMH-Bg0Hnyyq.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-BfujiUUC.js → chunk-4BX2VUAB-DfNc0_mN.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-CbFE9tWc.js → chunk-55IACEB6-lSLgeJjv.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-727SXJPM-B5uYRnS4.js → chunk-727SXJPM-CLJOi1hb.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-AQP2D5EJ-BpScXOxx.js → chunk-AQP2D5EJ-DTW4EE2i.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-QOgv52FY.js → chunk-FMBD7UC4-B57Jsd13.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-Bsk0izyp.js → chunk-ND2GUHAM-B_msF9ch.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-CqUHgfxZ.js → chunk-QZHKN3VN-BKdDyhR-.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-DjCl6Ad4.js +1 -0
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-DjCl6Ad4.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{code-block-IT6T5CEO-BdhOgxxC.js → code-block-IT6T5CEO-By1Tu8pm.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{copy-BId_ACIH.js → copy-CIv8ifYN.js} +1 -1
- package/{dist/rcs-web/assets/cose-bilkent-S5V4N54A-CBemG5Rz.js → packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A--virsnmO.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{dagre-BM42HDAG-DFC7hnTe.js → dagre-BM42HDAG-DWHB77EO.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-2AECGRRQ--coK1eZ0.js → diagram-2AECGRRQ-Cngu5yzm.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-5GNKFQAL-DFv2rFe2.js → diagram-5GNKFQAL-KVNmlovi.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-KO2AKTUF-RJHPBcaH.js → diagram-KO2AKTUF-B7VOvyr-.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-LMA3HP47-C9WP4Nfe.js → diagram-LMA3HP47-BhGlWLSy.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-OG6HWLK6-BhsW9xiu.js → diagram-OG6HWLK6-B0nF0JCP.js} +1 -1
- package/{dist/rcs-web/assets/dialog-DfUuWIZS.js → packages/remote-control-server/web/dist/assets/dialog-D__YHb1u.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{erDiagram-TEJ5UH35-B5L7aiZD.js → erDiagram-TEJ5UH35-CEl50M4S.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{flowDiagram-I6XJVG4X-ABhMkIfD.js → flowDiagram-I6XJVG4X-fWBjdCNK.js} +1 -1
- package/{dist/rcs-web/assets/ganttDiagram-6RSMTGT7-DlOhmTa2.js → packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-BAL8_-XH.js} +1 -1
- package/{dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-Bce8oSuR.js → packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-BM_6XPU4.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{infoDiagram-5YYISTIA-Dkb2brY2.js → infoDiagram-5YYISTIA-DtwjUdP-.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ishikawaDiagram-YF4QCWOH-ChFn0SpW.js → ishikawaDiagram-YF4QCWOH-BwxFByT9.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{journeyDiagram-JHISSGLW-WgDIwjKo.js → journeyDiagram-JHISSGLW-DeWYaHqa.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-CsOBZQC_.js → kanban-definition-UN3LZRKU-DMLiWipt.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{linear-D8M0DKLB.js → linear-DigNGInH.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{main-bilGpQlS.js → main-5C_gChTh.js} +18 -18
- package/{dist/rcs-web/assets/mermaid-VLURNSYL-B3Kglw26.js → packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-DiBP7v4h.js} +4 -4
- package/packages/remote-control-server/web/dist/assets/{mermaid.core-DCyqolwk.js → mermaid.core-CQwjc9NW.js} +5 -5
- package/{dist/rcs-web/assets/mindmap-definition-RKZ34NQL-N5OXbLbx.js → packages/remote-control-server/web/dist/assets/mindmap-definition-RKZ34NQL-CasTiX1-.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{pieDiagram-4H26LBE5-Bwldm4aG.js → pieDiagram-4H26LBE5-D94TX46C.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{quadrantDiagram-W4KKPZXB-ktiotrS5.js → quadrantDiagram-W4KKPZXB-wgKU_sO2.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{requirementDiagram-4Y6WPE33-D3G8oEBo.js → requirementDiagram-4Y6WPE33-DYKKTIT3.js} +1 -1
- package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-CKzAXLVh.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-BCovzmUn.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{sequenceDiagram-3UESZ5HK-BJk1-iXO.js → sequenceDiagram-3UESZ5HK-BLtYwGaQ.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{stateDiagram-AJRCARHV-DTJbbn1C.js → stateDiagram-AJRCARHV-jMZaPO--.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{stateDiagram-v2-BHNVJYJU-DTZV1yyE.js → stateDiagram-v2-BHNVJYJU-CoUWNtqD.js} +1 -1
- 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
- package/{dist/rcs-web/assets/vennDiagram-CIIHVFJN-D4sxw7d3.js → packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-F9m0_HxT.js} +1 -1
- package/{dist/rcs-web/assets/wardleyDiagram-YWT4CUSO-DaHUQFiA.js → packages/remote-control-server/web/dist/assets/wardleyDiagram-YWT4CUSO-d_Uua5Ta.js} +1 -1
- package/{dist/rcs-web/assets/xychartDiagram-2RQKCTM6-BS0clo8i.js → packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-BDwGBZT6.js} +1 -1
- package/packages/remote-control-server/web/dist/index.html +1 -1
- package/dist/rcs-web/assets/Login-CvYL4i61.js +0 -1
- package/dist/rcs-web/assets/channel-CpK1-PDN.js +0 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-BIsjdqJz.js +0 -1
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-BIsjdqJz.js +0 -1
- package/packages/remote-control-server/web/dist/assets/Login-CvYL4i61.js +0 -1
- package/packages/remote-control-server/web/dist/assets/channel-CpK1-PDN.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-BIsjdqJz.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-BIsjdqJz.js +0 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
9
|
* slz rcs (zero-config: API key defaults to sk-slz)
|
|
10
|
+
* slz rcs one (single-user mode: admin/admin123, workers auto-owned)
|
|
10
11
|
* RCS_API_KEYS=sk-1234 slz rcs
|
|
11
12
|
*/
|
|
12
13
|
import { serve } from '@hono/node-server'
|
|
@@ -23,18 +24,71 @@ import { closeAllConnections } from './transport/ws-handler.js'
|
|
|
23
24
|
import { closeAllAcpConnections } from './transport/acp-ws-handler.js'
|
|
24
25
|
import { closeAllRelayConnections } from './transport/acp-relay-handler.js'
|
|
25
26
|
import { startDisconnectMonitor } from './services/disconnect-monitor.js'
|
|
27
|
+
import {
|
|
28
|
+
ensureSingleUserAdmin,
|
|
29
|
+
SINGLE_USER_DEFAULT_PASSWORD,
|
|
30
|
+
SINGLE_USER_USERNAME,
|
|
31
|
+
} from './services/single-user.js'
|
|
26
32
|
|
|
27
33
|
export interface RcsServerOptions {
|
|
28
34
|
apiKeys: string[]
|
|
29
35
|
port?: number
|
|
30
36
|
host?: string
|
|
37
|
+
/** Single-user mode (`slz rcs one`): auto-init admin account, workers
|
|
38
|
+
* auto-owned, registration disabled, host defaults to 127.0.0.1. */
|
|
39
|
+
singleUser?: boolean
|
|
40
|
+
/** Embedded mode: the server shares its process with something else that
|
|
41
|
+
* owns the terminal and the process lifecycle (e.g. `slz rcs one` boots
|
|
42
|
+
* an in-process REPL as the single repl worker). Skips the SIGINT/SIGTERM
|
|
43
|
+
* handlers — they would process.exit(0) on the REPL's first Ctrl+C — and
|
|
44
|
+
* skips the block-forever await so the caller can continue booting. */
|
|
45
|
+
embedded?: boolean
|
|
31
46
|
}
|
|
32
47
|
|
|
33
|
-
|
|
48
|
+
/** Bound server coordinates, as returned by startRcsServer. */
|
|
49
|
+
export interface RcsServerInfo {
|
|
50
|
+
port: number
|
|
51
|
+
/** The address the server actually bound (may be 0.0.0.0). */
|
|
52
|
+
host: string
|
|
53
|
+
/** Browser-reachable origin (0.0.0.0/:: mapped to localhost). */
|
|
54
|
+
webUiUrl: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function startRcsServer(
|
|
58
|
+
opts: RcsServerOptions,
|
|
59
|
+
): Promise<RcsServerInfo> {
|
|
34
60
|
// Set env vars so config module picks them up
|
|
35
61
|
process.env.RCS_API_KEYS = opts.apiKeys.join(',')
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
// The config module may already be evaluated (static import above) — its
|
|
63
|
+
// env-derived values are frozen, so apiKeys/host/port must also be applied
|
|
64
|
+
// to the live object or they would silently no-op.
|
|
65
|
+
config.apiKeys = opts.apiKeys
|
|
66
|
+
if (opts.port) {
|
|
67
|
+
process.env.RCS_PORT = String(opts.port)
|
|
68
|
+
config.port = opts.port
|
|
69
|
+
}
|
|
70
|
+
if (opts.host) {
|
|
71
|
+
process.env.RCS_HOST = opts.host
|
|
72
|
+
config.host = opts.host
|
|
73
|
+
}
|
|
74
|
+
if (opts.singleUser) process.env.RCS_SINGLE_USER = 'true'
|
|
75
|
+
|
|
76
|
+
// Effective single-user mode: the `one` argument OR RCS_SINGLE_USER=true
|
|
77
|
+
// set before process start (config.singleUser already reflects the env).
|
|
78
|
+
const singleUser = opts.singleUser || config.singleUser
|
|
79
|
+
|
|
80
|
+
// The config module may already be evaluated (static import above), so
|
|
81
|
+
// reflect single-user mode on the live object too — request-time reads
|
|
82
|
+
// (allowRegistration, route gates) must see it regardless of import order.
|
|
83
|
+
if (singleUser) {
|
|
84
|
+
config.singleUser = true
|
|
85
|
+
config.allowRegistration = false
|
|
86
|
+
// Local convenience mode: bind loopback unless the operator explicitly
|
|
87
|
+
// chose a host (RCS_HOST at process start or opts.host above).
|
|
88
|
+
if (!process.env.RCS_HOST) {
|
|
89
|
+
config.host = '127.0.0.1'
|
|
90
|
+
}
|
|
91
|
+
}
|
|
38
92
|
|
|
39
93
|
const port = config.port
|
|
40
94
|
const host = config.host
|
|
@@ -44,8 +98,13 @@ export async function startRcsServer(opts: RcsServerOptions): Promise<void> {
|
|
|
44
98
|
// Create Node.js WebSocket adapter
|
|
45
99
|
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app })
|
|
46
100
|
|
|
47
|
-
// Middleware
|
|
48
|
-
|
|
101
|
+
// Middleware. Per-request hono logger only in standalone mode — embedded
|
|
102
|
+
// mode shares the terminal with the host flow (`slz rcs one` prints a
|
|
103
|
+
// clean banner; the worker logs to its own file), so request spam would
|
|
104
|
+
// just be noise.
|
|
105
|
+
if (!opts.embedded) {
|
|
106
|
+
app.use('*', logger())
|
|
107
|
+
}
|
|
49
108
|
app.use('*', async (c: Context, next: Next) => {
|
|
50
109
|
const path = new URL(c.req.url).pathname
|
|
51
110
|
if (path.includes('//')) {
|
|
@@ -66,6 +125,26 @@ export async function startRcsServer(opts: RcsServerOptions): Promise<void> {
|
|
|
66
125
|
// Register all routes with Node.js serveStatic and upgradeWebSocket
|
|
67
126
|
registerRoutes(app, serveStatic as any, upgradeWebSocket)
|
|
68
127
|
|
|
128
|
+
// Single-user mode: bootstrap the sole admin account (admin/admin123 on
|
|
129
|
+
// first deployment) BEFORE the server accepts requests, so every worker
|
|
130
|
+
// registration can resolve an owner and no request ever races the setup.
|
|
131
|
+
let singleUserAdmin: Awaited<ReturnType<typeof ensureSingleUserAdmin>> = null
|
|
132
|
+
if (singleUser) {
|
|
133
|
+
try {
|
|
134
|
+
singleUserAdmin = await ensureSingleUserAdmin()
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error('[RCS] Single-user admin bootstrap failed:', err)
|
|
137
|
+
throw err
|
|
138
|
+
}
|
|
139
|
+
if (!singleUserAdmin) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
'[RCS] Single-user mode requires an empty or admin-bearing users ' +
|
|
142
|
+
'table, but the existing DB has neither. Run `slz rcs` (full mode) ' +
|
|
143
|
+
'or point RCS_DB_PATH at a fresh database.',
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
69
148
|
// Start HTTP server
|
|
70
149
|
const server = serve({
|
|
71
150
|
fetch: app.fetch,
|
|
@@ -73,11 +152,28 @@ export async function startRcsServer(opts: RcsServerOptions): Promise<void> {
|
|
|
73
152
|
hostname: host,
|
|
74
153
|
})
|
|
75
154
|
|
|
155
|
+
// Port conflicts (EADDRINUSE) surface asynchronously as an 'error' event —
|
|
156
|
+
// without a listener the process crashes with an ugly unhandled error.
|
|
157
|
+
// Embedded mode keeps running (the REPL owns the process; the bridge will
|
|
158
|
+
// talk to whatever server already owns the port); standalone exits loudly.
|
|
159
|
+
server.on('error', (err: NodeJS.ErrnoException) => {
|
|
160
|
+
console.error(`[RCS] Server error: ${err.message}`)
|
|
161
|
+
if (!opts.embedded) {
|
|
162
|
+
// eslint-disable-next-line custom-rules/no-process-exit
|
|
163
|
+
process.exit(1)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
76
167
|
// Inject WebSocket support for Node.js
|
|
77
168
|
injectWebSocket(server)
|
|
78
169
|
|
|
79
|
-
// Start disconnect monitor
|
|
80
|
-
|
|
170
|
+
// Start disconnect monitor. Unref'd: the HTTP server (standalone) or the
|
|
171
|
+
// host process (embedded) owns the event loop — the interval must not
|
|
172
|
+
// keep an exited REPL's process alive.
|
|
173
|
+
const monitorTimer = startDisconnectMonitor()
|
|
174
|
+
if (monitorTimer.unref) {
|
|
175
|
+
monitorTimer.unref()
|
|
176
|
+
}
|
|
81
177
|
|
|
82
178
|
console.log(
|
|
83
179
|
`[RCS] Remote Control Server starting on ${host}:${port} (Node.js)`,
|
|
@@ -89,17 +185,48 @@ export async function startRcsServer(opts: RcsServerOptions): Promise<void> {
|
|
|
89
185
|
const webUiHost = host === '0.0.0.0' || host === '::' ? 'localhost' : host
|
|
90
186
|
console.log(`[RCS] Web UI: http://${webUiHost}:${port}/code/`)
|
|
91
187
|
|
|
188
|
+
if (singleUser) {
|
|
189
|
+
console.log('[RCS] Single-user mode (slz rcs one):')
|
|
190
|
+
if (singleUserAdmin?.created) {
|
|
191
|
+
console.log(
|
|
192
|
+
`[RCS] admin account initialized — username: ${SINGLE_USER_USERNAME} password: ${SINGLE_USER_DEFAULT_PASSWORD}`,
|
|
193
|
+
)
|
|
194
|
+
console.log(
|
|
195
|
+
'[RCS] (local convenience default — change it after first login)',
|
|
196
|
+
)
|
|
197
|
+
} else {
|
|
198
|
+
console.log(
|
|
199
|
+
`[RCS] using existing account: ${singleUserAdmin?.username ?? SINGLE_USER_USERNAME}`,
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
console.log('[RCS] registration disabled — this is the only account')
|
|
203
|
+
console.log(
|
|
204
|
+
'[RCS] workers bind zero-config and are owned by this account (no claim)',
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
92
208
|
const workerBaseUrl = config.baseUrl || `http://${webUiHost}:${port}`
|
|
93
209
|
console.log('[RCS] Worker connect:')
|
|
94
210
|
console.log(
|
|
95
211
|
'[RCS] same host: zero config — workers default to http://localhost:3000 + sk-slz',
|
|
96
212
|
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
213
|
+
if (!singleUser) {
|
|
214
|
+
console.log(
|
|
215
|
+
`[RCS] remote host: export SALUZI_BRIDGE_BASE_URL=${workerBaseUrl}`,
|
|
216
|
+
)
|
|
217
|
+
console.log(
|
|
218
|
+
'[RCS] export SALUZI_BRIDGE_OAUTH_TOKEN=<your-api-key>',
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Embedded mode: the caller owns the process lifecycle (e.g. `slz rcs one`
|
|
223
|
+
// boots an in-process REPL as the single repl worker). No signal handlers
|
|
224
|
+
// — they would process.exit(0) on the REPL's first Ctrl+C — and no
|
|
225
|
+
// block-forever: return the bound coordinates so the caller can wire the
|
|
226
|
+
// in-process bridge + browser to it.
|
|
227
|
+
if (opts.embedded) {
|
|
228
|
+
return { port, host, webUiUrl: `http://${webUiHost}:${port}` }
|
|
229
|
+
}
|
|
103
230
|
|
|
104
231
|
// Graceful shutdown
|
|
105
232
|
function gracefulShutdown(signal: string) {
|
|
@@ -114,6 +241,7 @@ export async function startRcsServer(opts: RcsServerOptions): Promise<void> {
|
|
|
114
241
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'))
|
|
115
242
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'))
|
|
116
243
|
|
|
117
|
-
// Block forever — server keeps running
|
|
118
|
-
await
|
|
244
|
+
// Block forever — server keeps running. The promise never resolves; it
|
|
245
|
+
// only exists so the caller's await parks and the return type holds.
|
|
246
|
+
return new Promise<RcsServerInfo>(() => {})
|
|
119
247
|
}
|
|
@@ -7,6 +7,11 @@ import { fileURLToPath } from 'node:url'
|
|
|
7
7
|
import { createAcpApp } from './routes/acp'
|
|
8
8
|
import { createSessionIngressApp } from './routes/v1/session-ingress'
|
|
9
9
|
import { csrfCheck } from './auth/csrf'
|
|
10
|
+
import { config } from './config'
|
|
11
|
+
import { getDb } from './db/sqlite'
|
|
12
|
+
import { issueSessionToken } from './auth/session'
|
|
13
|
+
import { buildSessionCookies } from './auth/cookie'
|
|
14
|
+
import { consumeWebLoginCode } from './services/web-login'
|
|
10
15
|
|
|
11
16
|
// Routes
|
|
12
17
|
import v1Artifacts from './routes/v1/artifacts'
|
|
@@ -134,6 +139,38 @@ export function registerRoutes(
|
|
|
134
139
|
app.get(`${p}/*`, ss({ root: webDir, path: 'index.html' }))
|
|
135
140
|
}
|
|
136
141
|
|
|
142
|
+
// GET /otp/:code — one-time browser login for `slz rcs one`.
|
|
143
|
+
//
|
|
144
|
+
// The CLI mints a single-use code in-process right before opening the
|
|
145
|
+
// browser (services/web-login.ts). The browser hits this route, the
|
|
146
|
+
// server sets the single admin's session cookies and redirects to the
|
|
147
|
+
// stored target path — typically /code/<sessionId>, so a fresh browser
|
|
148
|
+
// lands straight in the just-created session instead of the login page.
|
|
149
|
+
// Codes are 2-minute TTL and only exist in single-user mode; full mode
|
|
150
|
+
// keeps the username/password ceremony.
|
|
151
|
+
app.get('/otp/:code', async c => {
|
|
152
|
+
if (!config.singleUser) {
|
|
153
|
+
return c.json({ error: 'Not found' }, 404)
|
|
154
|
+
}
|
|
155
|
+
const record = consumeWebLoginCode(c.req.param('code')!)
|
|
156
|
+
if (!record) {
|
|
157
|
+
// Unknown/expired/used — the manual login page still works
|
|
158
|
+
// (single-user mode hints the default credentials there).
|
|
159
|
+
return c.redirect('/login')
|
|
160
|
+
}
|
|
161
|
+
const db = getDb()
|
|
162
|
+
const { accessToken, refreshToken } = issueSessionToken(record.userId, db)
|
|
163
|
+
const secure =
|
|
164
|
+
c.req.header('X-Forwarded-Proto') === 'https' ||
|
|
165
|
+
c.req.url.startsWith('https')
|
|
166
|
+
for (const cookie of buildSessionCookies(accessToken, refreshToken, {
|
|
167
|
+
secure,
|
|
168
|
+
})) {
|
|
169
|
+
c.header('Set-Cookie', cookie, { append: true })
|
|
170
|
+
}
|
|
171
|
+
return c.redirect(record.targetPath)
|
|
172
|
+
})
|
|
173
|
+
|
|
137
174
|
// Root redirect → /code/ (main dashboard entry point)
|
|
138
175
|
app.get('/', c => c.redirect('/code/'))
|
|
139
176
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Hono } from 'hono'
|
|
2
2
|
import { randomUUID } from 'node:crypto'
|
|
3
|
+
import { config } from '../../config'
|
|
3
4
|
import {
|
|
4
5
|
registerEnvironment,
|
|
5
6
|
deregisterEnvironment,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
} from '../../auth/bridge-auth'
|
|
15
16
|
import { getDb } from '../../db/sqlite'
|
|
16
17
|
import { isSessionClosedStatus } from '../../services/session'
|
|
18
|
+
import { getSingleUserAdminUuid } from '../../services/single-user'
|
|
17
19
|
import {
|
|
18
20
|
storeBindEnvSessionsToTeam,
|
|
19
21
|
storeGetEnvironment,
|
|
@@ -216,6 +218,15 @@ app.post('/bridge', acceptCliHeaders, bridgeAuth, async c => {
|
|
|
216
218
|
}
|
|
217
219
|
}
|
|
218
220
|
|
|
221
|
+
// Single-user mode (`slz rcs one`): every worker belongs to the sole
|
|
222
|
+
// admin account. Workers register with just the API key (no X-User-Id,
|
|
223
|
+
// no X-Username) — instead of falling into the claim flow below, the
|
|
224
|
+
// env is owned by the single admin immediately. Local convenience:
|
|
225
|
+
// the server binds 127.0.0.1, so the API key is the only credential.
|
|
226
|
+
if (config.singleUser && !ownerUserId && !teamId) {
|
|
227
|
+
ownerUserId = getSingleUserAdminUuid(db)
|
|
228
|
+
}
|
|
229
|
+
|
|
219
230
|
// If neither X-User-Id nor the resolved userId yields an owner (or the
|
|
220
231
|
// value is unresolvable), and X-Team-Id is also absent, the env is
|
|
221
232
|
// "unowned" — a claim_token is generated and returned so the worker can
|
|
@@ -379,12 +379,18 @@ app.post('/logout', async c => {
|
|
|
379
379
|
|
|
380
380
|
// GET /register-status — Whether self-registration is open
|
|
381
381
|
app.get('/register-status', c => {
|
|
382
|
-
return c.json({ allowed: config.allowRegistration })
|
|
382
|
+
return c.json({ allowed: config.allowRegistration && !config.singleUser })
|
|
383
383
|
})
|
|
384
384
|
|
|
385
385
|
// POST /register — Self-register a new member account (no team association).
|
|
386
386
|
// Only available when RCS_ALLOW_REGISTRATION !== 'false'.
|
|
387
387
|
app.post('/register', async c => {
|
|
388
|
+
if (config.singleUser) {
|
|
389
|
+
return c.json(
|
|
390
|
+
{ error: 'Registration is disabled in single-user mode' },
|
|
391
|
+
403,
|
|
392
|
+
)
|
|
393
|
+
}
|
|
388
394
|
if (!config.allowRegistration) {
|
|
389
395
|
return c.json({ error: 'Registration is disabled' }, 403)
|
|
390
396
|
}
|
|
@@ -443,6 +449,15 @@ app.post('/register', async c => {
|
|
|
443
449
|
|
|
444
450
|
// POST /join — Join via invitation token
|
|
445
451
|
app.post('/join', async c => {
|
|
452
|
+
// Single-user mode: exactly one account exists, by design. New accounts
|
|
453
|
+
// would need an owner model the mode doesn't have — run full mode instead.
|
|
454
|
+
if (config.singleUser) {
|
|
455
|
+
return c.json(
|
|
456
|
+
{ error: 'Invitations are disabled in single-user mode' },
|
|
457
|
+
403,
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
|
|
446
461
|
let body: Record<string, unknown>
|
|
447
462
|
try {
|
|
448
463
|
body = await c.req.json()
|
|
@@ -605,10 +620,13 @@ app.get('/setup-status', c => {
|
|
|
605
620
|
const count = db.query('SELECT COUNT(*) as count FROM users').get() as {
|
|
606
621
|
count: number
|
|
607
622
|
}
|
|
608
|
-
return c.json({
|
|
623
|
+
return c.json({
|
|
624
|
+
needsSetup: count.count === 0,
|
|
625
|
+
singleUser: config.singleUser,
|
|
626
|
+
})
|
|
609
627
|
} catch (err) {
|
|
610
628
|
console.warn('[/setup-status] DB check failed:', err)
|
|
611
|
-
return c.json({ needsSetup: true })
|
|
629
|
+
return c.json({ needsSetup: true, singleUser: config.singleUser })
|
|
612
630
|
}
|
|
613
631
|
})
|
|
614
632
|
|
|
@@ -51,8 +51,8 @@ export function runDisconnectMonitorSweep(now = Date.now()) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
export function startDisconnectMonitor() {
|
|
55
|
-
setInterval(() => {
|
|
54
|
+
export function startDisconnectMonitor(): ReturnType<typeof setInterval> {
|
|
55
|
+
return setInterval(() => {
|
|
56
56
|
runDisconnectMonitorSweep()
|
|
57
57
|
}, 60_000) // Check every minute
|
|
58
58
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import type { Database } from 'bun:sqlite'
|
|
3
|
+
import { getDb } from '../db/sqlite'
|
|
4
|
+
import { hashPasswordAsync } from '../auth/password'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Single-user mode (`slz rcs one`) — the server runs with exactly one
|
|
8
|
+
* admin account, auto-created on first deployment. Every worker that
|
|
9
|
+
* binds is owned by this account (no X-User-Id, no claim flow), and
|
|
10
|
+
* self-registration / invitation joins are disabled (see auth-routes).
|
|
11
|
+
* Intended for local deployments (host binds 127.0.0.1) where multi-user
|
|
12
|
+
* auth is ceremony, not protection.
|
|
13
|
+
*/
|
|
14
|
+
export const SINGLE_USER_USERNAME = 'admin'
|
|
15
|
+
export const SINGLE_USER_DEFAULT_PASSWORD = 'admin123'
|
|
16
|
+
|
|
17
|
+
interface UserRow {
|
|
18
|
+
id: string
|
|
19
|
+
username: string
|
|
20
|
+
role: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SingleUserAdmin {
|
|
24
|
+
userId: string
|
|
25
|
+
username: string
|
|
26
|
+
/** True when this call created the account (first deployment). */
|
|
27
|
+
created: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Idempotent bootstrap of the single-user admin account.
|
|
32
|
+
*
|
|
33
|
+
* Resolution order:
|
|
34
|
+
* 1. An existing user named `admin` — reuse it (previously initialized
|
|
35
|
+
* single-user DB, or a full-mode DB where someone already chose that
|
|
36
|
+
* name).
|
|
37
|
+
* 2. Any existing admin — reuse the first one (full-mode DB set up via
|
|
38
|
+
* /setup: that admin IS the single user; never mint a second account).
|
|
39
|
+
* 3. Empty users table — create `admin` / `admin123` with role=admin.
|
|
40
|
+
*
|
|
41
|
+
* Returns null only when the users table has rows but none is an admin
|
|
42
|
+
* and none is named `admin` (a corrupted/mixed DB) — the caller decides
|
|
43
|
+
* whether that is fatal.
|
|
44
|
+
*/
|
|
45
|
+
export async function ensureSingleUserAdmin(
|
|
46
|
+
db?: Database,
|
|
47
|
+
): Promise<SingleUserAdmin | null> {
|
|
48
|
+
const database = db ?? getDb()
|
|
49
|
+
|
|
50
|
+
const byName = database
|
|
51
|
+
.query('SELECT id, username, role FROM users WHERE username = $u LIMIT 1')
|
|
52
|
+
.get({ $u: SINGLE_USER_USERNAME }) as UserRow | null
|
|
53
|
+
if (byName) {
|
|
54
|
+
return { userId: byName.id, username: byName.username, created: false }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const anyAdmin = database
|
|
58
|
+
.query(
|
|
59
|
+
'SELECT id, username, role FROM users WHERE role = $r ORDER BY created_at LIMIT 1',
|
|
60
|
+
)
|
|
61
|
+
.get({ $r: 'admin' }) as UserRow | null
|
|
62
|
+
if (anyAdmin) {
|
|
63
|
+
return { userId: anyAdmin.id, username: anyAdmin.username, created: false }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const count = database.query('SELECT COUNT(*) as count FROM users').get() as {
|
|
67
|
+
count: number
|
|
68
|
+
}
|
|
69
|
+
if (count.count > 0) {
|
|
70
|
+
// Users exist but no admin and no `admin` — cannot pick a single user
|
|
71
|
+
// without silently granting it admin rights. Let the caller surface it.
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const userId = `usr_${randomUUID().replace(/-/g, '')}`
|
|
76
|
+
const passwordHash = await hashPasswordAsync(SINGLE_USER_DEFAULT_PASSWORD)
|
|
77
|
+
const now = new Date().toISOString()
|
|
78
|
+
database
|
|
79
|
+
.query(
|
|
80
|
+
`INSERT OR IGNORE INTO users (id, username, password_hash, role, created_at)
|
|
81
|
+
VALUES ($id, $username, $passwordHash, 'admin', $now)`,
|
|
82
|
+
)
|
|
83
|
+
.run({
|
|
84
|
+
$id: userId,
|
|
85
|
+
$username: SINGLE_USER_USERNAME,
|
|
86
|
+
$passwordHash: passwordHash,
|
|
87
|
+
$now: now,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const inserted = database
|
|
91
|
+
.query('SELECT id, username, role FROM users WHERE username = $u LIMIT 1')
|
|
92
|
+
.get({ $u: SINGLE_USER_USERNAME }) as UserRow | null
|
|
93
|
+
if (!inserted) {
|
|
94
|
+
// INSERT OR IGNORE lost a race (concurrent bootstrap) — resolve the winner.
|
|
95
|
+
return ensureSingleUserAdmin(database)
|
|
96
|
+
}
|
|
97
|
+
return { userId: inserted.id, username: inserted.username, created: true }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Resolve the single-user admin's users.id for worker auto-ownership.
|
|
102
|
+
*
|
|
103
|
+
* Sync and cheap — called on every unowned worker registration in
|
|
104
|
+
* single-user mode. Returns null when no admin exists yet (bootstrap not
|
|
105
|
+
* run / DB error) — the caller falls back to the regular claim flow.
|
|
106
|
+
*/
|
|
107
|
+
export function getSingleUserAdminUuid(db?: Database): string | null {
|
|
108
|
+
try {
|
|
109
|
+
const database = db ?? getDb()
|
|
110
|
+
const row = database
|
|
111
|
+
.query(
|
|
112
|
+
`SELECT id FROM users
|
|
113
|
+
WHERE username = $u OR role = $r
|
|
114
|
+
ORDER BY CASE WHEN username = $u THEN 0 ELSE 1 END, created_at
|
|
115
|
+
LIMIT 1`,
|
|
116
|
+
)
|
|
117
|
+
.get({ $u: SINGLE_USER_USERNAME, $r: 'admin' }) as { id: string } | null
|
|
118
|
+
return row?.id ?? null
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error('[single-user] admin lookup failed:', err)
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One-time browser login codes for single-user mode (`slz rcs one`).
|
|
5
|
+
*
|
|
6
|
+
* `slz rcs one` auto-opens the Web UI straight into the freshly created
|
|
7
|
+
* session — but a fresh browser has no session cookies, so a plain deep
|
|
8
|
+
* link would bounce off the login page. Instead the CLI mints a
|
|
9
|
+
* short-lived one-time code in-process (only the server process itself
|
|
10
|
+
* can mint one), the browser hits GET /otp/<code>, and the server sets
|
|
11
|
+
* the single admin's session cookies before redirecting to the stored
|
|
12
|
+
* target path (see registerRoutes.ts).
|
|
13
|
+
*
|
|
14
|
+
* Trust model: same as the rest of single-user mode — loopback binding,
|
|
15
|
+
* the API key is the only real credential, and the code is a random
|
|
16
|
+
* 122-bit value that is single-use and expires in two minutes. Codes
|
|
17
|
+
* live in memory only: a server restart invalidates any unspent ones.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export interface WebLoginCodeRecord {
|
|
21
|
+
code: string
|
|
22
|
+
userId: string
|
|
23
|
+
/** Path the browser is redirected to after login (must start with '/'). */
|
|
24
|
+
targetPath: string
|
|
25
|
+
expiresAt: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const CODE_TTL_MS = 2 * 60 * 1000
|
|
29
|
+
/** Upper bound so a pathological minter can't grow the map forever. */
|
|
30
|
+
export const MAX_PENDING_CODES = 64
|
|
31
|
+
|
|
32
|
+
const pendingCodes = new Map<string, WebLoginCodeRecord>()
|
|
33
|
+
|
|
34
|
+
function pruneExpired(now = Date.now()): void {
|
|
35
|
+
for (const [code, record] of pendingCodes) {
|
|
36
|
+
if (record.expiresAt <= now) {
|
|
37
|
+
pendingCodes.delete(code)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Clamp a redirect target to a same-origin path ('/…', never '//…'). */
|
|
43
|
+
function toSafeTargetPath(targetPath: string): string {
|
|
44
|
+
return targetPath.startsWith('/') && !targetPath.startsWith('//')
|
|
45
|
+
? targetPath
|
|
46
|
+
: '/'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Mint a one-time login code for a user + redirect target.
|
|
51
|
+
* The target path is clamped to a same-origin path — protocol-relative
|
|
52
|
+
* ('//host') and absolute URLs are rejected to block open redirects.
|
|
53
|
+
*/
|
|
54
|
+
export function issueWebLoginCode(
|
|
55
|
+
userId: string,
|
|
56
|
+
targetPath: string,
|
|
57
|
+
): WebLoginCodeRecord {
|
|
58
|
+
pruneExpired()
|
|
59
|
+
if (pendingCodes.size >= MAX_PENDING_CODES) {
|
|
60
|
+
// Drop the oldest — Map preserves insertion order.
|
|
61
|
+
const oldest = pendingCodes.keys().next().value
|
|
62
|
+
if (oldest !== undefined) {
|
|
63
|
+
pendingCodes.delete(oldest)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const record: WebLoginCodeRecord = {
|
|
67
|
+
code: `wlc_${randomUUID().replace(/-/g, '')}`,
|
|
68
|
+
userId,
|
|
69
|
+
targetPath: toSafeTargetPath(targetPath),
|
|
70
|
+
expiresAt: Date.now() + CODE_TTL_MS,
|
|
71
|
+
}
|
|
72
|
+
pendingCodes.set(record.code, record)
|
|
73
|
+
return record
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Consume a login code (single use). Returns null when the code is
|
|
78
|
+
* unknown, already used, or expired.
|
|
79
|
+
*/
|
|
80
|
+
export function consumeWebLoginCode(code: string): WebLoginCodeRecord | null {
|
|
81
|
+
pruneExpired()
|
|
82
|
+
const record = pendingCodes.get(code)
|
|
83
|
+
if (!record) {
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
pendingCodes.delete(code)
|
|
87
|
+
if (record.expiresAt <= Date.now()) {
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
return record
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Test hook — clear all pending codes. */
|
|
94
|
+
export function _resetWebLoginCodes(): void {
|
|
95
|
+
pendingCodes.clear()
|
|
96
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{e as d,j as e}from"./vendor-DE_-CLIE.js";import{at as c,au as o,Q as m,ao as s,ag as l,x as u}from"./main-
|
|
1
|
+
import{e as d,j as e}from"./vendor-DE_-CLIE.js";import{at as c,au as o,Q as m,ao as s,ag as l,x as u}from"./main-5C_gChTh.js";import"./shiki-mEP5vQQu.js";import"./motion-BILbV7YQ.js";function j(){c();const[a,i]=d.useState([]),{myTeams:r,isLoading:x}=o(),n=d.useCallback(async()=>{try{const t=await m();i(t||[])}catch(t){console.error("Admin: failed to load sessions",t)}},[]);return d.useEffect(()=>{n()},[n]),e.jsx("div",{className:"flex-1 overflow-y-auto",children:e.jsxs("div",{className:"mx-auto max-w-5xl px-4 py-8",children:[e.jsx("h1",{className:"mb-6 font-display text-2xl font-semibold text-text-primary",children:s("admin.title")}),e.jsxs("section",{className:"mb-10",children:[e.jsxs("h2",{className:"mb-4 font-display text-lg font-semibold text-text-primary",children:[s("admin.teams")," (",r.length,")"]}),x?e.jsx("div",{className:"rounded-xl border border-border bg-surface-1 p-4 text-center text-text-muted",children:s("common.loading")}):r.length===0?e.jsx("div",{className:"rounded-xl border border-border bg-surface-1 p-4 text-center text-text-muted",children:s("admin.noTeams")}):e.jsx("div",{className:"space-y-2",children:r.map(t=>e.jsxs("a",{href:`/teams/${t.slug}`,className:"flex items-center justify-between rounded-xl border border-border bg-surface-1 px-4 py-3 no-underline transition-colors hover:border-border-light hover:bg-surface-2",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("span",{className:"font-medium text-text-primary",children:t.name}),e.jsx("span",{className:"ml-2 text-xs text-text-muted",children:t.slug})]}),e.jsx("span",{className:"text-xs text-text-muted",children:l(new Date(t.created_at).getTime()/1e3)})]},t.id))})]}),e.jsxs("section",{children:[e.jsxs("h2",{className:"mb-4 font-display text-lg font-semibold text-text-primary",children:[s("admin.sessions")," (",a.length,")"]}),a.length===0?e.jsx("div",{className:"rounded-xl border border-border bg-surface-1 p-4 text-center text-text-muted",children:s("admin.noSessions")}):e.jsx("div",{className:"space-y-2",children:a.map(t=>e.jsxs("a",{href:`/code/${t.id}`,className:"flex items-center justify-between rounded-xl border border-border bg-surface-1 px-4 py-3 no-underline transition-colors hover:border-border-light hover:bg-surface-2",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"truncate font-medium text-text-primary",children:t.title||t.id}),e.jsx("span",{className:"rounded-full bg-surface-2 px-2 py-0.5 text-xs text-text-muted",children:t.source||"unknown"})]}),e.jsx("div",{className:"truncate text-xs text-text-muted",children:t.id})]}),e.jsxs("div",{className:"ml-4 flex items-center gap-3",children:[e.jsx(u,{status:t.status}),e.jsx("span",{className:"text-xs text-text-muted",children:l(t.updated_at||t.created_at)})]})]},t.id))})]})]})})}export{j as AdminPanel};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{e as a,j as t}from"./vendor-DE_-CLIE.js";import{ad as q,at as A,V as j,ao as r,K as M,ac as U,P as F,z as O}from"./main-
|
|
1
|
+
import{e as a,j as t}from"./vendor-DE_-CLIE.js";import{ad as q,at as A,V as j,ao as r,K as M,ac as U,P as F,z as O}from"./main-5C_gChTh.js";import{S as R,E as K,C as G}from"./ArtifactsGallery-BaqQmrpp.js";import{C as P}from"./copy-CIv8ifYN.js";import"./shiki-mEP5vQQu.js";import"./motion-BILbV7YQ.js";import"./dialog-D__YHb1u.js";/**
|
|
2
2
|
* @license lucide-react v0.555.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var ht=Object.defineProperty;var ut=(t,e,r)=>e in t?ht(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var S=(t,e,r)=>ut(t,typeof e!="symbol"?e+"":e,r);import{e as k,j as o}from"./vendor-DE_-CLIE.js";import{ad as Re,ah as ft,at as Ve,au as mt,W as xt,ao as f,K as gt,ac as bt,F as kt,g as yt,P as wt,z as vt,p as He,V as St,a0 as Rt,al as Tt,$ as $t,a6 as jt,a7 as Et,H as Nt,b as Ct}from"./main-
|
|
1
|
+
var ht=Object.defineProperty;var ut=(t,e,r)=>e in t?ht(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var S=(t,e,r)=>ut(t,typeof e!="symbol"?e+"":e,r);import{e as k,j as o}from"./vendor-DE_-CLIE.js";import{ad as Re,ah as ft,at as Ve,au as mt,W as xt,ao as f,K as gt,ac as bt,F as kt,g as yt,P as wt,z as vt,p as He,V as St,a0 as Rt,al as Tt,$ as $t,a6 as jt,a7 as Et,H as Nt,b as Ct}from"./main-5C_gChTh.js";import{D as It,a as At,c as Lt,d as zt,b as _t}from"./dialog-D__YHb1u.js";import{C as Dt}from"./copy-CIv8ifYN.js";/**
|
|
2
2
|
* @license lucide-react v0.555.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{e as r,j as e}from"./vendor-DE_-CLIE.js";import{E as u,Q as f}from"./main-
|
|
1
|
+
import{e as r,j as e}from"./vendor-DE_-CLIE.js";import{E as u,Q as f}from"./main-5C_gChTh.js";import"./shiki-mEP5vQQu.js";import"./motion-BILbV7YQ.js";const v=2e3,N=3e4;function E(){const c=window.location.pathname.split("/"),i=c[c.length-1]||"",[s,n]=r.useState("idle"),[m,l]=r.useState(null),[a,d]=r.useState(null);return r.useEffect(()=>{if(!i||!i.startsWith("clm_")){l("Invalid claim token"),n("error");return}n("claiming"),u(i).then(t=>{d(t.environment_id),n("waiting")}).catch(t=>{l(t instanceof Error?t.message:"Claim failed"),n("error")})},[i]),r.useEffect(()=>{if(s!=="waiting"||!a)return;let t=!1;const x=Date.now(),o=setInterval(()=>{if(Date.now()-x>N){clearInterval(o),t||n("timeout");return}f().then(h=>{t||(h||[]).some(p=>p.environment_id===a)&&(clearInterval(o),window.location.href="/code/")}).catch(()=>{})},v);return()=>{t=!0,clearInterval(o)}},[s,a]),s==="claiming"?e.jsx("div",{className:"px-6 py-16 sm:px-10 sm:py-20",children:e.jsx("p",{className:"text-sm text-text-muted",children:"Claiming environment..."})}):s==="error"?e.jsxs("div",{className:"px-6 py-16 sm:px-10 sm:py-20",children:[e.jsx("p",{className:"text-sm text-destructive",children:m}),e.jsx("a",{href:"/code",className:"mt-4 inline-block text-accent hover:underline",children:"Back to dashboard"})]}):s==="waiting"||s==="timeout"?e.jsxs("div",{className:"px-6 py-16 sm:px-10 sm:py-20",children:[e.jsxs("header",{className:"mb-10",children:[e.jsx("h1",{className:"font-display text-2xl font-semibold tracking-tight text-text-primary",children:"Environment claimed"}),e.jsx("p",{className:"mt-2 text-sm text-text-muted",children:"You are now the owner of this environment."})]}),e.jsxs("div",{className:"space-y-4",children:[a&&e.jsx("p",{className:"font-mono text-xs text-text-muted",children:a}),s==="waiting"?e.jsxs("p",{className:"text-sm text-text-muted",children:["Waiting for the worker to create a session",e.jsx("span",{className:"animate-pulse",children:"…"})]}):e.jsx("p",{className:"text-sm text-text-muted",children:"The worker has not created a session yet — it may be offline or stopped. The session will appear on the dashboard once the worker connects."}),e.jsx("a",{href:"/code",className:"inline-block rounded-md bg-accent px-4 py-2 text-sm font-medium text-white hover:bg-accent/90 transition-colors",children:"Go to dashboard"})]})]}):e.jsx("div",{className:"px-6 py-16 sm:px-10 sm:py-20",children:e.jsx("p",{className:"text-sm text-text-muted",children:"Claiming environment..."})})}export{E as Claim};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{j as e,e as l}from"./vendor-DE_-CLIE.js";import{ad as O,ab as _,au as E,as as T,x as F,at as L,ao as a,ag as A,q as $,s as W,e as H,r as V,d as P,aj as q,I as Q,Q as R,Y as U,N as Y}from"./main-
|
|
1
|
+
import{j as e,e as l}from"./vendor-DE_-CLIE.js";import{ad as O,ab as _,au as E,as as T,x as F,at as L,ao as a,ag as A,q as $,s as W,e as H,r as V,d as P,aj as q,I as Q,Q as R,Y as U,N as Y}from"./main-5C_gChTh.js";import{g as D}from"./workerType-Bd-zteMi.js";import{S as Z}from"./SessionVisibilityBadge-BKEoqr7V.js";import{D as z,a as G,c as J,d as K,b as X}from"./dialog-D__YHb1u.js";import"./shiki-mEP5vQQu.js";import"./motion-BILbV7YQ.js";/**
|
|
2
2
|
* @license lucide-react v0.555.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|