@saluzi/saluzi-edu 0.2.9 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +336 -333
- package/dist/rcs-web/assets/Dashboard-BwotBWlJ.js +1 -0
- package/dist/rcs-web/assets/Join-4yrksbdX.js +1 -0
- package/dist/rcs-web/assets/Login-Cxm_yEeE.js +1 -0
- package/dist/rcs-web/assets/SessionDetail-DNdfPOYy.js +50 -0
- package/dist/rcs-web/assets/Setup-BiAAxugt.js +1 -0
- package/dist/rcs-web/assets/ShareLanding-CP3Z6ckr.js +1 -0
- package/dist/rcs-web/assets/TeamCreate-DgdCwRSb.js +1 -0
- package/dist/rcs-web/assets/TeamDetail-CkNTFoMB.js +11 -0
- package/dist/rcs-web/assets/TeamList-Bf0sQ5RQ.js +1 -0
- package/dist/rcs-web/assets/TeamSettings-BaNAfXU3.js +1 -0
- package/dist/rcs-web/assets/UserSettings-CsutQjxG.js +1 -0
- package/{packages/remote-control-server/web/dist/assets/arc-D90597o-.js → dist/rcs-web/assets/arc-BxxmNQO6.js} +1 -1
- package/dist/rcs-web/assets/architectureDiagram-3BPJPVTR-DBjkRdbd.js +36 -0
- package/dist/rcs-web/assets/{blockDiagram-GPEHLZMM-DHd8IbOs.js → blockDiagram-GPEHLZMM-BF6COWlM.js} +2 -2
- package/dist/rcs-web/assets/{c4Diagram-AAUBKEIU-CjFkQJ9h.js → c4Diagram-AAUBKEIU-DHOf390v.js} +2 -2
- package/dist/rcs-web/assets/channel-JsvbMQjW.js +1 -0
- package/dist/rcs-web/assets/{chunk-2J33WTMH-C21XWnGZ.js → chunk-2J33WTMH-CeJN8yjM.js} +1 -1
- package/dist/rcs-web/assets/{chunk-4BX2VUAB-B3SXiJlt.js → chunk-4BX2VUAB-jP76_P2A.js} +1 -1
- package/dist/rcs-web/assets/{chunk-55IACEB6-DjImv45F.js → chunk-55IACEB6-BsFA6emB.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-DtnaqsyD.js → dist/rcs-web/assets/chunk-727SXJPM-BF4HrQjZ.js} +1 -1
- package/dist/rcs-web/assets/{chunk-AQP2D5EJ-BVFgH5mS.js → chunk-AQP2D5EJ-DToJ74Kc.js} +1 -1
- package/dist/rcs-web/assets/{chunk-FMBD7UC4-Bl8jgb57.js → chunk-FMBD7UC4-DVTARgsa.js} +1 -1
- package/dist/rcs-web/assets/{chunk-ND2GUHAM-BpK111zh.js → chunk-ND2GUHAM-C50Gqv9c.js} +1 -1
- package/dist/rcs-web/assets/{chunk-QZHKN3VN-ruhIHBim.js → chunk-QZHKN3VN-EsIIh6zV.js} +1 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-BXn8Uc4c.js +1 -0
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-BXn8Uc4c.js +1 -0
- package/dist/rcs-web/assets/code-block-IT6T5CEO-BnufHNTT.js +2 -0
- package/dist/rcs-web/assets/cose-bilkent-S5V4N54A-M6tNt14I.js +1 -0
- package/dist/rcs-web/assets/dagre-BM42HDAG-DaTm29qG.js +4 -0
- package/dist/rcs-web/assets/diagram-2AECGRRQ-fqoeOvq2.js +43 -0
- package/dist/rcs-web/assets/diagram-5GNKFQAL-6s2CiaGG.js +10 -0
- package/dist/rcs-web/assets/{diagram-KO2AKTUF-Uy3PDP5S.js → diagram-KO2AKTUF-C0aw_N95.js} +2 -2
- package/dist/rcs-web/assets/diagram-LMA3HP47-C8_Rv3rB.js +24 -0
- package/dist/rcs-web/assets/diagram-OG6HWLK6-Dae4u8Uc.js +24 -0
- package/{packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-sre_MTeK.js → dist/rcs-web/assets/erDiagram-TEJ5UH35-CosS5VWr.js} +13 -13
- package/dist/rcs-web/assets/{flowDiagram-I6XJVG4X-DwrWaLoe.js → flowDiagram-I6XJVG4X-D_Mxs46-.js} +2 -2
- package/dist/rcs-web/assets/ganttDiagram-6RSMTGT7-B0jy8Am_.js +292 -0
- package/dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-CSdu2HVI.js +106 -0
- package/dist/rcs-web/assets/i18n-Dbt-vEQ3.js +1 -0
- package/dist/rcs-web/assets/infoDiagram-5YYISTIA-Dcbc9L1S.js +2 -0
- package/{packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-SO0nn3--.js → dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-BLMjFE4t.js} +2 -2
- package/dist/rcs-web/assets/{journeyDiagram-JHISSGLW-D4igFUd4.js → journeyDiagram-JHISSGLW-Dw4nh1xI.js} +2 -2
- package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-Cm_yKsZC.js → kanban-definition-UN3LZRKU-DkU3j_ly.js} +2 -2
- package/dist/rcs-web/assets/{linear-4IMGll_E.js → linear-N5Cify7M.js} +1 -1
- package/dist/rcs-web/assets/main-30YqUvTd.js +238 -0
- package/dist/rcs-web/assets/main-EP6Lac2m.css +1 -0
- package/{packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-D_yQ7nAd.js → dist/rcs-web/assets/mermaid-VLURNSYL-gfiJUpl_.js} +5 -5
- package/dist/rcs-web/assets/{mermaid.core-B1qOUdiU.js → mermaid.core-B_Xc65Bf.js} +5 -5
- package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-yETom5qt.js → mindmap-definition-RKZ34NQL-DUavHfNu.js} +2 -2
- package/{packages/remote-control-server/web/dist/assets/pieDiagram-4H26LBE5-KIhqXGT6.js → dist/rcs-web/assets/pieDiagram-4H26LBE5-DSJaToTU.js} +2 -2
- package/{packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-DISqmoA6.js → dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-BFp6TnHj.js} +2 -2
- package/dist/rcs-web/assets/{requirementDiagram-4Y6WPE33-DsgT4ptk.js → requirementDiagram-4Y6WPE33-eil6F3-O.js} +2 -2
- package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-Dn67kbn0.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-DbGhoepK.js} +2 -2
- package/{packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-CtWaXZHl.js → dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-B0YrN1ud.js} +14 -14
- package/dist/rcs-web/assets/stateDiagram-AJRCARHV-CofFPj3N.js +1 -0
- package/dist/rcs-web/assets/stateDiagram-v2-BHNVJYJU-DPlcvPn1.js +1 -0
- package/{packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-Dk5WUenC.js → dist/rcs-web/assets/timeline-definition-PNZ67QCA-Bx4dusy8.js} +2 -2
- package/dist/rcs-web/assets/useTeams-MDI4djXg.js +1 -0
- package/{packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-TY8EiRrC.js → dist/rcs-web/assets/vennDiagram-CIIHVFJN-B_-3l-MF.js} +2 -2
- package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-BK3oi47X.js → wardleyDiagram-YWT4CUSO-DNSn-pGp.js} +2 -2
- package/dist/rcs-web/assets/workerType-OM09y7Lk.js +1 -0
- package/{packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-DhsxqX67.js → dist/rcs-web/assets/xychartDiagram-2RQKCTM6-TqUbNp0j.js} +2 -2
- package/dist/rcs-web/index.html +2 -3
- package/package.json +2 -1
- package/packages/remote-control-server/src/__tests__/acp-ws-handler.test.ts +7 -19
- package/packages/remote-control-server/src/__tests__/auth-login.test.ts +561 -0
- package/packages/remote-control-server/src/__tests__/auth-security.test.ts +547 -0
- package/packages/remote-control-server/src/__tests__/auth.test.ts +7 -18
- package/packages/remote-control-server/src/__tests__/bind-replacement.test.ts +251 -0
- package/packages/remote-control-server/src/__tests__/cookie.test.ts +155 -0
- package/packages/remote-control-server/src/__tests__/data-layer-fixes.test.ts +147 -0
- package/packages/remote-control-server/src/__tests__/db.test.ts +582 -0
- package/packages/remote-control-server/src/__tests__/design-gaps.test.ts +440 -0
- package/packages/remote-control-server/src/__tests__/disconnect-monitor.test.ts +2 -18
- package/packages/remote-control-server/src/__tests__/helpers/db.ts +43 -0
- package/packages/remote-control-server/src/__tests__/helpers/fixtures.ts +94 -0
- package/packages/remote-control-server/src/__tests__/helpers/mock-config.ts +31 -0
- package/packages/remote-control-server/src/__tests__/middleware.test.ts +4 -112
- package/packages/remote-control-server/src/__tests__/password.test.ts +76 -0
- package/packages/remote-control-server/src/__tests__/phase4-cleanup.test.ts +290 -0
- package/packages/remote-control-server/src/__tests__/phase4-integration.test.ts +224 -0
- package/packages/remote-control-server/src/__tests__/routes.test.ts +292 -207
- package/packages/remote-control-server/src/__tests__/services.test.ts +2 -18
- package/packages/remote-control-server/src/__tests__/session-visibility.test.ts +452 -0
- package/packages/remote-control-server/src/__tests__/session.test.ts +216 -0
- package/packages/remote-control-server/src/__tests__/shares.test.ts +1419 -0
- package/packages/remote-control-server/src/__tests__/sse-writer.test.ts +2 -18
- package/packages/remote-control-server/src/__tests__/teams.test.ts +2089 -0
- package/packages/remote-control-server/src/__tests__/work-dispatch.test.ts +2 -18
- package/packages/remote-control-server/src/__tests__/worker-token-removal.test.ts +487 -0
- package/packages/remote-control-server/src/__tests__/ws-handler.test.ts +2 -18
- package/packages/remote-control-server/src/auth/constants.ts +13 -0
- package/packages/remote-control-server/src/auth/cookie.ts +75 -0
- package/packages/remote-control-server/src/auth/middleware.ts +75 -165
- package/packages/remote-control-server/src/auth/password.ts +116 -0
- package/packages/remote-control-server/src/auth/session.ts +130 -0
- package/packages/remote-control-server/src/db/schema.sql +116 -0
- package/packages/remote-control-server/src/db/sqlite.ts +99 -0
- package/packages/remote-control-server/src/routes/acp/index.ts +6 -11
- package/packages/remote-control-server/src/routes/v1/environments.ts +111 -8
- package/packages/remote-control-server/src/routes/web/auth-routes.ts +634 -0
- package/packages/remote-control-server/src/routes/web/control.ts +5 -5
- package/packages/remote-control-server/src/routes/web/environments.ts +5 -27
- package/packages/remote-control-server/src/routes/web/sessions.ts +19 -41
- package/packages/remote-control-server/src/routes/web/shares.ts +371 -0
- package/packages/remote-control-server/src/routes/web/teams.ts +850 -0
- package/packages/remote-control-server/src/services/environment.ts +1 -4
- package/packages/remote-control-server/src/services/session-visibility.ts +79 -0
- package/packages/remote-control-server/src/services/session.ts +0 -13
- package/packages/remote-control-server/src/store.ts +150 -56
- package/packages/remote-control-server/src/types/api.ts +2 -8
- package/packages/remote-control-server/web/dist/assets/Dashboard-BwotBWlJ.js +1 -0
- package/packages/remote-control-server/web/dist/assets/Join-4yrksbdX.js +1 -0
- package/packages/remote-control-server/web/dist/assets/Login-Cxm_yEeE.js +1 -0
- package/packages/remote-control-server/web/dist/assets/SessionDetail-DNdfPOYy.js +50 -0
- package/packages/remote-control-server/web/dist/assets/Setup-BiAAxugt.js +1 -0
- package/packages/remote-control-server/web/dist/assets/ShareLanding-CP3Z6ckr.js +1 -0
- package/packages/remote-control-server/web/dist/assets/TeamCreate-DgdCwRSb.js +1 -0
- package/packages/remote-control-server/web/dist/assets/TeamDetail-CkNTFoMB.js +11 -0
- package/packages/remote-control-server/web/dist/assets/TeamList-Bf0sQ5RQ.js +1 -0
- package/packages/remote-control-server/web/dist/assets/TeamSettings-BaNAfXU3.js +1 -0
- package/packages/remote-control-server/web/dist/assets/UserSettings-CsutQjxG.js +1 -0
- package/{dist/rcs-web/assets/arc-D90597o-.js → packages/remote-control-server/web/dist/assets/arc-BxxmNQO6.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/architectureDiagram-3BPJPVTR-DBjkRdbd.js +36 -0
- package/packages/remote-control-server/web/dist/assets/{blockDiagram-GPEHLZMM-DHd8IbOs.js → blockDiagram-GPEHLZMM-BF6COWlM.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{c4Diagram-AAUBKEIU-CjFkQJ9h.js → c4Diagram-AAUBKEIU-DHOf390v.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/channel-JsvbMQjW.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-C21XWnGZ.js → chunk-2J33WTMH-CeJN8yjM.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-B3SXiJlt.js → chunk-4BX2VUAB-jP76_P2A.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-DjImv45F.js → chunk-55IACEB6-BsFA6emB.js} +1 -1
- package/{dist/rcs-web/assets/chunk-727SXJPM-DtnaqsyD.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-BF4HrQjZ.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-AQP2D5EJ-BVFgH5mS.js → chunk-AQP2D5EJ-DToJ74Kc.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-Bl8jgb57.js → chunk-FMBD7UC4-DVTARgsa.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-BpK111zh.js → chunk-ND2GUHAM-C50Gqv9c.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-ruhIHBim.js → chunk-QZHKN3VN-EsIIh6zV.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-BXn8Uc4c.js +1 -0
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-BXn8Uc4c.js +1 -0
- package/packages/remote-control-server/web/dist/assets/code-block-IT6T5CEO-BnufHNTT.js +2 -0
- package/packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-M6tNt14I.js +1 -0
- package/packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-DaTm29qG.js +4 -0
- package/packages/remote-control-server/web/dist/assets/diagram-2AECGRRQ-fqoeOvq2.js +43 -0
- package/packages/remote-control-server/web/dist/assets/diagram-5GNKFQAL-6s2CiaGG.js +10 -0
- package/packages/remote-control-server/web/dist/assets/{diagram-KO2AKTUF-Uy3PDP5S.js → diagram-KO2AKTUF-C0aw_N95.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/diagram-LMA3HP47-C8_Rv3rB.js +24 -0
- package/packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-Dae4u8Uc.js +24 -0
- package/{dist/rcs-web/assets/erDiagram-TEJ5UH35-sre_MTeK.js → packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-CosS5VWr.js} +13 -13
- package/packages/remote-control-server/web/dist/assets/{flowDiagram-I6XJVG4X-DwrWaLoe.js → flowDiagram-I6XJVG4X-D_Mxs46-.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-B0jy8Am_.js +292 -0
- package/packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-CSdu2HVI.js +106 -0
- package/packages/remote-control-server/web/dist/assets/i18n-Dbt-vEQ3.js +1 -0
- package/packages/remote-control-server/web/dist/assets/infoDiagram-5YYISTIA-Dcbc9L1S.js +2 -0
- package/{dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-SO0nn3--.js → packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-BLMjFE4t.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{journeyDiagram-JHISSGLW-D4igFUd4.js → journeyDiagram-JHISSGLW-Dw4nh1xI.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-Cm_yKsZC.js → kanban-definition-UN3LZRKU-DkU3j_ly.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{linear-4IMGll_E.js → linear-N5Cify7M.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/main-30YqUvTd.js +238 -0
- package/packages/remote-control-server/web/dist/assets/main-EP6Lac2m.css +1 -0
- package/{dist/rcs-web/assets/mermaid-VLURNSYL-D_yQ7nAd.js → packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-gfiJUpl_.js} +5 -5
- package/packages/remote-control-server/web/dist/assets/{mermaid.core-B1qOUdiU.js → mermaid.core-B_Xc65Bf.js} +5 -5
- package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-yETom5qt.js → mindmap-definition-RKZ34NQL-DUavHfNu.js} +2 -2
- package/{dist/rcs-web/assets/pieDiagram-4H26LBE5-KIhqXGT6.js → packages/remote-control-server/web/dist/assets/pieDiagram-4H26LBE5-DSJaToTU.js} +2 -2
- package/{dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-DISqmoA6.js → packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-BFp6TnHj.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{requirementDiagram-4Y6WPE33-DsgT4ptk.js → requirementDiagram-4Y6WPE33-eil6F3-O.js} +2 -2
- package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-Dn67kbn0.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-DbGhoepK.js} +2 -2
- package/{dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-CtWaXZHl.js → packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-B0YrN1ud.js} +14 -14
- package/packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-CofFPj3N.js +1 -0
- package/packages/remote-control-server/web/dist/assets/stateDiagram-v2-BHNVJYJU-DPlcvPn1.js +1 -0
- package/{dist/rcs-web/assets/timeline-definition-PNZ67QCA-Dk5WUenC.js → packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-Bx4dusy8.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/useTeams-MDI4djXg.js +1 -0
- package/{dist/rcs-web/assets/vennDiagram-CIIHVFJN-TY8EiRrC.js → packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-B_-3l-MF.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-BK3oi47X.js → wardleyDiagram-YWT4CUSO-DNSn-pGp.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/workerType-OM09y7Lk.js +1 -0
- package/{dist/rcs-web/assets/xychartDiagram-2RQKCTM6-DhsxqX67.js → packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-TqUbNp0j.js} +2 -2
- package/packages/remote-control-server/web/dist/index.html +2 -3
- package/dist/rcs-web/assets/Dashboard-Cyj-Bb86.js +0 -2
- package/dist/rcs-web/assets/SessionDetail-SA9QBkyj.js +0 -51
- package/dist/rcs-web/assets/architectureDiagram-3BPJPVTR-CG9q9_bk.js +0 -36
- package/dist/rcs-web/assets/channel-CQFGglg-.js +0 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-C5rBdShD.js +0 -1
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-C5rBdShD.js +0 -1
- package/dist/rcs-web/assets/code-block-IT6T5CEO-BLGFrBw3.js +0 -2
- package/dist/rcs-web/assets/cose-bilkent-S5V4N54A-DBKEXjqM.js +0 -1
- package/dist/rcs-web/assets/dagre-BM42HDAG-DB_SWlAe.js +0 -4
- package/dist/rcs-web/assets/diagram-2AECGRRQ-Gbm8G3Xz.js +0 -43
- package/dist/rcs-web/assets/diagram-5GNKFQAL-BBuWqfDc.js +0 -10
- package/dist/rcs-web/assets/diagram-LMA3HP47-DyUwNrlg.js +0 -24
- package/dist/rcs-web/assets/diagram-OG6HWLK6-BXpvRwkJ.js +0 -24
- package/dist/rcs-web/assets/ganttDiagram-6RSMTGT7-ByDYviDl.js +0 -292
- package/dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-BUnOPw74.js +0 -106
- package/dist/rcs-web/assets/infoDiagram-5YYISTIA-B1RpwJHG.js +0 -2
- package/dist/rcs-web/assets/main-0A4a3FDU.css +0 -1
- package/dist/rcs-web/assets/main-Dom16YJk.js +0 -283
- package/dist/rcs-web/assets/qr-C0jP9HcD.js +0 -8
- package/dist/rcs-web/assets/qr-scanner-worker.min-D85Z9gVD.js +0 -98
- package/dist/rcs-web/assets/stateDiagram-AJRCARHV-D6xp64f3.js +0 -1
- package/dist/rcs-web/assets/stateDiagram-v2-BHNVJYJU-D76vZCyQ.js +0 -1
- package/packages/remote-control-server/web/dist/assets/Dashboard-Cyj-Bb86.js +0 -2
- package/packages/remote-control-server/web/dist/assets/SessionDetail-SA9QBkyj.js +0 -51
- package/packages/remote-control-server/web/dist/assets/architectureDiagram-3BPJPVTR-CG9q9_bk.js +0 -36
- package/packages/remote-control-server/web/dist/assets/channel-CQFGglg-.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-C5rBdShD.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-C5rBdShD.js +0 -1
- package/packages/remote-control-server/web/dist/assets/code-block-IT6T5CEO-BLGFrBw3.js +0 -2
- package/packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-DBKEXjqM.js +0 -1
- package/packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-DB_SWlAe.js +0 -4
- package/packages/remote-control-server/web/dist/assets/diagram-2AECGRRQ-Gbm8G3Xz.js +0 -43
- package/packages/remote-control-server/web/dist/assets/diagram-5GNKFQAL-BBuWqfDc.js +0 -10
- package/packages/remote-control-server/web/dist/assets/diagram-LMA3HP47-DyUwNrlg.js +0 -24
- package/packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-BXpvRwkJ.js +0 -24
- package/packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-ByDYviDl.js +0 -292
- package/packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-BUnOPw74.js +0 -106
- package/packages/remote-control-server/web/dist/assets/infoDiagram-5YYISTIA-B1RpwJHG.js +0 -2
- package/packages/remote-control-server/web/dist/assets/main-0A4a3FDU.css +0 -1
- package/packages/remote-control-server/web/dist/assets/main-Dom16YJk.js +0 -283
- package/packages/remote-control-server/web/dist/assets/qr-C0jP9HcD.js +0 -8
- package/packages/remote-control-server/web/dist/assets/qr-scanner-worker.min-D85Z9gVD.js +0 -98
- package/packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-D6xp64f3.js +0 -1
- package/packages/remote-control-server/web/dist/assets/stateDiagram-v2-BHNVJYJU-D76vZCyQ.js +0 -1
|
@@ -1,24 +1,8 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, mock } from 'bun:test'
|
|
2
|
+
import { mockConfigModule } from './helpers/mock-config'
|
|
2
3
|
|
|
3
4
|
// Mock config before importing modules
|
|
4
|
-
|
|
5
|
-
port: 3000,
|
|
6
|
-
host: '0.0.0.0',
|
|
7
|
-
apiKeys: ['test-api-key'],
|
|
8
|
-
baseUrl: 'http://localhost:3000',
|
|
9
|
-
pollTimeout: 8,
|
|
10
|
-
heartbeatInterval: 20,
|
|
11
|
-
jwtExpiresIn: 3600,
|
|
12
|
-
disconnectTimeout: 300,
|
|
13
|
-
webCorsOrigins: [],
|
|
14
|
-
wsIdleTimeout: 30,
|
|
15
|
-
wsKeepaliveInterval: 20,
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
mock.module('../config', () => ({
|
|
19
|
-
config: mockConfig,
|
|
20
|
-
getBaseUrl: () => 'http://localhost:3000',
|
|
21
|
-
}))
|
|
5
|
+
mock.module('../config', () => mockConfigModule())
|
|
22
6
|
|
|
23
7
|
import { storeReset, storeCreateEnvironment } from '../store'
|
|
24
8
|
import {
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, mock } from 'bun:test'
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
5
|
+
import { mockConfigModule } from './helpers/mock-config'
|
|
6
|
+
|
|
7
|
+
// Mock config before any module imports
|
|
8
|
+
mock.module('../config', () => mockConfigModule())
|
|
9
|
+
|
|
10
|
+
import { Database } from 'bun:sqlite'
|
|
11
|
+
|
|
12
|
+
// Dynamic imports — Phase 2 modules may not exist yet (TDD)
|
|
13
|
+
let migrateDatabase: ((db: Database) => void) | undefined
|
|
14
|
+
let initDatabase: ((path?: string) => Database) | undefined
|
|
15
|
+
let resetDbSingleton: (() => void) | undefined
|
|
16
|
+
let getDb: (() => Database) | undefined
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const dbMod = await import('../db/sqlite')
|
|
20
|
+
migrateDatabase = dbMod.migrateDatabase
|
|
21
|
+
getDb = dbMod.getDb
|
|
22
|
+
initDatabase = dbMod.initDatabase
|
|
23
|
+
resetDbSingleton = dbMod.resetDbSingleton
|
|
24
|
+
} catch {
|
|
25
|
+
// Module not implemented yet
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Dynamic import for session visibility function
|
|
29
|
+
let storeListSessionsVisibleToUser:
|
|
30
|
+
| ((userId: string, db: Database) => Array<Record<string, unknown>>)
|
|
31
|
+
| undefined
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const mod = await import('../services/session-visibility')
|
|
35
|
+
storeListSessionsVisibleToUser = mod.storeListSessionsVisibleToUser
|
|
36
|
+
} catch {
|
|
37
|
+
// Module not implemented yet
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Database helpers
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
function freshDb(): Database {
|
|
45
|
+
if (resetDbSingleton) {
|
|
46
|
+
try {
|
|
47
|
+
resetDbSingleton()
|
|
48
|
+
} catch {
|
|
49
|
+
// ignore cleanup errors
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (initDatabase) {
|
|
53
|
+
const uniquePath = resolve(
|
|
54
|
+
tmpdir(),
|
|
55
|
+
`rcs-test-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
|
|
56
|
+
)
|
|
57
|
+
return initDatabase(uniquePath)
|
|
58
|
+
}
|
|
59
|
+
const db = new Database(':memory:')
|
|
60
|
+
db.exec('PRAGMA foreign_keys = ON')
|
|
61
|
+
if (migrateDatabase) migrateDatabase(db)
|
|
62
|
+
return db
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Fixture helpers
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
function createUser(
|
|
70
|
+
db: Database,
|
|
71
|
+
opts: { username: string; role: string },
|
|
72
|
+
): string {
|
|
73
|
+
const id = `usr_${randomUUID().replace(/-/g, '')}`
|
|
74
|
+
const now = new Date().toISOString()
|
|
75
|
+
const hash =
|
|
76
|
+
'$argon2id$v=19$m=65536,t=3,p=1$dGVzdHNhbHQ$dGVzdGhhc2h0ZXN0aGFzaA'
|
|
77
|
+
db.query(
|
|
78
|
+
`INSERT INTO users (id, username, password_hash, role, created_at)
|
|
79
|
+
VALUES ($id, $u, $h, $r, $now)`,
|
|
80
|
+
).run({ $id: id, $u: opts.username, $h: hash, $r: opts.role, $now: now })
|
|
81
|
+
return id
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function createTeam(
|
|
85
|
+
db: Database,
|
|
86
|
+
opts: { slug: string; createdBy: string },
|
|
87
|
+
): string {
|
|
88
|
+
const id = `team_${randomUUID().replace(/-/g, '')}`
|
|
89
|
+
const now = new Date().toISOString()
|
|
90
|
+
try {
|
|
91
|
+
db.query(
|
|
92
|
+
`INSERT INTO teams (id, name, slug, created_by, created_at, updated_at)
|
|
93
|
+
VALUES ($id, $name, $slug, $by, $now, $now)`,
|
|
94
|
+
).run({
|
|
95
|
+
$id: id,
|
|
96
|
+
$name: opts.slug,
|
|
97
|
+
$slug: opts.slug,
|
|
98
|
+
$by: opts.createdBy,
|
|
99
|
+
$now: now,
|
|
100
|
+
})
|
|
101
|
+
} catch {
|
|
102
|
+
// teams table may not exist yet
|
|
103
|
+
}
|
|
104
|
+
return id
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function addMember(
|
|
108
|
+
db: Database,
|
|
109
|
+
teamId: string,
|
|
110
|
+
userId: string,
|
|
111
|
+
role: string,
|
|
112
|
+
): void {
|
|
113
|
+
const now = new Date().toISOString()
|
|
114
|
+
try {
|
|
115
|
+
db.query(
|
|
116
|
+
`INSERT OR IGNORE INTO team_members (team_id, user_id, role, added_at, added_by)
|
|
117
|
+
VALUES ($t, $u, $r, $now, $by)`,
|
|
118
|
+
).run({ $t: teamId, $u: userId, $r: role, $by: userId, $now: now })
|
|
119
|
+
} catch {
|
|
120
|
+
// team_members may not exist
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface SessionFixture {
|
|
125
|
+
id: string
|
|
126
|
+
visibility: string
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function createSession(
|
|
130
|
+
db: Database,
|
|
131
|
+
opts: {
|
|
132
|
+
id?: string
|
|
133
|
+
visibility?: string
|
|
134
|
+
ownerType?: string
|
|
135
|
+
ownerId?: string
|
|
136
|
+
environmentId?: string
|
|
137
|
+
},
|
|
138
|
+
): SessionFixture {
|
|
139
|
+
const id = opts.id || `ses_${randomUUID().replace(/-/g, '')}`
|
|
140
|
+
const visibility =
|
|
141
|
+
opts.visibility || (opts.ownerType === 'team' ? 'team' : 'private')
|
|
142
|
+
const now = new Date().toISOString()
|
|
143
|
+
|
|
144
|
+
// Try to insert into a sessions table (Phase 2 schema)
|
|
145
|
+
try {
|
|
146
|
+
db.query(
|
|
147
|
+
`INSERT INTO sessions (id, visibility, environment_id, created_at, updated_at)
|
|
148
|
+
VALUES ($id, $vis, $env, $now, $now)`,
|
|
149
|
+
).run({
|
|
150
|
+
$id: id,
|
|
151
|
+
$vis: visibility,
|
|
152
|
+
$env: opts.environmentId || null,
|
|
153
|
+
$now: now,
|
|
154
|
+
})
|
|
155
|
+
} catch {
|
|
156
|
+
// sessions table may not have visibility column yet;
|
|
157
|
+
// try without it
|
|
158
|
+
try {
|
|
159
|
+
db.query(
|
|
160
|
+
`INSERT INTO sessions (id, environment_id, created_at, updated_at)
|
|
161
|
+
VALUES ($id, $env, $now, $now)`,
|
|
162
|
+
).run({ $id: id, $env: opts.environmentId || null, $now: now })
|
|
163
|
+
} catch {
|
|
164
|
+
// sessions table may not exist at all
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Add owner record
|
|
169
|
+
if (opts.ownerType && opts.ownerId) {
|
|
170
|
+
try {
|
|
171
|
+
db.query(
|
|
172
|
+
`INSERT INTO session_owners (session_id, owner_type, owner_id)
|
|
173
|
+
VALUES ($sid, $type, $oid)`,
|
|
174
|
+
).run({ $sid: id, $type: opts.ownerType, $oid: opts.ownerId })
|
|
175
|
+
} catch {
|
|
176
|
+
// session_owners may not exist
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { id, visibility }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function addSessionShare(
|
|
184
|
+
db: Database,
|
|
185
|
+
opts: {
|
|
186
|
+
sessionId: string
|
|
187
|
+
grantedToUser?: string
|
|
188
|
+
grantedToTeam?: string
|
|
189
|
+
permission?: string
|
|
190
|
+
grantedBy: string
|
|
191
|
+
expiresAt?: string
|
|
192
|
+
},
|
|
193
|
+
): string {
|
|
194
|
+
const id = `shr_${randomUUID().replace(/-/g, '')}`
|
|
195
|
+
const now = new Date().toISOString()
|
|
196
|
+
try {
|
|
197
|
+
db.query(
|
|
198
|
+
`INSERT INTO session_shares (id, session_id, granted_to_user, granted_to_team, permission, expires_at, granted_by, granted_at)
|
|
199
|
+
VALUES ($id, $sid, $gtu, $gtt, $perm, $exp, $by, $now)`,
|
|
200
|
+
).run({
|
|
201
|
+
$id: id,
|
|
202
|
+
$sid: opts.sessionId,
|
|
203
|
+
$gtu: opts.grantedToUser || null,
|
|
204
|
+
$gtt: opts.grantedToTeam || null,
|
|
205
|
+
$perm: opts.permission || 'read',
|
|
206
|
+
$exp: opts.expiresAt || null,
|
|
207
|
+
$by: opts.grantedBy,
|
|
208
|
+
$now: now,
|
|
209
|
+
})
|
|
210
|
+
} catch {
|
|
211
|
+
// session_shares may not exist
|
|
212
|
+
}
|
|
213
|
+
return id
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ===========================================================================
|
|
217
|
+
// TESTS
|
|
218
|
+
// ===========================================================================
|
|
219
|
+
|
|
220
|
+
describe('storeListSessionsVisibleToUser', () => {
|
|
221
|
+
let db: Database
|
|
222
|
+
let aliceId: string
|
|
223
|
+
let bobId: string
|
|
224
|
+
let charlieId: string
|
|
225
|
+
let adminId: string
|
|
226
|
+
let teamAId: string
|
|
227
|
+
|
|
228
|
+
beforeEach(() => {
|
|
229
|
+
db = freshDb()
|
|
230
|
+
aliceId = createUser(db, { username: 'alice', role: 'member' })
|
|
231
|
+
bobId = createUser(db, { username: 'bob', role: 'member' })
|
|
232
|
+
charlieId = createUser(db, { username: 'charlie', role: 'member' })
|
|
233
|
+
adminId = createUser(db, { username: 'sysadmin', role: 'admin' })
|
|
234
|
+
teamAId = createTeam(db, { slug: 'team-a', createdBy: aliceId })
|
|
235
|
+
addMember(db, teamAId, aliceId, 'owner')
|
|
236
|
+
addMember(db, teamAId, bobId, 'member')
|
|
237
|
+
// charlie is NOT in teamA
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test('owner is self → visible (private/team/public all visible)', () => {
|
|
241
|
+
expect(storeListSessionsVisibleToUser).toBeDefined()
|
|
242
|
+
const privateSession = createSession(db, {
|
|
243
|
+
visibility: 'private',
|
|
244
|
+
ownerType: 'user',
|
|
245
|
+
ownerId: aliceId,
|
|
246
|
+
})
|
|
247
|
+
const teamSession = createSession(db, {
|
|
248
|
+
visibility: 'team',
|
|
249
|
+
ownerType: 'user',
|
|
250
|
+
ownerId: aliceId,
|
|
251
|
+
})
|
|
252
|
+
const publicSession = createSession(db, {
|
|
253
|
+
visibility: 'public',
|
|
254
|
+
ownerType: 'user',
|
|
255
|
+
ownerId: aliceId,
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
const sessions = storeListSessionsVisibleToUser!(aliceId, db)
|
|
259
|
+
const ids = sessions.map(s => s.id as string)
|
|
260
|
+
expect(ids).toContain(privateSession.id)
|
|
261
|
+
expect(ids).toContain(teamSession.id)
|
|
262
|
+
expect(ids).toContain(publicSession.id)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
test('owner is own team + visibility=team → visible', () => {
|
|
266
|
+
expect(storeListSessionsVisibleToUser).toBeDefined()
|
|
267
|
+
const session = createSession(db, {
|
|
268
|
+
visibility: 'team',
|
|
269
|
+
ownerType: 'team',
|
|
270
|
+
ownerId: teamAId,
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
const sessions = storeListSessionsVisibleToUser!(bobId, db)
|
|
274
|
+
const ids = sessions.map(s => s.id as string)
|
|
275
|
+
expect(ids).toContain(session.id)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test('owner is own team + visibility=private → not visible', () => {
|
|
279
|
+
expect(storeListSessionsVisibleToUser).toBeDefined()
|
|
280
|
+
const session = createSession(db, {
|
|
281
|
+
visibility: 'private',
|
|
282
|
+
ownerType: 'team',
|
|
283
|
+
ownerId: teamAId,
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
const sessions = storeListSessionsVisibleToUser!(bobId, db)
|
|
287
|
+
const ids = sessions.map(s => s.id as string)
|
|
288
|
+
expect(ids).not.toContain(session.id)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test('owner is own team + visibility=public → visible', () => {
|
|
292
|
+
expect(storeListSessionsVisibleToUser).toBeDefined()
|
|
293
|
+
const session = createSession(db, {
|
|
294
|
+
visibility: 'public',
|
|
295
|
+
ownerType: 'team',
|
|
296
|
+
ownerId: teamAId,
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
const sessions = storeListSessionsVisibleToUser!(bobId, db)
|
|
300
|
+
const ids = sessions.map(s => s.id as string)
|
|
301
|
+
expect(ids).toContain(session.id)
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
test('explicitly granted via session_shares → visible', () => {
|
|
305
|
+
expect(storeListSessionsVisibleToUser).toBeDefined()
|
|
306
|
+
const session = createSession(db, {
|
|
307
|
+
visibility: 'private',
|
|
308
|
+
ownerType: 'user',
|
|
309
|
+
ownerId: aliceId,
|
|
310
|
+
})
|
|
311
|
+
addSessionShare(db, {
|
|
312
|
+
sessionId: session.id,
|
|
313
|
+
grantedToUser: charlieId,
|
|
314
|
+
permission: 'read',
|
|
315
|
+
grantedBy: aliceId,
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
const sessions = storeListSessionsVisibleToUser!(charlieId, db)
|
|
319
|
+
const ids = sessions.map(s => s.id as string)
|
|
320
|
+
expect(ids).toContain(session.id)
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
test('visibility=public + owner is stranger → visible', () => {
|
|
324
|
+
expect(storeListSessionsVisibleToUser).toBeDefined()
|
|
325
|
+
const session = createSession(db, {
|
|
326
|
+
visibility: 'public',
|
|
327
|
+
ownerType: 'user',
|
|
328
|
+
ownerId: aliceId,
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
// charlie is a stranger (not in alice's team, no share)
|
|
332
|
+
const sessions = storeListSessionsVisibleToUser!(charlieId, db)
|
|
333
|
+
const ids = sessions.map(s => s.id as string)
|
|
334
|
+
expect(ids).toContain(session.id)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
test('system admin → all sessions visible (including orphans)', () => {
|
|
338
|
+
expect(storeListSessionsVisibleToUser).toBeDefined()
|
|
339
|
+
// Create sessions owned by different users
|
|
340
|
+
createSession(db, {
|
|
341
|
+
visibility: 'private',
|
|
342
|
+
ownerType: 'user',
|
|
343
|
+
ownerId: aliceId,
|
|
344
|
+
})
|
|
345
|
+
createSession(db, {
|
|
346
|
+
visibility: 'private',
|
|
347
|
+
ownerType: 'user',
|
|
348
|
+
ownerId: bobId,
|
|
349
|
+
})
|
|
350
|
+
// Create an orphan session (no owner)
|
|
351
|
+
createSession(db, { visibility: 'private' })
|
|
352
|
+
|
|
353
|
+
const sessions = storeListSessionsVisibleToUser!(adminId, db)
|
|
354
|
+
// Admin should see all sessions — at least the 3 we created
|
|
355
|
+
expect(sessions.length).toBeGreaterThanOrEqual(3)
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
test('orphan sessions (no owner) → only admin visible', () => {
|
|
359
|
+
expect(storeListSessionsVisibleToUser).toBeDefined()
|
|
360
|
+
const orphan = createSession(db, { visibility: 'private' })
|
|
361
|
+
|
|
362
|
+
// Regular user should NOT see orphan
|
|
363
|
+
const aliceSessions = storeListSessionsVisibleToUser!(aliceId, db)
|
|
364
|
+
const aliceIds = aliceSessions.map(s => s.id as string)
|
|
365
|
+
expect(aliceIds).not.toContain(orphan.id)
|
|
366
|
+
|
|
367
|
+
// Admin should see orphan
|
|
368
|
+
const adminSessions = storeListSessionsVisibleToUser!(adminId, db)
|
|
369
|
+
const adminIds = adminSessions.map(s => s.id as string)
|
|
370
|
+
expect(adminIds).toContain(orphan.id)
|
|
371
|
+
})
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
describe('session visibility field', () => {
|
|
375
|
+
let db: Database
|
|
376
|
+
let aliceId: string
|
|
377
|
+
let teamAId: string
|
|
378
|
+
|
|
379
|
+
beforeEach(() => {
|
|
380
|
+
db = freshDb()
|
|
381
|
+
aliceId = createUser(db, { username: 'alice', role: 'member' })
|
|
382
|
+
teamAId = createTeam(db, { slug: 'team-a', createdBy: aliceId })
|
|
383
|
+
addMember(db, teamAId, aliceId, 'owner')
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
test('team environment → default visibility=team', () => {
|
|
387
|
+
// When a session is created under a team-owned environment,
|
|
388
|
+
// the default visibility should be 'team'
|
|
389
|
+
const envId = `env_${randomUUID().replace(/-/g, '')}`
|
|
390
|
+
const session = createSession(db, {
|
|
391
|
+
ownerType: 'team',
|
|
392
|
+
ownerId: teamAId,
|
|
393
|
+
environmentId: envId,
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
// Verify the session has team visibility
|
|
397
|
+
const row = db.query('SELECT visibility FROM sessions WHERE id = $id').get({
|
|
398
|
+
$id: session.id,
|
|
399
|
+
}) as { visibility: string } | null
|
|
400
|
+
if (row) {
|
|
401
|
+
expect(row.visibility).toBe('team')
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
test('personal environment → default visibility=private', () => {
|
|
406
|
+
const envId = `env_${randomUUID().replace(/-/g, '')}`
|
|
407
|
+
const session = createSession(db, {
|
|
408
|
+
ownerType: 'user',
|
|
409
|
+
ownerId: aliceId,
|
|
410
|
+
environmentId: envId,
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
const row = db.query('SELECT visibility FROM sessions WHERE id = $id').get({
|
|
414
|
+
$id: session.id,
|
|
415
|
+
}) as { visibility: string } | null
|
|
416
|
+
if (row) {
|
|
417
|
+
expect(row.visibility).toBe('private')
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
test('owner switches visibility private → team → 200', () => {
|
|
422
|
+
expect(storeListSessionsVisibleToUser).toBeDefined()
|
|
423
|
+
const session = createSession(db, {
|
|
424
|
+
visibility: 'private',
|
|
425
|
+
ownerType: 'user',
|
|
426
|
+
ownerId: aliceId,
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
// Owner changes visibility to 'team'
|
|
430
|
+
try {
|
|
431
|
+
db.query(`UPDATE sessions SET visibility = 'team' WHERE id = $id`).run({
|
|
432
|
+
$id: session.id,
|
|
433
|
+
})
|
|
434
|
+
} catch {
|
|
435
|
+
// Column may not exist
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const row = db.query('SELECT visibility FROM sessions WHERE id = $id').get({
|
|
439
|
+
$id: session.id,
|
|
440
|
+
}) as { visibility: string } | null
|
|
441
|
+
if (row) {
|
|
442
|
+
expect(row.visibility).toBe('team')
|
|
443
|
+
}
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
test.todo('non-owner cannot switch visibility → 403', () => {
|
|
447
|
+
// TODO: Implement PATCH /web/sessions/:id/visibility route, then test:
|
|
448
|
+
// 1. Create session owned by alice
|
|
449
|
+
// 2. Bob (non-owner) sends PATCH to change visibility
|
|
450
|
+
// 3. Assert 403 Forbidden
|
|
451
|
+
})
|
|
452
|
+
})
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'bun:test'
|
|
2
|
+
import { Database } from 'bun:sqlite'
|
|
3
|
+
|
|
4
|
+
// Dynamic imports — modules may not exist yet (TDD)
|
|
5
|
+
let issueSessionToken:
|
|
6
|
+
| ((
|
|
7
|
+
userId: string,
|
|
8
|
+
db: Database,
|
|
9
|
+
) => { accessToken: string; refreshToken: string })
|
|
10
|
+
| undefined
|
|
11
|
+
let resolveSessionToken:
|
|
12
|
+
| ((token: string, db: Database) => string | null)
|
|
13
|
+
| undefined
|
|
14
|
+
let revokeSessionToken: ((token: string, db: Database) => void) | undefined
|
|
15
|
+
let revokeAllUserTokens: ((userId: string, db: Database) => void) | undefined
|
|
16
|
+
let migrateDatabase: ((db: Database) => void) | undefined
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const sessionMod = await import('../auth/session')
|
|
20
|
+
issueSessionToken = sessionMod.issueSessionToken
|
|
21
|
+
resolveSessionToken = sessionMod.resolveSessionToken
|
|
22
|
+
revokeSessionToken = sessionMod.revokeSessionToken
|
|
23
|
+
revokeAllUserTokens = sessionMod.revokeAllUserTokens
|
|
24
|
+
} catch {
|
|
25
|
+
// Module not implemented yet
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const dbMod = await import('../db/sqlite')
|
|
30
|
+
migrateDatabase = dbMod.migrateDatabase
|
|
31
|
+
} catch {
|
|
32
|
+
// Module not implemented yet
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Helper: create a fresh in-memory database with schema
|
|
36
|
+
function freshDb(): Database {
|
|
37
|
+
const db = new Database(':memory:')
|
|
38
|
+
if (migrateDatabase) {
|
|
39
|
+
migrateDatabase(db)
|
|
40
|
+
}
|
|
41
|
+
// Ensure users table has a test user
|
|
42
|
+
db.exec(
|
|
43
|
+
"INSERT OR IGNORE INTO users (id, username, password_hash, role, created_at) VALUES ('usr_test1', 'testuser', 'hash', 'admin', '2026-01-01T00:00:00Z')",
|
|
44
|
+
)
|
|
45
|
+
return db
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('issueSessionToken', () => {
|
|
49
|
+
test('returns access token with rct_ prefix and refresh token', () => {
|
|
50
|
+
expect(issueSessionToken).toBeDefined()
|
|
51
|
+
const db = freshDb()
|
|
52
|
+
const result = issueSessionToken!('usr_test1', db)
|
|
53
|
+
expect(result.accessToken).toBeDefined()
|
|
54
|
+
expect(result.refreshToken).toBeDefined()
|
|
55
|
+
expect(result.accessToken.startsWith('rct_')).toBe(true)
|
|
56
|
+
expect(typeof result.refreshToken).toBe('string')
|
|
57
|
+
expect(result.refreshToken.length).toBeGreaterThan(0)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('access token expires in approximately 1 hour', () => {
|
|
61
|
+
expect(issueSessionToken).toBeDefined()
|
|
62
|
+
const db = freshDb()
|
|
63
|
+
const result = issueSessionToken!('usr_test1', db)
|
|
64
|
+
|
|
65
|
+
// Check the token record in the database
|
|
66
|
+
const row = db
|
|
67
|
+
.query(
|
|
68
|
+
"SELECT expires_at, kind FROM session_tokens WHERE token_hash = (SELECT token_hash FROM session_tokens WHERE kind = 'access' AND user_id = 'usr_test1' ORDER BY created_at DESC LIMIT 1)",
|
|
69
|
+
)
|
|
70
|
+
.get() as { expires_at: string; kind: string } | null
|
|
71
|
+
|
|
72
|
+
expect(row).not.toBeNull()
|
|
73
|
+
expect(row!.kind).toBe('access')
|
|
74
|
+
const expiresAt = new Date(row!.expires_at).getTime()
|
|
75
|
+
const now = Date.now()
|
|
76
|
+
// Should expire in roughly 1 hour (3600s ± 60s tolerance)
|
|
77
|
+
const diffSeconds = (expiresAt - now) / 1000
|
|
78
|
+
expect(diffSeconds).toBeGreaterThan(3500)
|
|
79
|
+
expect(diffSeconds).toBeLessThan(3700)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('refresh token expires in approximately 30 days', () => {
|
|
83
|
+
expect(issueSessionToken).toBeDefined()
|
|
84
|
+
const db = freshDb()
|
|
85
|
+
issueSessionToken!('usr_test1', db)
|
|
86
|
+
|
|
87
|
+
const row = db
|
|
88
|
+
.query(
|
|
89
|
+
"SELECT expires_at, kind FROM session_tokens WHERE kind = 'refresh' AND user_id = 'usr_test1' ORDER BY created_at DESC LIMIT 1",
|
|
90
|
+
)
|
|
91
|
+
.get() as { expires_at: string; kind: string } | null
|
|
92
|
+
|
|
93
|
+
expect(row).not.toBeNull()
|
|
94
|
+
expect(row!.kind).toBe('refresh')
|
|
95
|
+
const expiresAt = new Date(row!.expires_at).getTime()
|
|
96
|
+
const now = Date.now()
|
|
97
|
+
// Should expire in roughly 30 days (2592000s ± 3600s tolerance)
|
|
98
|
+
const diffSeconds = (expiresAt - now) / 1000
|
|
99
|
+
expect(diffSeconds).toBeGreaterThan(2588400)
|
|
100
|
+
expect(diffSeconds).toBeLessThan(2595600)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('writes tokens to session_tokens table in SQLite', () => {
|
|
104
|
+
expect(issueSessionToken).toBeDefined()
|
|
105
|
+
const db = freshDb()
|
|
106
|
+
issueSessionToken!('usr_test1', db)
|
|
107
|
+
|
|
108
|
+
const rows = db
|
|
109
|
+
.query("SELECT * FROM session_tokens WHERE user_id = 'usr_test1'")
|
|
110
|
+
.all() as Array<{ token_hash: string; kind: string }>
|
|
111
|
+
|
|
112
|
+
expect(rows.length).toBeGreaterThanOrEqual(2)
|
|
113
|
+
const kinds = rows.map(r => r.kind).sort()
|
|
114
|
+
expect(kinds).toContain('access')
|
|
115
|
+
expect(kinds).toContain('refresh')
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('resolveSessionToken', () => {
|
|
120
|
+
test('returns userId for valid access token', () => {
|
|
121
|
+
expect(issueSessionToken).toBeDefined()
|
|
122
|
+
expect(resolveSessionToken).toBeDefined()
|
|
123
|
+
const db = freshDb()
|
|
124
|
+
const { accessToken } = issueSessionToken!('usr_test1', db)
|
|
125
|
+
const userId = resolveSessionToken!(accessToken, db)
|
|
126
|
+
expect(userId).toBe('usr_test1')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('returns null for invalid token', () => {
|
|
130
|
+
expect(resolveSessionToken).toBeDefined()
|
|
131
|
+
const db = freshDb()
|
|
132
|
+
const userId = resolveSessionToken!('rct_nonexistent_token', db)
|
|
133
|
+
expect(userId).toBeNull()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('returns null for expired token', () => {
|
|
137
|
+
expect(issueSessionToken).toBeDefined()
|
|
138
|
+
expect(resolveSessionToken).toBeDefined()
|
|
139
|
+
const db = freshDb()
|
|
140
|
+
const { accessToken } = issueSessionToken!('usr_test1', db)
|
|
141
|
+
|
|
142
|
+
// Manually set the token's expiry to the past
|
|
143
|
+
db.exec(
|
|
144
|
+
"UPDATE session_tokens SET expires_at = '2020-01-01T00:00:00Z' WHERE user_id = 'usr_test1' AND kind = 'access'",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
const userId = resolveSessionToken!(accessToken, db)
|
|
148
|
+
expect(userId).toBeNull()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('returns null for revoked token', () => {
|
|
152
|
+
expect(issueSessionToken).toBeDefined()
|
|
153
|
+
expect(resolveSessionToken).toBeDefined()
|
|
154
|
+
expect(revokeSessionToken).toBeDefined()
|
|
155
|
+
const db = freshDb()
|
|
156
|
+
const { accessToken } = issueSessionToken!('usr_test1', db)
|
|
157
|
+
|
|
158
|
+
revokeSessionToken!(accessToken, db)
|
|
159
|
+
const userId = resolveSessionToken!(accessToken, db)
|
|
160
|
+
expect(userId).toBeNull()
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('revokeSessionToken', () => {
|
|
165
|
+
test('after revocation resolveSessionToken returns null', () => {
|
|
166
|
+
expect(issueSessionToken).toBeDefined()
|
|
167
|
+
expect(resolveSessionToken).toBeDefined()
|
|
168
|
+
expect(revokeSessionToken).toBeDefined()
|
|
169
|
+
const db = freshDb()
|
|
170
|
+
const { accessToken } = issueSessionToken!('usr_test1', db)
|
|
171
|
+
|
|
172
|
+
// Token should resolve before revocation
|
|
173
|
+
expect(resolveSessionToken!(accessToken, db)).toBe('usr_test1')
|
|
174
|
+
|
|
175
|
+
revokeSessionToken!(accessToken, db)
|
|
176
|
+
|
|
177
|
+
// Token should not resolve after revocation
|
|
178
|
+
expect(resolveSessionToken!(accessToken, db)).toBeNull()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test('revoking an already-revoked token does not throw', () => {
|
|
182
|
+
expect(issueSessionToken).toBeDefined()
|
|
183
|
+
expect(revokeSessionToken).toBeDefined()
|
|
184
|
+
const db = freshDb()
|
|
185
|
+
const { accessToken } = issueSessionToken!('usr_test1', db)
|
|
186
|
+
|
|
187
|
+
revokeSessionToken!(accessToken, db)
|
|
188
|
+
// Second revocation should not throw
|
|
189
|
+
expect(() => revokeSessionToken!(accessToken, db)).not.toThrow()
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('revokeAllUserTokens', () => {
|
|
194
|
+
test('revokes all tokens for a user', () => {
|
|
195
|
+
expect(issueSessionToken).toBeDefined()
|
|
196
|
+
expect(resolveSessionToken).toBeDefined()
|
|
197
|
+
expect(revokeAllUserTokens).toBeDefined()
|
|
198
|
+
const db = freshDb()
|
|
199
|
+
|
|
200
|
+
// Issue multiple sessions
|
|
201
|
+
const session1 = issueSessionToken!('usr_test1', db)
|
|
202
|
+
const session2 = issueSessionToken!('usr_test1', db)
|
|
203
|
+
|
|
204
|
+
// Both should resolve
|
|
205
|
+
expect(resolveSessionToken!(session1.accessToken, db)).toBe('usr_test1')
|
|
206
|
+
expect(resolveSessionToken!(session2.accessToken, db)).toBe('usr_test1')
|
|
207
|
+
|
|
208
|
+
revokeAllUserTokens!('usr_test1', db)
|
|
209
|
+
|
|
210
|
+
// Neither should resolve
|
|
211
|
+
expect(resolveSessionToken!(session1.accessToken, db)).toBeNull()
|
|
212
|
+
expect(resolveSessionToken!(session2.accessToken, db)).toBeNull()
|
|
213
|
+
expect(resolveSessionToken!(session1.refreshToken, db)).toBeNull()
|
|
214
|
+
expect(resolveSessionToken!(session2.refreshToken, db)).toBeNull()
|
|
215
|
+
})
|
|
216
|
+
})
|