@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,10 +1,10 @@
|
|
|
1
|
-
import { createHash, timingSafeEqual } from 'node:crypto'
|
|
2
1
|
import type { Context, Next } from 'hono'
|
|
3
2
|
import { validateApiKey } from './api-key'
|
|
4
3
|
import { verifyWorkerJwt } from './jwt'
|
|
5
4
|
import { resolveToken } from './token'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { resolveSessionToken } from './session'
|
|
6
|
+
import { getDb } from '../db/sqlite'
|
|
7
|
+
import { SYSTEM_USER_ID } from './constants'
|
|
8
8
|
|
|
9
9
|
const WS_AUTH_PROTOCOL_PREFIX = 'rcs.auth.'
|
|
10
10
|
|
|
@@ -149,177 +149,87 @@ export async function acceptCliHeaders(c: Context, next: Next) {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
|
-
*
|
|
153
|
-
*/
|
|
154
|
-
export function getUuidFromRequest(c: Context): string | undefined {
|
|
155
|
-
return c.req.query('uuid') || c.req.header('X-UUID')
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Environment statuses in which the worker is gone and the per-environment
|
|
160
|
-
* worker token no longer gates access. The token's purpose is to gate Web UI
|
|
161
|
-
* access while a worker is live; once the worker is offline/disconnected/
|
|
162
|
-
* deregistered, the owner UUID (and admin API key) is sufficient — otherwise
|
|
163
|
-
* users can't delete or inspect leftover sessions from a dead worker.
|
|
164
|
-
*/
|
|
165
|
-
const ENV_DEAD_STATUSES = new Set(['offline', 'disconnected', 'deregistered'])
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Verify a plaintext worker token against an environment's stored hash.
|
|
169
|
-
* Returns true if: token is a valid admin API key (full access bypass), OR
|
|
170
|
-
* environment has no workerTokenHash (open access), OR env is offline/
|
|
171
|
-
* disconnected/deregistered (worker gone, token no longer meaningful), OR
|
|
172
|
-
* hash matches. False otherwise (including timing-safe mismatch).
|
|
173
|
-
*/
|
|
174
|
-
export function verifyWorkerTokenForEnv(
|
|
175
|
-
env:
|
|
176
|
-
| { workerTokenHash: string | null; status?: string | null }
|
|
177
|
-
| null
|
|
178
|
-
| undefined,
|
|
179
|
-
token: string | undefined,
|
|
180
|
-
): boolean {
|
|
181
|
-
// Admin API key bypasses per-environment token check (full access).
|
|
182
|
-
if (token && validateApiKey(token)) return true
|
|
183
|
-
// No environment or no token hash registered = open access.
|
|
184
|
-
if (!env || !env.workerTokenHash) return true
|
|
185
|
-
// Worker gone — token no longer gates access; UUID/auth still applies upstream.
|
|
186
|
-
if (env.status && ENV_DEAD_STATUSES.has(env.status)) return true
|
|
187
|
-
if (!token) return false
|
|
188
|
-
const tokenHash = createHash('sha256').update(token).digest('hex')
|
|
189
|
-
const a = Buffer.from(tokenHash, 'utf8')
|
|
190
|
-
const b = Buffer.from(env.workerTokenHash, 'utf8')
|
|
191
|
-
return a.length === b.length && timingSafeEqual(a, b)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Per-environment worker token verification.
|
|
152
|
+
* Session-based authentication (Phase 1+).
|
|
196
153
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
154
|
+
* Priority:
|
|
155
|
+
* 1. Admin API key bypass (Bearer token matching RCS_API_KEYS) → SYSTEM_USER_ID
|
|
156
|
+
* (operations via API key are attributed to the system, not a specific admin)
|
|
157
|
+
* If DB is unavailable, returns 503 to avoid silent misattribution.
|
|
158
|
+
* 2. Cookie access token (rcs_access) — in-memory or SQLite resolution
|
|
159
|
+
* 3. Bearer session token — in-memory or SQLite resolution
|
|
160
|
+
* 4. 401 if no valid credentials
|
|
202
161
|
*
|
|
203
|
-
*
|
|
204
|
-
* Authorization: Bearer (admin API key bypass only). Routes must have :id
|
|
205
|
-
* (session id) param so the environment can be resolved.
|
|
162
|
+
* Injects `userId` (string) and `isAdmin` (boolean) into context.
|
|
206
163
|
*/
|
|
207
|
-
export async function
|
|
208
|
-
const token = c.req.header('X-Worker-Token') || c.req.query('worker_token')
|
|
209
|
-
// Admin API key bypasses per-environment token check (full access).
|
|
210
|
-
if (token && validateApiKey(token)) {
|
|
211
|
-
await next()
|
|
212
|
-
return
|
|
213
|
-
}
|
|
214
|
-
// Fall back to Authorization: Bearer — the Web UI's API Key tab only sends
|
|
215
|
-
// Bearer, so without this branch the admin bypass never fires for browser
|
|
216
|
-
// session routes.
|
|
164
|
+
export async function sessionAuth(c: Context, next: Next) {
|
|
217
165
|
const bearer = extractBearerToken(c)
|
|
218
|
-
if (bearer && validateApiKey(bearer)) {
|
|
219
|
-
await next()
|
|
220
|
-
return
|
|
221
|
-
}
|
|
222
|
-
const requestedSessionId = c.req.param('id')
|
|
223
|
-
if (!requestedSessionId) {
|
|
224
|
-
return c.json(
|
|
225
|
-
{ error: { type: 'bad_request', message: 'Missing session id' } },
|
|
226
|
-
400,
|
|
227
|
-
)
|
|
228
|
-
}
|
|
229
|
-
const uuid = c.get('uuid')
|
|
230
|
-
const resolvedSessionId = uuid
|
|
231
|
-
? resolveOwnedWebSessionId(requestedSessionId, uuid)
|
|
232
|
-
: requestedSessionId
|
|
233
|
-
if (!resolvedSessionId) {
|
|
234
|
-
return c.json(
|
|
235
|
-
{ error: { type: 'forbidden', message: 'Not your session' } },
|
|
236
|
-
403,
|
|
237
|
-
)
|
|
238
|
-
}
|
|
239
|
-
const session = getSession(resolvedSessionId)
|
|
240
|
-
if (!session) {
|
|
241
|
-
return c.json(
|
|
242
|
-
{ error: { type: 'not_found', message: 'Session not found' } },
|
|
243
|
-
404,
|
|
244
|
-
)
|
|
245
|
-
}
|
|
246
|
-
// Session without environment_id = no per-environment token to verify,
|
|
247
|
-
// open access (test/legacy sessions). Only verify when an environment is
|
|
248
|
-
// linked AND has a workerTokenHash registered.
|
|
249
|
-
if (!session.environment_id) {
|
|
250
|
-
await next()
|
|
251
|
-
return
|
|
252
|
-
}
|
|
253
|
-
const env = storeGetEnvironment(session.environment_id)
|
|
254
|
-
if (!env) {
|
|
255
|
-
return c.json(
|
|
256
|
-
{ error: { type: 'not_found', message: 'Environment not found' } },
|
|
257
|
-
404,
|
|
258
|
-
)
|
|
259
|
-
}
|
|
260
|
-
// No token hash registered = open access (legacy/claude.ai path).
|
|
261
|
-
if (!env.workerTokenHash) {
|
|
262
|
-
await next()
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
// Worker gone — token no longer gates access. Lets users delete/inspect
|
|
266
|
-
// leftover sessions from a dead worker (env offline/disconnected/deregistered).
|
|
267
|
-
if (ENV_DEAD_STATUSES.has(env.status)) {
|
|
268
|
-
await next()
|
|
269
|
-
return
|
|
270
|
-
}
|
|
271
|
-
if (!token) {
|
|
272
|
-
return c.json(
|
|
273
|
-
{
|
|
274
|
-
error: {
|
|
275
|
-
type: 'worker_token_required',
|
|
276
|
-
message:
|
|
277
|
-
'该 environment 需要 worker token 才能访问,请输入 RCS_WORKER_TOKENS 对应的 token',
|
|
278
|
-
},
|
|
279
|
-
},
|
|
280
|
-
401,
|
|
281
|
-
)
|
|
282
|
-
}
|
|
283
|
-
const tokenHash = createHash('sha256').update(token).digest('hex')
|
|
284
|
-
const a = Buffer.from(tokenHash, 'utf8')
|
|
285
|
-
const b = Buffer.from(env.workerTokenHash, 'utf8')
|
|
286
|
-
if (a.length !== b.length || !timingSafeEqual(a, b)) {
|
|
287
|
-
return c.json(
|
|
288
|
-
{
|
|
289
|
-
error: {
|
|
290
|
-
type: 'worker_token_mismatch',
|
|
291
|
-
message: 'Worker token 不匹配',
|
|
292
|
-
},
|
|
293
|
-
},
|
|
294
|
-
403,
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
await next()
|
|
298
|
-
}
|
|
299
166
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
export async function uuidAuth(c: Context, next: Next) {
|
|
305
|
-
// Try API key auth via Authorization header
|
|
306
|
-
const bearer = extractBearerToken(c)
|
|
167
|
+
// Admin API key bypass — attribute to __system__ identity.
|
|
168
|
+
// Verify DB connectivity first: if the DB is unreachable we cannot
|
|
169
|
+
// reliably serve requests, so return 503 rather than silently
|
|
170
|
+
// impersonating a system identity without audit trail.
|
|
307
171
|
if (bearer && validateApiKey(bearer)) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
172
|
+
try {
|
|
173
|
+
const db = getDb()
|
|
174
|
+
// Verify the DB is reachable with a simple query
|
|
175
|
+
db.query('SELECT 1').get()
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.warn('[sessionAuth] DB unavailable for API key auth:', err)
|
|
178
|
+
return c.json(
|
|
179
|
+
{
|
|
180
|
+
error: {
|
|
181
|
+
type: 'service_unavailable',
|
|
182
|
+
message: 'Database unavailable — cannot process API key request',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
503,
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
c.set('userId', SYSTEM_USER_ID)
|
|
189
|
+
c.set('isAdmin', true)
|
|
311
190
|
await next()
|
|
312
191
|
return
|
|
313
192
|
}
|
|
314
193
|
|
|
315
|
-
//
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
194
|
+
// Try cookie-based token
|
|
195
|
+
const cookieHeader = c.req.header('Cookie') || ''
|
|
196
|
+
const accessMatch = cookieHeader.match(/rcs_access=([^;]+)/)
|
|
197
|
+
const cookieToken = accessMatch ? accessMatch[1] : undefined
|
|
198
|
+
|
|
199
|
+
const tokenToResolve = cookieToken || bearer
|
|
200
|
+
|
|
201
|
+
if (tokenToResolve) {
|
|
202
|
+
// Try in-memory token resolution (existing issueToken flow)
|
|
203
|
+
const username = resolveToken(tokenToResolve)
|
|
204
|
+
if (username) {
|
|
205
|
+
c.set('userId', username)
|
|
206
|
+
c.set('isAdmin', false)
|
|
207
|
+
await next()
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Try SQLite-backed session token resolution
|
|
212
|
+
try {
|
|
213
|
+
const db = getDb()
|
|
214
|
+
const userId = resolveSessionToken(tokenToResolve, db)
|
|
215
|
+
if (userId) {
|
|
216
|
+
c.set('userId', userId)
|
|
217
|
+
c.set('isAdmin', false)
|
|
218
|
+
await next()
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.warn('[sessionAuth] DB resolution failed:', err)
|
|
223
|
+
}
|
|
322
224
|
}
|
|
323
|
-
|
|
324
|
-
|
|
225
|
+
|
|
226
|
+
return c.json(
|
|
227
|
+
{
|
|
228
|
+
error: {
|
|
229
|
+
type: 'unauthorized',
|
|
230
|
+
message: 'Invalid or missing credentials',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
401,
|
|
234
|
+
)
|
|
325
235
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { argon2id } from '@noble/hashes/argon2.js'
|
|
2
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto'
|
|
3
|
+
|
|
4
|
+
// Argon2id parameters
|
|
5
|
+
const MEMORY_COST = 65536 // 64 MB
|
|
6
|
+
const TIME_COST = 3
|
|
7
|
+
const PARALLELISM = 1
|
|
8
|
+
const SALT_LENGTH = 16
|
|
9
|
+
const HASH_LENGTH = 32
|
|
10
|
+
|
|
11
|
+
function base64NoPadding(buf: Uint8Array): string {
|
|
12
|
+
return Buffer.from(buf).toString('base64').replace(/=+$/, '')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fromBase64NoPadding(s: string): Buffer {
|
|
16
|
+
// Add padding back if needed
|
|
17
|
+
const pad = s.length % 4
|
|
18
|
+
const padded = pad ? s + '='.repeat(4 - pad) : s
|
|
19
|
+
return Buffer.from(padded, 'base64')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Hash a password using argon2id.
|
|
24
|
+
* Returns a PHC-formatted string:
|
|
25
|
+
* $argon2id$v=19$m=65536,t=3,p=1$<salt>$<hash>
|
|
26
|
+
*
|
|
27
|
+
* @deprecated Use {@link hashPasswordAsync} instead. This synchronous version
|
|
28
|
+
* blocks the event loop for 100-300ms due to argon2id computation. Kept for
|
|
29
|
+
* backward compatibility; will be removed in a future release.
|
|
30
|
+
*/
|
|
31
|
+
export function hashPassword(password: string): string {
|
|
32
|
+
const salt = randomBytes(SALT_LENGTH)
|
|
33
|
+
const hash = argon2id(password, salt, {
|
|
34
|
+
m: MEMORY_COST,
|
|
35
|
+
t: TIME_COST,
|
|
36
|
+
p: PARALLELISM,
|
|
37
|
+
})
|
|
38
|
+
const saltB64 = base64NoPadding(salt)
|
|
39
|
+
const hashB64 = base64NoPadding(hash)
|
|
40
|
+
return `$argon2id$v=19$m=${MEMORY_COST},t=${TIME_COST},p=${PARALLELISM}$${saltB64}$${hashB64}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Verify a password against an argon2id hash string.
|
|
45
|
+
* Returns true if the password matches.
|
|
46
|
+
*
|
|
47
|
+
* @deprecated Use {@link verifyPasswordAsync} instead. This synchronous version
|
|
48
|
+
* blocks the event loop for 100-300ms due to argon2id computation. Kept for
|
|
49
|
+
* backward compatibility; will be removed in a future release.
|
|
50
|
+
*/
|
|
51
|
+
export function verifyPassword(password: string, hash: string): boolean {
|
|
52
|
+
if (!password || !hash) return false
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Parse PHC format: $argon2id$v=19$m=65536,t=3,p=1$<salt>$<hash>
|
|
56
|
+
const parts = hash.split('$')
|
|
57
|
+
// parts[0] = '' (empty before first $)
|
|
58
|
+
// parts[1] = 'argon2id'
|
|
59
|
+
// parts[2] = 'v=19'
|
|
60
|
+
// parts[3] = 'm=65536,t=3,p=1'
|
|
61
|
+
// parts[4] = salt (base64)
|
|
62
|
+
// parts[5] = hash (base64)
|
|
63
|
+
if (parts.length !== 6 || parts[1] !== 'argon2id') return false
|
|
64
|
+
|
|
65
|
+
const paramParts = parts[3].split(',')
|
|
66
|
+
const params: Record<string, number> = {}
|
|
67
|
+
for (const p of paramParts) {
|
|
68
|
+
const [key, val] = p.split('=')
|
|
69
|
+
params[key] = parseInt(val, 10)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const salt = fromBase64NoPadding(parts[4])
|
|
73
|
+
const expectedHash = fromBase64NoPadding(parts[5])
|
|
74
|
+
|
|
75
|
+
const candidateHash = argon2id(password, salt, {
|
|
76
|
+
m: params.m || MEMORY_COST,
|
|
77
|
+
t: params.t || TIME_COST,
|
|
78
|
+
p: params.p || PARALLELISM,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const a = Buffer.from(candidateHash)
|
|
82
|
+
const b = Buffer.from(expectedHash)
|
|
83
|
+
if (a.length !== b.length) return false
|
|
84
|
+
return timingSafeEqual(a, b)
|
|
85
|
+
} catch {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Async version of {@link hashPassword}.
|
|
92
|
+
*
|
|
93
|
+
* Currently delegates to the synchronous implementation (argon2id is CPU-bound
|
|
94
|
+
* and login/signup are low-frequency operations, so brief blocking is
|
|
95
|
+
* acceptable). The async signature allows callers to `await` and makes it
|
|
96
|
+
* straightforward to swap in a Worker-based implementation later without
|
|
97
|
+
* touching call sites.
|
|
98
|
+
*/
|
|
99
|
+
export async function hashPasswordAsync(password: string): Promise<string> {
|
|
100
|
+
// Yield once to avoid blocking the microtask queue before the heavy compute.
|
|
101
|
+
await Promise.resolve()
|
|
102
|
+
return hashPassword(password)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Async version of {@link verifyPassword}.
|
|
107
|
+
*
|
|
108
|
+
* See {@link hashPasswordAsync} for the design rationale.
|
|
109
|
+
*/
|
|
110
|
+
export async function verifyPasswordAsync(
|
|
111
|
+
password: string,
|
|
112
|
+
hash: string,
|
|
113
|
+
): Promise<boolean> {
|
|
114
|
+
await Promise.resolve()
|
|
115
|
+
return verifyPassword(password, hash)
|
|
116
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto'
|
|
2
|
+
import type { Database } from 'bun:sqlite'
|
|
3
|
+
|
|
4
|
+
const ACCESS_TTL_SECONDS = 3600 // 1 hour
|
|
5
|
+
const REFRESH_TTL_SECONDS = 2592000 // 30 days
|
|
6
|
+
|
|
7
|
+
function generateToken(): string {
|
|
8
|
+
const bytes = randomBytes(32)
|
|
9
|
+
return `rct_${bytes.toString('hex')}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function sha256hex(value: string): string {
|
|
13
|
+
return createHash('sha256').update(value).digest('hex')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function insertToken(
|
|
17
|
+
db: Database,
|
|
18
|
+
token: string,
|
|
19
|
+
userId: string,
|
|
20
|
+
kind: 'access' | 'refresh',
|
|
21
|
+
ttlSeconds: number,
|
|
22
|
+
): void {
|
|
23
|
+
const tokenHash = sha256hex(token)
|
|
24
|
+
const now = new Date()
|
|
25
|
+
const expiresAt = new Date(now.getTime() + ttlSeconds * 1000)
|
|
26
|
+
db.query(
|
|
27
|
+
`INSERT INTO session_tokens (token_hash, user_id, kind, expires_at, created_at)
|
|
28
|
+
VALUES ($tokenHash, $userId, $kind, $expiresAt, $createdAt)`,
|
|
29
|
+
).run({
|
|
30
|
+
$tokenHash: tokenHash,
|
|
31
|
+
$userId: userId,
|
|
32
|
+
$kind: kind,
|
|
33
|
+
$expiresAt: expiresAt.toISOString(),
|
|
34
|
+
$createdAt: now.toISOString(),
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Issue a new access + refresh token pair for the given user.
|
|
40
|
+
* Stores sha256(token) in the session_tokens table.
|
|
41
|
+
*/
|
|
42
|
+
export function issueSessionToken(
|
|
43
|
+
userId: string,
|
|
44
|
+
db: Database,
|
|
45
|
+
): { accessToken: string; refreshToken: string } {
|
|
46
|
+
const accessToken = generateToken()
|
|
47
|
+
const refreshToken = generateToken()
|
|
48
|
+
|
|
49
|
+
insertToken(db, accessToken, userId, 'access', ACCESS_TTL_SECONDS)
|
|
50
|
+
insertToken(db, refreshToken, userId, 'refresh', REFRESH_TTL_SECONDS)
|
|
51
|
+
|
|
52
|
+
return { accessToken, refreshToken }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a session token to a userId.
|
|
57
|
+
* Returns null if token is invalid, expired, or revoked.
|
|
58
|
+
*
|
|
59
|
+
* @param kind - The expected token kind. Defaults to `'access'`.
|
|
60
|
+
* Pass `'refresh'` when resolving a refresh token (e.g. in the
|
|
61
|
+
* `/refresh` endpoint). This prevents an access token from being
|
|
62
|
+
* accepted where a refresh token is expected, and vice versa.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveSessionToken(
|
|
65
|
+
token: string,
|
|
66
|
+
db: Database,
|
|
67
|
+
kind: 'access' | 'refresh' = 'access',
|
|
68
|
+
): string | null {
|
|
69
|
+
if (!token) return null
|
|
70
|
+
|
|
71
|
+
const tokenHash = sha256hex(token)
|
|
72
|
+
const row = db
|
|
73
|
+
.query(
|
|
74
|
+
`SELECT user_id, expires_at, revoked_at FROM session_tokens
|
|
75
|
+
WHERE token_hash = $tokenHash AND kind = $kind`,
|
|
76
|
+
)
|
|
77
|
+
.get({ $tokenHash: tokenHash, $kind: kind }) as {
|
|
78
|
+
user_id: string
|
|
79
|
+
expires_at: string
|
|
80
|
+
revoked_at: string | null
|
|
81
|
+
} | null
|
|
82
|
+
|
|
83
|
+
if (!row) return null
|
|
84
|
+
|
|
85
|
+
// Check revoked
|
|
86
|
+
if (row.revoked_at) return null
|
|
87
|
+
|
|
88
|
+
// Check expired
|
|
89
|
+
const expiresAt = new Date(row.expires_at).getTime()
|
|
90
|
+
if (expiresAt < Date.now()) return null
|
|
91
|
+
|
|
92
|
+
return row.user_id
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Revoke a specific session token.
|
|
97
|
+
*/
|
|
98
|
+
export function revokeSessionToken(token: string, db: Database): void {
|
|
99
|
+
const tokenHash = sha256hex(token)
|
|
100
|
+
const now = new Date().toISOString()
|
|
101
|
+
db.query(
|
|
102
|
+
`UPDATE session_tokens SET revoked_at = $now WHERE token_hash = $tokenHash`,
|
|
103
|
+
).run({ $now: now, $tokenHash: tokenHash })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Revoke all tokens for a given user.
|
|
108
|
+
*/
|
|
109
|
+
export function revokeAllUserTokens(userId: string, db: Database): void {
|
|
110
|
+
const now = new Date().toISOString()
|
|
111
|
+
db.query(
|
|
112
|
+
`UPDATE session_tokens SET revoked_at = $now WHERE user_id = $userId`,
|
|
113
|
+
).run({ $now: now, $userId: userId })
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Delete all expired session tokens from the database.
|
|
118
|
+
* Returns the number of deleted rows.
|
|
119
|
+
*
|
|
120
|
+
* Should be called once at server startup and optionally on a periodic
|
|
121
|
+
* timer (e.g. every hour) to keep the session_tokens table tidy.
|
|
122
|
+
*/
|
|
123
|
+
export function reapExpiredSessions(db: Database): number {
|
|
124
|
+
const result = db
|
|
125
|
+
.query(
|
|
126
|
+
`DELETE FROM session_tokens WHERE expires_at < strftime('%Y-%m-%dT%H:%M:%S.000Z', 'now')`,
|
|
127
|
+
)
|
|
128
|
+
.run()
|
|
129
|
+
return result.changes
|
|
130
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
-- RCS Authentication & Session Schema
|
|
2
|
+
-- Phase 1: users, session_tokens, invitations
|
|
3
|
+
-- Phase 2: teams, team_members, team_environments, session_owners, session_shares, sessions
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
6
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
7
|
+
username TEXT UNIQUE NOT NULL,
|
|
8
|
+
password_hash TEXT NOT NULL,
|
|
9
|
+
role TEXT NOT NULL CHECK (role IN ('admin', 'member', 'guest')),
|
|
10
|
+
created_at TEXT NOT NULL,
|
|
11
|
+
last_login_at TEXT
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE TABLE IF NOT EXISTS session_tokens (
|
|
15
|
+
token_hash TEXT NOT NULL PRIMARY KEY,
|
|
16
|
+
user_id TEXT NOT NULL REFERENCES users(id),
|
|
17
|
+
kind TEXT NOT NULL CHECK (kind IN ('access', 'refresh')),
|
|
18
|
+
expires_at TEXT NOT NULL,
|
|
19
|
+
revoked_at TEXT,
|
|
20
|
+
created_at TEXT NOT NULL
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS invitations (
|
|
24
|
+
token_hash TEXT NOT NULL PRIMARY KEY,
|
|
25
|
+
team_id TEXT,
|
|
26
|
+
role TEXT NOT NULL,
|
|
27
|
+
expires_at TEXT NOT NULL,
|
|
28
|
+
max_uses INTEGER DEFAULT 1,
|
|
29
|
+
uses INTEGER DEFAULT 0,
|
|
30
|
+
created_by TEXT NOT NULL REFERENCES users(id),
|
|
31
|
+
consumed_at TEXT,
|
|
32
|
+
consumed_by TEXT REFERENCES users(id)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
-- Phase 2 tables
|
|
36
|
+
|
|
37
|
+
CREATE TABLE IF NOT EXISTS teams (
|
|
38
|
+
id TEXT PRIMARY KEY,
|
|
39
|
+
name TEXT NOT NULL,
|
|
40
|
+
slug TEXT UNIQUE NOT NULL,
|
|
41
|
+
description TEXT,
|
|
42
|
+
created_by TEXT NOT NULL REFERENCES users(id),
|
|
43
|
+
created_at TEXT NOT NULL,
|
|
44
|
+
updated_at TEXT NOT NULL,
|
|
45
|
+
deleted_at TEXT
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS team_members (
|
|
49
|
+
team_id TEXT NOT NULL REFERENCES teams(id),
|
|
50
|
+
user_id TEXT NOT NULL REFERENCES users(id),
|
|
51
|
+
role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member')),
|
|
52
|
+
added_at TEXT NOT NULL,
|
|
53
|
+
added_by TEXT NOT NULL REFERENCES users(id),
|
|
54
|
+
PRIMARY KEY (team_id, user_id)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
CREATE TABLE IF NOT EXISTS team_environments (
|
|
58
|
+
team_id TEXT NOT NULL REFERENCES teams(id),
|
|
59
|
+
environment_id TEXT NOT NULL,
|
|
60
|
+
PRIMARY KEY (team_id, environment_id)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
CREATE TABLE IF NOT EXISTS session_owners (
|
|
64
|
+
session_id TEXT NOT NULL,
|
|
65
|
+
owner_type TEXT NOT NULL CHECK (owner_type IN ('user', 'team')),
|
|
66
|
+
owner_id TEXT NOT NULL,
|
|
67
|
+
PRIMARY KEY (session_id, owner_type, owner_id)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
CREATE TABLE IF NOT EXISTS session_shares (
|
|
71
|
+
id TEXT PRIMARY KEY,
|
|
72
|
+
session_id TEXT NOT NULL,
|
|
73
|
+
granted_to_user TEXT REFERENCES users(id),
|
|
74
|
+
granted_to_team TEXT REFERENCES teams(id),
|
|
75
|
+
permission TEXT NOT NULL CHECK (permission IN ('read', 'write')),
|
|
76
|
+
expires_at TEXT,
|
|
77
|
+
granted_by TEXT NOT NULL REFERENCES users(id),
|
|
78
|
+
granted_at TEXT NOT NULL
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
CREATE TABLE IF NOT EXISTS environments (
|
|
82
|
+
id TEXT PRIMARY KEY,
|
|
83
|
+
secret_hash TEXT NOT NULL,
|
|
84
|
+
team_id TEXT,
|
|
85
|
+
registered_by TEXT,
|
|
86
|
+
machine_name TEXT,
|
|
87
|
+
worker_type TEXT NOT NULL DEFAULT 'claude_code',
|
|
88
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
89
|
+
last_poll_at TEXT,
|
|
90
|
+
created_at TEXT NOT NULL
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
94
|
+
id TEXT PRIMARY KEY,
|
|
95
|
+
environment_id TEXT,
|
|
96
|
+
title TEXT,
|
|
97
|
+
status TEXT NOT NULL DEFAULT 'idle',
|
|
98
|
+
source TEXT NOT NULL DEFAULT 'remote-control',
|
|
99
|
+
visibility TEXT NOT NULL DEFAULT 'private',
|
|
100
|
+
permission_mode TEXT,
|
|
101
|
+
created_at TEXT NOT NULL,
|
|
102
|
+
updated_at TEXT NOT NULL
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
-- Performance indexes
|
|
106
|
+
CREATE INDEX IF NOT EXISTS idx_session_owners_owner ON session_owners(owner_type, owner_id);
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_session_owners_session ON session_owners(session_id);
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_team_members_user ON team_members(user_id);
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_team_members_team ON team_members(team_id);
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_session_shares_user ON session_shares(granted_to_user);
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_session_shares_team ON session_shares(granted_to_team);
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_session_shares_session ON session_shares(session_id);
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_visibility ON sessions(visibility);
|
|
114
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_environment ON sessions(environment_id);
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_session_tokens_user ON session_tokens(user_id);
|
|
116
|
+
CREATE INDEX IF NOT EXISTS idx_environments_team ON environments(team_id);
|