@saluzi/saluzi-edu 0.2.20 → 0.2.21
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 +106 -83
- package/dist/rcs-web/assets/AdminPanel-B0hhIa7W.js +1 -0
- package/dist/rcs-web/assets/Claim-BTx12YJw.js +1 -0
- package/dist/rcs-web/assets/Dashboard-C_qeuXGp.js +1 -0
- package/dist/rcs-web/assets/Join-DDMaEXX-.js +1 -0
- package/dist/rcs-web/assets/Login-eCEGVdjw.js +1 -0
- package/dist/rcs-web/assets/Register-gqlt4O0J.js +1 -0
- package/dist/rcs-web/assets/{SessionDetail-B5C0o1YT.js → SessionDetail-BfrG6lOA.js} +10 -10
- package/dist/rcs-web/assets/SessionVisibilityBadge-CN3bz2gb.js +11 -0
- package/dist/rcs-web/assets/Setup-yO-c5mfy.js +1 -0
- package/dist/rcs-web/assets/{ShareLanding-DBC9gbJo.js → ShareLanding-S3XUdrDL.js} +1 -1
- package/dist/rcs-web/assets/TeamCreate-DGUK4ghi.js +1 -0
- package/dist/rcs-web/assets/TeamDetail-Ct07DirR.js +6 -0
- package/dist/rcs-web/assets/TeamList-CJTYa2Ah.js +1 -0
- package/dist/rcs-web/assets/TeamSettings-DXb0w8GG.js +1 -0
- package/dist/rcs-web/assets/UserSettings-DzreS8Mi.js +1 -0
- package/dist/rcs-web/assets/{arc-DBPKxYzS.js → arc-wiB_u0W8.js} +1 -1
- package/dist/rcs-web/assets/{architectureDiagram-3BPJPVTR-D88rj_js.js → architectureDiagram-3BPJPVTR-70tgIgZq.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-xzgUpRMn.js → dist/rcs-web/assets/blockDiagram-GPEHLZMM-Drz4LSdp.js} +1 -1
- package/dist/rcs-web/assets/{c4Diagram-AAUBKEIU-vpg_EoPb.js → c4Diagram-AAUBKEIU-BM7C7SEx.js} +1 -1
- package/dist/rcs-web/assets/channel-D-a_TNjA.js +1 -0
- package/dist/rcs-web/assets/{chunk-2J33WTMH-DXWsXvhS.js → chunk-2J33WTMH-BSqxyVTy.js} +1 -1
- package/dist/rcs-web/assets/{chunk-4BX2VUAB-BtTxwj9K.js → chunk-4BX2VUAB-HFKHN20V.js} +1 -1
- package/dist/rcs-web/assets/{chunk-55IACEB6-D2-Q95qg.js → chunk-55IACEB6-DOBdvIv4.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-Ce3e5x8u.js → dist/rcs-web/assets/chunk-727SXJPM-B2qn-WmZ.js} +1 -1
- package/dist/rcs-web/assets/{chunk-AQP2D5EJ-C0UbKVtd.js → chunk-AQP2D5EJ-DFC3ivzL.js} +1 -1
- package/dist/rcs-web/assets/{chunk-FMBD7UC4-BCwppVqh.js → chunk-FMBD7UC4-CkVr3MVW.js} +1 -1
- package/dist/rcs-web/assets/{chunk-ND2GUHAM-CnOBwWrm.js → chunk-ND2GUHAM-B2WO2xxD.js} +1 -1
- package/dist/rcs-web/assets/{chunk-QZHKN3VN-CDdzB71P.js → chunk-QZHKN3VN-YpZyVA2c.js} +1 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-Dl3V5TVw.js +1 -0
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-Dl3V5TVw.js +1 -0
- package/dist/rcs-web/assets/{code-block-IT6T5CEO-C1_H_ztH.js → code-block-IT6T5CEO-Bmds1dKk.js} +1 -1
- package/dist/rcs-web/assets/{copy-DXU1lQqc.js → copy-DdSkINCW.js} +1 -1
- package/dist/rcs-web/assets/{cose-bilkent-S5V4N54A-BC6nfjTF.js → cose-bilkent-S5V4N54A-CTtGg7Fx.js} +1 -1
- package/dist/rcs-web/assets/{dagre-BM42HDAG-CEPR_pRx.js → dagre-BM42HDAG-CFX48pZZ.js} +1 -1
- package/dist/rcs-web/assets/{diagram-2AECGRRQ-fVMGnSYa.js → diagram-2AECGRRQ-BwZE2jAF.js} +1 -1
- package/dist/rcs-web/assets/{diagram-5GNKFQAL-BPzN2rbK.js → diagram-5GNKFQAL-BuHnggju.js} +1 -1
- package/dist/rcs-web/assets/{diagram-KO2AKTUF-Boneg9n-.js → diagram-KO2AKTUF-Dmd3MO7f.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/diagram-LMA3HP47-B8AtvjO9.js → dist/rcs-web/assets/diagram-LMA3HP47-34ZNfvQd.js} +1 -1
- package/dist/rcs-web/assets/{diagram-OG6HWLK6-DjY7A8jK.js → diagram-OG6HWLK6-CjZvlFxO.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-C1BEfKxk.js → dist/rcs-web/assets/erDiagram-TEJ5UH35-BWR0WD_N.js} +1 -1
- package/dist/rcs-web/assets/{flowDiagram-I6XJVG4X-7TbI9MLV.js → flowDiagram-I6XJVG4X-DjZ7uFyw.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-CMAV0A01.js → dist/rcs-web/assets/ganttDiagram-6RSMTGT7-BuAM7awZ.js} +1 -1
- package/dist/rcs-web/assets/{gitGraphDiagram-PVQCEYII-DAlR8G81.js → gitGraphDiagram-PVQCEYII-B_6bqpod.js} +1 -1
- package/dist/rcs-web/assets/{infoDiagram-5YYISTIA-0T0DAV4W.js → infoDiagram-5YYISTIA-BbhCTAn2.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-BAw8txlx.js → dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-1VPCYnPS.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-DWvYKwz-.js → dist/rcs-web/assets/journeyDiagram-JHISSGLW-Dzl0f8Im.js} +1 -1
- package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-Qzqw1H5B.js → kanban-definition-UN3LZRKU-BGoVgBze.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/linear-D2KIJoCI.js → dist/rcs-web/assets/linear-DF7ChHtW.js} +1 -1
- package/dist/rcs-web/assets/main-B_OE0doJ.css +1 -0
- package/dist/rcs-web/assets/main-CEsvX5rj.js +253 -0
- package/dist/rcs-web/assets/{mermaid-VLURNSYL-CxWk1wHz.js → mermaid-VLURNSYL-3EqupjjN.js} +4 -4
- package/dist/rcs-web/assets/{mermaid.core-C48YvKu4.js → mermaid.core-BnVRH5R_.js} +5 -5
- package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-BfmcTuCA.js → mindmap-definition-RKZ34NQL-R3usKqMg.js} +1 -1
- package/dist/rcs-web/assets/{pieDiagram-4H26LBE5-DVNZpnfb.js → pieDiagram-4H26LBE5-U1XXn3t9.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-COL55Z3i.js → dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-CocIpolQ.js} +1 -1
- package/dist/rcs-web/assets/{requirementDiagram-4Y6WPE33-CFczYQ0a.js → requirementDiagram-4Y6WPE33-DX7pHOr5.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-BrQEv3WQ.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-TD6XCrii.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-CMxtYmQu.js → dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-EyZvfcbT.js} +1 -1
- package/dist/rcs-web/assets/{stateDiagram-AJRCARHV-Dw9xJZ-A.js → stateDiagram-AJRCARHV-B_SU6QSv.js} +1 -1
- package/dist/rcs-web/assets/{stateDiagram-v2-BHNVJYJU-Br0D-weX.js → stateDiagram-v2-BHNVJYJU-C8vEqOLP.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-DuWDwYBt.js → dist/rcs-web/assets/timeline-definition-PNZ67QCA-C2XHcj96.js} +1 -1
- package/dist/rcs-web/assets/{vennDiagram-CIIHVFJN--7IV65HQ.js → vennDiagram-CIIHVFJN-aAmhPOm3.js} +1 -1
- package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-CVTgWbvT.js → wardleyDiagram-YWT4CUSO-Db_mMfd6.js} +1 -1
- package/dist/rcs-web/assets/workerType-Bd-zteMi.js +1 -0
- package/{packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-B9om4xAF.js → dist/rcs-web/assets/xychartDiagram-2RQKCTM6-DPjdOqDe.js} +1 -1
- package/dist/rcs-web/index.html +2 -2
- package/package.json +1 -1
- package/packages/remote-control-server/src/__tests__/bind-replacement.test.ts +5 -5
- package/packages/remote-control-server/src/__tests__/phase4-integration.test.ts +2 -2
- package/packages/remote-control-server/src/__tests__/route-mounting.test.ts +2 -2
- package/packages/remote-control-server/src/__tests__/routes.test.ts +21 -5
- package/packages/remote-control-server/src/__tests__/shares.test.ts +21 -20
- package/packages/remote-control-server/src/__tests__/teams.test.ts +119 -0
- package/packages/remote-control-server/src/config.ts +3 -0
- package/packages/remote-control-server/src/db/schema.sql +6 -1
- package/packages/remote-control-server/src/db/sqlite.ts +46 -1
- package/packages/remote-control-server/src/registerRoutes.ts +17 -1
- package/packages/remote-control-server/src/routes/v1/environments.ts +35 -1
- package/packages/remote-control-server/src/routes/v1/sessions.ts +48 -2
- package/packages/remote-control-server/src/routes/web/auth-routes.ts +64 -0
- package/packages/remote-control-server/src/routes/web/environments.ts +7 -3
- package/packages/remote-control-server/src/routes/web/shares.ts +17 -26
- package/packages/remote-control-server/src/routes/web/teams.ts +241 -4
- package/packages/remote-control-server/src/services/environment.ts +12 -1
- package/packages/remote-control-server/src/store.ts +87 -4
- package/packages/remote-control-server/src/types/api.ts +1 -0
- package/packages/remote-control-server/web/dist/assets/AdminPanel-B0hhIa7W.js +1 -0
- package/packages/remote-control-server/web/dist/assets/Claim-BTx12YJw.js +1 -0
- package/packages/remote-control-server/web/dist/assets/Dashboard-C_qeuXGp.js +1 -0
- package/packages/remote-control-server/web/dist/assets/Join-DDMaEXX-.js +1 -0
- package/packages/remote-control-server/web/dist/assets/Login-eCEGVdjw.js +1 -0
- package/packages/remote-control-server/web/dist/assets/Register-gqlt4O0J.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{SessionDetail-B5C0o1YT.js → SessionDetail-BfrG6lOA.js} +10 -10
- package/packages/remote-control-server/web/dist/assets/SessionVisibilityBadge-CN3bz2gb.js +11 -0
- package/packages/remote-control-server/web/dist/assets/Setup-yO-c5mfy.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{ShareLanding-DBC9gbJo.js → ShareLanding-S3XUdrDL.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/TeamCreate-DGUK4ghi.js +1 -0
- package/packages/remote-control-server/web/dist/assets/TeamDetail-Ct07DirR.js +6 -0
- package/packages/remote-control-server/web/dist/assets/TeamList-CJTYa2Ah.js +1 -0
- package/packages/remote-control-server/web/dist/assets/TeamSettings-DXb0w8GG.js +1 -0
- package/packages/remote-control-server/web/dist/assets/UserSettings-DzreS8Mi.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{arc-DBPKxYzS.js → arc-wiB_u0W8.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{architectureDiagram-3BPJPVTR-D88rj_js.js → architectureDiagram-3BPJPVTR-70tgIgZq.js} +1 -1
- package/{dist/rcs-web/assets/blockDiagram-GPEHLZMM-xzgUpRMn.js → packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-Drz4LSdp.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{c4Diagram-AAUBKEIU-vpg_EoPb.js → c4Diagram-AAUBKEIU-BM7C7SEx.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/channel-D-a_TNjA.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-DXWsXvhS.js → chunk-2J33WTMH-BSqxyVTy.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-BtTxwj9K.js → chunk-4BX2VUAB-HFKHN20V.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-D2-Q95qg.js → chunk-55IACEB6-DOBdvIv4.js} +1 -1
- package/{dist/rcs-web/assets/chunk-727SXJPM-Ce3e5x8u.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-B2qn-WmZ.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-AQP2D5EJ-C0UbKVtd.js → chunk-AQP2D5EJ-DFC3ivzL.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-BCwppVqh.js → chunk-FMBD7UC4-CkVr3MVW.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-CnOBwWrm.js → chunk-ND2GUHAM-B2WO2xxD.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-CDdzB71P.js → chunk-QZHKN3VN-YpZyVA2c.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-Dl3V5TVw.js +1 -0
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-Dl3V5TVw.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{code-block-IT6T5CEO-C1_H_ztH.js → code-block-IT6T5CEO-Bmds1dKk.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{copy-DXU1lQqc.js → copy-DdSkINCW.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{cose-bilkent-S5V4N54A-BC6nfjTF.js → cose-bilkent-S5V4N54A-CTtGg7Fx.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{dagre-BM42HDAG-CEPR_pRx.js → dagre-BM42HDAG-CFX48pZZ.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-2AECGRRQ-fVMGnSYa.js → diagram-2AECGRRQ-BwZE2jAF.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-5GNKFQAL-BPzN2rbK.js → diagram-5GNKFQAL-BuHnggju.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-KO2AKTUF-Boneg9n-.js → diagram-KO2AKTUF-Dmd3MO7f.js} +1 -1
- package/{dist/rcs-web/assets/diagram-LMA3HP47-B8AtvjO9.js → packages/remote-control-server/web/dist/assets/diagram-LMA3HP47-34ZNfvQd.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-OG6HWLK6-DjY7A8jK.js → diagram-OG6HWLK6-CjZvlFxO.js} +1 -1
- package/{dist/rcs-web/assets/erDiagram-TEJ5UH35-C1BEfKxk.js → packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-BWR0WD_N.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{flowDiagram-I6XJVG4X-7TbI9MLV.js → flowDiagram-I6XJVG4X-DjZ7uFyw.js} +1 -1
- package/{dist/rcs-web/assets/ganttDiagram-6RSMTGT7-CMAV0A01.js → packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-BuAM7awZ.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{gitGraphDiagram-PVQCEYII-DAlR8G81.js → gitGraphDiagram-PVQCEYII-B_6bqpod.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{infoDiagram-5YYISTIA-0T0DAV4W.js → infoDiagram-5YYISTIA-BbhCTAn2.js} +1 -1
- package/{dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-BAw8txlx.js → packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-1VPCYnPS.js} +1 -1
- package/{dist/rcs-web/assets/journeyDiagram-JHISSGLW-DWvYKwz-.js → packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-Dzl0f8Im.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-Qzqw1H5B.js → kanban-definition-UN3LZRKU-BGoVgBze.js} +1 -1
- package/{dist/rcs-web/assets/linear-D2KIJoCI.js → packages/remote-control-server/web/dist/assets/linear-DF7ChHtW.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/main-B_OE0doJ.css +1 -0
- package/packages/remote-control-server/web/dist/assets/main-CEsvX5rj.js +253 -0
- package/packages/remote-control-server/web/dist/assets/{mermaid-VLURNSYL-CxWk1wHz.js → mermaid-VLURNSYL-3EqupjjN.js} +4 -4
- package/packages/remote-control-server/web/dist/assets/{mermaid.core-C48YvKu4.js → mermaid.core-BnVRH5R_.js} +5 -5
- package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-BfmcTuCA.js → mindmap-definition-RKZ34NQL-R3usKqMg.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{pieDiagram-4H26LBE5-DVNZpnfb.js → pieDiagram-4H26LBE5-U1XXn3t9.js} +1 -1
- package/{dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-COL55Z3i.js → packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-CocIpolQ.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{requirementDiagram-4Y6WPE33-CFczYQ0a.js → requirementDiagram-4Y6WPE33-DX7pHOr5.js} +1 -1
- package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-BrQEv3WQ.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-TD6XCrii.js} +1 -1
- package/{dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-CMxtYmQu.js → packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-EyZvfcbT.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{stateDiagram-AJRCARHV-Dw9xJZ-A.js → stateDiagram-AJRCARHV-B_SU6QSv.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{stateDiagram-v2-BHNVJYJU-Br0D-weX.js → stateDiagram-v2-BHNVJYJU-C8vEqOLP.js} +1 -1
- package/{dist/rcs-web/assets/timeline-definition-PNZ67QCA-DuWDwYBt.js → packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-C2XHcj96.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{vennDiagram-CIIHVFJN--7IV65HQ.js → vennDiagram-CIIHVFJN-aAmhPOm3.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-CVTgWbvT.js → wardleyDiagram-YWT4CUSO-Db_mMfd6.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/workerType-Bd-zteMi.js +1 -0
- package/{dist/rcs-web/assets/xychartDiagram-2RQKCTM6-B9om4xAF.js → packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-DPjdOqDe.js} +1 -1
- package/packages/remote-control-server/web/dist/index.html +2 -2
- package/dist/rcs-web/assets/AdminPanel-CQU763nr.js +0 -1
- package/dist/rcs-web/assets/Dashboard-B1hQfqI9.js +0 -1
- package/dist/rcs-web/assets/Join-7hMl360t.js +0 -1
- package/dist/rcs-web/assets/Login-BE8mNc0U.js +0 -1
- package/dist/rcs-web/assets/SessionVisibilityBadge-CQN3bJW4.js +0 -11
- package/dist/rcs-web/assets/Setup-CqZ5gEBQ.js +0 -1
- package/dist/rcs-web/assets/TeamCreate-DSYdTaPe.js +0 -1
- package/dist/rcs-web/assets/TeamDetail-CmJGsgOP.js +0 -1
- package/dist/rcs-web/assets/TeamList-BMS_Qq8U.js +0 -1
- package/dist/rcs-web/assets/TeamSettings-C0sYgCFT.js +0 -1
- package/dist/rcs-web/assets/UserSettings-CqzeJ5cc.js +0 -1
- package/dist/rcs-web/assets/channel-Daz3yMSu.js +0 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-DufdcV1P.js +0 -1
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-DufdcV1P.js +0 -1
- package/dist/rcs-web/assets/main-EP6Lac2m.css +0 -1
- package/dist/rcs-web/assets/main-J6cPs2y8.js +0 -253
- package/packages/remote-control-server/web/dist/assets/AdminPanel-CQU763nr.js +0 -1
- package/packages/remote-control-server/web/dist/assets/Dashboard-B1hQfqI9.js +0 -1
- package/packages/remote-control-server/web/dist/assets/Join-7hMl360t.js +0 -1
- package/packages/remote-control-server/web/dist/assets/Login-BE8mNc0U.js +0 -1
- package/packages/remote-control-server/web/dist/assets/SessionVisibilityBadge-CQN3bJW4.js +0 -11
- package/packages/remote-control-server/web/dist/assets/Setup-CqZ5gEBQ.js +0 -1
- package/packages/remote-control-server/web/dist/assets/TeamCreate-DSYdTaPe.js +0 -1
- package/packages/remote-control-server/web/dist/assets/TeamDetail-CmJGsgOP.js +0 -1
- package/packages/remote-control-server/web/dist/assets/TeamList-BMS_Qq8U.js +0 -1
- package/packages/remote-control-server/web/dist/assets/TeamSettings-C0sYgCFT.js +0 -1
- package/packages/remote-control-server/web/dist/assets/UserSettings-CqzeJ5cc.js +0 -1
- package/packages/remote-control-server/web/dist/assets/channel-Daz3yMSu.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-DufdcV1P.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-DufdcV1P.js +0 -1
- package/packages/remote-control-server/web/dist/assets/main-EP6Lac2m.css +0 -1
- package/packages/remote-control-server/web/dist/assets/main-J6cPs2y8.js +0 -253
|
@@ -1587,6 +1587,125 @@ describe('POST /web/environments/:id/transfer', () => {
|
|
|
1587
1587
|
expect([200, 201]).toContain(res.status)
|
|
1588
1588
|
// Detailed session visibility is tested in session-visibility.test.ts
|
|
1589
1589
|
})
|
|
1590
|
+
|
|
1591
|
+
test('personal env (no source team) → target team admin can claim → 200', async () => {
|
|
1592
|
+
// Use a fresh env id that's not in any team
|
|
1593
|
+
const personalEnvId = `env_${randomUUID().replace(/-/g, '')}`
|
|
1594
|
+
const res = await app.request(
|
|
1595
|
+
`/web/environments/${personalEnvId}/transfer`,
|
|
1596
|
+
{
|
|
1597
|
+
method: 'POST',
|
|
1598
|
+
headers: {
|
|
1599
|
+
...authHeader(owner.accessToken),
|
|
1600
|
+
'Content-Type': 'application/json',
|
|
1601
|
+
},
|
|
1602
|
+
body: JSON.stringify({ toTeamId: targetTeam.id }),
|
|
1603
|
+
},
|
|
1604
|
+
)
|
|
1605
|
+
expect(res.status).toBe(200)
|
|
1606
|
+
// Verify the link exists
|
|
1607
|
+
const linkRow = db
|
|
1608
|
+
.query(
|
|
1609
|
+
'SELECT team_id FROM team_environments WHERE environment_id = $envId',
|
|
1610
|
+
)
|
|
1611
|
+
.get({ $envId: personalEnvId }) as { team_id: string } | null
|
|
1612
|
+
expect(linkRow?.team_id).toBe(targetTeam.id)
|
|
1613
|
+
})
|
|
1614
|
+
|
|
1615
|
+
test('personal env → target team member (non-admin) → 403', async () => {
|
|
1616
|
+
const personalEnvId = `env_${randomUUID().replace(/-/g, '')}`
|
|
1617
|
+
// teamMember is only a 'member' of sourceTeam, not targetTeam at all
|
|
1618
|
+
const res = await app.request(
|
|
1619
|
+
`/web/environments/${personalEnvId}/transfer`,
|
|
1620
|
+
{
|
|
1621
|
+
method: 'POST',
|
|
1622
|
+
headers: {
|
|
1623
|
+
...authHeader(teamMember.accessToken),
|
|
1624
|
+
'Content-Type': 'application/json',
|
|
1625
|
+
},
|
|
1626
|
+
body: JSON.stringify({ toTeamId: targetTeam.id }),
|
|
1627
|
+
},
|
|
1628
|
+
)
|
|
1629
|
+
expect(res.status).toBe(403)
|
|
1630
|
+
})
|
|
1631
|
+
})
|
|
1632
|
+
|
|
1633
|
+
describe('DELETE /web/teams/:id/environments/:envId', () => {
|
|
1634
|
+
let app: Hono
|
|
1635
|
+
let db: Database
|
|
1636
|
+
let admin: TestUser
|
|
1637
|
+
let owner: TestUser
|
|
1638
|
+
let teamMember: TestUser
|
|
1639
|
+
let team: { id: string }
|
|
1640
|
+
let envId: string
|
|
1641
|
+
|
|
1642
|
+
beforeEach(async () => {
|
|
1643
|
+
db = freshDb()
|
|
1644
|
+
app = createApp()
|
|
1645
|
+
admin = await setupAdmin(app)
|
|
1646
|
+
owner = await createAndLoginUser(db, app, {
|
|
1647
|
+
username: 'owner2',
|
|
1648
|
+
role: 'member',
|
|
1649
|
+
})
|
|
1650
|
+
teamMember = await createAndLoginUser(db, app, {
|
|
1651
|
+
username: 'tmember2',
|
|
1652
|
+
role: 'member',
|
|
1653
|
+
})
|
|
1654
|
+
team = createTeamDirectly(db, {
|
|
1655
|
+
name: 'Team2',
|
|
1656
|
+
slug: 'team2',
|
|
1657
|
+
createdBy: owner.id,
|
|
1658
|
+
})
|
|
1659
|
+
addTeamMember(db, {
|
|
1660
|
+
teamId: team.id,
|
|
1661
|
+
userId: teamMember.id,
|
|
1662
|
+
role: 'member',
|
|
1663
|
+
addedBy: owner.id,
|
|
1664
|
+
})
|
|
1665
|
+
envId = `env_${randomUUID().replace(/-/g, '')}`
|
|
1666
|
+
db.query(
|
|
1667
|
+
`INSERT INTO team_environments (team_id, environment_id) VALUES ($teamId, $envId)`,
|
|
1668
|
+
).run({ $teamId: team.id, $envId: envId })
|
|
1669
|
+
})
|
|
1670
|
+
|
|
1671
|
+
test('team owner → 200, link removed', async () => {
|
|
1672
|
+
const res = await app.request(
|
|
1673
|
+
`/web/teams/${team.id}/environments/${envId}`,
|
|
1674
|
+
{
|
|
1675
|
+
method: 'DELETE',
|
|
1676
|
+
headers: authHeader(owner.accessToken),
|
|
1677
|
+
},
|
|
1678
|
+
)
|
|
1679
|
+
expect(res.status).toBe(200)
|
|
1680
|
+
const row = db
|
|
1681
|
+
.query(
|
|
1682
|
+
'SELECT team_id FROM team_environments WHERE environment_id = $envId',
|
|
1683
|
+
)
|
|
1684
|
+
.get({ $envId: envId })
|
|
1685
|
+
expect(row).toBeNull()
|
|
1686
|
+
})
|
|
1687
|
+
|
|
1688
|
+
test('team member (non-admin) → 403', async () => {
|
|
1689
|
+
const res = await app.request(
|
|
1690
|
+
`/web/teams/${team.id}/environments/${envId}`,
|
|
1691
|
+
{
|
|
1692
|
+
method: 'DELETE',
|
|
1693
|
+
headers: authHeader(teamMember.accessToken),
|
|
1694
|
+
},
|
|
1695
|
+
)
|
|
1696
|
+
expect(res.status).toBe(403)
|
|
1697
|
+
})
|
|
1698
|
+
|
|
1699
|
+
test('system admin → 200', async () => {
|
|
1700
|
+
const res = await app.request(
|
|
1701
|
+
`/web/teams/${team.id}/environments/${envId}`,
|
|
1702
|
+
{
|
|
1703
|
+
method: 'DELETE',
|
|
1704
|
+
headers: authHeader(admin.accessToken),
|
|
1705
|
+
},
|
|
1706
|
+
)
|
|
1707
|
+
expect(res.status).toBe(200)
|
|
1708
|
+
})
|
|
1590
1709
|
})
|
|
1591
1710
|
|
|
1592
1711
|
// ===========================================================================
|
|
@@ -12,6 +12,9 @@ export const config = {
|
|
|
12
12
|
.split(',')
|
|
13
13
|
.map(origin => origin.trim())
|
|
14
14
|
.filter(Boolean),
|
|
15
|
+
/** Whether the web UI allows open user self-registration (POST /web/auth/register).
|
|
16
|
+
* When false, only admin invitation (/join) can create new users. Default true. */
|
|
17
|
+
allowRegistration: process.env.RCS_ALLOW_REGISTRATION !== 'false',
|
|
15
18
|
/** Bun WebSocket idle timeout (seconds). Bun sends protocol-level pings after
|
|
16
19
|
* this many seconds of no received data. Must be shorter than any reverse
|
|
17
20
|
* proxy's idle timeout (nginx default 60s, Cloudflare 100s). Default 30s. */
|
|
@@ -87,7 +87,10 @@ CREATE TABLE IF NOT EXISTS environments (
|
|
|
87
87
|
worker_type TEXT NOT NULL DEFAULT 'claude_code',
|
|
88
88
|
status TEXT NOT NULL DEFAULT 'active',
|
|
89
89
|
last_poll_at TEXT,
|
|
90
|
-
created_at TEXT NOT NULL
|
|
90
|
+
created_at TEXT NOT NULL,
|
|
91
|
+
owner_user_id TEXT,
|
|
92
|
+
claim_token TEXT,
|
|
93
|
+
claim_expires_at TEXT
|
|
91
94
|
);
|
|
92
95
|
|
|
93
96
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -114,3 +117,5 @@ CREATE INDEX IF NOT EXISTS idx_sessions_visibility ON sessions(visibility);
|
|
|
114
117
|
CREATE INDEX IF NOT EXISTS idx_sessions_environment ON sessions(environment_id);
|
|
115
118
|
CREATE INDEX IF NOT EXISTS idx_session_tokens_user ON session_tokens(user_id);
|
|
116
119
|
CREATE INDEX IF NOT EXISTS idx_environments_team ON environments(team_id);
|
|
120
|
+
CREATE INDEX IF NOT EXISTS idx_environments_owner_user ON environments(owner_user_id);
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_environments_claim_token ON environments(claim_token);
|
|
@@ -11,7 +11,9 @@ let _dbPath: string | null = null
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Run all schema migrations on the given database.
|
|
14
|
-
* Idempotent — uses CREATE TABLE IF NOT EXISTS
|
|
14
|
+
* Idempotent — uses CREATE TABLE IF NOT EXISTS for new tables, plus
|
|
15
|
+
* ALTER TABLE ADD COLUMN for columns added to existing tables (gated by
|
|
16
|
+
* PRAGMA table_info so re-running on an already-migrated DB is a no-op).
|
|
15
17
|
* Reads schema.sql and executes as a single db.exec() call,
|
|
16
18
|
* which correctly handles semicolons inside CHECK constraints.
|
|
17
19
|
*/
|
|
@@ -20,9 +22,52 @@ export function migrateDatabase(db: Database): void {
|
|
|
20
22
|
const schema = readFileSync(schemaPath, 'utf8')
|
|
21
23
|
|
|
22
24
|
db.exec('PRAGMA foreign_keys = ON')
|
|
25
|
+
// Run ALTER TABLE migrations BEFORE db.exec(schema) — schema.sql's
|
|
26
|
+
// CREATE INDEX statements reference the new columns, so the columns
|
|
27
|
+
// must exist before the indexes can be created. CREATE TABLE IF NOT
|
|
28
|
+
// EXISTS skips existing tables (so the ALTER adds the missing columns
|
|
29
|
+
// to pre-existing tables); on a fresh DB the ALTER is a no-op because
|
|
30
|
+
// the table doesn't exist yet, then db.exec(schema) creates it with
|
|
31
|
+
// all columns.
|
|
32
|
+
migrateEnvironmentsColumns(db)
|
|
23
33
|
db.exec(schema)
|
|
24
34
|
}
|
|
25
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Add columns to pre-existing `environments` tables that were created
|
|
38
|
+
* before owner_user_id / claim_token / claim_expires_at existed.
|
|
39
|
+
* CREATE TABLE IF NOT EXISTS skips existing tables entirely, so without
|
|
40
|
+
* this the new columns would never appear on upgraded DBs. Must run
|
|
41
|
+
* BEFORE db.exec(schema) because schema.sql's CREATE INDEX statements
|
|
42
|
+
* reference the new columns. On a fresh DB (table doesn't exist yet),
|
|
43
|
+
* skip entirely — db.exec(schema) will create the table with all columns.
|
|
44
|
+
*/
|
|
45
|
+
function migrateEnvironmentsColumns(db: Database): void {
|
|
46
|
+
// Check if environments table exists at all. On a fresh DB it won't,
|
|
47
|
+
// and ALTER TABLE on a non-existent table throws. PRAGMA table_info
|
|
48
|
+
// returns empty for non-existent tables, but ALTER still errors — so
|
|
49
|
+
// gate on table existence via sqlite_master.
|
|
50
|
+
const tableExists = db
|
|
51
|
+
.query(
|
|
52
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='environments'",
|
|
53
|
+
)
|
|
54
|
+
.get() as { 1: number } | undefined
|
|
55
|
+
if (!tableExists) return
|
|
56
|
+
|
|
57
|
+
const cols = db.query('PRAGMA table_info(environments)').all() as Array<{
|
|
58
|
+
name: string
|
|
59
|
+
}>
|
|
60
|
+
const names = new Set(cols.map(c => c.name))
|
|
61
|
+
const addIfMissing = (name: string, defn: string): void => {
|
|
62
|
+
if (!names.has(name)) {
|
|
63
|
+
db.exec(`ALTER TABLE environments ADD COLUMN ${name} ${defn}`)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
addIfMissing('owner_user_id', 'TEXT')
|
|
67
|
+
addIfMissing('claim_token', 'TEXT')
|
|
68
|
+
addIfMissing('claim_expires_at', 'TEXT')
|
|
69
|
+
}
|
|
70
|
+
|
|
26
71
|
/**
|
|
27
72
|
* Resolve the default database path.
|
|
28
73
|
* - Uses RCS_DB_PATH env var if set
|
|
@@ -56,6 +56,10 @@ export function registerRoutes(
|
|
|
56
56
|
app.get('/code', ss({ root: webDir, path: 'index.html' }))
|
|
57
57
|
app.get('/code/', ss({ root: webDir, path: 'index.html' }))
|
|
58
58
|
app.get('/code/:sessionId', ss({ root: webDir, path: 'index.html' }))
|
|
59
|
+
// /code/s/:shareToken — share landing page (two-segment path, not matched by /code/:sessionId)
|
|
60
|
+
app.get('/code/s/:shareToken', ss({ root: webDir, path: 'index.html' }))
|
|
61
|
+
// /code/claim/:claimToken — environment claim landing page
|
|
62
|
+
app.get('/code/claim/:claimToken', ss({ root: webDir, path: 'index.html' }))
|
|
59
63
|
|
|
60
64
|
// v1 Environment routes
|
|
61
65
|
app.route('/v1/environments', v1Environments)
|
|
@@ -107,9 +111,21 @@ export function registerRoutes(
|
|
|
107
111
|
|
|
108
112
|
// SPA fallback — serve index.html for frontend routes handled by React Router.
|
|
109
113
|
// Must be registered AFTER all API routes to avoid intercepting /web/*, /v1/*, /acp/* etc.
|
|
110
|
-
const spaPaths = [
|
|
114
|
+
const spaPaths = [
|
|
115
|
+
'/login',
|
|
116
|
+
'/register',
|
|
117
|
+
'/setup',
|
|
118
|
+
'/join',
|
|
119
|
+
'/settings',
|
|
120
|
+
'/teams',
|
|
121
|
+
'/admin',
|
|
122
|
+
'/share',
|
|
123
|
+
]
|
|
111
124
|
for (const p of spaPaths) {
|
|
112
125
|
app.get(p, ss({ root: webDir, path: 'index.html' }))
|
|
113
126
|
app.get(`${p}/*`, ss({ root: webDir, path: 'index.html' }))
|
|
114
127
|
}
|
|
128
|
+
|
|
129
|
+
// Root redirect → /code/ (main dashboard entry point)
|
|
130
|
+
app.get('/', c => c.redirect('/code/'))
|
|
115
131
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Hono } from 'hono'
|
|
2
2
|
import type { Context, Next } from 'hono'
|
|
3
|
+
import { randomUUID } from 'node:crypto'
|
|
3
4
|
import {
|
|
4
5
|
registerEnvironment,
|
|
5
6
|
deregisterEnvironment,
|
|
@@ -62,6 +63,18 @@ app.post('/bridge', acceptCliHeaders, bridgeAuth, async c => {
|
|
|
62
63
|
const userId = c.get('userId') as string | undefined
|
|
63
64
|
const username = userId
|
|
64
65
|
const teamId = c.req.header('X-Team-Id')
|
|
66
|
+
const headerUserId = c.req.header('X-User-Id')
|
|
67
|
+
|
|
68
|
+
// X-User-Id explicitly sets env.owner_user_id. If both X-User-Id and
|
|
69
|
+
// X-Team-Id are absent, the env is "unowned" — a claim_token is generated
|
|
70
|
+
// and returned so the worker can print a /code/claim/<token> URL for the
|
|
71
|
+
// first logged-in user to claim ownership.
|
|
72
|
+
const ownerUserId = headerUserId || null
|
|
73
|
+
const needsClaim = !ownerUserId && !teamId
|
|
74
|
+
const claimToken = needsClaim ? `clm_${randomUUID().replace(/-/g, '')}` : null
|
|
75
|
+
const claimExpiresAt = claimToken
|
|
76
|
+
? new Date(Date.now() + 24 * 60 * 60 * 1000)
|
|
77
|
+
: null
|
|
65
78
|
|
|
66
79
|
// Validate team if X-Team-Id is provided
|
|
67
80
|
if (teamId && userId) {
|
|
@@ -109,7 +122,13 @@ app.post('/bridge', acceptCliHeaders, bridgeAuth, async c => {
|
|
|
109
122
|
}
|
|
110
123
|
}
|
|
111
124
|
|
|
112
|
-
const result = registerEnvironment({
|
|
125
|
+
const result = registerEnvironment({
|
|
126
|
+
...body,
|
|
127
|
+
username,
|
|
128
|
+
ownerUserId,
|
|
129
|
+
claimToken,
|
|
130
|
+
claimExpiresAt,
|
|
131
|
+
})
|
|
113
132
|
|
|
114
133
|
// Link environment to team if X-Team-Id was provided
|
|
115
134
|
if (teamId) {
|
|
@@ -124,6 +143,21 @@ app.post('/bridge', acceptCliHeaders, bridgeAuth, async c => {
|
|
|
124
143
|
}
|
|
125
144
|
}
|
|
126
145
|
|
|
146
|
+
// Append claim info to response so worker can print the claim URL.
|
|
147
|
+
// Worker reads `claim_url` and prints it to terminal; first logged-in
|
|
148
|
+
// user to visit claims the env.
|
|
149
|
+
if (claimToken) {
|
|
150
|
+
const host = c.req.header('host') || ''
|
|
151
|
+
const proto = c.req.header('x-forwarded-proto') || 'http'
|
|
152
|
+
const claimUrl = host
|
|
153
|
+
? `${proto}://${host}/code/claim/${claimToken}`
|
|
154
|
+
: `/code/claim/${claimToken}`
|
|
155
|
+
return c.json(
|
|
156
|
+
{ ...result, claim_token: claimToken, claim_url: claimUrl },
|
|
157
|
+
200,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
127
161
|
return c.json(result, 200)
|
|
128
162
|
})
|
|
129
163
|
|
|
@@ -10,14 +10,60 @@ import {
|
|
|
10
10
|
import { createWorkItem } from '../../services/work-dispatch'
|
|
11
11
|
import { apiKeyAuth, acceptCliHeaders } from '../../auth/middleware'
|
|
12
12
|
import { publishSessionEvent } from '../../services/transport'
|
|
13
|
+
import { storeBindSession, storeGetEnvironmentOwnership } from '../../store'
|
|
13
14
|
|
|
14
15
|
const app = new Hono()
|
|
15
16
|
|
|
16
|
-
/** POST /v1/sessions — Create session
|
|
17
|
+
/** POST /v1/sessions — Create session.
|
|
18
|
+
* Used by replBridge/ACP workers to auto-create their initial session.
|
|
19
|
+
* Session ownership: user owner = env.owner_user_id, team owner = env.team_id
|
|
20
|
+
* (auto-inherited). If env has no owner_user_id yet (claim flow pending),
|
|
21
|
+
* returns 409 so the worker can retry after claim completes. */
|
|
17
22
|
app.post('/', acceptCliHeaders, apiKeyAuth, async c => {
|
|
18
23
|
const body = await c.req.json()
|
|
19
24
|
const username = c.get('userId')
|
|
20
|
-
|
|
25
|
+
|
|
26
|
+
// Resolve env ownership to bind session_owners.
|
|
27
|
+
let envOwnerUserId: string | null = null
|
|
28
|
+
let envTeamId: string | null = null
|
|
29
|
+
if (body.environment_id) {
|
|
30
|
+
const ownership = storeGetEnvironmentOwnership(body.environment_id)
|
|
31
|
+
if (ownership) {
|
|
32
|
+
envOwnerUserId = ownership.ownerUserId
|
|
33
|
+
envTeamId = ownership.teamId
|
|
34
|
+
// Claim flow pending: env exists but has no user owner yet. Worker
|
|
35
|
+
// should retry after claim completes (Q4 = option A).
|
|
36
|
+
if (!ownership.ownerUserId) {
|
|
37
|
+
return c.json(
|
|
38
|
+
{
|
|
39
|
+
error: {
|
|
40
|
+
type: 'environment_unowned',
|
|
41
|
+
message:
|
|
42
|
+
'Environment has no owner yet; claim it via the printed URL before creating sessions',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
409,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const session = createSession({
|
|
52
|
+
...body,
|
|
53
|
+
username,
|
|
54
|
+
visibility: envTeamId ? 'team' : 'private',
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// Write session_owners: team owner inherited from env, user owner = env owner.
|
|
58
|
+
// This is the key fix for the "not your sessions" bug — previously /v1/sessions
|
|
59
|
+
// only set sessions.username, never wrote session_owners, so the Web UI list
|
|
60
|
+
// (which filters by session_owners) never showed these sessions.
|
|
61
|
+
if (envTeamId) {
|
|
62
|
+
storeBindSession(session.id, envTeamId, 'team')
|
|
63
|
+
}
|
|
64
|
+
if (envOwnerUserId) {
|
|
65
|
+
storeBindSession(session.id, envOwnerUserId, 'user')
|
|
66
|
+
}
|
|
21
67
|
|
|
22
68
|
// Create work item if environment is specified
|
|
23
69
|
if (body.environment_id) {
|
|
@@ -377,6 +377,70 @@ app.post('/logout', async c => {
|
|
|
377
377
|
return c.json({ ok: true })
|
|
378
378
|
})
|
|
379
379
|
|
|
380
|
+
// GET /register-status — Whether self-registration is open
|
|
381
|
+
app.get('/register-status', c => {
|
|
382
|
+
return c.json({ allowed: config.allowRegistration })
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
// POST /register — Self-register a new member account (no team association).
|
|
386
|
+
// Only available when RCS_ALLOW_REGISTRATION !== 'false'.
|
|
387
|
+
app.post('/register', async c => {
|
|
388
|
+
if (!config.allowRegistration) {
|
|
389
|
+
return c.json({ error: 'Registration is disabled' }, 403)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let body: Record<string, unknown>
|
|
393
|
+
try {
|
|
394
|
+
body = await c.req.json()
|
|
395
|
+
} catch {
|
|
396
|
+
return c.json({ error: 'Invalid JSON' }, 400)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const { username, password } = body as {
|
|
400
|
+
username?: string
|
|
401
|
+
password?: string
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!username || typeof username !== 'string') {
|
|
405
|
+
return c.json({ error: 'Username is required' }, 400)
|
|
406
|
+
}
|
|
407
|
+
if (!password || typeof password !== 'string') {
|
|
408
|
+
return c.json({ error: 'Password is required' }, 400)
|
|
409
|
+
}
|
|
410
|
+
if (password.length < 8) {
|
|
411
|
+
return c.json({ error: 'Password must be at least 8 characters' }, 400)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const db = getDb()
|
|
415
|
+
const userId = `usr_${randomUUID().replace(/-/g, '')}`
|
|
416
|
+
const passwordHash = await hashPasswordAsync(password)
|
|
417
|
+
const now = new Date().toISOString()
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
const result = db
|
|
421
|
+
.query(
|
|
422
|
+
`INSERT OR IGNORE INTO users (id, username, password_hash, role, created_at)
|
|
423
|
+
VALUES ($id, $username, $passwordHash, 'member', $now)`,
|
|
424
|
+
)
|
|
425
|
+
.run({
|
|
426
|
+
$id: userId,
|
|
427
|
+
$username: username,
|
|
428
|
+
$passwordHash: passwordHash,
|
|
429
|
+
$now: now,
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
if (result.changes === 0) {
|
|
433
|
+
return c.json({ error: 'Username already taken' }, 409)
|
|
434
|
+
}
|
|
435
|
+
} catch (err) {
|
|
436
|
+
console.warn('[/register] insert failed:', err)
|
|
437
|
+
return c.json({ error: 'Registration failed' }, 500)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const isHttps = isSecureRequest(c)
|
|
441
|
+
return issueAndRespond(c, userId, isHttps)
|
|
442
|
+
})
|
|
443
|
+
|
|
380
444
|
// POST /join — Join via invitation token
|
|
381
445
|
app.post('/join', async c => {
|
|
382
446
|
let body: Record<string, unknown>
|
|
@@ -9,8 +9,9 @@ import { getDb } from '../../db/sqlite'
|
|
|
9
9
|
const app = new Hono()
|
|
10
10
|
|
|
11
11
|
/** GET /web/environments — List active environments.
|
|
12
|
-
* Admin sees all. Non-admin sees only environments
|
|
13
|
-
*
|
|
12
|
+
* Admin sees all. Non-admin sees only environments they own (owner_user_id = me)
|
|
13
|
+
* OR environments linked to teams they belong to. Unowned personal envs (no
|
|
14
|
+
* owner, no team) are invisible — they're in claim flow pending state. */
|
|
14
15
|
app.get('/environments', sessionAuth, async c => {
|
|
15
16
|
const userId = c.get('userId') as string
|
|
16
17
|
const isAdmin = c.get('isAdmin') as boolean
|
|
@@ -28,8 +29,11 @@ app.get('/environments', sessionAuth, async c => {
|
|
|
28
29
|
.all({ $userId: userId }) as Array<{ team_id: string }>
|
|
29
30
|
const teamIds = new Set(teamRows.map(r => r.team_id))
|
|
30
31
|
|
|
32
|
+
// Visible if: env.owner_user_id is me OR env.team_id is one of my teams.
|
|
31
33
|
const filtered = allEnvs.filter(
|
|
32
|
-
env =>
|
|
34
|
+
env =>
|
|
35
|
+
env.owner_user_id === userId ||
|
|
36
|
+
(env.team_id !== null && teamIds.has(env.team_id)),
|
|
33
37
|
)
|
|
34
38
|
return c.json(filtered, 200)
|
|
35
39
|
})
|
|
@@ -27,17 +27,6 @@ function isTeamMember(
|
|
|
27
27
|
return !!row
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
interface ShareRow {
|
|
31
|
-
id: string
|
|
32
|
-
session_id: string
|
|
33
|
-
granted_to_user: string | null
|
|
34
|
-
granted_to_team: string | null
|
|
35
|
-
permission: string
|
|
36
|
-
expires_at: string | null
|
|
37
|
-
granted_by: string
|
|
38
|
-
granted_at: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
30
|
/**
|
|
42
31
|
* Helper: check if the current user is the session owner.
|
|
43
32
|
* Returns the userId on success, or a Response on failure.
|
|
@@ -112,10 +101,10 @@ function checkSessionOwnership(
|
|
|
112
101
|
}
|
|
113
102
|
|
|
114
103
|
/**
|
|
115
|
-
* POST /sessions/:id/
|
|
104
|
+
* POST /sessions/:id/shares — Create a share for a session.
|
|
116
105
|
*/
|
|
117
106
|
app.post(
|
|
118
|
-
'/sessions/:id/
|
|
107
|
+
'/sessions/:id/shares',
|
|
119
108
|
async (c, next) => {
|
|
120
109
|
const result = await sessionAuth(c, next)
|
|
121
110
|
if (result) return result
|
|
@@ -228,17 +217,18 @@ app.post(
|
|
|
228
217
|
$now: now,
|
|
229
218
|
})
|
|
230
219
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
220
|
+
// Return { share, shareToken } to match frontend CreateShareResponse type.
|
|
221
|
+
const share = {
|
|
222
|
+
id: shareId,
|
|
223
|
+
session_id: sessionId,
|
|
224
|
+
granted_to_user: grantedTo.userId || null,
|
|
225
|
+
granted_to_team: grantedTo.teamId || null,
|
|
226
|
+
permission,
|
|
227
|
+
expires_at: expiresAt || null,
|
|
228
|
+
created_at: now,
|
|
229
|
+
created_by: userId,
|
|
230
|
+
}
|
|
231
|
+
return c.json({ share, shareToken: shareId }, 201)
|
|
242
232
|
},
|
|
243
233
|
)
|
|
244
234
|
|
|
@@ -260,11 +250,12 @@ app.get(
|
|
|
260
250
|
const shares = db
|
|
261
251
|
.query(
|
|
262
252
|
`SELECT id, session_id, granted_to_user, granted_to_team,
|
|
263
|
-
permission, expires_at,
|
|
253
|
+
permission, expires_at,
|
|
254
|
+
granted_by AS created_by, granted_at AS created_at
|
|
264
255
|
FROM session_shares
|
|
265
256
|
WHERE session_id = $sid`,
|
|
266
257
|
)
|
|
267
|
-
.all({ $sid: sessionId })
|
|
258
|
+
.all({ $sid: sessionId })
|
|
268
259
|
|
|
269
260
|
return c.json(shares, 200)
|
|
270
261
|
},
|