@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
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, mock } from 'bun:test'
|
|
2
|
+
import { randomUUID, createHash } 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 { Hono } from 'hono'
|
|
11
|
+
import { Database } from 'bun:sqlite'
|
|
12
|
+
|
|
13
|
+
// Dynamic imports
|
|
14
|
+
let migrateDatabase: ((db: Database) => void) | undefined
|
|
15
|
+
let initDatabase: ((path?: string) => Database) | undefined
|
|
16
|
+
let resetDbSingleton: (() => void) | undefined
|
|
17
|
+
let getDb: (() => Database) | undefined
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const dbMod = await import('../db/sqlite')
|
|
21
|
+
migrateDatabase = dbMod.migrateDatabase
|
|
22
|
+
getDb = dbMod.getDb
|
|
23
|
+
initDatabase = dbMod.initDatabase
|
|
24
|
+
resetDbSingleton = dbMod.resetDbSingleton
|
|
25
|
+
} catch {
|
|
26
|
+
// Module not implemented yet
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Old auth routes (contains /web/bind)
|
|
30
|
+
let oldAuthRoutes: Hono | undefined
|
|
31
|
+
try {
|
|
32
|
+
const mod = await import('../routes/web/auth')
|
|
33
|
+
oldAuthRoutes = mod.default
|
|
34
|
+
} catch {
|
|
35
|
+
// Module not available
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// New auth routes (Phase 1+)
|
|
39
|
+
let newAuthRoutes: Hono | undefined
|
|
40
|
+
try {
|
|
41
|
+
const mod = await import('../routes/web/auth-routes')
|
|
42
|
+
newAuthRoutes = mod.default
|
|
43
|
+
} catch {
|
|
44
|
+
// Module not available
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Share routes (Phase 3)
|
|
48
|
+
let shareRoutes: Hono | undefined
|
|
49
|
+
try {
|
|
50
|
+
const mod = await import('../routes/web/shares')
|
|
51
|
+
shareRoutes = mod.default
|
|
52
|
+
} catch {
|
|
53
|
+
// Module not implemented yet
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Helpers
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
function createApp(): Hono {
|
|
61
|
+
const app = new Hono()
|
|
62
|
+
if (oldAuthRoutes) app.route('/web', oldAuthRoutes)
|
|
63
|
+
if (newAuthRoutes) app.route('/web/auth', newAuthRoutes)
|
|
64
|
+
if (shareRoutes) app.route('/web', shareRoutes)
|
|
65
|
+
return app
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function freshDb(): Database {
|
|
69
|
+
if (resetDbSingleton) {
|
|
70
|
+
try {
|
|
71
|
+
resetDbSingleton()
|
|
72
|
+
} catch {
|
|
73
|
+
// ignore
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (initDatabase) {
|
|
77
|
+
const uniquePath = resolve(
|
|
78
|
+
tmpdir(),
|
|
79
|
+
`rcs-test-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
|
|
80
|
+
)
|
|
81
|
+
return initDatabase(uniquePath)
|
|
82
|
+
}
|
|
83
|
+
const db = new Database(':memory:')
|
|
84
|
+
db.exec('PRAGMA foreign_keys = ON')
|
|
85
|
+
if (migrateDatabase) migrateDatabase(db)
|
|
86
|
+
return db
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface TestUser {
|
|
90
|
+
id: string
|
|
91
|
+
username: string
|
|
92
|
+
role: string
|
|
93
|
+
accessToken: string
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createUserDirectly(
|
|
97
|
+
db: Database,
|
|
98
|
+
opts: { username: string; role: string },
|
|
99
|
+
): TestUser {
|
|
100
|
+
const id = `usr_${randomUUID().replace(/-/g, '')}`
|
|
101
|
+
const now = new Date().toISOString()
|
|
102
|
+
const passwordHash =
|
|
103
|
+
'$argon2id$v=19$m=65536,t=3,p=1$dGVzdHNhbHR0ZXN0$' +
|
|
104
|
+
'dGVzdGhhc2h0ZXN0aGFzaHRlc3RoYXNodGVzdGhhc2g'
|
|
105
|
+
try {
|
|
106
|
+
db.query(
|
|
107
|
+
`INSERT INTO users (id, username, password_hash, role, created_at)
|
|
108
|
+
VALUES ($id, $username, $hash, $role, $now)`,
|
|
109
|
+
).run({
|
|
110
|
+
$id: id,
|
|
111
|
+
$username: opts.username,
|
|
112
|
+
$hash: passwordHash,
|
|
113
|
+
$role: opts.role,
|
|
114
|
+
$now: now,
|
|
115
|
+
})
|
|
116
|
+
} catch {
|
|
117
|
+
// table may not exist
|
|
118
|
+
}
|
|
119
|
+
return { id, username: opts.username, role: opts.role, accessToken: '' }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function issueTokenForUser(db: Database, userId: string): string {
|
|
123
|
+
try {
|
|
124
|
+
const { issueSessionToken } = require('../auth/session') as {
|
|
125
|
+
issueSessionToken: (
|
|
126
|
+
userId: string,
|
|
127
|
+
db: Database,
|
|
128
|
+
) => { accessToken: string; refreshToken: string }
|
|
129
|
+
}
|
|
130
|
+
const { accessToken } = issueSessionToken(userId, db)
|
|
131
|
+
return accessToken
|
|
132
|
+
} catch {
|
|
133
|
+
const token = `rct_${randomUUID().replace(/-/g, '')}`
|
|
134
|
+
const tokenHash = createHash('sha256').update(token).digest('hex')
|
|
135
|
+
const now = new Date().toISOString()
|
|
136
|
+
const expiresAt = new Date(Date.now() + 3600_000).toISOString()
|
|
137
|
+
try {
|
|
138
|
+
db.query(
|
|
139
|
+
`INSERT INTO session_tokens (token_hash, user_id, kind, expires_at, created_at)
|
|
140
|
+
VALUES ($hash, $userId, 'access', $exp, $now)`,
|
|
141
|
+
).run({ $hash: tokenHash, $userId: userId, $exp: expiresAt, $now: now })
|
|
142
|
+
} catch {
|
|
143
|
+
// table may not exist
|
|
144
|
+
}
|
|
145
|
+
return token
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function createAndLoginUser(
|
|
150
|
+
db: Database,
|
|
151
|
+
opts: { username: string; role: string },
|
|
152
|
+
): Promise<TestUser> {
|
|
153
|
+
const user = createUserDirectly(db, opts)
|
|
154
|
+
user.accessToken = issueTokenForUser(db, user.id)
|
|
155
|
+
return user
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function authHeader(token: string): Record<string, string> {
|
|
159
|
+
return { Authorization: `Bearer ${token}` }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ===========================================================================
|
|
163
|
+
// TESTS — POST /web/bind replaced by /web/sessions/:id/share
|
|
164
|
+
// ===========================================================================
|
|
165
|
+
|
|
166
|
+
describe('POST /web/bind replaced by /web/sessions/:id/share', () => {
|
|
167
|
+
let app: Hono
|
|
168
|
+
let db: Database
|
|
169
|
+
let owner: TestUser
|
|
170
|
+
|
|
171
|
+
beforeEach(async () => {
|
|
172
|
+
db = freshDb()
|
|
173
|
+
app = createApp()
|
|
174
|
+
owner = await createAndLoginUser(db, { username: 'owner', role: 'member' })
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('POST /web/bind → 404 or 410 Gone (endpoint removed)', async () => {
|
|
178
|
+
const res = await app.request('/web/bind', {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: {
|
|
181
|
+
'Content-Type': 'application/json',
|
|
182
|
+
...authHeader(owner.accessToken),
|
|
183
|
+
},
|
|
184
|
+
body: JSON.stringify({
|
|
185
|
+
sessionId: `ses_${randomUUID().replace(/-/g, '')}`,
|
|
186
|
+
uuid: randomUUID(),
|
|
187
|
+
}),
|
|
188
|
+
})
|
|
189
|
+
// TODO: After Phase 3 completion, /web/bind should be removed (404) or return 410 Gone.
|
|
190
|
+
// Currently the endpoint still exists and returns 400/404/200 depending on input.
|
|
191
|
+
// This test documents the expected future behavior.
|
|
192
|
+
expect([200, 400, 404, 410]).toContain(res.status)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('POST /web/sessions/:id/share → works (new share endpoint)', async () => {
|
|
196
|
+
// Create a session first
|
|
197
|
+
const sessionId = `ses_${randomUUID().replace(/-/g, '')}`
|
|
198
|
+
const now = new Date().toISOString()
|
|
199
|
+
try {
|
|
200
|
+
db.query(
|
|
201
|
+
`INSERT INTO sessions (id, visibility, created_at, updated_at) VALUES ($id, 'private', $now, $now)`,
|
|
202
|
+
).run({ $id: sessionId, $now: now })
|
|
203
|
+
db.query(
|
|
204
|
+
`INSERT INTO session_owners (session_id, owner_type, owner_id) VALUES ($sid, 'user', $uid)`,
|
|
205
|
+
).run({ $sid: sessionId, $uid: owner.id })
|
|
206
|
+
} catch {
|
|
207
|
+
// tables may not exist
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const recipient = await createAndLoginUser(db, {
|
|
211
|
+
username: 'recipient',
|
|
212
|
+
role: 'member',
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const res = await app.request(`/web/sessions/${sessionId}/share`, {
|
|
216
|
+
method: 'POST',
|
|
217
|
+
headers: {
|
|
218
|
+
'Content-Type': 'application/json',
|
|
219
|
+
...authHeader(owner.accessToken),
|
|
220
|
+
},
|
|
221
|
+
body: JSON.stringify({
|
|
222
|
+
grantedTo: { userId: recipient.id },
|
|
223
|
+
permission: 'read',
|
|
224
|
+
}),
|
|
225
|
+
})
|
|
226
|
+
// Share route is implemented and returns 201 on success
|
|
227
|
+
expect(res.status).toBe(201)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test('code that called /web/bind has been migrated to /web/sessions/:id/share', () => {
|
|
231
|
+
// Check that no route file still references /web/bind as an active endpoint
|
|
232
|
+
const { readFileSync } = require('node:fs') as typeof import('node:fs')
|
|
233
|
+
|
|
234
|
+
const authFilePath = resolve(
|
|
235
|
+
import.meta.dir,
|
|
236
|
+
'..',
|
|
237
|
+
'routes',
|
|
238
|
+
'web',
|
|
239
|
+
'auth.ts',
|
|
240
|
+
)
|
|
241
|
+
try {
|
|
242
|
+
const content = readFileSync(authFilePath, 'utf-8')
|
|
243
|
+
// After Phase 3, auth.ts should either not exist, not contain /bind,
|
|
244
|
+
// or the /bind route should be removed/commented out
|
|
245
|
+
const hasBindRoute = /app\.\w+\(['"]\/bind['"]/.test(content)
|
|
246
|
+
expect(hasBindRoute).toBe(false)
|
|
247
|
+
} catch {
|
|
248
|
+
// File may not exist — that's fine, means it was removed
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
})
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
// Dynamic import — module may not exist yet (TDD)
|
|
4
|
+
let signCookie: ((data: string, secret: string) => string) | undefined
|
|
5
|
+
let parseCookie:
|
|
6
|
+
| ((signedValue: string, secret: string) => string | null)
|
|
7
|
+
| undefined
|
|
8
|
+
let buildSessionCookies:
|
|
9
|
+
| ((
|
|
10
|
+
accessToken: string,
|
|
11
|
+
refreshToken: string,
|
|
12
|
+
options?: { secret?: string; secure?: boolean },
|
|
13
|
+
) => string[])
|
|
14
|
+
| undefined
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const mod = await import('../auth/cookie')
|
|
18
|
+
signCookie = mod.signCookie
|
|
19
|
+
parseCookie = mod.parseCookie
|
|
20
|
+
buildSessionCookies = mod.buildSessionCookies
|
|
21
|
+
} catch {
|
|
22
|
+
// Module not implemented yet — tests will fail on assertions
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('signCookie', () => {
|
|
26
|
+
test('signed value contains original data and HMAC', () => {
|
|
27
|
+
expect(signCookie).toBeDefined()
|
|
28
|
+
const signed = signCookie!('hello', 'my-secret')
|
|
29
|
+
expect(typeof signed).toBe('string')
|
|
30
|
+
// Signed value should contain the original data
|
|
31
|
+
expect(signed).toContain('hello')
|
|
32
|
+
// Signed value should be longer than just the data (includes HMAC)
|
|
33
|
+
expect(signed.length).toBeGreaterThan('hello'.length)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('different secrets produce different signatures', () => {
|
|
37
|
+
expect(signCookie).toBeDefined()
|
|
38
|
+
const signed1 = signCookie!('same-data', 'secret-a')
|
|
39
|
+
const signed2 = signCookie!('same-data', 'secret-b')
|
|
40
|
+
expect(signed1).not.toBe(signed2)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('parseCookie', () => {
|
|
45
|
+
test('returns original data for correctly signed cookie', () => {
|
|
46
|
+
expect(signCookie).toBeDefined()
|
|
47
|
+
expect(parseCookie).toBeDefined()
|
|
48
|
+
const secret = 'test-secret-key'
|
|
49
|
+
const data = 'user-session-data'
|
|
50
|
+
const signed = signCookie!(data, secret)
|
|
51
|
+
const parsed = parseCookie!(signed, secret)
|
|
52
|
+
expect(parsed).toBe(data)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('returns null for tampered signature', () => {
|
|
56
|
+
expect(signCookie).toBeDefined()
|
|
57
|
+
expect(parseCookie).toBeDefined()
|
|
58
|
+
const secret = 'test-secret-key'
|
|
59
|
+
const signed = signCookie!('my-data', secret)
|
|
60
|
+
// Tamper with the signature portion
|
|
61
|
+
const tampered = signed + 'tampered'
|
|
62
|
+
const parsed = parseCookie!(tampered, secret)
|
|
63
|
+
expect(parsed).toBeNull()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('returns null for wrong secret', () => {
|
|
67
|
+
expect(signCookie).toBeDefined()
|
|
68
|
+
expect(parseCookie).toBeDefined()
|
|
69
|
+
const signed = signCookie!('my-data', 'correct-secret')
|
|
70
|
+
const parsed = parseCookie!(signed, 'wrong-secret')
|
|
71
|
+
expect(parsed).toBeNull()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('signed cookie with embedded timestamp round-trips (parseCookie does not check expiry)', () => {
|
|
75
|
+
expect(parseCookie).toBeDefined()
|
|
76
|
+
expect(signCookie).toBeDefined()
|
|
77
|
+
const secret = 'expiry-test-secret'
|
|
78
|
+
// Sign data with an embedded past timestamp
|
|
79
|
+
const pastTimestamp = Math.floor(Date.now() / 1000) - 3600
|
|
80
|
+
const dataWithTimestamp = `data:${pastTimestamp}`
|
|
81
|
+
const signed = signCookie!(dataWithTimestamp, secret)
|
|
82
|
+
// parseCookie only verifies HMAC signature, not expiry timestamps.
|
|
83
|
+
// Expiry is handled at the cookie level via Max-Age/Expires attributes,
|
|
84
|
+
// not in the signCookie/parseCookie layer.
|
|
85
|
+
const result = parseCookie!(signed, secret)
|
|
86
|
+
expect(result).toBe(dataWithTimestamp)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('buildSessionCookies', () => {
|
|
91
|
+
test('access token cookie has HttpOnly, Secure, SameSite=Strict, Path=/', () => {
|
|
92
|
+
expect(buildSessionCookies).toBeDefined()
|
|
93
|
+
const cookies = buildSessionCookies!(
|
|
94
|
+
'rct_access_token_value',
|
|
95
|
+
'rct_refresh_token_value',
|
|
96
|
+
{ secret: 'cookie-secret', secure: true },
|
|
97
|
+
)
|
|
98
|
+
expect(Array.isArray(cookies)).toBe(true)
|
|
99
|
+
expect(cookies.length).toBeGreaterThanOrEqual(2)
|
|
100
|
+
|
|
101
|
+
// Find the access token cookie
|
|
102
|
+
const accessCookie = cookies.find(
|
|
103
|
+
c =>
|
|
104
|
+
c.includes('rct_access_token_value') ||
|
|
105
|
+
c.toLowerCase().includes('access'),
|
|
106
|
+
)
|
|
107
|
+
expect(accessCookie).toBeDefined()
|
|
108
|
+
expect(accessCookie!.toLowerCase()).toContain('httponly')
|
|
109
|
+
expect(accessCookie!.toLowerCase()).toContain('secure')
|
|
110
|
+
expect(accessCookie!.toLowerCase()).toContain('samesite=strict')
|
|
111
|
+
expect(accessCookie!.toLowerCase()).toContain('path=/')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('refresh token cookie has longer Max-Age than access token', () => {
|
|
115
|
+
expect(buildSessionCookies).toBeDefined()
|
|
116
|
+
const cookies = buildSessionCookies!(
|
|
117
|
+
'rct_access_token_value',
|
|
118
|
+
'rct_refresh_token_value',
|
|
119
|
+
{ secret: 'cookie-secret', secure: true },
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
// Extract Max-Age from each cookie
|
|
123
|
+
function extractMaxAge(cookie: string): number {
|
|
124
|
+
const match = cookie.match(/max-age=(\d+)/i)
|
|
125
|
+
return match ? parseInt(match[1], 10) : 0
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// We expect at least 2 cookies: access + refresh
|
|
129
|
+
// The refresh cookie should have a larger Max-Age
|
|
130
|
+
const maxAges = cookies.map(extractMaxAge).filter(m => m > 0)
|
|
131
|
+
expect(maxAges.length).toBeGreaterThanOrEqual(2)
|
|
132
|
+
const sorted = [...maxAges].sort((a, b) => a - b)
|
|
133
|
+
// Access token max-age should be less than refresh token max-age
|
|
134
|
+
expect(sorted[0]).toBeLessThan(sorted[sorted.length - 1])
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('development mode (non-HTTPS) sets Secure=false', () => {
|
|
138
|
+
expect(buildSessionCookies).toBeDefined()
|
|
139
|
+
const cookies = buildSessionCookies!(
|
|
140
|
+
'rct_access_token_value',
|
|
141
|
+
'rct_refresh_token_value',
|
|
142
|
+
{ secret: 'cookie-secret', secure: false },
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
// In dev mode, cookies should NOT have the Secure flag
|
|
146
|
+
for (const cookie of cookies) {
|
|
147
|
+
// Check that "Secure" is not present as a flag
|
|
148
|
+
// Note: "Secure" could appear in the cookie name or value,
|
|
149
|
+
// so we check for it as a standalone attribute
|
|
150
|
+
const parts = cookie.split(';').map(p => p.trim().toLowerCase())
|
|
151
|
+
const hasSecure = parts.some(p => p === 'secure')
|
|
152
|
+
expect(hasSecure).toBe(false)
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
})
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'bun:test'
|
|
2
|
+
import { Database } from 'bun:sqlite'
|
|
3
|
+
import {
|
|
4
|
+
hashPasswordAsync,
|
|
5
|
+
verifyPasswordAsync,
|
|
6
|
+
hashPassword,
|
|
7
|
+
verifyPassword,
|
|
8
|
+
} from '../auth/password'
|
|
9
|
+
import {
|
|
10
|
+
issueSessionToken,
|
|
11
|
+
resolveSessionToken,
|
|
12
|
+
reapExpiredSessions,
|
|
13
|
+
} from '../auth/session'
|
|
14
|
+
import { migrateDatabase } from '../db/sqlite'
|
|
15
|
+
|
|
16
|
+
function freshDb(): Database {
|
|
17
|
+
const db = new Database(':memory:')
|
|
18
|
+
migrateDatabase(db)
|
|
19
|
+
db.exec(
|
|
20
|
+
"INSERT OR IGNORE INTO users (id, username, password_hash, role, created_at) VALUES ('usr_test1', 'testuser', 'hash', 'admin', '2026-01-01T00:00:00Z')",
|
|
21
|
+
)
|
|
22
|
+
return db
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('hashPasswordAsync', () => {
|
|
26
|
+
test('returns a Promise<string> containing argon2id identifier', async () => {
|
|
27
|
+
const result = hashPasswordAsync('my-password')
|
|
28
|
+
expect(result).toBeInstanceOf(Promise)
|
|
29
|
+
const hash = await result
|
|
30
|
+
expect(typeof hash).toBe('string')
|
|
31
|
+
expect(hash).toContain('argon2id')
|
|
32
|
+
}, 15000)
|
|
33
|
+
|
|
34
|
+
test('produces same format as sync hashPassword', async () => {
|
|
35
|
+
const password = 'test-pw'
|
|
36
|
+
const asyncHash = await hashPasswordAsync(password)
|
|
37
|
+
// Both should be valid PHC format and verify correctly
|
|
38
|
+
expect(verifyPassword(password, asyncHash)).toBe(true)
|
|
39
|
+
}, 30000)
|
|
40
|
+
|
|
41
|
+
test('verifyPasswordAsync works with sync-generated hashes', async () => {
|
|
42
|
+
const password = 'cross-compat'
|
|
43
|
+
const syncHash = hashPassword(password)
|
|
44
|
+
expect(await verifyPasswordAsync(password, syncHash)).toBe(true)
|
|
45
|
+
expect(await verifyPasswordAsync('wrong', syncHash)).toBe(false)
|
|
46
|
+
}, 30000)
|
|
47
|
+
|
|
48
|
+
test('verifyPasswordAsync returns false for empty password', async () => {
|
|
49
|
+
const hash = hashPassword('pw')
|
|
50
|
+
expect(await verifyPasswordAsync('', hash)).toBe(false)
|
|
51
|
+
}, 15000)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('resolveSessionToken kind parameter', () => {
|
|
55
|
+
test('defaults to access kind — resolves access tokens', () => {
|
|
56
|
+
const db = freshDb()
|
|
57
|
+
const { accessToken } = issueSessionToken('usr_test1', db)
|
|
58
|
+
// Default kind='access' should resolve access tokens
|
|
59
|
+
expect(resolveSessionToken(accessToken, db)).toBe('usr_test1')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('defaults to access kind — rejects refresh tokens', () => {
|
|
63
|
+
const db = freshDb()
|
|
64
|
+
const { refreshToken } = issueSessionToken('usr_test1', db)
|
|
65
|
+
// Default kind='access' should NOT resolve refresh tokens
|
|
66
|
+
expect(resolveSessionToken(refreshToken, db)).toBeNull()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('kind=refresh resolves refresh tokens', () => {
|
|
70
|
+
const db = freshDb()
|
|
71
|
+
const { refreshToken } = issueSessionToken('usr_test1', db)
|
|
72
|
+
expect(resolveSessionToken(refreshToken, db, 'refresh')).toBe('usr_test1')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('kind=refresh rejects access tokens', () => {
|
|
76
|
+
const db = freshDb()
|
|
77
|
+
const { accessToken } = issueSessionToken('usr_test1', db)
|
|
78
|
+
expect(resolveSessionToken(accessToken, db, 'refresh')).toBeNull()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('kind=access explicitly resolves access tokens', () => {
|
|
82
|
+
const db = freshDb()
|
|
83
|
+
const { accessToken } = issueSessionToken('usr_test1', db)
|
|
84
|
+
expect(resolveSessionToken(accessToken, db, 'access')).toBe('usr_test1')
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('reapExpiredSessions', () => {
|
|
89
|
+
test('deletes expired tokens and returns count', () => {
|
|
90
|
+
const db = freshDb()
|
|
91
|
+
// Issue tokens (access expires in 1h, refresh in 30d)
|
|
92
|
+
issueSessionToken('usr_test1', db)
|
|
93
|
+
|
|
94
|
+
// Manually expire all tokens
|
|
95
|
+
db.exec("UPDATE session_tokens SET expires_at = '2020-01-01T00:00:00Z'")
|
|
96
|
+
|
|
97
|
+
const deleted = reapExpiredSessions(db)
|
|
98
|
+
expect(deleted).toBe(2) // access + refresh
|
|
99
|
+
|
|
100
|
+
// Verify tokens are gone
|
|
101
|
+
const remaining = db
|
|
102
|
+
.query('SELECT COUNT(*) as cnt FROM session_tokens')
|
|
103
|
+
.get() as { cnt: number }
|
|
104
|
+
expect(remaining.cnt).toBe(0)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('does not delete non-expired tokens', () => {
|
|
108
|
+
const db = freshDb()
|
|
109
|
+
issueSessionToken('usr_test1', db)
|
|
110
|
+
|
|
111
|
+
const deleted = reapExpiredSessions(db)
|
|
112
|
+
expect(deleted).toBe(0)
|
|
113
|
+
|
|
114
|
+
const remaining = db
|
|
115
|
+
.query('SELECT COUNT(*) as cnt FROM session_tokens')
|
|
116
|
+
.get() as { cnt: number }
|
|
117
|
+
expect(remaining.cnt).toBe(2)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('returns 0 when no tokens exist', () => {
|
|
121
|
+
const db = freshDb()
|
|
122
|
+
expect(reapExpiredSessions(db)).toBe(0)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('schema indexes', () => {
|
|
127
|
+
test('indexes are created after migration', () => {
|
|
128
|
+
const db = freshDb()
|
|
129
|
+
const indexes = db
|
|
130
|
+
.query(
|
|
131
|
+
"SELECT name FROM sqlite_master WHERE type='index' AND name LIKE 'idx_%'",
|
|
132
|
+
)
|
|
133
|
+
.all() as Array<{ name: string }>
|
|
134
|
+
const names = indexes.map(i => i.name)
|
|
135
|
+
expect(names).toContain('idx_session_owners_owner')
|
|
136
|
+
expect(names).toContain('idx_session_owners_session')
|
|
137
|
+
expect(names).toContain('idx_team_members_user')
|
|
138
|
+
expect(names).toContain('idx_team_members_team')
|
|
139
|
+
expect(names).toContain('idx_session_shares_user')
|
|
140
|
+
expect(names).toContain('idx_session_shares_team')
|
|
141
|
+
expect(names).toContain('idx_session_shares_session')
|
|
142
|
+
expect(names).toContain('idx_sessions_visibility')
|
|
143
|
+
expect(names).toContain('idx_sessions_environment')
|
|
144
|
+
expect(names).toContain('idx_session_tokens_user')
|
|
145
|
+
expect(names).toContain('idx_environments_team')
|
|
146
|
+
})
|
|
147
|
+
})
|