gitspace 0.2.0-rc.2 → 0.2.0-rc.21

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 (316) hide show
  1. package/README.md +68 -38
  2. package/package.json +36 -25
  3. package/.claude/settings.local.json +0 -21
  4. package/.gitspace/bundle.json +0 -50
  5. package/.gitspace/select/01-status.sh +0 -40
  6. package/.gitspace/setup/01-install-deps.sh +0 -12
  7. package/.gitspace/setup/02-typecheck.sh +0 -16
  8. package/AGENTS.md +0 -439
  9. package/CLAUDE.md +0 -1
  10. package/bun.lock +0 -647
  11. package/docs/CONNECTION.md +0 -623
  12. package/docs/GATEWAY-WORKER.md +0 -319
  13. package/docs/GETTING-STARTED.md +0 -448
  14. package/docs/GITSPACE-PLATFORM.md +0 -1819
  15. package/docs/INFRASTRUCTURE.md +0 -1347
  16. package/docs/PROTOCOL.md +0 -619
  17. package/docs/QUICKSTART.md +0 -174
  18. package/docs/RELAY.md +0 -327
  19. package/docs/REMOTE-DESIGN.md +0 -549
  20. package/docs/ROADMAP.md +0 -564
  21. package/docs/SITE_DOCS_FIGMA_MAKE.md +0 -1167
  22. package/docs/STACK-DESIGN.md +0 -588
  23. package/docs/UNIFIED_ARCHITECTURE.md +0 -292
  24. package/experiments/pty-benchmark.ts +0 -148
  25. package/experiments/pty-latency.ts +0 -100
  26. package/experiments/router/client.ts +0 -199
  27. package/experiments/router/protocol.ts +0 -74
  28. package/experiments/router/router.ts +0 -217
  29. package/experiments/router/session.ts +0 -180
  30. package/experiments/router/test.ts +0 -133
  31. package/experiments/socket-bandwidth.ts +0 -77
  32. package/homebrew/gitspace.rb +0 -45
  33. package/landing-page/ATTRIBUTIONS.md +0 -3
  34. package/landing-page/README.md +0 -11
  35. package/landing-page/bun.lock +0 -801
  36. package/landing-page/guidelines/Guidelines.md +0 -61
  37. package/landing-page/index.html +0 -37
  38. package/landing-page/package.json +0 -90
  39. package/landing-page/postcss.config.mjs +0 -15
  40. package/landing-page/public/_redirects +0 -1
  41. package/landing-page/public/favicon.png +0 -0
  42. package/landing-page/src/app/App.tsx +0 -53
  43. package/landing-page/src/app/components/figma/ImageWithFallback.tsx +0 -27
  44. package/landing-page/src/app/components/ui/accordion.tsx +0 -66
  45. package/landing-page/src/app/components/ui/alert-dialog.tsx +0 -157
  46. package/landing-page/src/app/components/ui/alert.tsx +0 -66
  47. package/landing-page/src/app/components/ui/aspect-ratio.tsx +0 -11
  48. package/landing-page/src/app/components/ui/avatar.tsx +0 -53
  49. package/landing-page/src/app/components/ui/badge.tsx +0 -46
  50. package/landing-page/src/app/components/ui/breadcrumb.tsx +0 -109
  51. package/landing-page/src/app/components/ui/button.tsx +0 -57
  52. package/landing-page/src/app/components/ui/calendar.tsx +0 -75
  53. package/landing-page/src/app/components/ui/card.tsx +0 -92
  54. package/landing-page/src/app/components/ui/carousel.tsx +0 -241
  55. package/landing-page/src/app/components/ui/chart.tsx +0 -353
  56. package/landing-page/src/app/components/ui/checkbox.tsx +0 -32
  57. package/landing-page/src/app/components/ui/collapsible.tsx +0 -33
  58. package/landing-page/src/app/components/ui/command.tsx +0 -177
  59. package/landing-page/src/app/components/ui/context-menu.tsx +0 -252
  60. package/landing-page/src/app/components/ui/dialog.tsx +0 -135
  61. package/landing-page/src/app/components/ui/drawer.tsx +0 -132
  62. package/landing-page/src/app/components/ui/dropdown-menu.tsx +0 -257
  63. package/landing-page/src/app/components/ui/form.tsx +0 -168
  64. package/landing-page/src/app/components/ui/hover-card.tsx +0 -44
  65. package/landing-page/src/app/components/ui/input-otp.tsx +0 -77
  66. package/landing-page/src/app/components/ui/input.tsx +0 -21
  67. package/landing-page/src/app/components/ui/label.tsx +0 -24
  68. package/landing-page/src/app/components/ui/menubar.tsx +0 -276
  69. package/landing-page/src/app/components/ui/navigation-menu.tsx +0 -168
  70. package/landing-page/src/app/components/ui/pagination.tsx +0 -127
  71. package/landing-page/src/app/components/ui/popover.tsx +0 -48
  72. package/landing-page/src/app/components/ui/progress.tsx +0 -31
  73. package/landing-page/src/app/components/ui/radio-group.tsx +0 -45
  74. package/landing-page/src/app/components/ui/resizable.tsx +0 -56
  75. package/landing-page/src/app/components/ui/scroll-area.tsx +0 -58
  76. package/landing-page/src/app/components/ui/select.tsx +0 -189
  77. package/landing-page/src/app/components/ui/separator.tsx +0 -28
  78. package/landing-page/src/app/components/ui/sheet.tsx +0 -139
  79. package/landing-page/src/app/components/ui/sidebar.tsx +0 -726
  80. package/landing-page/src/app/components/ui/skeleton.tsx +0 -13
  81. package/landing-page/src/app/components/ui/slider.tsx +0 -63
  82. package/landing-page/src/app/components/ui/sonner.tsx +0 -25
  83. package/landing-page/src/app/components/ui/switch.tsx +0 -31
  84. package/landing-page/src/app/components/ui/table.tsx +0 -116
  85. package/landing-page/src/app/components/ui/tabs.tsx +0 -66
  86. package/landing-page/src/app/components/ui/textarea.tsx +0 -18
  87. package/landing-page/src/app/components/ui/toggle-group.tsx +0 -73
  88. package/landing-page/src/app/components/ui/toggle.tsx +0 -47
  89. package/landing-page/src/app/components/ui/tooltip.tsx +0 -61
  90. package/landing-page/src/app/components/ui/use-mobile.ts +0 -21
  91. package/landing-page/src/app/components/ui/utils.ts +0 -6
  92. package/landing-page/src/components/docs/DocsContent.tsx +0 -718
  93. package/landing-page/src/components/docs/DocsSidebar.tsx +0 -84
  94. package/landing-page/src/components/landing/CTA.tsx +0 -59
  95. package/landing-page/src/components/landing/Comparison.tsx +0 -84
  96. package/landing-page/src/components/landing/FaultyTerminal.tsx +0 -424
  97. package/landing-page/src/components/landing/Features.tsx +0 -201
  98. package/landing-page/src/components/landing/Hero.tsx +0 -142
  99. package/landing-page/src/components/landing/Pricing.tsx +0 -140
  100. package/landing-page/src/components/landing/Roadmap.tsx +0 -86
  101. package/landing-page/src/components/landing/Security.tsx +0 -81
  102. package/landing-page/src/components/landing/TerminalWindow.tsx +0 -27
  103. package/landing-page/src/components/landing/UseCases.tsx +0 -55
  104. package/landing-page/src/components/landing/Workflow.tsx +0 -101
  105. package/landing-page/src/components/layout/DashboardNavbar.tsx +0 -37
  106. package/landing-page/src/components/layout/Footer.tsx +0 -55
  107. package/landing-page/src/components/layout/LandingNavbar.tsx +0 -82
  108. package/landing-page/src/components/ui/badge.tsx +0 -39
  109. package/landing-page/src/components/ui/breadcrumb.tsx +0 -115
  110. package/landing-page/src/components/ui/button.tsx +0 -57
  111. package/landing-page/src/components/ui/card.tsx +0 -79
  112. package/landing-page/src/components/ui/mock-terminal.tsx +0 -68
  113. package/landing-page/src/components/ui/separator.tsx +0 -28
  114. package/landing-page/src/lib/utils.ts +0 -6
  115. package/landing-page/src/main.tsx +0 -10
  116. package/landing-page/src/pages/Dashboard.tsx +0 -133
  117. package/landing-page/src/pages/DocsPage.tsx +0 -79
  118. package/landing-page/src/pages/LandingPage.tsx +0 -31
  119. package/landing-page/src/pages/TerminalView.tsx +0 -106
  120. package/landing-page/src/styles/fonts.css +0 -0
  121. package/landing-page/src/styles/index.css +0 -3
  122. package/landing-page/src/styles/tailwind.css +0 -4
  123. package/landing-page/src/styles/theme.css +0 -181
  124. package/landing-page/vite.config.ts +0 -19
  125. package/npm/darwin-arm64/bin/gssh +0 -0
  126. package/npm/darwin-arm64/package.json +0 -20
  127. package/scripts/build.ts +0 -298
  128. package/scripts/release.ts +0 -140
  129. package/src/__tests__/test-utils.ts +0 -298
  130. package/src/commands/__tests__/serve-messages.test.ts +0 -190
  131. package/src/commands/access.ts +0 -298
  132. package/src/commands/add.ts +0 -452
  133. package/src/commands/auth.ts +0 -364
  134. package/src/commands/connect.ts +0 -287
  135. package/src/commands/directory.ts +0 -16
  136. package/src/commands/host.ts +0 -396
  137. package/src/commands/identity.ts +0 -184
  138. package/src/commands/list.ts +0 -200
  139. package/src/commands/relay.ts +0 -315
  140. package/src/commands/remove.ts +0 -241
  141. package/src/commands/serve.ts +0 -1493
  142. package/src/commands/share.ts +0 -456
  143. package/src/commands/status.ts +0 -125
  144. package/src/commands/switch.ts +0 -353
  145. package/src/commands/tmux.ts +0 -317
  146. package/src/core/__tests__/access.test.ts +0 -240
  147. package/src/core/access.ts +0 -277
  148. package/src/core/bundle.ts +0 -342
  149. package/src/core/config.ts +0 -510
  150. package/src/core/git.ts +0 -317
  151. package/src/core/github.ts +0 -151
  152. package/src/core/identity.ts +0 -631
  153. package/src/core/linear.ts +0 -225
  154. package/src/core/shell.ts +0 -161
  155. package/src/core/trusted-relays.ts +0 -315
  156. package/src/index.ts +0 -810
  157. package/src/lib/remote-session/index.ts +0 -7
  158. package/src/lib/remote-session/protocol.ts +0 -267
  159. package/src/lib/remote-session/session-handler.ts +0 -581
  160. package/src/lib/remote-session/workspace-scanner.ts +0 -167
  161. package/src/lib/tmux-lite/README.md +0 -81
  162. package/src/lib/tmux-lite/cli.ts +0 -796
  163. package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +0 -349
  164. package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +0 -291
  165. package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +0 -142
  166. package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +0 -339
  167. package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +0 -477
  168. package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +0 -499
  169. package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +0 -371
  170. package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +0 -573
  171. package/src/lib/tmux-lite/crypto/access-control.test.ts +0 -512
  172. package/src/lib/tmux-lite/crypto/access-control.ts +0 -320
  173. package/src/lib/tmux-lite/crypto/frames.test.ts +0 -262
  174. package/src/lib/tmux-lite/crypto/frames.ts +0 -141
  175. package/src/lib/tmux-lite/crypto/handshake.ts +0 -894
  176. package/src/lib/tmux-lite/crypto/identity.test.ts +0 -220
  177. package/src/lib/tmux-lite/crypto/identity.ts +0 -286
  178. package/src/lib/tmux-lite/crypto/index.ts +0 -51
  179. package/src/lib/tmux-lite/crypto/invites.test.ts +0 -381
  180. package/src/lib/tmux-lite/crypto/invites.ts +0 -215
  181. package/src/lib/tmux-lite/crypto/keyexchange.ts +0 -435
  182. package/src/lib/tmux-lite/crypto/keys.test.ts +0 -58
  183. package/src/lib/tmux-lite/crypto/keys.ts +0 -47
  184. package/src/lib/tmux-lite/crypto/secretbox.test.ts +0 -169
  185. package/src/lib/tmux-lite/crypto/secretbox.ts +0 -124
  186. package/src/lib/tmux-lite/handshake-handler.ts +0 -451
  187. package/src/lib/tmux-lite/protocol.test.ts +0 -307
  188. package/src/lib/tmux-lite/protocol.ts +0 -266
  189. package/src/lib/tmux-lite/relay-client.ts +0 -506
  190. package/src/lib/tmux-lite/server.ts +0 -1250
  191. package/src/lib/tmux-lite/shell-integration.sh +0 -37
  192. package/src/lib/tmux-lite/terminal-queries.test.ts +0 -54
  193. package/src/lib/tmux-lite/terminal-queries.ts +0 -49
  194. package/src/relay/__tests__/e2e-flow.test.ts +0 -1284
  195. package/src/relay/__tests__/helpers/auth.ts +0 -354
  196. package/src/relay/__tests__/helpers/ports.ts +0 -51
  197. package/src/relay/__tests__/protocol-validation.test.ts +0 -265
  198. package/src/relay/authorization.ts +0 -303
  199. package/src/relay/embedded-assets.generated.d.ts +0 -15
  200. package/src/relay/identity.ts +0 -352
  201. package/src/relay/index.ts +0 -57
  202. package/src/relay/pipes.test.ts +0 -427
  203. package/src/relay/pipes.ts +0 -195
  204. package/src/relay/protocol.ts +0 -804
  205. package/src/relay/registries.test.ts +0 -437
  206. package/src/relay/registries.ts +0 -593
  207. package/src/relay/server.test.ts +0 -1323
  208. package/src/relay/server.ts +0 -1092
  209. package/src/relay/signing.ts +0 -238
  210. package/src/relay/types.ts +0 -69
  211. package/src/serve/client-session-manager.ts +0 -622
  212. package/src/serve/daemon.ts +0 -497
  213. package/src/serve/pty-session.ts +0 -236
  214. package/src/serve/types.ts +0 -169
  215. package/src/shared/components/Flow.tsx +0 -453
  216. package/src/shared/components/Flow.tui.tsx +0 -343
  217. package/src/shared/components/Flow.web.tsx +0 -442
  218. package/src/shared/components/Inbox.tsx +0 -446
  219. package/src/shared/components/Inbox.tui.tsx +0 -262
  220. package/src/shared/components/Inbox.web.tsx +0 -329
  221. package/src/shared/components/MachineList.tsx +0 -187
  222. package/src/shared/components/MachineList.tui.tsx +0 -161
  223. package/src/shared/components/MachineList.web.tsx +0 -210
  224. package/src/shared/components/ProjectList.tsx +0 -176
  225. package/src/shared/components/ProjectList.tui.tsx +0 -109
  226. package/src/shared/components/ProjectList.web.tsx +0 -143
  227. package/src/shared/components/SpacesBrowser.tsx +0 -332
  228. package/src/shared/components/SpacesBrowser.tui.tsx +0 -163
  229. package/src/shared/components/SpacesBrowser.web.tsx +0 -221
  230. package/src/shared/components/index.ts +0 -103
  231. package/src/shared/hooks/index.ts +0 -16
  232. package/src/shared/hooks/useNavigation.ts +0 -226
  233. package/src/shared/index.ts +0 -122
  234. package/src/shared/providers/LocalMachineProvider.ts +0 -425
  235. package/src/shared/providers/MachineProvider.ts +0 -165
  236. package/src/shared/providers/RemoteMachineProvider.ts +0 -444
  237. package/src/shared/providers/index.ts +0 -26
  238. package/src/shared/types.ts +0 -145
  239. package/src/tui/adapters.ts +0 -120
  240. package/src/tui/app.tsx +0 -1816
  241. package/src/tui/components/Terminal.tsx +0 -580
  242. package/src/tui/hooks/index.ts +0 -35
  243. package/src/tui/hooks/useAppState.ts +0 -314
  244. package/src/tui/hooks/useDaemonStatus.ts +0 -174
  245. package/src/tui/hooks/useInboxTUI.ts +0 -113
  246. package/src/tui/hooks/useRemoteMachines.ts +0 -209
  247. package/src/tui/index.ts +0 -24
  248. package/src/tui/state.ts +0 -299
  249. package/src/tui/terminal-bracketed-paste.test.ts +0 -45
  250. package/src/tui/terminal-bracketed-paste.ts +0 -47
  251. package/src/types/bundle.ts +0 -112
  252. package/src/types/config.ts +0 -89
  253. package/src/types/errors.ts +0 -206
  254. package/src/types/identity.ts +0 -284
  255. package/src/types/workspace-fuzzy.ts +0 -49
  256. package/src/types/workspace.ts +0 -151
  257. package/src/utils/bun-socket-writer.ts +0 -80
  258. package/src/utils/deps.ts +0 -127
  259. package/src/utils/fuzzy-match.ts +0 -125
  260. package/src/utils/logger.ts +0 -127
  261. package/src/utils/markdown.ts +0 -254
  262. package/src/utils/onboarding.ts +0 -229
  263. package/src/utils/prompts.ts +0 -114
  264. package/src/utils/run-commands.ts +0 -112
  265. package/src/utils/run-scripts.ts +0 -142
  266. package/src/utils/sanitize.ts +0 -98
  267. package/src/utils/secrets.ts +0 -122
  268. package/src/utils/shell-escape.ts +0 -40
  269. package/src/utils/utf8.ts +0 -79
  270. package/src/utils/workspace-state.ts +0 -47
  271. package/src/web/README.md +0 -73
  272. package/src/web/bun.lock +0 -575
  273. package/src/web/eslint.config.js +0 -23
  274. package/src/web/index.html +0 -16
  275. package/src/web/package.json +0 -37
  276. package/src/web/public/vite.svg +0 -1
  277. package/src/web/src/App.tsx +0 -604
  278. package/src/web/src/assets/react.svg +0 -1
  279. package/src/web/src/components/Terminal.tsx +0 -207
  280. package/src/web/src/hooks/useRelayConnection.ts +0 -224
  281. package/src/web/src/hooks/useTerminal.ts +0 -699
  282. package/src/web/src/index.css +0 -55
  283. package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +0 -1158
  284. package/src/web/src/lib/crypto/frames.ts +0 -205
  285. package/src/web/src/lib/crypto/handshake.ts +0 -396
  286. package/src/web/src/lib/crypto/identity.ts +0 -128
  287. package/src/web/src/lib/crypto/keyexchange.ts +0 -246
  288. package/src/web/src/lib/crypto/relay-signing.ts +0 -53
  289. package/src/web/src/lib/invite.ts +0 -58
  290. package/src/web/src/lib/storage/identity-store.ts +0 -94
  291. package/src/web/src/main.tsx +0 -10
  292. package/src/web/src/types/identity.ts +0 -45
  293. package/src/web/tsconfig.app.json +0 -28
  294. package/src/web/tsconfig.json +0 -7
  295. package/src/web/tsconfig.node.json +0 -26
  296. package/src/web/vite.config.ts +0 -31
  297. package/todo-security.md +0 -92
  298. package/tsconfig.json +0 -23
  299. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
  300. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
  301. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
  302. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
  303. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
  304. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
  305. package/worker/bun.lock +0 -237
  306. package/worker/package.json +0 -22
  307. package/worker/schema.sql +0 -96
  308. package/worker/src/handlers/auth.ts +0 -451
  309. package/worker/src/handlers/subdomains.ts +0 -376
  310. package/worker/src/handlers/user.ts +0 -98
  311. package/worker/src/index.ts +0 -70
  312. package/worker/src/middleware/auth.ts +0 -152
  313. package/worker/src/services/cloudflare.ts +0 -609
  314. package/worker/src/types.ts +0 -96
  315. package/worker/tsconfig.json +0 -15
  316. package/worker/wrangler.toml +0 -26
