@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.
Files changed (219) hide show
  1. package/dist/cli.js +336 -333
  2. package/dist/rcs-web/assets/Dashboard-BwotBWlJ.js +1 -0
  3. package/dist/rcs-web/assets/Join-4yrksbdX.js +1 -0
  4. package/dist/rcs-web/assets/Login-Cxm_yEeE.js +1 -0
  5. package/dist/rcs-web/assets/SessionDetail-DNdfPOYy.js +50 -0
  6. package/dist/rcs-web/assets/Setup-BiAAxugt.js +1 -0
  7. package/dist/rcs-web/assets/ShareLanding-CP3Z6ckr.js +1 -0
  8. package/dist/rcs-web/assets/TeamCreate-DgdCwRSb.js +1 -0
  9. package/dist/rcs-web/assets/TeamDetail-CkNTFoMB.js +11 -0
  10. package/dist/rcs-web/assets/TeamList-Bf0sQ5RQ.js +1 -0
  11. package/dist/rcs-web/assets/TeamSettings-BaNAfXU3.js +1 -0
  12. package/dist/rcs-web/assets/UserSettings-CsutQjxG.js +1 -0
  13. package/{packages/remote-control-server/web/dist/assets/arc-D90597o-.js → dist/rcs-web/assets/arc-BxxmNQO6.js} +1 -1
  14. package/dist/rcs-web/assets/architectureDiagram-3BPJPVTR-DBjkRdbd.js +36 -0
  15. package/dist/rcs-web/assets/{blockDiagram-GPEHLZMM-DHd8IbOs.js → blockDiagram-GPEHLZMM-BF6COWlM.js} +2 -2
  16. package/dist/rcs-web/assets/{c4Diagram-AAUBKEIU-CjFkQJ9h.js → c4Diagram-AAUBKEIU-DHOf390v.js} +2 -2
  17. package/dist/rcs-web/assets/channel-JsvbMQjW.js +1 -0
  18. package/dist/rcs-web/assets/{chunk-2J33WTMH-C21XWnGZ.js → chunk-2J33WTMH-CeJN8yjM.js} +1 -1
  19. package/dist/rcs-web/assets/{chunk-4BX2VUAB-B3SXiJlt.js → chunk-4BX2VUAB-jP76_P2A.js} +1 -1
  20. package/dist/rcs-web/assets/{chunk-55IACEB6-DjImv45F.js → chunk-55IACEB6-BsFA6emB.js} +1 -1
  21. package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-DtnaqsyD.js → dist/rcs-web/assets/chunk-727SXJPM-BF4HrQjZ.js} +1 -1
  22. package/dist/rcs-web/assets/{chunk-AQP2D5EJ-BVFgH5mS.js → chunk-AQP2D5EJ-DToJ74Kc.js} +1 -1
  23. package/dist/rcs-web/assets/{chunk-FMBD7UC4-Bl8jgb57.js → chunk-FMBD7UC4-DVTARgsa.js} +1 -1
  24. package/dist/rcs-web/assets/{chunk-ND2GUHAM-BpK111zh.js → chunk-ND2GUHAM-C50Gqv9c.js} +1 -1
  25. package/dist/rcs-web/assets/{chunk-QZHKN3VN-ruhIHBim.js → chunk-QZHKN3VN-EsIIh6zV.js} +1 -1
  26. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-BXn8Uc4c.js +1 -0
  27. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-BXn8Uc4c.js +1 -0
  28. package/dist/rcs-web/assets/code-block-IT6T5CEO-BnufHNTT.js +2 -0
  29. package/dist/rcs-web/assets/cose-bilkent-S5V4N54A-M6tNt14I.js +1 -0
  30. package/dist/rcs-web/assets/dagre-BM42HDAG-DaTm29qG.js +4 -0
  31. package/dist/rcs-web/assets/diagram-2AECGRRQ-fqoeOvq2.js +43 -0
  32. package/dist/rcs-web/assets/diagram-5GNKFQAL-6s2CiaGG.js +10 -0
  33. package/dist/rcs-web/assets/{diagram-KO2AKTUF-Uy3PDP5S.js → diagram-KO2AKTUF-C0aw_N95.js} +2 -2
  34. package/dist/rcs-web/assets/diagram-LMA3HP47-C8_Rv3rB.js +24 -0
  35. package/dist/rcs-web/assets/diagram-OG6HWLK6-Dae4u8Uc.js +24 -0
  36. package/{packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-sre_MTeK.js → dist/rcs-web/assets/erDiagram-TEJ5UH35-CosS5VWr.js} +13 -13
  37. package/dist/rcs-web/assets/{flowDiagram-I6XJVG4X-DwrWaLoe.js → flowDiagram-I6XJVG4X-D_Mxs46-.js} +2 -2
  38. package/dist/rcs-web/assets/ganttDiagram-6RSMTGT7-B0jy8Am_.js +292 -0
  39. package/dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-CSdu2HVI.js +106 -0
  40. package/dist/rcs-web/assets/i18n-Dbt-vEQ3.js +1 -0
  41. package/dist/rcs-web/assets/infoDiagram-5YYISTIA-Dcbc9L1S.js +2 -0
  42. package/{packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-SO0nn3--.js → dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-BLMjFE4t.js} +2 -2
  43. package/dist/rcs-web/assets/{journeyDiagram-JHISSGLW-D4igFUd4.js → journeyDiagram-JHISSGLW-Dw4nh1xI.js} +2 -2
  44. package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-Cm_yKsZC.js → kanban-definition-UN3LZRKU-DkU3j_ly.js} +2 -2
  45. package/dist/rcs-web/assets/{linear-4IMGll_E.js → linear-N5Cify7M.js} +1 -1
  46. package/dist/rcs-web/assets/main-30YqUvTd.js +238 -0
  47. package/dist/rcs-web/assets/main-EP6Lac2m.css +1 -0
  48. package/{packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-D_yQ7nAd.js → dist/rcs-web/assets/mermaid-VLURNSYL-gfiJUpl_.js} +5 -5
  49. package/dist/rcs-web/assets/{mermaid.core-B1qOUdiU.js → mermaid.core-B_Xc65Bf.js} +5 -5
  50. package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-yETom5qt.js → mindmap-definition-RKZ34NQL-DUavHfNu.js} +2 -2
  51. package/{packages/remote-control-server/web/dist/assets/pieDiagram-4H26LBE5-KIhqXGT6.js → dist/rcs-web/assets/pieDiagram-4H26LBE5-DSJaToTU.js} +2 -2
  52. package/{packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-DISqmoA6.js → dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-BFp6TnHj.js} +2 -2
  53. package/dist/rcs-web/assets/{requirementDiagram-4Y6WPE33-DsgT4ptk.js → requirementDiagram-4Y6WPE33-eil6F3-O.js} +2 -2
  54. package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-Dn67kbn0.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-DbGhoepK.js} +2 -2
  55. package/{packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-CtWaXZHl.js → dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-B0YrN1ud.js} +14 -14
  56. package/dist/rcs-web/assets/stateDiagram-AJRCARHV-CofFPj3N.js +1 -0
  57. package/dist/rcs-web/assets/stateDiagram-v2-BHNVJYJU-DPlcvPn1.js +1 -0
  58. package/{packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-Dk5WUenC.js → dist/rcs-web/assets/timeline-definition-PNZ67QCA-Bx4dusy8.js} +2 -2
  59. package/dist/rcs-web/assets/useTeams-MDI4djXg.js +1 -0
  60. package/{packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-TY8EiRrC.js → dist/rcs-web/assets/vennDiagram-CIIHVFJN-B_-3l-MF.js} +2 -2
  61. package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-BK3oi47X.js → wardleyDiagram-YWT4CUSO-DNSn-pGp.js} +2 -2
  62. package/dist/rcs-web/assets/workerType-OM09y7Lk.js +1 -0
  63. package/{packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-DhsxqX67.js → dist/rcs-web/assets/xychartDiagram-2RQKCTM6-TqUbNp0j.js} +2 -2
  64. package/dist/rcs-web/index.html +2 -3
  65. package/package.json +2 -1
  66. package/packages/remote-control-server/src/__tests__/acp-ws-handler.test.ts +7 -19
  67. package/packages/remote-control-server/src/__tests__/auth-login.test.ts +561 -0
  68. package/packages/remote-control-server/src/__tests__/auth-security.test.ts +547 -0
  69. package/packages/remote-control-server/src/__tests__/auth.test.ts +7 -18
  70. package/packages/remote-control-server/src/__tests__/bind-replacement.test.ts +251 -0
  71. package/packages/remote-control-server/src/__tests__/cookie.test.ts +155 -0
  72. package/packages/remote-control-server/src/__tests__/data-layer-fixes.test.ts +147 -0
  73. package/packages/remote-control-server/src/__tests__/db.test.ts +582 -0
  74. package/packages/remote-control-server/src/__tests__/design-gaps.test.ts +440 -0
  75. package/packages/remote-control-server/src/__tests__/disconnect-monitor.test.ts +2 -18
  76. package/packages/remote-control-server/src/__tests__/helpers/db.ts +43 -0
  77. package/packages/remote-control-server/src/__tests__/helpers/fixtures.ts +94 -0
  78. package/packages/remote-control-server/src/__tests__/helpers/mock-config.ts +31 -0
  79. package/packages/remote-control-server/src/__tests__/middleware.test.ts +4 -112
  80. package/packages/remote-control-server/src/__tests__/password.test.ts +76 -0
  81. package/packages/remote-control-server/src/__tests__/phase4-cleanup.test.ts +290 -0
  82. package/packages/remote-control-server/src/__tests__/phase4-integration.test.ts +224 -0
  83. package/packages/remote-control-server/src/__tests__/routes.test.ts +292 -207
  84. package/packages/remote-control-server/src/__tests__/services.test.ts +2 -18
  85. package/packages/remote-control-server/src/__tests__/session-visibility.test.ts +452 -0
  86. package/packages/remote-control-server/src/__tests__/session.test.ts +216 -0
  87. package/packages/remote-control-server/src/__tests__/shares.test.ts +1419 -0
  88. package/packages/remote-control-server/src/__tests__/sse-writer.test.ts +2 -18
  89. package/packages/remote-control-server/src/__tests__/teams.test.ts +2089 -0
  90. package/packages/remote-control-server/src/__tests__/work-dispatch.test.ts +2 -18
  91. package/packages/remote-control-server/src/__tests__/worker-token-removal.test.ts +487 -0
  92. package/packages/remote-control-server/src/__tests__/ws-handler.test.ts +2 -18
  93. package/packages/remote-control-server/src/auth/constants.ts +13 -0
  94. package/packages/remote-control-server/src/auth/cookie.ts +75 -0
  95. package/packages/remote-control-server/src/auth/middleware.ts +75 -165
  96. package/packages/remote-control-server/src/auth/password.ts +116 -0
  97. package/packages/remote-control-server/src/auth/session.ts +130 -0
  98. package/packages/remote-control-server/src/db/schema.sql +116 -0
  99. package/packages/remote-control-server/src/db/sqlite.ts +99 -0
  100. package/packages/remote-control-server/src/routes/acp/index.ts +6 -11
  101. package/packages/remote-control-server/src/routes/v1/environments.ts +111 -8
  102. package/packages/remote-control-server/src/routes/web/auth-routes.ts +634 -0
  103. package/packages/remote-control-server/src/routes/web/control.ts +5 -5
  104. package/packages/remote-control-server/src/routes/web/environments.ts +5 -27
  105. package/packages/remote-control-server/src/routes/web/sessions.ts +19 -41
  106. package/packages/remote-control-server/src/routes/web/shares.ts +371 -0
  107. package/packages/remote-control-server/src/routes/web/teams.ts +850 -0
  108. package/packages/remote-control-server/src/services/environment.ts +1 -4
  109. package/packages/remote-control-server/src/services/session-visibility.ts +79 -0
  110. package/packages/remote-control-server/src/services/session.ts +0 -13
  111. package/packages/remote-control-server/src/store.ts +150 -56
  112. package/packages/remote-control-server/src/types/api.ts +2 -8
  113. package/packages/remote-control-server/web/dist/assets/Dashboard-BwotBWlJ.js +1 -0
  114. package/packages/remote-control-server/web/dist/assets/Join-4yrksbdX.js +1 -0
  115. package/packages/remote-control-server/web/dist/assets/Login-Cxm_yEeE.js +1 -0
  116. package/packages/remote-control-server/web/dist/assets/SessionDetail-DNdfPOYy.js +50 -0
  117. package/packages/remote-control-server/web/dist/assets/Setup-BiAAxugt.js +1 -0
  118. package/packages/remote-control-server/web/dist/assets/ShareLanding-CP3Z6ckr.js +1 -0
  119. package/packages/remote-control-server/web/dist/assets/TeamCreate-DgdCwRSb.js +1 -0
  120. package/packages/remote-control-server/web/dist/assets/TeamDetail-CkNTFoMB.js +11 -0
  121. package/packages/remote-control-server/web/dist/assets/TeamList-Bf0sQ5RQ.js +1 -0
  122. package/packages/remote-control-server/web/dist/assets/TeamSettings-BaNAfXU3.js +1 -0
  123. package/packages/remote-control-server/web/dist/assets/UserSettings-CsutQjxG.js +1 -0
  124. package/{dist/rcs-web/assets/arc-D90597o-.js → packages/remote-control-server/web/dist/assets/arc-BxxmNQO6.js} +1 -1
  125. package/packages/remote-control-server/web/dist/assets/architectureDiagram-3BPJPVTR-DBjkRdbd.js +36 -0
  126. package/packages/remote-control-server/web/dist/assets/{blockDiagram-GPEHLZMM-DHd8IbOs.js → blockDiagram-GPEHLZMM-BF6COWlM.js} +2 -2
  127. package/packages/remote-control-server/web/dist/assets/{c4Diagram-AAUBKEIU-CjFkQJ9h.js → c4Diagram-AAUBKEIU-DHOf390v.js} +2 -2
  128. package/packages/remote-control-server/web/dist/assets/channel-JsvbMQjW.js +1 -0
  129. package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-C21XWnGZ.js → chunk-2J33WTMH-CeJN8yjM.js} +1 -1
  130. package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-B3SXiJlt.js → chunk-4BX2VUAB-jP76_P2A.js} +1 -1
  131. package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-DjImv45F.js → chunk-55IACEB6-BsFA6emB.js} +1 -1
  132. package/{dist/rcs-web/assets/chunk-727SXJPM-DtnaqsyD.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-BF4HrQjZ.js} +1 -1
  133. package/packages/remote-control-server/web/dist/assets/{chunk-AQP2D5EJ-BVFgH5mS.js → chunk-AQP2D5EJ-DToJ74Kc.js} +1 -1
  134. package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-Bl8jgb57.js → chunk-FMBD7UC4-DVTARgsa.js} +1 -1
  135. package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-BpK111zh.js → chunk-ND2GUHAM-C50Gqv9c.js} +1 -1
  136. package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-ruhIHBim.js → chunk-QZHKN3VN-EsIIh6zV.js} +1 -1
  137. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-BXn8Uc4c.js +1 -0
  138. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-BXn8Uc4c.js +1 -0
  139. package/packages/remote-control-server/web/dist/assets/code-block-IT6T5CEO-BnufHNTT.js +2 -0
  140. package/packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-M6tNt14I.js +1 -0
  141. package/packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-DaTm29qG.js +4 -0
  142. package/packages/remote-control-server/web/dist/assets/diagram-2AECGRRQ-fqoeOvq2.js +43 -0
  143. package/packages/remote-control-server/web/dist/assets/diagram-5GNKFQAL-6s2CiaGG.js +10 -0
  144. package/packages/remote-control-server/web/dist/assets/{diagram-KO2AKTUF-Uy3PDP5S.js → diagram-KO2AKTUF-C0aw_N95.js} +2 -2
  145. package/packages/remote-control-server/web/dist/assets/diagram-LMA3HP47-C8_Rv3rB.js +24 -0
  146. package/packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-Dae4u8Uc.js +24 -0
  147. package/{dist/rcs-web/assets/erDiagram-TEJ5UH35-sre_MTeK.js → packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-CosS5VWr.js} +13 -13
  148. package/packages/remote-control-server/web/dist/assets/{flowDiagram-I6XJVG4X-DwrWaLoe.js → flowDiagram-I6XJVG4X-D_Mxs46-.js} +2 -2
  149. package/packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-B0jy8Am_.js +292 -0
  150. package/packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-CSdu2HVI.js +106 -0
  151. package/packages/remote-control-server/web/dist/assets/i18n-Dbt-vEQ3.js +1 -0
  152. package/packages/remote-control-server/web/dist/assets/infoDiagram-5YYISTIA-Dcbc9L1S.js +2 -0
  153. package/{dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-SO0nn3--.js → packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-BLMjFE4t.js} +2 -2
  154. package/packages/remote-control-server/web/dist/assets/{journeyDiagram-JHISSGLW-D4igFUd4.js → journeyDiagram-JHISSGLW-Dw4nh1xI.js} +2 -2
  155. package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-Cm_yKsZC.js → kanban-definition-UN3LZRKU-DkU3j_ly.js} +2 -2
  156. package/packages/remote-control-server/web/dist/assets/{linear-4IMGll_E.js → linear-N5Cify7M.js} +1 -1
  157. package/packages/remote-control-server/web/dist/assets/main-30YqUvTd.js +238 -0
  158. package/packages/remote-control-server/web/dist/assets/main-EP6Lac2m.css +1 -0
  159. package/{dist/rcs-web/assets/mermaid-VLURNSYL-D_yQ7nAd.js → packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-gfiJUpl_.js} +5 -5
  160. package/packages/remote-control-server/web/dist/assets/{mermaid.core-B1qOUdiU.js → mermaid.core-B_Xc65Bf.js} +5 -5
  161. package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-yETom5qt.js → mindmap-definition-RKZ34NQL-DUavHfNu.js} +2 -2
  162. package/{dist/rcs-web/assets/pieDiagram-4H26LBE5-KIhqXGT6.js → packages/remote-control-server/web/dist/assets/pieDiagram-4H26LBE5-DSJaToTU.js} +2 -2
  163. package/{dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-DISqmoA6.js → packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-BFp6TnHj.js} +2 -2
  164. package/packages/remote-control-server/web/dist/assets/{requirementDiagram-4Y6WPE33-DsgT4ptk.js → requirementDiagram-4Y6WPE33-eil6F3-O.js} +2 -2
  165. package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-Dn67kbn0.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-DbGhoepK.js} +2 -2
  166. package/{dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-CtWaXZHl.js → packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-B0YrN1ud.js} +14 -14
  167. package/packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-CofFPj3N.js +1 -0
  168. package/packages/remote-control-server/web/dist/assets/stateDiagram-v2-BHNVJYJU-DPlcvPn1.js +1 -0
  169. package/{dist/rcs-web/assets/timeline-definition-PNZ67QCA-Dk5WUenC.js → packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-Bx4dusy8.js} +2 -2
  170. package/packages/remote-control-server/web/dist/assets/useTeams-MDI4djXg.js +1 -0
  171. package/{dist/rcs-web/assets/vennDiagram-CIIHVFJN-TY8EiRrC.js → packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-B_-3l-MF.js} +2 -2
  172. package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-BK3oi47X.js → wardleyDiagram-YWT4CUSO-DNSn-pGp.js} +2 -2
  173. package/packages/remote-control-server/web/dist/assets/workerType-OM09y7Lk.js +1 -0
  174. package/{dist/rcs-web/assets/xychartDiagram-2RQKCTM6-DhsxqX67.js → packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-TqUbNp0j.js} +2 -2
  175. package/packages/remote-control-server/web/dist/index.html +2 -3
  176. package/dist/rcs-web/assets/Dashboard-Cyj-Bb86.js +0 -2
  177. package/dist/rcs-web/assets/SessionDetail-SA9QBkyj.js +0 -51
  178. package/dist/rcs-web/assets/architectureDiagram-3BPJPVTR-CG9q9_bk.js +0 -36
  179. package/dist/rcs-web/assets/channel-CQFGglg-.js +0 -1
  180. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-C5rBdShD.js +0 -1
  181. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-C5rBdShD.js +0 -1
  182. package/dist/rcs-web/assets/code-block-IT6T5CEO-BLGFrBw3.js +0 -2
  183. package/dist/rcs-web/assets/cose-bilkent-S5V4N54A-DBKEXjqM.js +0 -1
  184. package/dist/rcs-web/assets/dagre-BM42HDAG-DB_SWlAe.js +0 -4
  185. package/dist/rcs-web/assets/diagram-2AECGRRQ-Gbm8G3Xz.js +0 -43
  186. package/dist/rcs-web/assets/diagram-5GNKFQAL-BBuWqfDc.js +0 -10
  187. package/dist/rcs-web/assets/diagram-LMA3HP47-DyUwNrlg.js +0 -24
  188. package/dist/rcs-web/assets/diagram-OG6HWLK6-BXpvRwkJ.js +0 -24
  189. package/dist/rcs-web/assets/ganttDiagram-6RSMTGT7-ByDYviDl.js +0 -292
  190. package/dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-BUnOPw74.js +0 -106
  191. package/dist/rcs-web/assets/infoDiagram-5YYISTIA-B1RpwJHG.js +0 -2
  192. package/dist/rcs-web/assets/main-0A4a3FDU.css +0 -1
  193. package/dist/rcs-web/assets/main-Dom16YJk.js +0 -283
  194. package/dist/rcs-web/assets/qr-C0jP9HcD.js +0 -8
  195. package/dist/rcs-web/assets/qr-scanner-worker.min-D85Z9gVD.js +0 -98
  196. package/dist/rcs-web/assets/stateDiagram-AJRCARHV-D6xp64f3.js +0 -1
  197. package/dist/rcs-web/assets/stateDiagram-v2-BHNVJYJU-D76vZCyQ.js +0 -1
  198. package/packages/remote-control-server/web/dist/assets/Dashboard-Cyj-Bb86.js +0 -2
  199. package/packages/remote-control-server/web/dist/assets/SessionDetail-SA9QBkyj.js +0 -51
  200. package/packages/remote-control-server/web/dist/assets/architectureDiagram-3BPJPVTR-CG9q9_bk.js +0 -36
  201. package/packages/remote-control-server/web/dist/assets/channel-CQFGglg-.js +0 -1
  202. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-C5rBdShD.js +0 -1
  203. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-C5rBdShD.js +0 -1
  204. package/packages/remote-control-server/web/dist/assets/code-block-IT6T5CEO-BLGFrBw3.js +0 -2
  205. package/packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-DBKEXjqM.js +0 -1
  206. package/packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-DB_SWlAe.js +0 -4
  207. package/packages/remote-control-server/web/dist/assets/diagram-2AECGRRQ-Gbm8G3Xz.js +0 -43
  208. package/packages/remote-control-server/web/dist/assets/diagram-5GNKFQAL-BBuWqfDc.js +0 -10
  209. package/packages/remote-control-server/web/dist/assets/diagram-LMA3HP47-DyUwNrlg.js +0 -24
  210. package/packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-BXpvRwkJ.js +0 -24
  211. package/packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-ByDYviDl.js +0 -292
  212. package/packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-BUnOPw74.js +0 -106
  213. package/packages/remote-control-server/web/dist/assets/infoDiagram-5YYISTIA-B1RpwJHG.js +0 -2
  214. package/packages/remote-control-server/web/dist/assets/main-0A4a3FDU.css +0 -1
  215. package/packages/remote-control-server/web/dist/assets/main-Dom16YJk.js +0 -283
  216. package/packages/remote-control-server/web/dist/assets/qr-C0jP9HcD.js +0 -8
  217. package/packages/remote-control-server/web/dist/assets/qr-scanner-worker.min-D85Z9gVD.js +0 -98
  218. package/packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-D6xp64f3.js +0 -1
  219. 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 { getSession, resolveOwnedWebSessionId } from '../services/session'
7
- import { storeGetEnvironment } from '../store'
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
- * Extract UUID from request — query param ?uuid= or header X-UUID
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
- * Web UI users must enter the environment's worker token (RCS_WORKER_TOKENS,
198
- * set on the worker side) to access session routes. The server stores only
199
- * the SHA256 hash (registered by the worker); this middleware hashes the
200
- * user-supplied token and compares. Admin API keys (RCS_API_KEYS) bypass
201
- * this check — they have full access to all environments.
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
- * Token is read from X-Worker-Token header, ?worker_token= query param, or
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 requireWorkerToken(c: Context, next: Next) {
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
- * UUID-based auth for Web UI routes (no-login mode).
302
- * Accepts UUID in query param/header, OR a valid API key via Authorization header.
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
- // Valid API key — generate a stable UUID from the key for downstream use
309
- const uuid = getUuidFromRequest(c)
310
- c.set('uuid', uuid || bearer)
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
- // Fall back to UUID auth
316
- const uuid = getUuidFromRequest(c)
317
- if (!uuid) {
318
- return c.json(
319
- { error: { type: 'unauthorized', message: 'Missing UUID' } },
320
- 401,
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
- c.set('uuid', uuid)
324
- await next()
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);