@saluzi/saluzi-edu 0.2.13 → 0.2.15
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 +79 -72
- package/dist/rcs-web/assets/AdminPanel-CQU763nr.js +1 -0
- package/dist/rcs-web/assets/Dashboard-B1hQfqI9.js +1 -0
- package/dist/rcs-web/assets/Join-7hMl360t.js +1 -0
- package/{packages/remote-control-server/web/dist/assets/Login-0VGZs85I.js → dist/rcs-web/assets/Login-BE8mNc0U.js} +1 -1
- package/dist/rcs-web/assets/SessionDetail-B5C0o1YT.js +55 -0
- package/dist/rcs-web/assets/SessionVisibilityBadge-CQN3bJW4.js +11 -0
- package/dist/rcs-web/assets/{Setup-C0cjxXMt.js → Setup-CqZ5gEBQ.js} +1 -1
- package/dist/rcs-web/assets/{ShareLanding-DUZrreJ7.js → ShareLanding-DBC9gbJo.js} +1 -1
- package/dist/rcs-web/assets/TeamCreate-DSYdTaPe.js +1 -0
- package/dist/rcs-web/assets/TeamDetail-CmJGsgOP.js +1 -0
- package/dist/rcs-web/assets/TeamList-BMS_Qq8U.js +1 -0
- package/dist/rcs-web/assets/{TeamSettings-CNVwYwcp.js → TeamSettings-C0sYgCFT.js} +1 -1
- package/dist/rcs-web/assets/UserSettings-CqzeJ5cc.js +1 -0
- package/{packages/remote-control-server/web/dist/assets/arc-CJxirXEQ.js → dist/rcs-web/assets/arc-DBPKxYzS.js} +1 -1
- package/dist/rcs-web/assets/{architectureDiagram-3BPJPVTR-BZvIHrLA.js → architectureDiagram-3BPJPVTR-D88rj_js.js} +1 -1
- package/dist/rcs-web/assets/{blockDiagram-GPEHLZMM-3p0y3Ija.js → blockDiagram-GPEHLZMM-xzgUpRMn.js} +1 -1
- package/dist/rcs-web/assets/{c4Diagram-AAUBKEIU-Bmz9A1wn.js → c4Diagram-AAUBKEIU-vpg_EoPb.js} +1 -1
- package/dist/rcs-web/assets/channel-Daz3yMSu.js +1 -0
- package/dist/rcs-web/assets/{chunk-2J33WTMH-B8gQz22j.js → chunk-2J33WTMH-DXWsXvhS.js} +1 -1
- package/dist/rcs-web/assets/{chunk-4BX2VUAB-CgLEZsTU.js → chunk-4BX2VUAB-BtTxwj9K.js} +1 -1
- package/dist/rcs-web/assets/{chunk-55IACEB6-0mdE_-IS.js → chunk-55IACEB6-D2-Q95qg.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-DdtjnIHR.js → dist/rcs-web/assets/chunk-727SXJPM-Ce3e5x8u.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-BtwFPk50.js → dist/rcs-web/assets/chunk-AQP2D5EJ-C0UbKVtd.js} +1 -1
- package/dist/rcs-web/assets/{chunk-FMBD7UC4-BcO9mQ0P.js → chunk-FMBD7UC4-BCwppVqh.js} +1 -1
- package/dist/rcs-web/assets/{chunk-ND2GUHAM-BvRq13S_.js → chunk-ND2GUHAM-CnOBwWrm.js} +1 -1
- package/dist/rcs-web/assets/{chunk-QZHKN3VN-D7GHkPpq.js → chunk-QZHKN3VN-CDdzB71P.js} +1 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-DufdcV1P.js +1 -0
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-DufdcV1P.js +1 -0
- package/dist/rcs-web/assets/{code-block-IT6T5CEO-B1p-vO-3.js → code-block-IT6T5CEO-C1_H_ztH.js} +1 -1
- package/dist/rcs-web/assets/copy-DXU1lQqc.js +6 -0
- package/{packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-L0rUZuH5.js → dist/rcs-web/assets/cose-bilkent-S5V4N54A-BC6nfjTF.js} +1 -1
- package/dist/rcs-web/assets/{dagre-BM42HDAG-CHGuio22.js → dagre-BM42HDAG-CEPR_pRx.js} +1 -1
- package/dist/rcs-web/assets/{diagram-2AECGRRQ-DIDRyQk8.js → diagram-2AECGRRQ-fVMGnSYa.js} +1 -1
- package/dist/rcs-web/assets/{diagram-5GNKFQAL-CNubKb6P.js → diagram-5GNKFQAL-BPzN2rbK.js} +1 -1
- package/dist/rcs-web/assets/{diagram-KO2AKTUF-CzQh664-.js → diagram-KO2AKTUF-Boneg9n-.js} +1 -1
- package/dist/rcs-web/assets/{diagram-LMA3HP47-CWBR7-VG.js → diagram-LMA3HP47-B8AtvjO9.js} +1 -1
- package/dist/rcs-web/assets/{diagram-OG6HWLK6-BMHdlbcZ.js → diagram-OG6HWLK6-DjY7A8jK.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-OgT8dC-K.js → dist/rcs-web/assets/erDiagram-TEJ5UH35-C1BEfKxk.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-DRrCY2oB.js → dist/rcs-web/assets/flowDiagram-I6XJVG4X-7TbI9MLV.js} +1 -1
- package/dist/rcs-web/assets/{ganttDiagram-6RSMTGT7-DctpzEKN.js → ganttDiagram-6RSMTGT7-CMAV0A01.js} +1 -1
- package/dist/rcs-web/assets/{gitGraphDiagram-PVQCEYII-5KpLszm9.js → gitGraphDiagram-PVQCEYII-DAlR8G81.js} +1 -1
- package/dist/rcs-web/assets/{infoDiagram-5YYISTIA-BAKD_Tt7.js → infoDiagram-5YYISTIA-0T0DAV4W.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-VHOPG4Nc.js → dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-BAw8txlx.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-BtGih-ik.js → dist/rcs-web/assets/journeyDiagram-JHISSGLW-DWvYKwz-.js} +1 -1
- package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-BsEGZsD1.js → kanban-definition-UN3LZRKU-Qzqw1H5B.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/linear-Dj636PNG.js → dist/rcs-web/assets/linear-D2KIJoCI.js} +1 -1
- package/dist/rcs-web/assets/main-J6cPs2y8.js +253 -0
- package/dist/rcs-web/assets/{mermaid-VLURNSYL-DutpHHm9.js → mermaid-VLURNSYL-CxWk1wHz.js} +4 -4
- package/dist/rcs-web/assets/{mermaid.core-XHHf0pen.js → mermaid.core-C48YvKu4.js} +5 -5
- package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-BBa8V_3g.js → mindmap-definition-RKZ34NQL-BfmcTuCA.js} +1 -1
- package/dist/rcs-web/assets/{pieDiagram-4H26LBE5-BuNEdo9H.js → pieDiagram-4H26LBE5-DVNZpnfb.js} +1 -1
- package/dist/rcs-web/assets/{quadrantDiagram-W4KKPZXB-CsJgyQIN.js → quadrantDiagram-W4KKPZXB-COL55Z3i.js} +1 -1
- package/dist/rcs-web/assets/{requirementDiagram-4Y6WPE33-DqMJZ914.js → requirementDiagram-4Y6WPE33-CFczYQ0a.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-CVsz09q2.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-BrQEv3WQ.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-BRp4Wddn.js → dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-CMxtYmQu.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-S-QqQr-D.js → dist/rcs-web/assets/stateDiagram-AJRCARHV-Dw9xJZ-A.js} +1 -1
- package/dist/rcs-web/assets/{stateDiagram-v2-BHNVJYJU-D77KfDr-.js → stateDiagram-v2-BHNVJYJU-Br0D-weX.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-De8bFWnE.js → dist/rcs-web/assets/timeline-definition-PNZ67QCA-DuWDwYBt.js} +1 -1
- package/dist/rcs-web/assets/{vennDiagram-CIIHVFJN-Dikwrn1L.js → vennDiagram-CIIHVFJN--7IV65HQ.js} +1 -1
- package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-DUzpiV6H.js → wardleyDiagram-YWT4CUSO-CVTgWbvT.js} +1 -1
- package/dist/rcs-web/assets/{xychartDiagram-2RQKCTM6-OZNbB32D.js → xychartDiagram-2RQKCTM6-B9om4xAF.js} +1 -1
- package/dist/rcs-web/index.html +1 -1
- package/package.json +1 -1
- package/packages/remote-control-server/src/__tests__/auth-login.test.ts +21 -4
- package/packages/remote-control-server/src/__tests__/auth-security.test.ts +12 -6
- package/packages/remote-control-server/src/__tests__/data-layer-round2.test.ts +12 -11
- package/packages/remote-control-server/src/__tests__/db-isolation.test.ts +61 -0
- package/packages/remote-control-server/src/__tests__/low-priority-fixes.test.ts +178 -0
- package/packages/remote-control-server/src/__tests__/middleware.test.ts +4 -4
- package/packages/remote-control-server/src/__tests__/route-mounting.test.ts +26 -8
- package/packages/remote-control-server/src/__tests__/security-fixes.test.ts +32 -27
- package/packages/remote-control-server/src/__tests__/security-round3.test.ts +5 -1
- package/packages/remote-control-server/src/__tests__/team-filter-api.test.ts +280 -0
- package/packages/remote-control-server/src/__tests__/team-session-env.test.ts +169 -0
- package/packages/remote-control-server/src/__tests__/teams.test.ts +7 -0
- package/packages/remote-control-server/src/auth/csrf.ts +1 -3
- package/packages/remote-control-server/src/auth/middleware.ts +13 -4
- package/packages/remote-control-server/src/index.ts +11 -8
- package/packages/remote-control-server/src/routes/v1/environments.ts +12 -9
- package/packages/remote-control-server/src/routes/v1/sessions.ts +1 -1
- package/packages/remote-control-server/src/routes/web/auth-routes.ts +3 -1
- package/packages/remote-control-server/src/routes/web/auth.ts +2 -1
- package/packages/remote-control-server/src/routes/web/environments.ts +22 -4
- package/packages/remote-control-server/src/routes/web/sessions.ts +24 -12
- package/packages/remote-control-server/src/routes/web/shares.ts +11 -2
- package/packages/remote-control-server/src/routes/web/teams.ts +6 -9
- package/packages/remote-control-server/src/services/environment.ts +8 -3
- package/packages/remote-control-server/src/services/session.ts +56 -45
- package/packages/remote-control-server/src/store.ts +128 -7
- package/packages/remote-control-server/src/types/api.ts +8 -1
- package/packages/remote-control-server/web/dist/assets/AdminPanel-CQU763nr.js +1 -0
- package/packages/remote-control-server/web/dist/assets/Dashboard-B1hQfqI9.js +1 -0
- package/packages/remote-control-server/web/dist/assets/Join-7hMl360t.js +1 -0
- package/{dist/rcs-web/assets/Login-0VGZs85I.js → packages/remote-control-server/web/dist/assets/Login-BE8mNc0U.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/SessionDetail-B5C0o1YT.js +55 -0
- package/packages/remote-control-server/web/dist/assets/SessionVisibilityBadge-CQN3bJW4.js +11 -0
- package/packages/remote-control-server/web/dist/assets/{Setup-C0cjxXMt.js → Setup-CqZ5gEBQ.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ShareLanding-DUZrreJ7.js → ShareLanding-DBC9gbJo.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/TeamCreate-DSYdTaPe.js +1 -0
- package/packages/remote-control-server/web/dist/assets/TeamDetail-CmJGsgOP.js +1 -0
- package/packages/remote-control-server/web/dist/assets/TeamList-BMS_Qq8U.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{TeamSettings-CNVwYwcp.js → TeamSettings-C0sYgCFT.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/UserSettings-CqzeJ5cc.js +1 -0
- package/{dist/rcs-web/assets/arc-CJxirXEQ.js → packages/remote-control-server/web/dist/assets/arc-DBPKxYzS.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{architectureDiagram-3BPJPVTR-BZvIHrLA.js → architectureDiagram-3BPJPVTR-D88rj_js.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{blockDiagram-GPEHLZMM-3p0y3Ija.js → blockDiagram-GPEHLZMM-xzgUpRMn.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{c4Diagram-AAUBKEIU-Bmz9A1wn.js → c4Diagram-AAUBKEIU-vpg_EoPb.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/channel-Daz3yMSu.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-B8gQz22j.js → chunk-2J33WTMH-DXWsXvhS.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-CgLEZsTU.js → chunk-4BX2VUAB-BtTxwj9K.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-0mdE_-IS.js → chunk-55IACEB6-D2-Q95qg.js} +1 -1
- package/{dist/rcs-web/assets/chunk-727SXJPM-DdtjnIHR.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-Ce3e5x8u.js} +1 -1
- package/{dist/rcs-web/assets/chunk-AQP2D5EJ-BtwFPk50.js → packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-C0UbKVtd.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-BcO9mQ0P.js → chunk-FMBD7UC4-BCwppVqh.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-BvRq13S_.js → chunk-ND2GUHAM-CnOBwWrm.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-D7GHkPpq.js → chunk-QZHKN3VN-CDdzB71P.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-DufdcV1P.js +1 -0
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-DufdcV1P.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{code-block-IT6T5CEO-B1p-vO-3.js → code-block-IT6T5CEO-C1_H_ztH.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/copy-DXU1lQqc.js +6 -0
- package/{dist/rcs-web/assets/cose-bilkent-S5V4N54A-L0rUZuH5.js → packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-BC6nfjTF.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{dagre-BM42HDAG-CHGuio22.js → dagre-BM42HDAG-CEPR_pRx.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-2AECGRRQ-DIDRyQk8.js → diagram-2AECGRRQ-fVMGnSYa.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-5GNKFQAL-CNubKb6P.js → diagram-5GNKFQAL-BPzN2rbK.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-KO2AKTUF-CzQh664-.js → diagram-KO2AKTUF-Boneg9n-.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-LMA3HP47-CWBR7-VG.js → diagram-LMA3HP47-B8AtvjO9.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-OG6HWLK6-BMHdlbcZ.js → diagram-OG6HWLK6-DjY7A8jK.js} +1 -1
- package/{dist/rcs-web/assets/erDiagram-TEJ5UH35-OgT8dC-K.js → packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-C1BEfKxk.js} +1 -1
- package/{dist/rcs-web/assets/flowDiagram-I6XJVG4X-DRrCY2oB.js → packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-7TbI9MLV.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ganttDiagram-6RSMTGT7-DctpzEKN.js → ganttDiagram-6RSMTGT7-CMAV0A01.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{gitGraphDiagram-PVQCEYII-5KpLszm9.js → gitGraphDiagram-PVQCEYII-DAlR8G81.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{infoDiagram-5YYISTIA-BAKD_Tt7.js → infoDiagram-5YYISTIA-0T0DAV4W.js} +1 -1
- package/{dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-VHOPG4Nc.js → packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-BAw8txlx.js} +1 -1
- package/{dist/rcs-web/assets/journeyDiagram-JHISSGLW-BtGih-ik.js → packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-DWvYKwz-.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-BsEGZsD1.js → kanban-definition-UN3LZRKU-Qzqw1H5B.js} +1 -1
- package/{dist/rcs-web/assets/linear-Dj636PNG.js → packages/remote-control-server/web/dist/assets/linear-D2KIJoCI.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/main-J6cPs2y8.js +253 -0
- package/packages/remote-control-server/web/dist/assets/{mermaid-VLURNSYL-DutpHHm9.js → mermaid-VLURNSYL-CxWk1wHz.js} +4 -4
- package/packages/remote-control-server/web/dist/assets/{mermaid.core-XHHf0pen.js → mermaid.core-C48YvKu4.js} +5 -5
- package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-BBa8V_3g.js → mindmap-definition-RKZ34NQL-BfmcTuCA.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{pieDiagram-4H26LBE5-BuNEdo9H.js → pieDiagram-4H26LBE5-DVNZpnfb.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{quadrantDiagram-W4KKPZXB-CsJgyQIN.js → quadrantDiagram-W4KKPZXB-COL55Z3i.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{requirementDiagram-4Y6WPE33-DqMJZ914.js → requirementDiagram-4Y6WPE33-CFczYQ0a.js} +1 -1
- package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-CVsz09q2.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-BrQEv3WQ.js} +1 -1
- package/{dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-BRp4Wddn.js → packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-CMxtYmQu.js} +1 -1
- package/{dist/rcs-web/assets/stateDiagram-AJRCARHV-S-QqQr-D.js → packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-Dw9xJZ-A.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{stateDiagram-v2-BHNVJYJU-D77KfDr-.js → stateDiagram-v2-BHNVJYJU-Br0D-weX.js} +1 -1
- package/{dist/rcs-web/assets/timeline-definition-PNZ67QCA-De8bFWnE.js → packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-DuWDwYBt.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{vennDiagram-CIIHVFJN-Dikwrn1L.js → vennDiagram-CIIHVFJN--7IV65HQ.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-DUzpiV6H.js → wardleyDiagram-YWT4CUSO-CVTgWbvT.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{xychartDiagram-2RQKCTM6-OZNbB32D.js → xychartDiagram-2RQKCTM6-B9om4xAF.js} +1 -1
- package/packages/remote-control-server/web/dist/index.html +1 -1
- package/dist/rcs-web/assets/Dashboard-YYwDrKTN.js +0 -1
- package/dist/rcs-web/assets/Join-DKCP5X6X.js +0 -1
- package/dist/rcs-web/assets/SessionDetail-DmK-DfBQ.js +0 -50
- package/dist/rcs-web/assets/TeamCreate-B8ZV6EdY.js +0 -1
- package/dist/rcs-web/assets/TeamDetail-DBAsJ__i.js +0 -11
- package/dist/rcs-web/assets/TeamList-C-Hte4nV.js +0 -1
- package/dist/rcs-web/assets/UserSettings-DRdz9kYE.js +0 -1
- package/dist/rcs-web/assets/channel-DaI_n8dp.js +0 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-pYllpkOq.js +0 -1
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-pYllpkOq.js +0 -1
- package/dist/rcs-web/assets/main-CkqtCllF.js +0 -238
- package/dist/rcs-web/assets/useTeams-CqJOqEy1.js +0 -1
- package/dist/rcs-web/assets/workerType-DOQ0AHrq.js +0 -1
- package/packages/remote-control-server/web/dist/assets/Dashboard-YYwDrKTN.js +0 -1
- package/packages/remote-control-server/web/dist/assets/Join-DKCP5X6X.js +0 -1
- package/packages/remote-control-server/web/dist/assets/SessionDetail-DmK-DfBQ.js +0 -50
- package/packages/remote-control-server/web/dist/assets/TeamCreate-B8ZV6EdY.js +0 -1
- package/packages/remote-control-server/web/dist/assets/TeamDetail-DBAsJ__i.js +0 -11
- package/packages/remote-control-server/web/dist/assets/TeamList-C-Hte4nV.js +0 -1
- package/packages/remote-control-server/web/dist/assets/UserSettings-DRdz9kYE.js +0 -1
- package/packages/remote-control-server/web/dist/assets/channel-DaI_n8dp.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-pYllpkOq.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-pYllpkOq.js +0 -1
- package/packages/remote-control-server/web/dist/assets/main-CkqtCllF.js +0 -238
- package/packages/remote-control-server/web/dist/assets/useTeams-CqJOqEy1.js +0 -1
- package/packages/remote-control-server/web/dist/assets/workerType-DOQ0AHrq.js +0 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, mock } from 'bun:test'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
import { randomUUID } from 'node:crypto'
|
|
5
|
+
import { mockConfigModule } from './helpers/mock-config'
|
|
6
|
+
|
|
7
|
+
mock.module('../config', () => mockConfigModule({ pollTimeout: 1 }))
|
|
8
|
+
|
|
9
|
+
import { Hono } from 'hono'
|
|
10
|
+
import {
|
|
11
|
+
storeReset,
|
|
12
|
+
storeCreateSession,
|
|
13
|
+
storeBindSession,
|
|
14
|
+
storeListSessionsByOwnerUuid,
|
|
15
|
+
} from '../store'
|
|
16
|
+
import { initDatabase, resetDbSingleton } from '../db/sqlite'
|
|
17
|
+
import { issueToken } from '../auth/token'
|
|
18
|
+
import { SYSTEM_USER_ID } from '../auth/constants'
|
|
19
|
+
import { resolveOwnedWebSessionId } from '../services/session'
|
|
20
|
+
import webSessions from '../routes/web/sessions'
|
|
21
|
+
|
|
22
|
+
function createApp() {
|
|
23
|
+
const app = new Hono()
|
|
24
|
+
app.route('/web', webSessions)
|
|
25
|
+
return app
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function freshDb() {
|
|
29
|
+
try {
|
|
30
|
+
resetDbSingleton()
|
|
31
|
+
} catch {
|
|
32
|
+
// ignore
|
|
33
|
+
}
|
|
34
|
+
const uniquePath = `/tmp/rcs-low-fixes-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`
|
|
35
|
+
return initDatabase(uniquePath)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Auth helper: issues a session token (non-admin) for the given user.
|
|
39
|
+
function userAuth(user: string): string {
|
|
40
|
+
return `Bearer ${issueToken(user).token}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// H1: resolveBindUserId isAdmin check
|
|
45
|
+
// =============================================================================
|
|
46
|
+
describe('H1: resolveBindUserId requires isAdmin for X-User-Id', () => {
|
|
47
|
+
let app: Hono
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
freshDb()
|
|
51
|
+
storeReset()
|
|
52
|
+
app = createApp()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('admin API key + X-User-Id creates session bound to X-User-Id user', async () => {
|
|
56
|
+
const res = await app.request('/web/sessions', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: 'Bearer test-api-key',
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
'X-User-Id': 'admin-chosen-user',
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({ title: 'Admin Bound' }),
|
|
64
|
+
})
|
|
65
|
+
expect(res.status).toBe(200)
|
|
66
|
+
const body = (await res.json()) as Record<string, unknown>
|
|
67
|
+
expect(body.id).toMatch(/^session_/)
|
|
68
|
+
|
|
69
|
+
// Verify the session is bound to the X-User-Id user
|
|
70
|
+
const sessions = storeListSessionsByOwnerUuid('admin-chosen-user')
|
|
71
|
+
expect(sessions.length).toBeGreaterThanOrEqual(1)
|
|
72
|
+
expect(sessions.some(s => s.id === body.id)).toBe(true)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('non-admin user token + X-User-Id ignores X-User-Id, binds to token user', async () => {
|
|
76
|
+
const res = await app.request('/web/sessions', {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: {
|
|
79
|
+
Authorization: userAuth('real-user'),
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
'X-User-Id': 'impersonated-user',
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({ title: 'Impersonation Attempt' }),
|
|
84
|
+
})
|
|
85
|
+
expect(res.status).toBe(200)
|
|
86
|
+
const body = (await res.json()) as Record<string, unknown>
|
|
87
|
+
|
|
88
|
+
// Session should be bound to the real token user, NOT the impersonated user
|
|
89
|
+
const realSessions = storeListSessionsByOwnerUuid('real-user')
|
|
90
|
+
expect(realSessions.some(s => s.id === body.id)).toBe(true)
|
|
91
|
+
|
|
92
|
+
const impersonated = storeListSessionsByOwnerUuid('impersonated-user')
|
|
93
|
+
expect(impersonated.some(s => s.id === body.id)).toBe(false)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('admin API key without X-User-Id creates orphan session (not bound)', async () => {
|
|
97
|
+
const res = await app.request('/web/sessions', {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: 'Bearer test-api-key',
|
|
101
|
+
'Content-Type': 'application/json',
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify({ title: 'Orphan Session' }),
|
|
104
|
+
})
|
|
105
|
+
expect(res.status).toBe(200)
|
|
106
|
+
const body = (await res.json()) as Record<string, unknown>
|
|
107
|
+
expect(body.id).toMatch(/^session_/)
|
|
108
|
+
|
|
109
|
+
// No user should own this session
|
|
110
|
+
const systemSessions = storeListSessionsByOwnerUuid(SYSTEM_USER_ID)
|
|
111
|
+
expect(systemSessions.some(s => s.id === body.id)).toBe(false)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('resolveBindUserId source includes isAdmin check', () => {
|
|
115
|
+
const source = readFileSync(
|
|
116
|
+
resolve(import.meta.dir, '../routes/web/sessions.ts'),
|
|
117
|
+
'utf8',
|
|
118
|
+
)
|
|
119
|
+
// The resolveBindUserId function should check isAdmin when userId is SYSTEM_USER_ID
|
|
120
|
+
const fnMatch = source.match(/function\s+resolveBindUserId[\s\S]*?^}/m)
|
|
121
|
+
expect(fnMatch).not.toBeNull()
|
|
122
|
+
const fnBody = fnMatch![0]
|
|
123
|
+
// Must reference isAdmin
|
|
124
|
+
expect(fnBody).toContain('isAdmin')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// =============================================================================
|
|
129
|
+
// M2: resolveOwnedWebSessionId removes redundant storeIsSessionOwner fallback
|
|
130
|
+
// =============================================================================
|
|
131
|
+
describe('M2: resolveOwnedWebSessionId removes redundant fallback', () => {
|
|
132
|
+
beforeEach(() => {
|
|
133
|
+
freshDb()
|
|
134
|
+
storeReset()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('returns session ID for owner (main logic works)', () => {
|
|
138
|
+
const session = storeCreateSession({})
|
|
139
|
+
storeBindSession(session.id, 'owner-user')
|
|
140
|
+
const result = resolveOwnedWebSessionId(session.id, 'owner-user')
|
|
141
|
+
expect(result).toBe(session.id)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('returns null for non-owner', () => {
|
|
145
|
+
const session = storeCreateSession({})
|
|
146
|
+
storeBindSession(session.id, 'other-user')
|
|
147
|
+
const result = resolveOwnedWebSessionId(session.id, 'not-owner')
|
|
148
|
+
expect(result).toBeNull()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('source does not use storeIsSessionOwner as fallback', () => {
|
|
152
|
+
const source = readFileSync(
|
|
153
|
+
resolve(import.meta.dir, '../services/session.ts'),
|
|
154
|
+
'utf8',
|
|
155
|
+
)
|
|
156
|
+
// The resolveOwnedWebSessionId function should NOT have a catch block
|
|
157
|
+
// that calls storeIsSessionOwner (redundant DB query)
|
|
158
|
+
const fnMatch = source.match(
|
|
159
|
+
/export\s+function\s+resolveOwnedWebSessionId[\s\S]*?^}/m,
|
|
160
|
+
)
|
|
161
|
+
expect(fnMatch).not.toBeNull()
|
|
162
|
+
const fnBody = fnMatch![0]
|
|
163
|
+
// Should not reference storeIsSessionOwner in the catch block
|
|
164
|
+
const catchMatch = fnBody.match(/catch\s*\{[\s\S]*?\}/)
|
|
165
|
+
if (catchMatch) {
|
|
166
|
+
expect(catchMatch[0]).not.toContain('storeIsSessionOwner')
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test('session.ts does not import storeIsSessionOwner', () => {
|
|
171
|
+
const source = readFileSync(
|
|
172
|
+
resolve(import.meta.dir, '../services/session.ts'),
|
|
173
|
+
'utf8',
|
|
174
|
+
)
|
|
175
|
+
// storeIsSessionOwner should be removed from imports
|
|
176
|
+
expect(source).not.toMatch(/import\s*\{[^}]*storeIsSessionOwner[^}]*\}/)
|
|
177
|
+
})
|
|
178
|
+
})
|
|
@@ -29,7 +29,7 @@ function createTestApp() {
|
|
|
29
29
|
|
|
30
30
|
// Test route for apiKeyAuth
|
|
31
31
|
app.get('/api-key-test', apiKeyAuth, c => {
|
|
32
|
-
return c.json({
|
|
32
|
+
return c.json({ userId: c.get('userId') || null })
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
// Test route for sessionIngressAuth
|
|
@@ -62,7 +62,7 @@ describe('Auth Middleware', () => {
|
|
|
62
62
|
})
|
|
63
63
|
expect(res.status).toBe(200)
|
|
64
64
|
const body = await res.json()
|
|
65
|
-
expect(body.
|
|
65
|
+
expect(body.userId).toBe('alice')
|
|
66
66
|
})
|
|
67
67
|
|
|
68
68
|
test('accepts valid API key with username query param', async () => {
|
|
@@ -71,7 +71,7 @@ describe('Auth Middleware', () => {
|
|
|
71
71
|
})
|
|
72
72
|
expect(res.status).toBe(200)
|
|
73
73
|
const body = await res.json()
|
|
74
|
-
expect(body.
|
|
74
|
+
expect(body.userId).toBe('bob')
|
|
75
75
|
})
|
|
76
76
|
|
|
77
77
|
test('accepts valid session token', async () => {
|
|
@@ -82,7 +82,7 @@ describe('Auth Middleware', () => {
|
|
|
82
82
|
})
|
|
83
83
|
expect(res.status).toBe(200)
|
|
84
84
|
const body = await res.json()
|
|
85
|
-
expect(body.
|
|
85
|
+
expect(body.userId).toBe('charlie')
|
|
86
86
|
})
|
|
87
87
|
|
|
88
88
|
test('rejects invalid token', async () => {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, mock } from 'bun:test'
|
|
2
|
+
import { randomUUID } from 'node:crypto'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
2
5
|
import { mockConfigModule } from './helpers/mock-config'
|
|
3
6
|
|
|
4
7
|
// Mock config before any imports
|
|
@@ -6,7 +9,7 @@ mock.module('../config', () => mockConfigModule())
|
|
|
6
9
|
|
|
7
10
|
import { Hono } from 'hono'
|
|
8
11
|
import type { MiddlewareHandler } from 'hono/types'
|
|
9
|
-
import {
|
|
12
|
+
import { initDatabase, resetDbSingleton } from '../db/sqlite'
|
|
10
13
|
|
|
11
14
|
// Dummy serveStatic — returns a middleware.
|
|
12
15
|
// When `path: 'index.html'` is set, serves SPA fallback HTML.
|
|
@@ -16,7 +19,7 @@ const serveStatic: (opts: Record<string, unknown>) => MiddlewareHandler = (
|
|
|
16
19
|
) => {
|
|
17
20
|
const path = opts.path as string | undefined
|
|
18
21
|
if (path === 'index.html') {
|
|
19
|
-
return c => c.html('<!DOCTYPE html><html><body>SPA</body></html>')
|
|
22
|
+
return async c => c.html('<!DOCTYPE html><html><body>SPA</body></html>')
|
|
20
23
|
}
|
|
21
24
|
return async (_c, next) => {
|
|
22
25
|
await next()
|
|
@@ -25,9 +28,11 @@ const serveStatic: (opts: Record<string, unknown>) => MiddlewareHandler = (
|
|
|
25
28
|
|
|
26
29
|
// Dummy upgradeWebSocket
|
|
27
30
|
const upgradeWebSocket = (() => {
|
|
28
|
-
return () =>
|
|
29
|
-
next()
|
|
30
|
-
|
|
31
|
+
return () =>
|
|
32
|
+
async (_c: import('hono').Context, next: import('hono').Next) => {
|
|
33
|
+
await next()
|
|
34
|
+
}
|
|
35
|
+
}) as unknown as import('hono/ws').UpgradeWebSocket<unknown>
|
|
31
36
|
|
|
32
37
|
import { registerRoutes } from '../registerRoutes'
|
|
33
38
|
|
|
@@ -38,9 +43,22 @@ function createApp(): Hono {
|
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
function setupTestDb(): void {
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
|
|
46
|
+
// Initialize to a unique temp file per test file so the singleton never
|
|
47
|
+
// resolves to the production default (tmpdir()/rcs-dev.db). We don't
|
|
48
|
+
// reset between tests within this file because route handlers share the
|
|
49
|
+
// singleton — closing it mid-test would break parallel requests. But
|
|
50
|
+
// each test file gets its own module graph under --isolate, so a
|
|
51
|
+
// file-scoped path is safe.
|
|
52
|
+
try {
|
|
53
|
+
resetDbSingleton()
|
|
54
|
+
} catch {
|
|
55
|
+
// ignore
|
|
56
|
+
}
|
|
57
|
+
const uniquePath = resolve(
|
|
58
|
+
tmpdir(),
|
|
59
|
+
`rcs-routes-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
|
|
60
|
+
)
|
|
61
|
+
initDatabase(uniquePath)
|
|
44
62
|
}
|
|
45
63
|
|
|
46
64
|
describe('Route mounting integration', () => {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, mock } from 'bun:test'
|
|
2
2
|
import { randomUUID, createHash } from 'node:crypto'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
3
5
|
import { mockConfigModule } from './helpers/mock-config'
|
|
4
6
|
|
|
5
7
|
// Mock config before imports
|
|
@@ -10,11 +12,13 @@ import { Hono } from 'hono'
|
|
|
10
12
|
let authRoutes: Hono | undefined
|
|
11
13
|
let webAuth: Hono | undefined
|
|
12
14
|
let resetDbSingleton: (() => void) | undefined
|
|
15
|
+
let initDatabase: ((path?: string) => import('bun:sqlite').Database) | undefined
|
|
13
16
|
let getDb: (() => import('bun:sqlite').Database) | undefined
|
|
14
17
|
let resetRateLimits: (() => void) | undefined
|
|
15
18
|
|
|
16
19
|
try {
|
|
17
20
|
const dbMod = await import('../db/sqlite')
|
|
21
|
+
initDatabase = dbMod.initDatabase
|
|
18
22
|
getDb = dbMod.getDb
|
|
19
23
|
resetDbSingleton = dbMod.resetDbSingleton
|
|
20
24
|
} catch {
|
|
@@ -44,8 +48,15 @@ function freshDb() {
|
|
|
44
48
|
// ignore
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
|
-
if (
|
|
48
|
-
|
|
51
|
+
if (initDatabase) {
|
|
52
|
+
// Use a unique temp file per test to avoid polluting the production
|
|
53
|
+
// default DB path (tmpdir()/rcs-dev.db). Without this, getDb() would
|
|
54
|
+
// resolve to that file and tests would write real users into it.
|
|
55
|
+
const uniquePath = resolve(
|
|
56
|
+
tmpdir(),
|
|
57
|
+
`rcs-sec-fixes-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
|
|
58
|
+
)
|
|
59
|
+
const db = initDatabase(uniquePath)
|
|
49
60
|
try {
|
|
50
61
|
db.exec('DELETE FROM session_tokens')
|
|
51
62
|
db.exec('DELETE FROM users')
|
|
@@ -54,7 +65,7 @@ function freshDb() {
|
|
|
54
65
|
}
|
|
55
66
|
return db
|
|
56
67
|
}
|
|
57
|
-
throw new Error('
|
|
68
|
+
throw new Error('initDatabase not available')
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
function createApp(): Hono {
|
|
@@ -275,17 +286,17 @@ describe('Security: consumed_at semantics for multi-use invitations', () => {
|
|
|
275
286
|
|
|
276
287
|
// Verify 4 users exist: admin + 3 joined
|
|
277
288
|
const db = getDb!()
|
|
278
|
-
const count = db
|
|
279
|
-
|
|
280
|
-
|
|
289
|
+
const count = db.query('SELECT COUNT(*) as count FROM users').get() as {
|
|
290
|
+
count: number
|
|
291
|
+
}
|
|
281
292
|
expect(count.count).toBe(4)
|
|
282
293
|
|
|
283
294
|
// Verify invitation uses count
|
|
284
|
-
const tokenHash = createHash('sha256')
|
|
285
|
-
.update(inviteToken)
|
|
286
|
-
.digest('hex')
|
|
295
|
+
const tokenHash = createHash('sha256').update(inviteToken).digest('hex')
|
|
287
296
|
const inv = db
|
|
288
|
-
.query(
|
|
297
|
+
.query(
|
|
298
|
+
'SELECT uses, max_uses, consumed_at FROM invitations WHERE token_hash = $hash',
|
|
299
|
+
)
|
|
289
300
|
.get({ $hash: tokenHash }) as {
|
|
290
301
|
uses: number
|
|
291
302
|
max_uses: number
|
|
@@ -493,15 +504,12 @@ describe('Security: CSRF middleware', () => {
|
|
|
493
504
|
app.use('/web/*', csrfCheck)
|
|
494
505
|
app.delete('/web/resource/:id', c => c.json({ ok: true }))
|
|
495
506
|
|
|
496
|
-
const res = await app.request(
|
|
497
|
-
|
|
498
|
-
{
|
|
499
|
-
|
|
500
|
-
headers: {
|
|
501
|
-
Origin: 'http://localhost:3000',
|
|
502
|
-
},
|
|
507
|
+
const res = await app.request('http://localhost:3000/web/resource/abc', {
|
|
508
|
+
method: 'DELETE',
|
|
509
|
+
headers: {
|
|
510
|
+
Origin: 'http://localhost:3000',
|
|
503
511
|
},
|
|
504
|
-
)
|
|
512
|
+
})
|
|
505
513
|
expect(res.status).toBe(200)
|
|
506
514
|
})
|
|
507
515
|
|
|
@@ -511,16 +519,13 @@ describe('Security: CSRF middleware', () => {
|
|
|
511
519
|
app.use('/web/*', csrfCheck)
|
|
512
520
|
app.put('/web/resource/:id', c => c.json({ ok: true }))
|
|
513
521
|
|
|
514
|
-
const res = await app.request(
|
|
515
|
-
|
|
516
|
-
{
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
Origin: 'https://evil.example',
|
|
520
|
-
'Content-Type': 'application/json',
|
|
521
|
-
},
|
|
522
|
+
const res = await app.request('http://localhost:3000/web/resource/abc', {
|
|
523
|
+
method: 'PUT',
|
|
524
|
+
headers: {
|
|
525
|
+
Origin: 'https://evil.example',
|
|
526
|
+
'Content-Type': 'application/json',
|
|
522
527
|
},
|
|
523
|
-
)
|
|
528
|
+
})
|
|
524
529
|
expect(res.status).toBe(403)
|
|
525
530
|
})
|
|
526
531
|
})
|
|
@@ -58,7 +58,11 @@ let issueSessionToken:
|
|
|
58
58
|
) => { accessToken: string; refreshToken: string })
|
|
59
59
|
| undefined
|
|
60
60
|
let resolveSessionToken:
|
|
61
|
-
| ((
|
|
61
|
+
| ((
|
|
62
|
+
token: string,
|
|
63
|
+
db: Database,
|
|
64
|
+
kind?: 'access' | 'refresh',
|
|
65
|
+
) => string | null)
|
|
62
66
|
| undefined
|
|
63
67
|
|
|
64
68
|
try {
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for team-filter API fields:
|
|
3
|
+
* - GET /web/environments returns team_id + team_name
|
|
4
|
+
* - GET /web/sessions returns owner_type + owner_id
|
|
5
|
+
* - GET /web/sessions/all returns owner_type + owner_id
|
|
6
|
+
*
|
|
7
|
+
* TDD: written before implementation.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, test, expect, beforeEach, mock } from 'bun:test'
|
|
10
|
+
import { randomUUID } from 'node:crypto'
|
|
11
|
+
import { tmpdir } from 'node:os'
|
|
12
|
+
import { resolve } from 'node:path'
|
|
13
|
+
import { mockConfigModule } from './helpers/mock-config'
|
|
14
|
+
|
|
15
|
+
// Mock config before importing modules
|
|
16
|
+
mock.module('../config', () => mockConfigModule())
|
|
17
|
+
|
|
18
|
+
import { storeReset, storeCreateEnvironment, storeBindSession } from '../store'
|
|
19
|
+
import { initDatabase, resetDbSingleton, getDb } from '../db/sqlite'
|
|
20
|
+
import { listActiveEnvironmentsResponse } from '../services/environment'
|
|
21
|
+
import {
|
|
22
|
+
createSession,
|
|
23
|
+
listWebSessionsByOwnerUuid,
|
|
24
|
+
listWebSessionSummariesByOwnerUuid,
|
|
25
|
+
} from '../services/session'
|
|
26
|
+
|
|
27
|
+
describe('Team filter API fields', () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
// Reset and re-init to a unique temp file so the singleton never
|
|
30
|
+
// resolves to the production default (tmpdir()/rcs-dev.db). Without
|
|
31
|
+
// this, getDb() below would write test data into that file and
|
|
32
|
+
// poison real server startups.
|
|
33
|
+
try {
|
|
34
|
+
resetDbSingleton()
|
|
35
|
+
} catch {
|
|
36
|
+
// ignore
|
|
37
|
+
}
|
|
38
|
+
const uniquePath = resolve(
|
|
39
|
+
tmpdir(),
|
|
40
|
+
`rcs-team-filter-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
|
|
41
|
+
)
|
|
42
|
+
initDatabase(uniquePath)
|
|
43
|
+
storeReset()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('EnvironmentResponse team fields', () => {
|
|
47
|
+
test('returns team_id=null and team_name=null when env has no team', () => {
|
|
48
|
+
storeCreateEnvironment({ secret: 's', machineName: 'solo-env' })
|
|
49
|
+
const envs = listActiveEnvironmentsResponse()
|
|
50
|
+
expect(envs.length).toBe(1)
|
|
51
|
+
expect(envs[0]!.team_id).toBeNull()
|
|
52
|
+
expect(envs[0]!.team_name).toBeNull()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('returns team_id and team_name when env belongs to a team', () => {
|
|
56
|
+
const env = storeCreateEnvironment({
|
|
57
|
+
secret: 's',
|
|
58
|
+
machineName: 'team-env',
|
|
59
|
+
})
|
|
60
|
+
const db = getDb()
|
|
61
|
+
|
|
62
|
+
// Create a user (FK constraint for teams.created_by)
|
|
63
|
+
db.query(
|
|
64
|
+
`INSERT INTO users (id, username, password_hash, role, created_at)
|
|
65
|
+
VALUES ($id, $u, $ph, 'admin', $ca)`,
|
|
66
|
+
).run({
|
|
67
|
+
$id: 'user_admin',
|
|
68
|
+
$u: 'admin',
|
|
69
|
+
$ph: 'hash',
|
|
70
|
+
$ca: new Date().toISOString(),
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Create a team
|
|
74
|
+
const teamId = 'team_001'
|
|
75
|
+
db.query(
|
|
76
|
+
`INSERT INTO teams (id, name, slug, created_by, created_at, updated_at)
|
|
77
|
+
VALUES ($id, $name, $slug, $cb, $ca, $ua)`,
|
|
78
|
+
).run({
|
|
79
|
+
$id: teamId,
|
|
80
|
+
$name: 'Engineering',
|
|
81
|
+
$slug: 'engineering',
|
|
82
|
+
$cb: 'user_admin',
|
|
83
|
+
$ca: new Date().toISOString(),
|
|
84
|
+
$ua: new Date().toISOString(),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Associate env with team
|
|
88
|
+
db.query(
|
|
89
|
+
`INSERT INTO team_environments (team_id, environment_id)
|
|
90
|
+
VALUES ($tid, $eid)`,
|
|
91
|
+
).run({ $tid: teamId, $eid: env.id })
|
|
92
|
+
|
|
93
|
+
const envs = listActiveEnvironmentsResponse()
|
|
94
|
+
expect(envs.length).toBe(1)
|
|
95
|
+
expect(envs[0]!.team_id).toBe(teamId)
|
|
96
|
+
expect(envs[0]!.team_name).toBe('Engineering')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('returns team_id=null when team is soft-deleted', () => {
|
|
100
|
+
const env = storeCreateEnvironment({
|
|
101
|
+
secret: 's',
|
|
102
|
+
machineName: 'orphan-env',
|
|
103
|
+
})
|
|
104
|
+
const db = getDb()
|
|
105
|
+
|
|
106
|
+
db.query(
|
|
107
|
+
`INSERT INTO users (id, username, password_hash, role, created_at)
|
|
108
|
+
VALUES ($id, $u, $ph, 'admin', $ca)`,
|
|
109
|
+
).run({
|
|
110
|
+
$id: 'user_admin',
|
|
111
|
+
$u: 'admin',
|
|
112
|
+
$ph: 'hash',
|
|
113
|
+
$ca: new Date().toISOString(),
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const teamId = 'team_deleted'
|
|
117
|
+
db.query(
|
|
118
|
+
`INSERT INTO teams (id, name, slug, created_by, created_at, updated_at, deleted_at)
|
|
119
|
+
VALUES ($id, $name, $slug, $cb, $ca, $ua, $da)`,
|
|
120
|
+
).run({
|
|
121
|
+
$id: teamId,
|
|
122
|
+
$name: 'DeletedTeam',
|
|
123
|
+
$slug: 'deleted-team',
|
|
124
|
+
$cb: 'user_admin',
|
|
125
|
+
$ca: new Date().toISOString(),
|
|
126
|
+
$ua: new Date().toISOString(),
|
|
127
|
+
$da: new Date().toISOString(),
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
db.query(
|
|
131
|
+
`INSERT INTO team_environments (team_id, environment_id)
|
|
132
|
+
VALUES ($tid, $eid)`,
|
|
133
|
+
).run({ $tid: teamId, $eid: env.id })
|
|
134
|
+
|
|
135
|
+
const envs = listActiveEnvironmentsResponse()
|
|
136
|
+
expect(envs.length).toBe(1)
|
|
137
|
+
// Soft-deleted team should not show up
|
|
138
|
+
expect(envs[0]!.team_id).toBeNull()
|
|
139
|
+
expect(envs[0]!.team_name).toBeNull()
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe('SessionResponse owner fields', () => {
|
|
144
|
+
test('returns owner_type and owner_id for user-owned session', () => {
|
|
145
|
+
const userId = 'user_alice'
|
|
146
|
+
const session = createSession({ title: 'Alice session' })
|
|
147
|
+
storeBindSession(session.id, userId, 'user')
|
|
148
|
+
|
|
149
|
+
const sessions = listWebSessionsByOwnerUuid(userId)
|
|
150
|
+
expect(sessions.length).toBeGreaterThanOrEqual(1)
|
|
151
|
+
const found = sessions.find(s => s.id === session.id)
|
|
152
|
+
expect(found).toBeDefined()
|
|
153
|
+
expect(found!.owner_type).toBe('user')
|
|
154
|
+
expect(found!.owner_id).toBe(userId)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test('returns owner_type=team for team-owned session', () => {
|
|
158
|
+
const db = getDb()
|
|
159
|
+
const userId = 'user_bob'
|
|
160
|
+
const teamId = 'team_bob'
|
|
161
|
+
|
|
162
|
+
// Create user + team
|
|
163
|
+
db.query(
|
|
164
|
+
`INSERT INTO users (id, username, password_hash, role, created_at)
|
|
165
|
+
VALUES ($id, $u, $ph, 'member', $ca)`,
|
|
166
|
+
).run({
|
|
167
|
+
$id: userId,
|
|
168
|
+
$u: 'bob',
|
|
169
|
+
$ph: 'hash',
|
|
170
|
+
$ca: new Date().toISOString(),
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
db.query(
|
|
174
|
+
`INSERT INTO teams (id, name, slug, created_by, created_at, updated_at)
|
|
175
|
+
VALUES ($id, $name, $slug, $cb, $ca, $ua)`,
|
|
176
|
+
).run({
|
|
177
|
+
$id: teamId,
|
|
178
|
+
$name: 'BobTeam',
|
|
179
|
+
$slug: 'bobteam',
|
|
180
|
+
$cb: userId,
|
|
181
|
+
$ca: new Date().toISOString(),
|
|
182
|
+
$ua: new Date().toISOString(),
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Add bob as team member so storeListSessionsByOwnerUuid includes team sessions
|
|
186
|
+
db.query(
|
|
187
|
+
`INSERT INTO team_members (team_id, user_id, role, added_at, added_by)
|
|
188
|
+
VALUES ($tid, $uid, 'owner', $aa, $ab)`,
|
|
189
|
+
).run({
|
|
190
|
+
$tid: teamId,
|
|
191
|
+
$uid: userId,
|
|
192
|
+
$aa: new Date().toISOString(),
|
|
193
|
+
$ab: userId,
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const session = createSession({ title: 'Team session' })
|
|
197
|
+
storeBindSession(session.id, teamId, 'team')
|
|
198
|
+
|
|
199
|
+
const sessions = listWebSessionsByOwnerUuid(userId)
|
|
200
|
+
const found = sessions.find(s => s.id === session.id)
|
|
201
|
+
expect(found).toBeDefined()
|
|
202
|
+
expect(found!.owner_type).toBe('team')
|
|
203
|
+
expect(found!.owner_id).toBe(teamId)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('returns null owner when session has no owner binding', () => {
|
|
207
|
+
const userId = 'user_charlie'
|
|
208
|
+
// Create a session bound to charlie via session_owners
|
|
209
|
+
const session = createSession({ title: 'Charlie session' })
|
|
210
|
+
storeBindSession(session.id, userId, 'user')
|
|
211
|
+
|
|
212
|
+
const sessions = listWebSessionsByOwnerUuid(userId)
|
|
213
|
+
const found = sessions.find(s => s.id === session.id)
|
|
214
|
+
expect(found).toBeDefined()
|
|
215
|
+
expect(found!.owner_type).toBe('user')
|
|
216
|
+
expect(found!.owner_id).toBe(userId)
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
describe('SessionSummaryResponse owner fields', () => {
|
|
221
|
+
test('returns owner_type and owner_id in /sessions/all summary', () => {
|
|
222
|
+
const userId = 'user_dave'
|
|
223
|
+
const session = createSession({ title: 'Dave session' })
|
|
224
|
+
storeBindSession(session.id, userId, 'user')
|
|
225
|
+
|
|
226
|
+
const summaries = listWebSessionSummariesByOwnerUuid(userId)
|
|
227
|
+
const found = summaries.find(s => s.id === session.id)
|
|
228
|
+
expect(found).toBeDefined()
|
|
229
|
+
expect(found!.owner_type).toBe('user')
|
|
230
|
+
expect(found!.owner_id).toBe(userId)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
test('returns team owner in summary for team-owned session', () => {
|
|
234
|
+
const db = getDb()
|
|
235
|
+
const userId = 'user_eve'
|
|
236
|
+
const teamId = 'team_eve'
|
|
237
|
+
|
|
238
|
+
db.query(
|
|
239
|
+
`INSERT INTO users (id, username, password_hash, role, created_at)
|
|
240
|
+
VALUES ($id, $u, $ph, 'member', $ca)`,
|
|
241
|
+
).run({
|
|
242
|
+
$id: userId,
|
|
243
|
+
$u: 'eve',
|
|
244
|
+
$ph: 'hash',
|
|
245
|
+
$ca: new Date().toISOString(),
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
db.query(
|
|
249
|
+
`INSERT INTO teams (id, name, slug, created_by, created_at, updated_at)
|
|
250
|
+
VALUES ($id, $name, $slug, $cb, $ca, $ua)`,
|
|
251
|
+
).run({
|
|
252
|
+
$id: teamId,
|
|
253
|
+
$name: 'EveTeam',
|
|
254
|
+
$slug: 'eveteam',
|
|
255
|
+
$cb: userId,
|
|
256
|
+
$ca: new Date().toISOString(),
|
|
257
|
+
$ua: new Date().toISOString(),
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
db.query(
|
|
261
|
+
`INSERT INTO team_members (team_id, user_id, role, added_at, added_by)
|
|
262
|
+
VALUES ($tid, $uid, 'owner', $aa, $ab)`,
|
|
263
|
+
).run({
|
|
264
|
+
$tid: teamId,
|
|
265
|
+
$uid: userId,
|
|
266
|
+
$aa: new Date().toISOString(),
|
|
267
|
+
$ab: userId,
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
const session = createSession({ title: 'Eve team session' })
|
|
271
|
+
storeBindSession(session.id, teamId, 'team')
|
|
272
|
+
|
|
273
|
+
const summaries = listWebSessionSummariesByOwnerUuid(userId)
|
|
274
|
+
const found = summaries.find(s => s.id === session.id)
|
|
275
|
+
expect(found).toBeDefined()
|
|
276
|
+
expect(found!.owner_type).toBe('team')
|
|
277
|
+
expect(found!.owner_id).toBe(teamId)
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
})
|