@@ -1,124 +0,0 @@
1
- /**
2
- * Authenticated encryption using AES-256-GCM
3
- *
4
- * This is similar to NaCl secretbox but uses AES-256-GCM
5
- * which is natively supported in Bun's node:crypto.
6
- */
7
-
8
- import {
9
- createCipheriv,
10
- createDecipheriv,
11
- randomBytes,
12
- } from "node:crypto";
13
-
14
- /** Nonce/IV length in bytes (96-bit for AES-GCM) */
15
- export const NONCE_LENGTH = 12;
16
-
17
- /** Auth tag length in bytes */
18
- export const AUTH_TAG_LENGTH = 16;
19
-
20
- /** Algorithm name */
21
- const ALGORITHM = "aes-256-gcm";
22
-
23
- /**
24
- * Generate a random nonce
25
- */
26
- export function generateNonce(): Buffer {
27
- return randomBytes(NONCE_LENGTH);
28
- }
29
-
30
- /**
31
- * Encrypt data using ChaCha20-Poly1305
32
- *
33
- * @param data - Plaintext data to encrypt
34
- * @param key - 256-bit key (from deriveKey)
35
- * @returns Object with nonce and ciphertext (includes auth tag)
36
- */
37
- export function encrypt(
38
- data: Uint8Array | Buffer,
39
- key: Uint8Array | Buffer
40
- ): { nonce: Buffer; ciphertext: Buffer } {
41
- const nonce = generateNonce();
42
-
43
- const cipher = createCipheriv(ALGORITHM, key, nonce, {
44
- authTagLength: AUTH_TAG_LENGTH,
45
- });
46
-
47
- const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
48
- const authTag = cipher.getAuthTag();
49
-
50
- // Append auth tag to ciphertext
51
- const ciphertext = Buffer.concat([encrypted, authTag]);
52
-
53
- return { nonce, ciphertext };
54
- }
55
-
56
- /**
57
- * Decrypt data using ChaCha20-Poly1305
58
- *
59
- * @param ciphertext - Encrypted data (includes auth tag at end)
60
- * @param nonce - Nonce used for encryption
61
- * @param key - 256-bit key (same as used for encryption)
62
- * @returns Decrypted plaintext, or null if authentication failed
63
- */
64
- export function decrypt(
65
- ciphertext: Uint8Array | Buffer,
66
- nonce: Uint8Array | Buffer,
67
- key: Uint8Array | Buffer
68
- ): Buffer | null {
69
- try {
70
- // Extract auth tag from end of ciphertext
71
- const encrypted = ciphertext.slice(0, -AUTH_TAG_LENGTH);
72
- const authTag = ciphertext.slice(-AUTH_TAG_LENGTH);
73
-
74
- const decipher = createDecipheriv(ALGORITHM, key, nonce, {
75
- authTagLength: AUTH_TAG_LENGTH,
76
- });
77
-
78
- decipher.setAuthTag(authTag);
79
-
80
- const decrypted = Buffer.concat([
81
- decipher.update(encrypted),
82
- decipher.final(),
83
- ]);
84
-
85
- return decrypted;
86
- } catch {
87
- // Authentication failed or other error
88
- return null;
89
- }
90
- }
91
-
92
- /**
93
- * Encrypt data and return a single buffer with nonce prepended
94
- *
95
- * Format: nonce (12 bytes) || ciphertext || authTag (16 bytes)
96
- */
97
- export function seal(
98
- data: Uint8Array | Buffer,
99
- key: Uint8Array | Buffer
100
- ): Buffer {
101
- const { nonce, ciphertext } = encrypt(data, key);
102
- return Buffer.concat([nonce, ciphertext]);
103
- }
104
-
105
- /**
106
- * Decrypt data from a sealed buffer (nonce prepended)
107
- *
108
- * @param sealed - Buffer with format: nonce || ciphertext || authTag
109
- * @param key - 256-bit key
110
- * @returns Decrypted plaintext, or null if authentication failed
111
- */
112
- export function open(
113
- sealed: Uint8Array | Buffer,
114
- key: Uint8Array | Buffer
115
- ): Buffer | null {
116
- if (sealed.length < NONCE_LENGTH + AUTH_TAG_LENGTH) {
117
- return null;
118
- }
119
-
120
- const nonce = sealed.slice(0, NONCE_LENGTH);
121
- const ciphertext = sealed.slice(NONCE_LENGTH);
122
-
123
- return decrypt(ciphertext, nonce, key);
124
- }
@@ -1,451 +0,0 @@
1
- /**
2
- * Machine-side X3DH handshake handler
3
- *
4
- * This class manages X3DH handshakes for multiple concurrent client connections.
5
- * It processes incoming handshake messages, validates clients via access lists
6
- * or invite tokens, and returns established sessions on success.
7
- *
8
- * The relay server forwards raw bytes between clients and machines - the handshake
9
- * is peer-to-peer between the CLIENT and MACHINE.
10
- *
11
- * Message flow:
12
- * 1. ClientHello → Machine creates state, returns ServerHello
13
- * 2. ClientAuth → Machine validates auth, returns ServerAuth with accept/reject
14
- * 3. On accept → Returns established session with keys
15
- *
16
- * @module handshake-handler
17
- */
18
-
19
- import {
20
- createServerState,
21
- processClientHello,
22
- createServerHello,
23
- processClientAuth,
24
- createServerAuth,
25
- type X3DHServerState,
26
- } from "./crypto/handshake.js";
27
- import { AccessControlList } from "./crypto/access-control.js";
28
- import { parseInviteToken, isInviteExpired } from "./crypto/invites.js";
29
- import type {
30
- Identity,
31
- SessionKeys,
32
- AccessType,
33
- X3DHInitMessage,
34
- X3DHAuthMessage,
35
- } from "../../types/identity.js";
36
-
37
- // ============================================================================
38
- // Types
39
- // ============================================================================
40
-
41
- /** Configuration for HandshakeHandler */
42
- export interface HandshakeHandlerConfig {
43
- /** Machine's identity for authentication */
44
- identity: Identity;
45
- /** Access control list for authorized clients */
46
- accessList: AccessControlList;
47
- /**
48
- * Optional custom invite validator
49
- * Returns access type if valid, null if rejected
50
- */
51
- validateInvite?: (token: string) => Promise<{ accessType: AccessType; sessionId?: string } | null>;
52
- /** Handshake timeout in milliseconds (default: 30000) */
53
- handshakeTimeoutMs?: number;
54
- }
55
-
56
- /** Per-connection handshake state */
57
- interface HandshakeContext {
58
- /** X3DH server state */
59
- state: X3DHServerState;
60
- /** When handshake started (for timeout) */
61
- startedAt: number;
62
- /** Timeout handle for cleanup */
63
- timeoutHandle?: ReturnType<typeof setTimeout>;
64
- }
65
-
66
- /** Handshake message envelope */
67
- export interface HandshakeMessage {
68
- type: "handshake";
69
- phase: "client_hello" | "server_hello" | "client_auth" | "server_auth";
70
- data: unknown;
71
- }
72
-
73
- /** Result of processing a handshake message */
74
- export type ProcessResult =
75
- | { type: "reply"; message: HandshakeMessage }
76
- | { type: "established"; session: EstablishedSession; message: HandshakeMessage }
77
- | { type: "error"; reason: string; close: boolean };
78
-
79
- /** Established session after successful handshake */
80
- export interface EstablishedSession {
81
- /** Connection ID (maps to relay connection) */
82
- connectionId: string;
83
- /** Peer's identity ID */
84
- peerIdentityId: string;
85
- /** Granted access type */
86
- accessType: AccessType;
87
- /** Session ID for session-invite access */
88
- sessionId?: string;
89
- /** Derived session keys for encryption */
90
- sessionKeys: SessionKeys;
91
- /** When session was established (Unix ms) */
92
- establishedAt: number;
93
- }
94
-
95
- // ============================================================================
96
- // HandshakeHandler Class
97
- // ============================================================================
98
-
99
- /**
100
- * Handles X3DH handshakes for multiple concurrent client connections
101
- *
102
- * @example
103
- * ```typescript
104
- * const handler = new HandshakeHandler({
105
- * identity: machineIdentity,
106
- * accessList: acl,
107
- * });
108
- *
109
- * // On receiving a handshake message from client
110
- * const result = await handler.processMessage(connectionId, message);
111
- * if (result.type === "reply") {
112
- * relay.send(connectionId, result.message);
113
- * } else if (result.type === "established") {
114
- * sessions.set(connectionId, result.session);
115
- * } else if (result.type === "error") {
116
- * console.error(result.reason);
117
- * if (result.close) relay.close(connectionId);
118
- * }
119
- *
120
- * // On client disconnect
121
- * handler.cleanup(connectionId);
122
- * ```
123
- */
124
- export class HandshakeHandler {
125
- private config: HandshakeHandlerConfig;
126
- private contexts: Map<string, HandshakeContext> = new Map();
127
- private readonly defaultTimeoutMs = 30000;
128
-
129
- /**
130
- * Create a new HandshakeHandler
131
- *
132
- * @param config - Handler configuration
133
- */
134
- constructor(config: HandshakeHandlerConfig) {
135
- this.config = config;
136
- }
137
-
138
- /**
139
- * Process an incoming handshake message from a client
140
- *
141
- * @param connectionId - Unique identifier for this connection
142
- * @param message - Handshake message to process
143
- * @returns Processing result (reply, established, or error)
144
- */
145
- async processMessage(
146
- connectionId: string,
147
- message: HandshakeMessage
148
- ): Promise<ProcessResult> {
149
- try {
150
- switch (message.phase) {
151
- case "client_hello":
152
- return this.handleClientHello(connectionId, message.data as X3DHInitMessage);
153
-
154
- case "client_auth":
155
- return await this.handleClientAuth(connectionId, message.data as X3DHAuthMessage);
156
-
157
- default:
158
- return {
159
- type: "error",
160
- reason: `Unexpected handshake phase: ${message.phase}`,
161
- close: true,
162
- };
163
- }
164
- } catch (error) {
165
- return {
166
- type: "error",
167
- reason: `Handshake error: ${error instanceof Error ? error.message : String(error)}`,
168
- close: true,
169
- };
170
- }
171
- }
172
-
173
- /**
174
- * Handle ClientHello message (phase 1)
175
- *
176
- * Creates fresh server state and returns ServerHello
177
- */
178
- private handleClientHello(
179
- connectionId: string,
180
- clientHello: X3DHInitMessage
181
- ): ProcessResult {
182
- // Clean up any existing state for this connection
183
- this.cleanup(connectionId);
184
-
185
- // Create fresh server state
186
- const serverState = createServerState(this.config.identity);
187
-
188
- // Process ClientHello
189
- const newState = processClientHello(serverState, clientHello);
190
- if (!newState) {
191
- return {
192
- type: "error",
193
- reason: "Invalid ClientHello",
194
- close: true,
195
- };
196
- }
197
-
198
- // Create ServerHello response
199
- const { state: stateAfterHello, message: serverHello } = createServerHello(
200
- newState,
201
- this.config.identity
202
- );
203
-
204
- // Store context with timeout
205
- const timeoutMs = this.config.handshakeTimeoutMs ?? this.defaultTimeoutMs;
206
- const timeoutHandle = setTimeout(() => {
207
- this.cleanup(connectionId);
208
- }, timeoutMs);
209
-
210
- this.contexts.set(connectionId, {
211
- state: stateAfterHello,
212
- startedAt: Date.now(),
213
- timeoutHandle,
214
- });
215
-
216
- return {
217
- type: "reply",
218
- message: {
219
- type: "handshake",
220
- phase: "server_hello",
221
- data: serverHello,
222
- },
223
- };
224
- }
225
-
226
- /**
227
- * Handle ClientAuth message (phase 2)
228
- *
229
- * Validates client identity and authorization, returns ServerAuth
230
- */
231
- private async handleClientAuth(
232
- connectionId: string,
233
- clientAuth: X3DHAuthMessage
234
- ): Promise<ProcessResult> {
235
- // Get existing context
236
- const context = this.contexts.get(connectionId);
237
- if (!context) {
238
- return {
239
- type: "error",
240
- reason: "No handshake in progress for this connection",
241
- close: true,
242
- };
243
- }
244
-
245
- // Clear timeout (we're completing the handshake)
246
- if (context.timeoutHandle) {
247
- clearTimeout(context.timeoutHandle);
248
- }
249
-
250
- // Process ClientAuth to get peer identity
251
- const authResult = processClientAuth(
252
- context.state,
253
- clientAuth,
254
- this.config.identity
255
- );
256
-
257
- if (!authResult) {
258
- this.cleanup(connectionId);
259
- return {
260
- type: "error",
261
- reason: "Invalid ClientAuth or identity proof",
262
- close: true,
263
- };
264
- }
265
-
266
- // Check authorization
267
- const authCheck = await this.checkAuthorization(
268
- authResult.peerIdentityId,
269
- authResult.authorization,
270
- authResult.clientIdentityKey,
271
- authResult.clientKeyExchangeKey
272
- );
273
-
274
- // Create ServerAuth response
275
- const { message: serverAuth, sessionKeys } = createServerAuth(
276
- this.config.identity,
277
- context.state,
278
- authResult.clientIdentityKey,
279
- authCheck
280
- );
281
-
282
- // Clean up handshake context
283
- this.cleanup(connectionId);
284
-
285
- // If rejected, send ServerAuth with rejection and close
286
- if (authCheck.type === "rejected") {
287
- return {
288
- type: "reply",
289
- message: {
290
- type: "handshake",
291
- phase: "server_auth",
292
- data: serverAuth,
293
- },
294
- };
295
- }
296
-
297
- // Success! Return established session with ServerAuth message
298
- const session: EstablishedSession = {
299
- connectionId,
300
- peerIdentityId: authResult.peerIdentityId,
301
- accessType: authCheck.accessType,
302
- sessionId: authCheck.sessionId,
303
- sessionKeys,
304
- establishedAt: Date.now(),
305
- };
306
-
307
- // Return established session with ServerAuth message
308
- // The caller should send the ServerAuth reply then handle the established session
309
- return {
310
- type: "established",
311
- session,
312
- message: {
313
- type: "handshake",
314
- phase: "server_auth",
315
- data: serverAuth,
316
- },
317
- };
318
- }
319
-
320
- /**
321
- * Check client authorization via access list or invite token
322
- */
323
- private async checkAuthorization(
324
- peerIdentityId: string,
325
- authorization: X3DHAuthMessage["authorization"],
326
- clientIdentityKey: Uint8Array,
327
- clientKeyExchangeKey: Uint8Array
328
- ): Promise<
329
- | { type: "accepted"; accessType: AccessType; sessionId?: string }
330
- | { type: "rejected"; reason: string }
331
- > {
332
- if (authorization.type === "access_list") {
333
- // Check access list
334
- const entry = this.config.accessList.getEntry(peerIdentityId);
335
- if (!entry) {
336
- return {
337
- type: "rejected",
338
- reason: "Not in access list",
339
- };
340
- }
341
-
342
- return {
343
- type: "accepted",
344
- accessType: entry.accessType,
345
- sessionId: entry.sessionId,
346
- };
347
- }
348
-
349
- if (authorization.type === "invite") {
350
- // Validate invite token
351
- const token = parseInviteToken(authorization.inviteToken);
352
-
353
- if (!token) {
354
- return {
355
- type: "rejected",
356
- reason: "Invalid invite token",
357
- };
358
- }
359
-
360
- if (isInviteExpired(token)) {
361
- return {
362
- type: "rejected",
363
- reason: "Invite token expired",
364
- };
365
- }
366
-
367
- // Verify token was issued by this machine
368
- if (token.machineId !== this.config.identity.id) {
369
- return {
370
- type: "rejected",
371
- reason: "Invite token not issued by this machine",
372
- };
373
- }
374
-
375
- // Check custom validator if provided
376
- if (this.config.validateInvite) {
377
- const customResult = await this.config.validateInvite(
378
- authorization.inviteToken
379
- );
380
- if (!customResult) {
381
- return {
382
- type: "rejected",
383
- reason: "Invite rejected by custom validator",
384
- };
385
- }
386
- return {
387
- type: "accepted",
388
- accessType: customResult.accessType,
389
- sessionId: customResult.sessionId,
390
- };
391
- }
392
-
393
- // Use access type from token
394
- // Security: Only add to permanent access list if NOT a single-use invite
395
- // Single-use invites grant access for this session only
396
- if (!token.singleUse) {
397
- this.config.accessList.addEntry(
398
- {
399
- id: peerIdentityId,
400
- signingPublicKey: Buffer.from(clientIdentityKey).toString("base64"),
401
- keyExchangePublicKey: Buffer.from(clientKeyExchangeKey).toString("base64"),
402
- },
403
- token.accessType,
404
- token.sessionId
405
- );
406
- }
407
-
408
- return {
409
- type: "accepted",
410
- accessType: token.accessType,
411
- sessionId: token.sessionId,
412
- };
413
- }
414
-
415
- return {
416
- type: "rejected",
417
- reason: "Unknown authorization type",
418
- };
419
- }
420
-
421
- /**
422
- * Clean up state for a disconnected client
423
- *
424
- * Call this when a client disconnects to free resources
425
- *
426
- * @param connectionId - Connection ID to clean up
427
- */
428
- cleanup(connectionId: string): void {
429
- const context = this.contexts.get(connectionId);
430
- if (context) {
431
- if (context.timeoutHandle) {
432
- clearTimeout(context.timeoutHandle);
433
- }
434
- this.contexts.delete(connectionId);
435
- }
436
- }
437
-
438
- /**
439
- * Get number of active handshakes
440
- */
441
- get activeHandshakes(): number {
442
- return this.contexts.size;
443
- }
444
-
445
- /**
446
- * Check if a connection has an active handshake
447
- */
448
- hasActiveHandshake(connectionId: string): boolean {
449
- return this.contexts.has(connectionId);
450
- }
451
- }