gitspace 0.2.0-rc.1

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 (318) hide show
  1. package/.claude/settings.local.json +21 -0
  2. package/.gitspace/bundle.json +50 -0
  3. package/.gitspace/select/01-status.sh +40 -0
  4. package/.gitspace/setup/01-install-deps.sh +12 -0
  5. package/.gitspace/setup/02-typecheck.sh +16 -0
  6. package/AGENTS.md +439 -0
  7. package/CLAUDE.md +1 -0
  8. package/LICENSE +25 -0
  9. package/README.md +607 -0
  10. package/bin/gssh +62 -0
  11. package/bun.lock +647 -0
  12. package/docs/CONNECTION.md +623 -0
  13. package/docs/GATEWAY-WORKER.md +319 -0
  14. package/docs/GETTING-STARTED.md +448 -0
  15. package/docs/GITSPACE-PLATFORM.md +1819 -0
  16. package/docs/INFRASTRUCTURE.md +1347 -0
  17. package/docs/PROTOCOL.md +619 -0
  18. package/docs/QUICKSTART.md +174 -0
  19. package/docs/RELAY.md +327 -0
  20. package/docs/REMOTE-DESIGN.md +549 -0
  21. package/docs/ROADMAP.md +564 -0
  22. package/docs/SITE_DOCS_FIGMA_MAKE.md +1167 -0
  23. package/docs/STACK-DESIGN.md +588 -0
  24. package/docs/UNIFIED_ARCHITECTURE.md +292 -0
  25. package/experiments/pty-benchmark.ts +148 -0
  26. package/experiments/pty-latency.ts +100 -0
  27. package/experiments/router/client.ts +199 -0
  28. package/experiments/router/protocol.ts +74 -0
  29. package/experiments/router/router.ts +217 -0
  30. package/experiments/router/session.ts +180 -0
  31. package/experiments/router/test.ts +133 -0
  32. package/experiments/socket-bandwidth.ts +77 -0
  33. package/homebrew/gitspace.rb +45 -0
  34. package/landing-page/ATTRIBUTIONS.md +3 -0
  35. package/landing-page/README.md +11 -0
  36. package/landing-page/bun.lock +801 -0
  37. package/landing-page/guidelines/Guidelines.md +61 -0
  38. package/landing-page/index.html +37 -0
  39. package/landing-page/package.json +90 -0
  40. package/landing-page/postcss.config.mjs +15 -0
  41. package/landing-page/public/_redirects +1 -0
  42. package/landing-page/public/favicon.png +0 -0
  43. package/landing-page/src/app/App.tsx +53 -0
  44. package/landing-page/src/app/components/figma/ImageWithFallback.tsx +27 -0
  45. package/landing-page/src/app/components/ui/accordion.tsx +66 -0
  46. package/landing-page/src/app/components/ui/alert-dialog.tsx +157 -0
  47. package/landing-page/src/app/components/ui/alert.tsx +66 -0
  48. package/landing-page/src/app/components/ui/aspect-ratio.tsx +11 -0
  49. package/landing-page/src/app/components/ui/avatar.tsx +53 -0
  50. package/landing-page/src/app/components/ui/badge.tsx +46 -0
  51. package/landing-page/src/app/components/ui/breadcrumb.tsx +109 -0
  52. package/landing-page/src/app/components/ui/button.tsx +57 -0
  53. package/landing-page/src/app/components/ui/calendar.tsx +75 -0
  54. package/landing-page/src/app/components/ui/card.tsx +92 -0
  55. package/landing-page/src/app/components/ui/carousel.tsx +241 -0
  56. package/landing-page/src/app/components/ui/chart.tsx +353 -0
  57. package/landing-page/src/app/components/ui/checkbox.tsx +32 -0
  58. package/landing-page/src/app/components/ui/collapsible.tsx +33 -0
  59. package/landing-page/src/app/components/ui/command.tsx +177 -0
  60. package/landing-page/src/app/components/ui/context-menu.tsx +252 -0
  61. package/landing-page/src/app/components/ui/dialog.tsx +135 -0
  62. package/landing-page/src/app/components/ui/drawer.tsx +132 -0
  63. package/landing-page/src/app/components/ui/dropdown-menu.tsx +257 -0
  64. package/landing-page/src/app/components/ui/form.tsx +168 -0
  65. package/landing-page/src/app/components/ui/hover-card.tsx +44 -0
  66. package/landing-page/src/app/components/ui/input-otp.tsx +77 -0
  67. package/landing-page/src/app/components/ui/input.tsx +21 -0
  68. package/landing-page/src/app/components/ui/label.tsx +24 -0
  69. package/landing-page/src/app/components/ui/menubar.tsx +276 -0
  70. package/landing-page/src/app/components/ui/navigation-menu.tsx +168 -0
  71. package/landing-page/src/app/components/ui/pagination.tsx +127 -0
  72. package/landing-page/src/app/components/ui/popover.tsx +48 -0
  73. package/landing-page/src/app/components/ui/progress.tsx +31 -0
  74. package/landing-page/src/app/components/ui/radio-group.tsx +45 -0
  75. package/landing-page/src/app/components/ui/resizable.tsx +56 -0
  76. package/landing-page/src/app/components/ui/scroll-area.tsx +58 -0
  77. package/landing-page/src/app/components/ui/select.tsx +189 -0
  78. package/landing-page/src/app/components/ui/separator.tsx +28 -0
  79. package/landing-page/src/app/components/ui/sheet.tsx +139 -0
  80. package/landing-page/src/app/components/ui/sidebar.tsx +726 -0
  81. package/landing-page/src/app/components/ui/skeleton.tsx +13 -0
  82. package/landing-page/src/app/components/ui/slider.tsx +63 -0
  83. package/landing-page/src/app/components/ui/sonner.tsx +25 -0
  84. package/landing-page/src/app/components/ui/switch.tsx +31 -0
  85. package/landing-page/src/app/components/ui/table.tsx +116 -0
  86. package/landing-page/src/app/components/ui/tabs.tsx +66 -0
  87. package/landing-page/src/app/components/ui/textarea.tsx +18 -0
  88. package/landing-page/src/app/components/ui/toggle-group.tsx +73 -0
  89. package/landing-page/src/app/components/ui/toggle.tsx +47 -0
  90. package/landing-page/src/app/components/ui/tooltip.tsx +61 -0
  91. package/landing-page/src/app/components/ui/use-mobile.ts +21 -0
  92. package/landing-page/src/app/components/ui/utils.ts +6 -0
  93. package/landing-page/src/components/docs/DocsContent.tsx +718 -0
  94. package/landing-page/src/components/docs/DocsSidebar.tsx +84 -0
  95. package/landing-page/src/components/landing/CTA.tsx +59 -0
  96. package/landing-page/src/components/landing/Comparison.tsx +84 -0
  97. package/landing-page/src/components/landing/FaultyTerminal.tsx +424 -0
  98. package/landing-page/src/components/landing/Features.tsx +201 -0
  99. package/landing-page/src/components/landing/Hero.tsx +142 -0
  100. package/landing-page/src/components/landing/Pricing.tsx +140 -0
  101. package/landing-page/src/components/landing/Roadmap.tsx +86 -0
  102. package/landing-page/src/components/landing/Security.tsx +81 -0
  103. package/landing-page/src/components/landing/TerminalWindow.tsx +27 -0
  104. package/landing-page/src/components/landing/UseCases.tsx +55 -0
  105. package/landing-page/src/components/landing/Workflow.tsx +101 -0
  106. package/landing-page/src/components/layout/DashboardNavbar.tsx +37 -0
  107. package/landing-page/src/components/layout/Footer.tsx +55 -0
  108. package/landing-page/src/components/layout/LandingNavbar.tsx +82 -0
  109. package/landing-page/src/components/ui/badge.tsx +39 -0
  110. package/landing-page/src/components/ui/breadcrumb.tsx +115 -0
  111. package/landing-page/src/components/ui/button.tsx +57 -0
  112. package/landing-page/src/components/ui/card.tsx +79 -0
  113. package/landing-page/src/components/ui/mock-terminal.tsx +68 -0
  114. package/landing-page/src/components/ui/separator.tsx +28 -0
  115. package/landing-page/src/lib/utils.ts +6 -0
  116. package/landing-page/src/main.tsx +10 -0
  117. package/landing-page/src/pages/Dashboard.tsx +133 -0
  118. package/landing-page/src/pages/DocsPage.tsx +79 -0
  119. package/landing-page/src/pages/LandingPage.tsx +31 -0
  120. package/landing-page/src/pages/TerminalView.tsx +106 -0
  121. package/landing-page/src/styles/fonts.css +0 -0
  122. package/landing-page/src/styles/index.css +3 -0
  123. package/landing-page/src/styles/tailwind.css +4 -0
  124. package/landing-page/src/styles/theme.css +181 -0
  125. package/landing-page/vite.config.ts +19 -0
  126. package/npm/darwin-arm64/bin/gssh +0 -0
  127. package/npm/darwin-arm64/package.json +20 -0
  128. package/package.json +74 -0
  129. package/scripts/build.ts +284 -0
  130. package/scripts/release.ts +140 -0
  131. package/src/__tests__/test-utils.ts +298 -0
  132. package/src/commands/__tests__/serve-messages.test.ts +190 -0
  133. package/src/commands/access.ts +298 -0
  134. package/src/commands/add.ts +452 -0
  135. package/src/commands/auth.ts +364 -0
  136. package/src/commands/connect.ts +287 -0
  137. package/src/commands/directory.ts +16 -0
  138. package/src/commands/host.ts +396 -0
  139. package/src/commands/identity.ts +184 -0
  140. package/src/commands/list.ts +200 -0
  141. package/src/commands/relay.ts +315 -0
  142. package/src/commands/remove.ts +241 -0
  143. package/src/commands/serve.ts +1493 -0
  144. package/src/commands/share.ts +456 -0
  145. package/src/commands/status.ts +125 -0
  146. package/src/commands/switch.ts +353 -0
  147. package/src/commands/tmux.ts +317 -0
  148. package/src/core/__tests__/access.test.ts +240 -0
  149. package/src/core/access.ts +277 -0
  150. package/src/core/bundle.ts +342 -0
  151. package/src/core/config.ts +510 -0
  152. package/src/core/git.ts +317 -0
  153. package/src/core/github.ts +151 -0
  154. package/src/core/identity.ts +631 -0
  155. package/src/core/linear.ts +225 -0
  156. package/src/core/shell.ts +161 -0
  157. package/src/core/trusted-relays.ts +315 -0
  158. package/src/index.ts +821 -0
  159. package/src/lib/remote-session/index.ts +7 -0
  160. package/src/lib/remote-session/protocol.ts +267 -0
  161. package/src/lib/remote-session/session-handler.ts +581 -0
  162. package/src/lib/remote-session/workspace-scanner.ts +167 -0
  163. package/src/lib/tmux-lite/README.md +81 -0
  164. package/src/lib/tmux-lite/cli.ts +796 -0
  165. package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +349 -0
  166. package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +291 -0
  167. package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +142 -0
  168. package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +339 -0
  169. package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +477 -0
  170. package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +499 -0
  171. package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +371 -0
  172. package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +573 -0
  173. package/src/lib/tmux-lite/crypto/access-control.test.ts +512 -0
  174. package/src/lib/tmux-lite/crypto/access-control.ts +320 -0
  175. package/src/lib/tmux-lite/crypto/frames.test.ts +262 -0
  176. package/src/lib/tmux-lite/crypto/frames.ts +141 -0
  177. package/src/lib/tmux-lite/crypto/handshake.ts +894 -0
  178. package/src/lib/tmux-lite/crypto/identity.test.ts +220 -0
  179. package/src/lib/tmux-lite/crypto/identity.ts +286 -0
  180. package/src/lib/tmux-lite/crypto/index.ts +51 -0
  181. package/src/lib/tmux-lite/crypto/invites.test.ts +381 -0
  182. package/src/lib/tmux-lite/crypto/invites.ts +215 -0
  183. package/src/lib/tmux-lite/crypto/keyexchange.ts +435 -0
  184. package/src/lib/tmux-lite/crypto/keys.test.ts +58 -0
  185. package/src/lib/tmux-lite/crypto/keys.ts +47 -0
  186. package/src/lib/tmux-lite/crypto/secretbox.test.ts +169 -0
  187. package/src/lib/tmux-lite/crypto/secretbox.ts +124 -0
  188. package/src/lib/tmux-lite/handshake-handler.ts +451 -0
  189. package/src/lib/tmux-lite/protocol.test.ts +307 -0
  190. package/src/lib/tmux-lite/protocol.ts +266 -0
  191. package/src/lib/tmux-lite/relay-client.ts +506 -0
  192. package/src/lib/tmux-lite/server.ts +1250 -0
  193. package/src/lib/tmux-lite/shell-integration.sh +37 -0
  194. package/src/lib/tmux-lite/terminal-queries.test.ts +54 -0
  195. package/src/lib/tmux-lite/terminal-queries.ts +49 -0
  196. package/src/relay/__tests__/e2e-flow.test.ts +1284 -0
  197. package/src/relay/__tests__/helpers/auth.ts +354 -0
  198. package/src/relay/__tests__/helpers/ports.ts +51 -0
  199. package/src/relay/__tests__/protocol-validation.test.ts +265 -0
  200. package/src/relay/authorization.ts +303 -0
  201. package/src/relay/embedded-assets.generated.d.ts +15 -0
  202. package/src/relay/identity.ts +352 -0
  203. package/src/relay/index.ts +57 -0
  204. package/src/relay/pipes.test.ts +427 -0
  205. package/src/relay/pipes.ts +195 -0
  206. package/src/relay/protocol.ts +804 -0
  207. package/src/relay/registries.test.ts +437 -0
  208. package/src/relay/registries.ts +593 -0
  209. package/src/relay/server.test.ts +1323 -0
  210. package/src/relay/server.ts +1092 -0
  211. package/src/relay/signing.ts +238 -0
  212. package/src/relay/types.ts +69 -0
  213. package/src/serve/client-session-manager.ts +622 -0
  214. package/src/serve/daemon.ts +497 -0
  215. package/src/serve/pty-session.ts +236 -0
  216. package/src/serve/types.ts +169 -0
  217. package/src/shared/components/Flow.tsx +453 -0
  218. package/src/shared/components/Flow.tui.tsx +343 -0
  219. package/src/shared/components/Flow.web.tsx +442 -0
  220. package/src/shared/components/Inbox.tsx +446 -0
  221. package/src/shared/components/Inbox.tui.tsx +262 -0
  222. package/src/shared/components/Inbox.web.tsx +329 -0
  223. package/src/shared/components/MachineList.tsx +187 -0
  224. package/src/shared/components/MachineList.tui.tsx +161 -0
  225. package/src/shared/components/MachineList.web.tsx +210 -0
  226. package/src/shared/components/ProjectList.tsx +176 -0
  227. package/src/shared/components/ProjectList.tui.tsx +109 -0
  228. package/src/shared/components/ProjectList.web.tsx +143 -0
  229. package/src/shared/components/SpacesBrowser.tsx +332 -0
  230. package/src/shared/components/SpacesBrowser.tui.tsx +163 -0
  231. package/src/shared/components/SpacesBrowser.web.tsx +221 -0
  232. package/src/shared/components/index.ts +103 -0
  233. package/src/shared/hooks/index.ts +16 -0
  234. package/src/shared/hooks/useNavigation.ts +226 -0
  235. package/src/shared/index.ts +122 -0
  236. package/src/shared/providers/LocalMachineProvider.ts +425 -0
  237. package/src/shared/providers/MachineProvider.ts +165 -0
  238. package/src/shared/providers/RemoteMachineProvider.ts +444 -0
  239. package/src/shared/providers/index.ts +26 -0
  240. package/src/shared/types.ts +145 -0
  241. package/src/tui/adapters.ts +120 -0
  242. package/src/tui/app.tsx +1816 -0
  243. package/src/tui/components/Terminal.tsx +580 -0
  244. package/src/tui/hooks/index.ts +35 -0
  245. package/src/tui/hooks/useAppState.ts +314 -0
  246. package/src/tui/hooks/useDaemonStatus.ts +174 -0
  247. package/src/tui/hooks/useInboxTUI.ts +113 -0
  248. package/src/tui/hooks/useRemoteMachines.ts +209 -0
  249. package/src/tui/index.ts +24 -0
  250. package/src/tui/state.ts +299 -0
  251. package/src/tui/terminal-bracketed-paste.test.ts +45 -0
  252. package/src/tui/terminal-bracketed-paste.ts +47 -0
  253. package/src/types/bundle.ts +112 -0
  254. package/src/types/config.ts +89 -0
  255. package/src/types/errors.ts +206 -0
  256. package/src/types/identity.ts +284 -0
  257. package/src/types/workspace-fuzzy.ts +49 -0
  258. package/src/types/workspace.ts +151 -0
  259. package/src/utils/bun-socket-writer.ts +80 -0
  260. package/src/utils/deps.ts +127 -0
  261. package/src/utils/fuzzy-match.ts +125 -0
  262. package/src/utils/logger.ts +127 -0
  263. package/src/utils/markdown.ts +254 -0
  264. package/src/utils/onboarding.ts +229 -0
  265. package/src/utils/prompts.ts +114 -0
  266. package/src/utils/run-commands.ts +112 -0
  267. package/src/utils/run-scripts.ts +142 -0
  268. package/src/utils/sanitize.ts +98 -0
  269. package/src/utils/secrets.ts +122 -0
  270. package/src/utils/shell-escape.ts +40 -0
  271. package/src/utils/utf8.ts +79 -0
  272. package/src/utils/workspace-state.ts +47 -0
  273. package/src/web/README.md +73 -0
  274. package/src/web/bun.lock +575 -0
  275. package/src/web/eslint.config.js +23 -0
  276. package/src/web/index.html +16 -0
  277. package/src/web/package.json +37 -0
  278. package/src/web/public/vite.svg +1 -0
  279. package/src/web/src/App.tsx +604 -0
  280. package/src/web/src/assets/react.svg +1 -0
  281. package/src/web/src/components/Terminal.tsx +207 -0
  282. package/src/web/src/hooks/useRelayConnection.ts +224 -0
  283. package/src/web/src/hooks/useTerminal.ts +699 -0
  284. package/src/web/src/index.css +55 -0
  285. package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +1158 -0
  286. package/src/web/src/lib/crypto/frames.ts +205 -0
  287. package/src/web/src/lib/crypto/handshake.ts +396 -0
  288. package/src/web/src/lib/crypto/identity.ts +128 -0
  289. package/src/web/src/lib/crypto/keyexchange.ts +246 -0
  290. package/src/web/src/lib/crypto/relay-signing.ts +53 -0
  291. package/src/web/src/lib/invite.ts +58 -0
  292. package/src/web/src/lib/storage/identity-store.ts +94 -0
  293. package/src/web/src/main.tsx +10 -0
  294. package/src/web/src/types/identity.ts +45 -0
  295. package/src/web/tsconfig.app.json +28 -0
  296. package/src/web/tsconfig.json +7 -0
  297. package/src/web/tsconfig.node.json +26 -0
  298. package/src/web/vite.config.ts +31 -0
  299. package/todo-security.md +92 -0
  300. package/tsconfig.json +23 -0
  301. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
  302. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
  303. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
  304. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
  305. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
  306. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
  307. package/worker/bun.lock +237 -0
  308. package/worker/package.json +22 -0
  309. package/worker/schema.sql +96 -0
  310. package/worker/src/handlers/auth.ts +451 -0
  311. package/worker/src/handlers/subdomains.ts +376 -0
  312. package/worker/src/handlers/user.ts +98 -0
  313. package/worker/src/index.ts +70 -0
  314. package/worker/src/middleware/auth.ts +152 -0
  315. package/worker/src/services/cloudflare.ts +609 -0
  316. package/worker/src/types.ts +96 -0
  317. package/worker/tsconfig.json +15 -0
  318. package/worker/wrangler.toml +26 -0
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Test identity generation helpers
3
+ *
4
+ * Provides utilities for generating test identities and public identity objects
5
+ * for use in integration tests.
6
+ */
7
+
8
+ import { generateIdentity, getPublicIdentity } from "../../identity.js";
9
+ import type { Identity, PublicIdentity } from "../../../../../types/identity.js";
10
+
11
+ /**
12
+ * Generate a test identity with optional label
13
+ *
14
+ * @param label - Optional label for the identity
15
+ * @returns Complete test identity
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const alice = createTestIdentity("Alice");
20
+ * const bob = createTestIdentity("Bob");
21
+ * ```
22
+ */
23
+ export function createTestIdentity(label?: string): Identity {
24
+ return generateIdentity(label);
25
+ }
26
+
27
+ /**
28
+ * Create a pair of test identities (client and machine)
29
+ *
30
+ * @param clientLabel - Label for client identity
31
+ * @param machineLabel - Label for machine identity
32
+ * @returns Object with client and machine identities
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const { client, machine } = createTestIdentityPair();
37
+ * ```
38
+ */
39
+ export function createTestIdentityPair(
40
+ clientLabel = "Test Client",
41
+ machineLabel = "Test Machine"
42
+ ): { client: Identity; machine: Identity } {
43
+ return {
44
+ client: createTestIdentity(clientLabel),
45
+ machine: createTestIdentity(machineLabel),
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Create multiple test identities
51
+ *
52
+ * @param count - Number of identities to create
53
+ * @param labelPrefix - Prefix for identity labels
54
+ * @returns Array of identities
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const clients = createTestIdentities(5, "Client");
59
+ * // Creates: "Client 1", "Client 2", etc.
60
+ * ```
61
+ */
62
+ export function createTestIdentities(
63
+ count: number,
64
+ labelPrefix = "Identity"
65
+ ): Identity[] {
66
+ return Array.from({ length: count }, (_, i) =>
67
+ createTestIdentity(`${labelPrefix} ${i + 1}`)
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Get public identity from a complete identity
73
+ *
74
+ * Wrapper around getPublicIdentity for convenience
75
+ *
76
+ * @param identity - Complete identity
77
+ * @returns Public identity (safe to share)
78
+ */
79
+ export function toPublicIdentity(identity: Identity): PublicIdentity {
80
+ return getPublicIdentity(identity);
81
+ }
82
+
83
+ /**
84
+ * Create a fixture set of identities for testing
85
+ *
86
+ * Returns a pre-configured set of identities with meaningful names
87
+ * for common testing scenarios.
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * const fixtures = createIdentityFixtures();
92
+ * // fixtures.alice - Client identity
93
+ * // fixtures.bob - Another client
94
+ * // fixtures.machine - Machine/server identity
95
+ * // fixtures.untrusted - Identity not in access list
96
+ * ```
97
+ */
98
+ export function createIdentityFixtures(): {
99
+ alice: Identity;
100
+ bob: Identity;
101
+ machine: Identity;
102
+ untrusted: Identity;
103
+ alicePublic: PublicIdentity;
104
+ bobPublic: PublicIdentity;
105
+ machinePublic: PublicIdentity;
106
+ untrustedPublic: PublicIdentity;
107
+ } {
108
+ const alice = createTestIdentity("Alice (Client)");
109
+ const bob = createTestIdentity("Bob (Client)");
110
+ const machine = createTestIdentity("Test Machine");
111
+ const untrusted = createTestIdentity("Untrusted Client");
112
+
113
+ return {
114
+ alice,
115
+ bob,
116
+ machine,
117
+ untrusted,
118
+ alicePublic: toPublicIdentity(alice),
119
+ bobPublic: toPublicIdentity(bob),
120
+ machinePublic: toPublicIdentity(machine),
121
+ untrustedPublic: toPublicIdentity(untrusted),
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Create an access control list pre-populated with test identities
127
+ *
128
+ * @param acl - AccessControlList to populate
129
+ * @param identities - Identities to add to the list
130
+ * @param accessType - Access type to grant (optional, default: 'full')
131
+ * @param sessionId - Session ID for session-invite (optional)
132
+ */
133
+ export function populateAccessList(
134
+ acl: import("../../access-control.js").AccessControlList,
135
+ identities: Identity[],
136
+ accessType?: import("../../../../../types/identity.js").AccessType,
137
+ sessionId?: string
138
+ ): void {
139
+ for (const identity of identities) {
140
+ acl.addEntry(toPublicIdentity(identity), accessType, sessionId);
141
+ }
142
+ }
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Integration tests for authorization scenarios
3
+ *
4
+ * Tests access list and invite token authorization during handshake.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach } from "bun:test";
8
+ import {
9
+ HandshakeHandler,
10
+ type HandshakeMessage,
11
+ } from "../../../handshake-handler.js";
12
+ import { AccessControlList } from "../../access-control.js";
13
+ import { createInviteToken, isInviteExpired } from "../../invites.js";
14
+ import {
15
+ createTestIdentityPair,
16
+ createTestIdentity,
17
+ toPublicIdentity,
18
+ createIdentityFixtures,
19
+ } from "../helpers/test-identities.js";
20
+ import { runCompleteHandshake } from "../helpers/handshake-runner.js";
21
+ import type { X3DHResponseMessage, X3DHResultMessage } from "../../../../../types/identity.js";
22
+ import {
23
+ isReplyResult,
24
+ getReplyData,
25
+ } from "../../../../../__tests__/test-utils.js";
26
+
27
+ describe("Authorization Integration", () => {
28
+ describe("access list authorization", () => {
29
+ it("should accept client in access list", async () => {
30
+ const { client, machine } = createTestIdentityPair();
31
+ const accessList = new AccessControlList();
32
+ accessList.addEntry(toPublicIdentity(client));
33
+
34
+ const result = await runCompleteHandshake(
35
+ client,
36
+ machine,
37
+ accessList,
38
+ { type: "access_list" }
39
+ );
40
+
41
+ expect(result.success).toBe(true);
42
+ expect(result.machineSession?.peerIdentityId).toBe(client.id);
43
+ });
44
+
45
+ it("should reject client not in access list", async () => {
46
+ const { client, machine } = createTestIdentityPair();
47
+ const accessList = new AccessControlList();
48
+ // Client NOT added to access list
49
+
50
+ const result = await runCompleteHandshake(
51
+ client,
52
+ machine,
53
+ accessList,
54
+ { type: "access_list" }
55
+ );
56
+
57
+ expect(result.success).toBe(false);
58
+ expect(result.error).toContain("Not in access list");
59
+ });
60
+
61
+ it("should respect session-invite access type", async () => {
62
+ const { client, machine } = createTestIdentityPair();
63
+ const accessList = new AccessControlList();
64
+ accessList.addEntry(toPublicIdentity(client), 'session-invite', 'test-session');
65
+
66
+ const result = await runCompleteHandshake(
67
+ client,
68
+ machine,
69
+ accessList,
70
+ { type: "access_list" }
71
+ );
72
+
73
+ expect(result.success).toBe(true);
74
+ expect(result.machineSession?.accessType).toBe('session-invite');
75
+ });
76
+
77
+ it("should respect full access type", async () => {
78
+ const { client, machine } = createTestIdentityPair();
79
+ const accessList = new AccessControlList();
80
+ accessList.addEntry(toPublicIdentity(client), 'full');
81
+
82
+ const result = await runCompleteHandshake(
83
+ client,
84
+ machine,
85
+ accessList,
86
+ { type: "access_list" }
87
+ );
88
+
89
+ expect(result.success).toBe(true);
90
+ expect(result.machineSession?.accessType).toBe('full');
91
+ });
92
+
93
+ it("should reject with expired access entry", async () => {
94
+ const { client, machine } = createTestIdentityPair();
95
+ const accessList = new AccessControlList();
96
+
97
+ // Add with expiry in the past
98
+ const entry = accessList.addEntry(toPublicIdentity(client));
99
+ // Manually set expiry to past
100
+ const entries = accessList.export();
101
+ entries[0].expiresAt = Date.now() - 1000;
102
+ accessList.import(entries);
103
+
104
+ const result = await runCompleteHandshake(
105
+ client,
106
+ machine,
107
+ accessList,
108
+ { type: "access_list" }
109
+ );
110
+
111
+ expect(result.success).toBe(false);
112
+ expect(result.error).toContain("Not in access list");
113
+ });
114
+ });
115
+
116
+ describe("invite token authorization", () => {
117
+ it("should accept valid invite token", async () => {
118
+ const { client, machine } = createTestIdentityPair();
119
+ const accessList = new AccessControlList();
120
+
121
+ const result = await runCompleteHandshake(
122
+ client,
123
+ machine,
124
+ accessList,
125
+ {
126
+ type: "invite",
127
+ accessType: 'full',
128
+ }
129
+ );
130
+
131
+ expect(result.success).toBe(true);
132
+ expect(result.machineSession?.peerIdentityId).toBe(client.id);
133
+ });
134
+
135
+ it("should use access type from invite token", async () => {
136
+ const { client, machine } = createTestIdentityPair();
137
+ const accessList = new AccessControlList();
138
+
139
+ const result = await runCompleteHandshake(
140
+ client,
141
+ machine,
142
+ accessList,
143
+ {
144
+ type: "invite",
145
+ accessType: 'session-invite',
146
+ sessionId: 'test-session',
147
+ }
148
+ );
149
+
150
+ expect(result.success).toBe(true);
151
+ expect(result.machineSession?.accessType).toBe('session-invite');
152
+ });
153
+
154
+ it("should add client to access list after invite acceptance", async () => {
155
+ const { client, machine } = createTestIdentityPair();
156
+ const accessList = new AccessControlList();
157
+
158
+ expect(accessList.hasAccess(client.id)).toBe(false);
159
+
160
+ const result = await runCompleteHandshake(
161
+ client,
162
+ machine,
163
+ accessList,
164
+ {
165
+ type: "invite",
166
+ accessType: 'full',
167
+ }
168
+ );
169
+
170
+ expect(result.success).toBe(true);
171
+
172
+ // Client should now be in access list
173
+ expect(accessList.hasAccess(client.id)).toBe(true);
174
+ });
175
+
176
+ it("should reject expired invite token", async () => {
177
+ const { client, machine } = createTestIdentityPair();
178
+ const accessList = new AccessControlList();
179
+
180
+ // Create an already-expired token
181
+ const result = await runCompleteHandshake(
182
+ client,
183
+ machine,
184
+ accessList,
185
+ {
186
+ type: "invite",
187
+ accessType: 'full',
188
+ validityMs: -1000, // Expired 1 second ago
189
+ }
190
+ );
191
+
192
+ expect(result.success).toBe(false);
193
+ expect(result.error).toContain("expired");
194
+ });
195
+
196
+ it("should reject invite token from different machine", async () => {
197
+ const { client, machine } = createTestIdentityPair();
198
+ const otherMachine = createTestIdentity("Other Machine");
199
+ const accessList = new AccessControlList();
200
+
201
+ const handler = new HandshakeHandler({
202
+ identity: machine,
203
+ accessList,
204
+ });
205
+
206
+ // Create invite from a different machine
207
+ const wrongToken = createInviteToken(otherMachine, "wss://test.relay", {
208
+ accessType: 'full',
209
+ });
210
+
211
+ // Start handshake
212
+ const { createClientHello, processServerHello, createClientAuth } =
213
+ await import("../../handshake.js");
214
+
215
+ const { state: state1, message: clientHello } = createClientHello();
216
+
217
+ const result1 = await handler.processMessage("conn-1", {
218
+ type: "handshake",
219
+ phase: "client_hello",
220
+ data: clientHello,
221
+ });
222
+
223
+ expect(result1.type).toBe("reply");
224
+ if (!isReplyResult(result1)) throw new Error("Expected reply");
225
+ const serverHello = getReplyData<X3DHResponseMessage>(result1);
226
+
227
+ const state2 = processServerHello(state1, serverHello);
228
+ expect(state2).not.toBeNull();
229
+
230
+ const { message: clientAuth } = createClientAuth(state2!, client, {
231
+ type: "invite",
232
+ inviteToken: wrongToken,
233
+ });
234
+
235
+ const result2 = await handler.processMessage("conn-1", {
236
+ type: "handshake",
237
+ phase: "client_auth",
238
+ data: clientAuth,
239
+ });
240
+
241
+ // Should return a reply with rejection (not an error)
242
+ // The ServerAuth message contains the rejection reason
243
+ expect(result2.type).toBe("reply");
244
+ if (isReplyResult(result2)) {
245
+ const serverAuth = getReplyData<X3DHResultMessage>(result2);
246
+ expect(serverAuth.result.type).toBe("rejected");
247
+ if (serverAuth.result.type === "rejected") {
248
+ expect(serverAuth.result.reason).toContain("not issued by this machine");
249
+ }
250
+ }
251
+ });
252
+ });
253
+
254
+ describe("multiple clients", () => {
255
+ it("should allow multiple clients with different access types", async () => {
256
+ const fixtures = createIdentityFixtures();
257
+ const accessList = new AccessControlList();
258
+
259
+ // Alice gets full access
260
+ accessList.addEntry(fixtures.alicePublic, 'full');
261
+
262
+ // Bob gets session-invite
263
+ accessList.addEntry(fixtures.bobPublic, 'session-invite', 'test-session');
264
+
265
+ const [aliceResult, bobResult] = await Promise.all([
266
+ runCompleteHandshake(
267
+ fixtures.alice,
268
+ fixtures.machine,
269
+ accessList,
270
+ { type: "access_list" }
271
+ ),
272
+ runCompleteHandshake(
273
+ fixtures.bob,
274
+ fixtures.machine,
275
+ accessList,
276
+ { type: "access_list" }
277
+ ),
278
+ ]);
279
+
280
+ expect(aliceResult.success).toBe(true);
281
+ expect(aliceResult.machineSession?.accessType).toBe('full');
282
+
283
+ expect(bobResult.success).toBe(true);
284
+ expect(bobResult.machineSession?.accessType).toBe('session-invite');
285
+ });
286
+
287
+ it("should accept authorized and reject unauthorized simultaneously", async () => {
288
+ const fixtures = createIdentityFixtures();
289
+ const accessList = new AccessControlList();
290
+
291
+ // Only Alice is authorized
292
+ accessList.addEntry(fixtures.alicePublic);
293
+
294
+ const [aliceResult, untrustedResult] = await Promise.all([
295
+ runCompleteHandshake(
296
+ fixtures.alice,
297
+ fixtures.machine,
298
+ accessList,
299
+ { type: "access_list" }
300
+ ),
301
+ runCompleteHandshake(
302
+ fixtures.untrusted,
303
+ fixtures.machine,
304
+ accessList,
305
+ { type: "access_list" }
306
+ ),
307
+ ]);
308
+
309
+ expect(aliceResult.success).toBe(true);
310
+ expect(untrustedResult.success).toBe(false);
311
+ });
312
+ });
313
+
314
+ describe("access type combinations", () => {
315
+ const accessTypeCombinations: Array<{ accessType: 'full' | 'session-invite'; sessionId?: string }> = [
316
+ { accessType: 'full' },
317
+ { accessType: 'session-invite', sessionId: 'session-1' },
318
+ { accessType: 'session-invite', sessionId: 'session-2' },
319
+ ];
320
+
321
+ for (const config of accessTypeCombinations) {
322
+ it(`should correctly grant access type: ${config.accessType}${config.sessionId ? ` with sessionId=${config.sessionId}` : ''}`, async () => {
323
+ const { client, machine } = createTestIdentityPair();
324
+ const accessList = new AccessControlList();
325
+ accessList.addEntry(toPublicIdentity(client), config.accessType, config.sessionId);
326
+
327
+ const result = await runCompleteHandshake(
328
+ client,
329
+ machine,
330
+ accessList,
331
+ { type: "access_list" }
332
+ );
333
+
334
+ expect(result.success).toBe(true);
335
+ expect(result.machineSession?.accessType).toBe(config.accessType);
336
+ });
337
+ }
338
+ });
339
+ });