@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,24 +1,8 @@
1
1
  import { describe, test, expect, beforeEach, mock } from 'bun:test'
2
+ import { mockConfigModule } from './helpers/mock-config'
2
3
 
3
4
  // Mock config before importing modules
4
- const mockConfig = {
5
- port: 3000,
6
- host: '0.0.0.0',
7
- apiKeys: ['test-api-key'],
8
- baseUrl: 'http://localhost:3000',
9
- pollTimeout: 8,
10
- heartbeatInterval: 20,
11
- jwtExpiresIn: 3600,
12
- disconnectTimeout: 300,
13
- webCorsOrigins: [],
14
- wsIdleTimeout: 30,
15
- wsKeepaliveInterval: 20,
16
- }
17
-
18
- mock.module('../config', () => ({
19
- config: mockConfig,
20
- getBaseUrl: () => 'http://localhost:3000',
21
- }))
5
+ mock.module('../config', () => mockConfigModule())
22
6
 
23
7
  import { storeReset, storeCreateEnvironment } from '../store'
24
8
  import {
@@ -0,0 +1,452 @@
1
+ import { describe, test, expect, beforeEach, mock } from 'bun:test'
2
+ import { createHash, randomUUID } from 'node:crypto'
3
+ import { tmpdir } from 'node:os'
4
+ import { resolve } from 'node:path'
5
+ import { mockConfigModule } from './helpers/mock-config'
6
+
7
+ // Mock config before any module imports
8
+ mock.module('../config', () => mockConfigModule())
9
+
10
+ import { Database } from 'bun:sqlite'
11
+
12
+ // Dynamic imports — Phase 2 modules may not exist yet (TDD)
13
+ let migrateDatabase: ((db: Database) => void) | undefined
14
+ let initDatabase: ((path?: string) => Database) | undefined
15
+ let resetDbSingleton: (() => void) | undefined
16
+ let getDb: (() => Database) | undefined
17
+
18
+ try {
19
+ const dbMod = await import('../db/sqlite')
20
+ migrateDatabase = dbMod.migrateDatabase
21
+ getDb = dbMod.getDb
22
+ initDatabase = dbMod.initDatabase
23
+ resetDbSingleton = dbMod.resetDbSingleton
24
+ } catch {
25
+ // Module not implemented yet
26
+ }
27
+
28
+ // Dynamic import for session visibility function
29
+ let storeListSessionsVisibleToUser:
30
+ | ((userId: string, db: Database) => Array<Record<string, unknown>>)
31
+ | undefined
32
+
33
+ try {
34
+ const mod = await import('../services/session-visibility')
35
+ storeListSessionsVisibleToUser = mod.storeListSessionsVisibleToUser
36
+ } catch {
37
+ // Module not implemented yet
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Database helpers
42
+ // ---------------------------------------------------------------------------
43
+
44
+ function freshDb(): Database {
45
+ if (resetDbSingleton) {
46
+ try {
47
+ resetDbSingleton()
48
+ } catch {
49
+ // ignore cleanup errors
50
+ }
51
+ }
52
+ if (initDatabase) {
53
+ const uniquePath = resolve(
54
+ tmpdir(),
55
+ `rcs-test-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
56
+ )
57
+ return initDatabase(uniquePath)
58
+ }
59
+ const db = new Database(':memory:')
60
+ db.exec('PRAGMA foreign_keys = ON')
61
+ if (migrateDatabase) migrateDatabase(db)
62
+ return db
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Fixture helpers
67
+ // ---------------------------------------------------------------------------
68
+
69
+ function createUser(
70
+ db: Database,
71
+ opts: { username: string; role: string },
72
+ ): string {
73
+ const id = `usr_${randomUUID().replace(/-/g, '')}`
74
+ const now = new Date().toISOString()
75
+ const hash =
76
+ '$argon2id$v=19$m=65536,t=3,p=1$dGVzdHNhbHQ$dGVzdGhhc2h0ZXN0aGFzaA'
77
+ db.query(
78
+ `INSERT INTO users (id, username, password_hash, role, created_at)
79
+ VALUES ($id, $u, $h, $r, $now)`,
80
+ ).run({ $id: id, $u: opts.username, $h: hash, $r: opts.role, $now: now })
81
+ return id
82
+ }
83
+
84
+ function createTeam(
85
+ db: Database,
86
+ opts: { slug: string; createdBy: string },
87
+ ): string {
88
+ const id = `team_${randomUUID().replace(/-/g, '')}`
89
+ const now = new Date().toISOString()
90
+ try {
91
+ db.query(
92
+ `INSERT INTO teams (id, name, slug, created_by, created_at, updated_at)
93
+ VALUES ($id, $name, $slug, $by, $now, $now)`,
94
+ ).run({
95
+ $id: id,
96
+ $name: opts.slug,
97
+ $slug: opts.slug,
98
+ $by: opts.createdBy,
99
+ $now: now,
100
+ })
101
+ } catch {
102
+ // teams table may not exist yet
103
+ }
104
+ return id
105
+ }
106
+
107
+ function addMember(
108
+ db: Database,
109
+ teamId: string,
110
+ userId: string,
111
+ role: string,
112
+ ): void {
113
+ const now = new Date().toISOString()
114
+ try {
115
+ db.query(
116
+ `INSERT OR IGNORE INTO team_members (team_id, user_id, role, added_at, added_by)
117
+ VALUES ($t, $u, $r, $now, $by)`,
118
+ ).run({ $t: teamId, $u: userId, $r: role, $by: userId, $now: now })
119
+ } catch {
120
+ // team_members may not exist
121
+ }
122
+ }
123
+
124
+ interface SessionFixture {
125
+ id: string
126
+ visibility: string
127
+ }
128
+
129
+ function createSession(
130
+ db: Database,
131
+ opts: {
132
+ id?: string
133
+ visibility?: string
134
+ ownerType?: string
135
+ ownerId?: string
136
+ environmentId?: string
137
+ },
138
+ ): SessionFixture {
139
+ const id = opts.id || `ses_${randomUUID().replace(/-/g, '')}`
140
+ const visibility =
141
+ opts.visibility || (opts.ownerType === 'team' ? 'team' : 'private')
142
+ const now = new Date().toISOString()
143
+
144
+ // Try to insert into a sessions table (Phase 2 schema)
145
+ try {
146
+ db.query(
147
+ `INSERT INTO sessions (id, visibility, environment_id, created_at, updated_at)
148
+ VALUES ($id, $vis, $env, $now, $now)`,
149
+ ).run({
150
+ $id: id,
151
+ $vis: visibility,
152
+ $env: opts.environmentId || null,
153
+ $now: now,
154
+ })
155
+ } catch {
156
+ // sessions table may not have visibility column yet;
157
+ // try without it
158
+ try {
159
+ db.query(
160
+ `INSERT INTO sessions (id, environment_id, created_at, updated_at)
161
+ VALUES ($id, $env, $now, $now)`,
162
+ ).run({ $id: id, $env: opts.environmentId || null, $now: now })
163
+ } catch {
164
+ // sessions table may not exist at all
165
+ }
166
+ }
167
+
168
+ // Add owner record
169
+ if (opts.ownerType && opts.ownerId) {
170
+ try {
171
+ db.query(
172
+ `INSERT INTO session_owners (session_id, owner_type, owner_id)
173
+ VALUES ($sid, $type, $oid)`,
174
+ ).run({ $sid: id, $type: opts.ownerType, $oid: opts.ownerId })
175
+ } catch {
176
+ // session_owners may not exist
177
+ }
178
+ }
179
+
180
+ return { id, visibility }
181
+ }
182
+
183
+ function addSessionShare(
184
+ db: Database,
185
+ opts: {
186
+ sessionId: string
187
+ grantedToUser?: string
188
+ grantedToTeam?: string
189
+ permission?: string
190
+ grantedBy: string
191
+ expiresAt?: string
192
+ },
193
+ ): string {
194
+ const id = `shr_${randomUUID().replace(/-/g, '')}`
195
+ const now = new Date().toISOString()
196
+ try {
197
+ db.query(
198
+ `INSERT INTO session_shares (id, session_id, granted_to_user, granted_to_team, permission, expires_at, granted_by, granted_at)
199
+ VALUES ($id, $sid, $gtu, $gtt, $perm, $exp, $by, $now)`,
200
+ ).run({
201
+ $id: id,
202
+ $sid: opts.sessionId,
203
+ $gtu: opts.grantedToUser || null,
204
+ $gtt: opts.grantedToTeam || null,
205
+ $perm: opts.permission || 'read',
206
+ $exp: opts.expiresAt || null,
207
+ $by: opts.grantedBy,
208
+ $now: now,
209
+ })
210
+ } catch {
211
+ // session_shares may not exist
212
+ }
213
+ return id
214
+ }
215
+
216
+ // ===========================================================================
217
+ // TESTS
218
+ // ===========================================================================
219
+
220
+ describe('storeListSessionsVisibleToUser', () => {
221
+ let db: Database
222
+ let aliceId: string
223
+ let bobId: string
224
+ let charlieId: string
225
+ let adminId: string
226
+ let teamAId: string
227
+
228
+ beforeEach(() => {
229
+ db = freshDb()
230
+ aliceId = createUser(db, { username: 'alice', role: 'member' })
231
+ bobId = createUser(db, { username: 'bob', role: 'member' })
232
+ charlieId = createUser(db, { username: 'charlie', role: 'member' })
233
+ adminId = createUser(db, { username: 'sysadmin', role: 'admin' })
234
+ teamAId = createTeam(db, { slug: 'team-a', createdBy: aliceId })
235
+ addMember(db, teamAId, aliceId, 'owner')
236
+ addMember(db, teamAId, bobId, 'member')
237
+ // charlie is NOT in teamA
238
+ })
239
+
240
+ test('owner is self → visible (private/team/public all visible)', () => {
241
+ expect(storeListSessionsVisibleToUser).toBeDefined()
242
+ const privateSession = createSession(db, {
243
+ visibility: 'private',
244
+ ownerType: 'user',
245
+ ownerId: aliceId,
246
+ })
247
+ const teamSession = createSession(db, {
248
+ visibility: 'team',
249
+ ownerType: 'user',
250
+ ownerId: aliceId,
251
+ })
252
+ const publicSession = createSession(db, {
253
+ visibility: 'public',
254
+ ownerType: 'user',
255
+ ownerId: aliceId,
256
+ })
257
+
258
+ const sessions = storeListSessionsVisibleToUser!(aliceId, db)
259
+ const ids = sessions.map(s => s.id as string)
260
+ expect(ids).toContain(privateSession.id)
261
+ expect(ids).toContain(teamSession.id)
262
+ expect(ids).toContain(publicSession.id)
263
+ })
264
+
265
+ test('owner is own team + visibility=team → visible', () => {
266
+ expect(storeListSessionsVisibleToUser).toBeDefined()
267
+ const session = createSession(db, {
268
+ visibility: 'team',
269
+ ownerType: 'team',
270
+ ownerId: teamAId,
271
+ })
272
+
273
+ const sessions = storeListSessionsVisibleToUser!(bobId, db)
274
+ const ids = sessions.map(s => s.id as string)
275
+ expect(ids).toContain(session.id)
276
+ })
277
+
278
+ test('owner is own team + visibility=private → not visible', () => {
279
+ expect(storeListSessionsVisibleToUser).toBeDefined()
280
+ const session = createSession(db, {
281
+ visibility: 'private',
282
+ ownerType: 'team',
283
+ ownerId: teamAId,
284
+ })
285
+
286
+ const sessions = storeListSessionsVisibleToUser!(bobId, db)
287
+ const ids = sessions.map(s => s.id as string)
288
+ expect(ids).not.toContain(session.id)
289
+ })
290
+
291
+ test('owner is own team + visibility=public → visible', () => {
292
+ expect(storeListSessionsVisibleToUser).toBeDefined()
293
+ const session = createSession(db, {
294
+ visibility: 'public',
295
+ ownerType: 'team',
296
+ ownerId: teamAId,
297
+ })
298
+
299
+ const sessions = storeListSessionsVisibleToUser!(bobId, db)
300
+ const ids = sessions.map(s => s.id as string)
301
+ expect(ids).toContain(session.id)
302
+ })
303
+
304
+ test('explicitly granted via session_shares → visible', () => {
305
+ expect(storeListSessionsVisibleToUser).toBeDefined()
306
+ const session = createSession(db, {
307
+ visibility: 'private',
308
+ ownerType: 'user',
309
+ ownerId: aliceId,
310
+ })
311
+ addSessionShare(db, {
312
+ sessionId: session.id,
313
+ grantedToUser: charlieId,
314
+ permission: 'read',
315
+ grantedBy: aliceId,
316
+ })
317
+
318
+ const sessions = storeListSessionsVisibleToUser!(charlieId, db)
319
+ const ids = sessions.map(s => s.id as string)
320
+ expect(ids).toContain(session.id)
321
+ })
322
+
323
+ test('visibility=public + owner is stranger → visible', () => {
324
+ expect(storeListSessionsVisibleToUser).toBeDefined()
325
+ const session = createSession(db, {
326
+ visibility: 'public',
327
+ ownerType: 'user',
328
+ ownerId: aliceId,
329
+ })
330
+
331
+ // charlie is a stranger (not in alice's team, no share)
332
+ const sessions = storeListSessionsVisibleToUser!(charlieId, db)
333
+ const ids = sessions.map(s => s.id as string)
334
+ expect(ids).toContain(session.id)
335
+ })
336
+
337
+ test('system admin → all sessions visible (including orphans)', () => {
338
+ expect(storeListSessionsVisibleToUser).toBeDefined()
339
+ // Create sessions owned by different users
340
+ createSession(db, {
341
+ visibility: 'private',
342
+ ownerType: 'user',
343
+ ownerId: aliceId,
344
+ })
345
+ createSession(db, {
346
+ visibility: 'private',
347
+ ownerType: 'user',
348
+ ownerId: bobId,
349
+ })
350
+ // Create an orphan session (no owner)
351
+ createSession(db, { visibility: 'private' })
352
+
353
+ const sessions = storeListSessionsVisibleToUser!(adminId, db)
354
+ // Admin should see all sessions — at least the 3 we created
355
+ expect(sessions.length).toBeGreaterThanOrEqual(3)
356
+ })
357
+
358
+ test('orphan sessions (no owner) → only admin visible', () => {
359
+ expect(storeListSessionsVisibleToUser).toBeDefined()
360
+ const orphan = createSession(db, { visibility: 'private' })
361
+
362
+ // Regular user should NOT see orphan
363
+ const aliceSessions = storeListSessionsVisibleToUser!(aliceId, db)
364
+ const aliceIds = aliceSessions.map(s => s.id as string)
365
+ expect(aliceIds).not.toContain(orphan.id)
366
+
367
+ // Admin should see orphan
368
+ const adminSessions = storeListSessionsVisibleToUser!(adminId, db)
369
+ const adminIds = adminSessions.map(s => s.id as string)
370
+ expect(adminIds).toContain(orphan.id)
371
+ })
372
+ })
373
+
374
+ describe('session visibility field', () => {
375
+ let db: Database
376
+ let aliceId: string
377
+ let teamAId: string
378
+
379
+ beforeEach(() => {
380
+ db = freshDb()
381
+ aliceId = createUser(db, { username: 'alice', role: 'member' })
382
+ teamAId = createTeam(db, { slug: 'team-a', createdBy: aliceId })
383
+ addMember(db, teamAId, aliceId, 'owner')
384
+ })
385
+
386
+ test('team environment → default visibility=team', () => {
387
+ // When a session is created under a team-owned environment,
388
+ // the default visibility should be 'team'
389
+ const envId = `env_${randomUUID().replace(/-/g, '')}`
390
+ const session = createSession(db, {
391
+ ownerType: 'team',
392
+ ownerId: teamAId,
393
+ environmentId: envId,
394
+ })
395
+
396
+ // Verify the session has team visibility
397
+ const row = db.query('SELECT visibility FROM sessions WHERE id = $id').get({
398
+ $id: session.id,
399
+ }) as { visibility: string } | null
400
+ if (row) {
401
+ expect(row.visibility).toBe('team')
402
+ }
403
+ })
404
+
405
+ test('personal environment → default visibility=private', () => {
406
+ const envId = `env_${randomUUID().replace(/-/g, '')}`
407
+ const session = createSession(db, {
408
+ ownerType: 'user',
409
+ ownerId: aliceId,
410
+ environmentId: envId,
411
+ })
412
+
413
+ const row = db.query('SELECT visibility FROM sessions WHERE id = $id').get({
414
+ $id: session.id,
415
+ }) as { visibility: string } | null
416
+ if (row) {
417
+ expect(row.visibility).toBe('private')
418
+ }
419
+ })
420
+
421
+ test('owner switches visibility private → team → 200', () => {
422
+ expect(storeListSessionsVisibleToUser).toBeDefined()
423
+ const session = createSession(db, {
424
+ visibility: 'private',
425
+ ownerType: 'user',
426
+ ownerId: aliceId,
427
+ })
428
+
429
+ // Owner changes visibility to 'team'
430
+ try {
431
+ db.query(`UPDATE sessions SET visibility = 'team' WHERE id = $id`).run({
432
+ $id: session.id,
433
+ })
434
+ } catch {
435
+ // Column may not exist
436
+ }
437
+
438
+ const row = db.query('SELECT visibility FROM sessions WHERE id = $id').get({
439
+ $id: session.id,
440
+ }) as { visibility: string } | null
441
+ if (row) {
442
+ expect(row.visibility).toBe('team')
443
+ }
444
+ })
445
+
446
+ test.todo('non-owner cannot switch visibility → 403', () => {
447
+ // TODO: Implement PATCH /web/sessions/:id/visibility route, then test:
448
+ // 1. Create session owned by alice
449
+ // 2. Bob (non-owner) sends PATCH to change visibility
450
+ // 3. Assert 403 Forbidden
451
+ })
452
+ })
@@ -0,0 +1,216 @@
1
+ import { describe, test, expect, beforeEach } from 'bun:test'
2
+ import { Database } from 'bun:sqlite'
3
+
4
+ // Dynamic imports — modules may not exist yet (TDD)
5
+ let issueSessionToken:
6
+ | ((
7
+ userId: string,
8
+ db: Database,
9
+ ) => { accessToken: string; refreshToken: string })
10
+ | undefined
11
+ let resolveSessionToken:
12
+ | ((token: string, db: Database) => string | null)
13
+ | undefined
14
+ let revokeSessionToken: ((token: string, db: Database) => void) | undefined
15
+ let revokeAllUserTokens: ((userId: string, db: Database) => void) | undefined
16
+ let migrateDatabase: ((db: Database) => void) | undefined
17
+
18
+ try {
19
+ const sessionMod = await import('../auth/session')
20
+ issueSessionToken = sessionMod.issueSessionToken
21
+ resolveSessionToken = sessionMod.resolveSessionToken
22
+ revokeSessionToken = sessionMod.revokeSessionToken
23
+ revokeAllUserTokens = sessionMod.revokeAllUserTokens
24
+ } catch {
25
+ // Module not implemented yet
26
+ }
27
+
28
+ try {
29
+ const dbMod = await import('../db/sqlite')
30
+ migrateDatabase = dbMod.migrateDatabase
31
+ } catch {
32
+ // Module not implemented yet
33
+ }
34
+
35
+ // Helper: create a fresh in-memory database with schema
36
+ function freshDb(): Database {
37
+ const db = new Database(':memory:')
38
+ if (migrateDatabase) {
39
+ migrateDatabase(db)
40
+ }
41
+ // Ensure users table has a test user
42
+ db.exec(
43
+ "INSERT OR IGNORE INTO users (id, username, password_hash, role, created_at) VALUES ('usr_test1', 'testuser', 'hash', 'admin', '2026-01-01T00:00:00Z')",
44
+ )
45
+ return db
46
+ }
47
+
48
+ describe('issueSessionToken', () => {
49
+ test('returns access token with rct_ prefix and refresh token', () => {
50
+ expect(issueSessionToken).toBeDefined()
51
+ const db = freshDb()
52
+ const result = issueSessionToken!('usr_test1', db)
53
+ expect(result.accessToken).toBeDefined()
54
+ expect(result.refreshToken).toBeDefined()
55
+ expect(result.accessToken.startsWith('rct_')).toBe(true)
56
+ expect(typeof result.refreshToken).toBe('string')
57
+ expect(result.refreshToken.length).toBeGreaterThan(0)
58
+ })
59
+
60
+ test('access token expires in approximately 1 hour', () => {
61
+ expect(issueSessionToken).toBeDefined()
62
+ const db = freshDb()
63
+ const result = issueSessionToken!('usr_test1', db)
64
+
65
+ // Check the token record in the database
66
+ const row = db
67
+ .query(
68
+ "SELECT expires_at, kind FROM session_tokens WHERE token_hash = (SELECT token_hash FROM session_tokens WHERE kind = 'access' AND user_id = 'usr_test1' ORDER BY created_at DESC LIMIT 1)",
69
+ )
70
+ .get() as { expires_at: string; kind: string } | null
71
+
72
+ expect(row).not.toBeNull()
73
+ expect(row!.kind).toBe('access')
74
+ const expiresAt = new Date(row!.expires_at).getTime()
75
+ const now = Date.now()
76
+ // Should expire in roughly 1 hour (3600s ± 60s tolerance)
77
+ const diffSeconds = (expiresAt - now) / 1000
78
+ expect(diffSeconds).toBeGreaterThan(3500)
79
+ expect(diffSeconds).toBeLessThan(3700)
80
+ })
81
+
82
+ test('refresh token expires in approximately 30 days', () => {
83
+ expect(issueSessionToken).toBeDefined()
84
+ const db = freshDb()
85
+ issueSessionToken!('usr_test1', db)
86
+
87
+ const row = db
88
+ .query(
89
+ "SELECT expires_at, kind FROM session_tokens WHERE kind = 'refresh' AND user_id = 'usr_test1' ORDER BY created_at DESC LIMIT 1",
90
+ )
91
+ .get() as { expires_at: string; kind: string } | null
92
+
93
+ expect(row).not.toBeNull()
94
+ expect(row!.kind).toBe('refresh')
95
+ const expiresAt = new Date(row!.expires_at).getTime()
96
+ const now = Date.now()
97
+ // Should expire in roughly 30 days (2592000s ± 3600s tolerance)
98
+ const diffSeconds = (expiresAt - now) / 1000
99
+ expect(diffSeconds).toBeGreaterThan(2588400)
100
+ expect(diffSeconds).toBeLessThan(2595600)
101
+ })
102
+
103
+ test('writes tokens to session_tokens table in SQLite', () => {
104
+ expect(issueSessionToken).toBeDefined()
105
+ const db = freshDb()
106
+ issueSessionToken!('usr_test1', db)
107
+
108
+ const rows = db
109
+ .query("SELECT * FROM session_tokens WHERE user_id = 'usr_test1'")
110
+ .all() as Array<{ token_hash: string; kind: string }>
111
+
112
+ expect(rows.length).toBeGreaterThanOrEqual(2)
113
+ const kinds = rows.map(r => r.kind).sort()
114
+ expect(kinds).toContain('access')
115
+ expect(kinds).toContain('refresh')
116
+ })
117
+ })
118
+
119
+ describe('resolveSessionToken', () => {
120
+ test('returns userId for valid access token', () => {
121
+ expect(issueSessionToken).toBeDefined()
122
+ expect(resolveSessionToken).toBeDefined()
123
+ const db = freshDb()
124
+ const { accessToken } = issueSessionToken!('usr_test1', db)
125
+ const userId = resolveSessionToken!(accessToken, db)
126
+ expect(userId).toBe('usr_test1')
127
+ })
128
+
129
+ test('returns null for invalid token', () => {
130
+ expect(resolveSessionToken).toBeDefined()
131
+ const db = freshDb()
132
+ const userId = resolveSessionToken!('rct_nonexistent_token', db)
133
+ expect(userId).toBeNull()
134
+ })
135
+
136
+ test('returns null for expired token', () => {
137
+ expect(issueSessionToken).toBeDefined()
138
+ expect(resolveSessionToken).toBeDefined()
139
+ const db = freshDb()
140
+ const { accessToken } = issueSessionToken!('usr_test1', db)
141
+
142
+ // Manually set the token's expiry to the past
143
+ db.exec(
144
+ "UPDATE session_tokens SET expires_at = '2020-01-01T00:00:00Z' WHERE user_id = 'usr_test1' AND kind = 'access'",
145
+ )
146
+
147
+ const userId = resolveSessionToken!(accessToken, db)
148
+ expect(userId).toBeNull()
149
+ })
150
+
151
+ test('returns null for revoked token', () => {
152
+ expect(issueSessionToken).toBeDefined()
153
+ expect(resolveSessionToken).toBeDefined()
154
+ expect(revokeSessionToken).toBeDefined()
155
+ const db = freshDb()
156
+ const { accessToken } = issueSessionToken!('usr_test1', db)
157
+
158
+ revokeSessionToken!(accessToken, db)
159
+ const userId = resolveSessionToken!(accessToken, db)
160
+ expect(userId).toBeNull()
161
+ })
162
+ })
163
+
164
+ describe('revokeSessionToken', () => {
165
+ test('after revocation resolveSessionToken returns null', () => {
166
+ expect(issueSessionToken).toBeDefined()
167
+ expect(resolveSessionToken).toBeDefined()
168
+ expect(revokeSessionToken).toBeDefined()
169
+ const db = freshDb()
170
+ const { accessToken } = issueSessionToken!('usr_test1', db)
171
+
172
+ // Token should resolve before revocation
173
+ expect(resolveSessionToken!(accessToken, db)).toBe('usr_test1')
174
+
175
+ revokeSessionToken!(accessToken, db)
176
+
177
+ // Token should not resolve after revocation
178
+ expect(resolveSessionToken!(accessToken, db)).toBeNull()
179
+ })
180
+
181
+ test('revoking an already-revoked token does not throw', () => {
182
+ expect(issueSessionToken).toBeDefined()
183
+ expect(revokeSessionToken).toBeDefined()
184
+ const db = freshDb()
185
+ const { accessToken } = issueSessionToken!('usr_test1', db)
186
+
187
+ revokeSessionToken!(accessToken, db)
188
+ // Second revocation should not throw
189
+ expect(() => revokeSessionToken!(accessToken, db)).not.toThrow()
190
+ })
191
+ })
192
+
193
+ describe('revokeAllUserTokens', () => {
194
+ test('revokes all tokens for a user', () => {
195
+ expect(issueSessionToken).toBeDefined()
196
+ expect(resolveSessionToken).toBeDefined()
197
+ expect(revokeAllUserTokens).toBeDefined()
198
+ const db = freshDb()
199
+
200
+ // Issue multiple sessions
201
+ const session1 = issueSessionToken!('usr_test1', db)
202
+ const session2 = issueSessionToken!('usr_test1', db)
203
+
204
+ // Both should resolve
205
+ expect(resolveSessionToken!(session1.accessToken, db)).toBe('usr_test1')
206
+ expect(resolveSessionToken!(session2.accessToken, db)).toBe('usr_test1')
207
+
208
+ revokeAllUserTokens!('usr_test1', db)
209
+
210
+ // Neither should resolve
211
+ expect(resolveSessionToken!(session1.accessToken, db)).toBeNull()
212
+ expect(resolveSessionToken!(session2.accessToken, db)).toBeNull()
213
+ expect(resolveSessionToken!(session1.refreshToken, db)).toBeNull()
214
+ expect(resolveSessionToken!(session2.refreshToken, db)).toBeNull()
215
+ })
216
+ })