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.
- package/.claude/settings.local.json +21 -0
- package/.gitspace/bundle.json +50 -0
- package/.gitspace/select/01-status.sh +40 -0
- package/.gitspace/setup/01-install-deps.sh +12 -0
- package/.gitspace/setup/02-typecheck.sh +16 -0
- package/AGENTS.md +439 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +25 -0
- package/README.md +607 -0
- package/bin/gssh +62 -0
- package/bun.lock +647 -0
- package/docs/CONNECTION.md +623 -0
- package/docs/GATEWAY-WORKER.md +319 -0
- package/docs/GETTING-STARTED.md +448 -0
- package/docs/GITSPACE-PLATFORM.md +1819 -0
- package/docs/INFRASTRUCTURE.md +1347 -0
- package/docs/PROTOCOL.md +619 -0
- package/docs/QUICKSTART.md +174 -0
- package/docs/RELAY.md +327 -0
- package/docs/REMOTE-DESIGN.md +549 -0
- package/docs/ROADMAP.md +564 -0
- package/docs/SITE_DOCS_FIGMA_MAKE.md +1167 -0
- package/docs/STACK-DESIGN.md +588 -0
- package/docs/UNIFIED_ARCHITECTURE.md +292 -0
- package/experiments/pty-benchmark.ts +148 -0
- package/experiments/pty-latency.ts +100 -0
- package/experiments/router/client.ts +199 -0
- package/experiments/router/protocol.ts +74 -0
- package/experiments/router/router.ts +217 -0
- package/experiments/router/session.ts +180 -0
- package/experiments/router/test.ts +133 -0
- package/experiments/socket-bandwidth.ts +77 -0
- package/homebrew/gitspace.rb +45 -0
- package/landing-page/ATTRIBUTIONS.md +3 -0
- package/landing-page/README.md +11 -0
- package/landing-page/bun.lock +801 -0
- package/landing-page/guidelines/Guidelines.md +61 -0
- package/landing-page/index.html +37 -0
- package/landing-page/package.json +90 -0
- package/landing-page/postcss.config.mjs +15 -0
- package/landing-page/public/_redirects +1 -0
- package/landing-page/public/favicon.png +0 -0
- package/landing-page/src/app/App.tsx +53 -0
- package/landing-page/src/app/components/figma/ImageWithFallback.tsx +27 -0
- package/landing-page/src/app/components/ui/accordion.tsx +66 -0
- package/landing-page/src/app/components/ui/alert-dialog.tsx +157 -0
- package/landing-page/src/app/components/ui/alert.tsx +66 -0
- package/landing-page/src/app/components/ui/aspect-ratio.tsx +11 -0
- package/landing-page/src/app/components/ui/avatar.tsx +53 -0
- package/landing-page/src/app/components/ui/badge.tsx +46 -0
- package/landing-page/src/app/components/ui/breadcrumb.tsx +109 -0
- package/landing-page/src/app/components/ui/button.tsx +57 -0
- package/landing-page/src/app/components/ui/calendar.tsx +75 -0
- package/landing-page/src/app/components/ui/card.tsx +92 -0
- package/landing-page/src/app/components/ui/carousel.tsx +241 -0
- package/landing-page/src/app/components/ui/chart.tsx +353 -0
- package/landing-page/src/app/components/ui/checkbox.tsx +32 -0
- package/landing-page/src/app/components/ui/collapsible.tsx +33 -0
- package/landing-page/src/app/components/ui/command.tsx +177 -0
- package/landing-page/src/app/components/ui/context-menu.tsx +252 -0
- package/landing-page/src/app/components/ui/dialog.tsx +135 -0
- package/landing-page/src/app/components/ui/drawer.tsx +132 -0
- package/landing-page/src/app/components/ui/dropdown-menu.tsx +257 -0
- package/landing-page/src/app/components/ui/form.tsx +168 -0
- package/landing-page/src/app/components/ui/hover-card.tsx +44 -0
- package/landing-page/src/app/components/ui/input-otp.tsx +77 -0
- package/landing-page/src/app/components/ui/input.tsx +21 -0
- package/landing-page/src/app/components/ui/label.tsx +24 -0
- package/landing-page/src/app/components/ui/menubar.tsx +276 -0
- package/landing-page/src/app/components/ui/navigation-menu.tsx +168 -0
- package/landing-page/src/app/components/ui/pagination.tsx +127 -0
- package/landing-page/src/app/components/ui/popover.tsx +48 -0
- package/landing-page/src/app/components/ui/progress.tsx +31 -0
- package/landing-page/src/app/components/ui/radio-group.tsx +45 -0
- package/landing-page/src/app/components/ui/resizable.tsx +56 -0
- package/landing-page/src/app/components/ui/scroll-area.tsx +58 -0
- package/landing-page/src/app/components/ui/select.tsx +189 -0
- package/landing-page/src/app/components/ui/separator.tsx +28 -0
- package/landing-page/src/app/components/ui/sheet.tsx +139 -0
- package/landing-page/src/app/components/ui/sidebar.tsx +726 -0
- package/landing-page/src/app/components/ui/skeleton.tsx +13 -0
- package/landing-page/src/app/components/ui/slider.tsx +63 -0
- package/landing-page/src/app/components/ui/sonner.tsx +25 -0
- package/landing-page/src/app/components/ui/switch.tsx +31 -0
- package/landing-page/src/app/components/ui/table.tsx +116 -0
- package/landing-page/src/app/components/ui/tabs.tsx +66 -0
- package/landing-page/src/app/components/ui/textarea.tsx +18 -0
- package/landing-page/src/app/components/ui/toggle-group.tsx +73 -0
- package/landing-page/src/app/components/ui/toggle.tsx +47 -0
- package/landing-page/src/app/components/ui/tooltip.tsx +61 -0
- package/landing-page/src/app/components/ui/use-mobile.ts +21 -0
- package/landing-page/src/app/components/ui/utils.ts +6 -0
- package/landing-page/src/components/docs/DocsContent.tsx +718 -0
- package/landing-page/src/components/docs/DocsSidebar.tsx +84 -0
- package/landing-page/src/components/landing/CTA.tsx +59 -0
- package/landing-page/src/components/landing/Comparison.tsx +84 -0
- package/landing-page/src/components/landing/FaultyTerminal.tsx +424 -0
- package/landing-page/src/components/landing/Features.tsx +201 -0
- package/landing-page/src/components/landing/Hero.tsx +142 -0
- package/landing-page/src/components/landing/Pricing.tsx +140 -0
- package/landing-page/src/components/landing/Roadmap.tsx +86 -0
- package/landing-page/src/components/landing/Security.tsx +81 -0
- package/landing-page/src/components/landing/TerminalWindow.tsx +27 -0
- package/landing-page/src/components/landing/UseCases.tsx +55 -0
- package/landing-page/src/components/landing/Workflow.tsx +101 -0
- package/landing-page/src/components/layout/DashboardNavbar.tsx +37 -0
- package/landing-page/src/components/layout/Footer.tsx +55 -0
- package/landing-page/src/components/layout/LandingNavbar.tsx +82 -0
- package/landing-page/src/components/ui/badge.tsx +39 -0
- package/landing-page/src/components/ui/breadcrumb.tsx +115 -0
- package/landing-page/src/components/ui/button.tsx +57 -0
- package/landing-page/src/components/ui/card.tsx +79 -0
- package/landing-page/src/components/ui/mock-terminal.tsx +68 -0
- package/landing-page/src/components/ui/separator.tsx +28 -0
- package/landing-page/src/lib/utils.ts +6 -0
- package/landing-page/src/main.tsx +10 -0
- package/landing-page/src/pages/Dashboard.tsx +133 -0
- package/landing-page/src/pages/DocsPage.tsx +79 -0
- package/landing-page/src/pages/LandingPage.tsx +31 -0
- package/landing-page/src/pages/TerminalView.tsx +106 -0
- package/landing-page/src/styles/fonts.css +0 -0
- package/landing-page/src/styles/index.css +3 -0
- package/landing-page/src/styles/tailwind.css +4 -0
- package/landing-page/src/styles/theme.css +181 -0
- package/landing-page/vite.config.ts +19 -0
- package/npm/darwin-arm64/bin/gssh +0 -0
- package/npm/darwin-arm64/package.json +20 -0
- package/package.json +74 -0
- package/scripts/build.ts +284 -0
- package/scripts/release.ts +140 -0
- package/src/__tests__/test-utils.ts +298 -0
- package/src/commands/__tests__/serve-messages.test.ts +190 -0
- package/src/commands/access.ts +298 -0
- package/src/commands/add.ts +452 -0
- package/src/commands/auth.ts +364 -0
- package/src/commands/connect.ts +287 -0
- package/src/commands/directory.ts +16 -0
- package/src/commands/host.ts +396 -0
- package/src/commands/identity.ts +184 -0
- package/src/commands/list.ts +200 -0
- package/src/commands/relay.ts +315 -0
- package/src/commands/remove.ts +241 -0
- package/src/commands/serve.ts +1493 -0
- package/src/commands/share.ts +456 -0
- package/src/commands/status.ts +125 -0
- package/src/commands/switch.ts +353 -0
- package/src/commands/tmux.ts +317 -0
- package/src/core/__tests__/access.test.ts +240 -0
- package/src/core/access.ts +277 -0
- package/src/core/bundle.ts +342 -0
- package/src/core/config.ts +510 -0
- package/src/core/git.ts +317 -0
- package/src/core/github.ts +151 -0
- package/src/core/identity.ts +631 -0
- package/src/core/linear.ts +225 -0
- package/src/core/shell.ts +161 -0
- package/src/core/trusted-relays.ts +315 -0
- package/src/index.ts +821 -0
- package/src/lib/remote-session/index.ts +7 -0
- package/src/lib/remote-session/protocol.ts +267 -0
- package/src/lib/remote-session/session-handler.ts +581 -0
- package/src/lib/remote-session/workspace-scanner.ts +167 -0
- package/src/lib/tmux-lite/README.md +81 -0
- package/src/lib/tmux-lite/cli.ts +796 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +349 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +291 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +142 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +339 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +477 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +499 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +371 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +573 -0
- package/src/lib/tmux-lite/crypto/access-control.test.ts +512 -0
- package/src/lib/tmux-lite/crypto/access-control.ts +320 -0
- package/src/lib/tmux-lite/crypto/frames.test.ts +262 -0
- package/src/lib/tmux-lite/crypto/frames.ts +141 -0
- package/src/lib/tmux-lite/crypto/handshake.ts +894 -0
- package/src/lib/tmux-lite/crypto/identity.test.ts +220 -0
- package/src/lib/tmux-lite/crypto/identity.ts +286 -0
- package/src/lib/tmux-lite/crypto/index.ts +51 -0
- package/src/lib/tmux-lite/crypto/invites.test.ts +381 -0
- package/src/lib/tmux-lite/crypto/invites.ts +215 -0
- package/src/lib/tmux-lite/crypto/keyexchange.ts +435 -0
- package/src/lib/tmux-lite/crypto/keys.test.ts +58 -0
- package/src/lib/tmux-lite/crypto/keys.ts +47 -0
- package/src/lib/tmux-lite/crypto/secretbox.test.ts +169 -0
- package/src/lib/tmux-lite/crypto/secretbox.ts +124 -0
- package/src/lib/tmux-lite/handshake-handler.ts +451 -0
- package/src/lib/tmux-lite/protocol.test.ts +307 -0
- package/src/lib/tmux-lite/protocol.ts +266 -0
- package/src/lib/tmux-lite/relay-client.ts +506 -0
- package/src/lib/tmux-lite/server.ts +1250 -0
- package/src/lib/tmux-lite/shell-integration.sh +37 -0
- package/src/lib/tmux-lite/terminal-queries.test.ts +54 -0
- package/src/lib/tmux-lite/terminal-queries.ts +49 -0
- package/src/relay/__tests__/e2e-flow.test.ts +1284 -0
- package/src/relay/__tests__/helpers/auth.ts +354 -0
- package/src/relay/__tests__/helpers/ports.ts +51 -0
- package/src/relay/__tests__/protocol-validation.test.ts +265 -0
- package/src/relay/authorization.ts +303 -0
- package/src/relay/embedded-assets.generated.d.ts +15 -0
- package/src/relay/identity.ts +352 -0
- package/src/relay/index.ts +57 -0
- package/src/relay/pipes.test.ts +427 -0
- package/src/relay/pipes.ts +195 -0
- package/src/relay/protocol.ts +804 -0
- package/src/relay/registries.test.ts +437 -0
- package/src/relay/registries.ts +593 -0
- package/src/relay/server.test.ts +1323 -0
- package/src/relay/server.ts +1092 -0
- package/src/relay/signing.ts +238 -0
- package/src/relay/types.ts +69 -0
- package/src/serve/client-session-manager.ts +622 -0
- package/src/serve/daemon.ts +497 -0
- package/src/serve/pty-session.ts +236 -0
- package/src/serve/types.ts +169 -0
- package/src/shared/components/Flow.tsx +453 -0
- package/src/shared/components/Flow.tui.tsx +343 -0
- package/src/shared/components/Flow.web.tsx +442 -0
- package/src/shared/components/Inbox.tsx +446 -0
- package/src/shared/components/Inbox.tui.tsx +262 -0
- package/src/shared/components/Inbox.web.tsx +329 -0
- package/src/shared/components/MachineList.tsx +187 -0
- package/src/shared/components/MachineList.tui.tsx +161 -0
- package/src/shared/components/MachineList.web.tsx +210 -0
- package/src/shared/components/ProjectList.tsx +176 -0
- package/src/shared/components/ProjectList.tui.tsx +109 -0
- package/src/shared/components/ProjectList.web.tsx +143 -0
- package/src/shared/components/SpacesBrowser.tsx +332 -0
- package/src/shared/components/SpacesBrowser.tui.tsx +163 -0
- package/src/shared/components/SpacesBrowser.web.tsx +221 -0
- package/src/shared/components/index.ts +103 -0
- package/src/shared/hooks/index.ts +16 -0
- package/src/shared/hooks/useNavigation.ts +226 -0
- package/src/shared/index.ts +122 -0
- package/src/shared/providers/LocalMachineProvider.ts +425 -0
- package/src/shared/providers/MachineProvider.ts +165 -0
- package/src/shared/providers/RemoteMachineProvider.ts +444 -0
- package/src/shared/providers/index.ts +26 -0
- package/src/shared/types.ts +145 -0
- package/src/tui/adapters.ts +120 -0
- package/src/tui/app.tsx +1816 -0
- package/src/tui/components/Terminal.tsx +580 -0
- package/src/tui/hooks/index.ts +35 -0
- package/src/tui/hooks/useAppState.ts +314 -0
- package/src/tui/hooks/useDaemonStatus.ts +174 -0
- package/src/tui/hooks/useInboxTUI.ts +113 -0
- package/src/tui/hooks/useRemoteMachines.ts +209 -0
- package/src/tui/index.ts +24 -0
- package/src/tui/state.ts +299 -0
- package/src/tui/terminal-bracketed-paste.test.ts +45 -0
- package/src/tui/terminal-bracketed-paste.ts +47 -0
- package/src/types/bundle.ts +112 -0
- package/src/types/config.ts +89 -0
- package/src/types/errors.ts +206 -0
- package/src/types/identity.ts +284 -0
- package/src/types/workspace-fuzzy.ts +49 -0
- package/src/types/workspace.ts +151 -0
- package/src/utils/bun-socket-writer.ts +80 -0
- package/src/utils/deps.ts +127 -0
- package/src/utils/fuzzy-match.ts +125 -0
- package/src/utils/logger.ts +127 -0
- package/src/utils/markdown.ts +254 -0
- package/src/utils/onboarding.ts +229 -0
- package/src/utils/prompts.ts +114 -0
- package/src/utils/run-commands.ts +112 -0
- package/src/utils/run-scripts.ts +142 -0
- package/src/utils/sanitize.ts +98 -0
- package/src/utils/secrets.ts +122 -0
- package/src/utils/shell-escape.ts +40 -0
- package/src/utils/utf8.ts +79 -0
- package/src/utils/workspace-state.ts +47 -0
- package/src/web/README.md +73 -0
- package/src/web/bun.lock +575 -0
- package/src/web/eslint.config.js +23 -0
- package/src/web/index.html +16 -0
- package/src/web/package.json +37 -0
- package/src/web/public/vite.svg +1 -0
- package/src/web/src/App.tsx +604 -0
- package/src/web/src/assets/react.svg +1 -0
- package/src/web/src/components/Terminal.tsx +207 -0
- package/src/web/src/hooks/useRelayConnection.ts +224 -0
- package/src/web/src/hooks/useTerminal.ts +699 -0
- package/src/web/src/index.css +55 -0
- package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +1158 -0
- package/src/web/src/lib/crypto/frames.ts +205 -0
- package/src/web/src/lib/crypto/handshake.ts +396 -0
- package/src/web/src/lib/crypto/identity.ts +128 -0
- package/src/web/src/lib/crypto/keyexchange.ts +246 -0
- package/src/web/src/lib/crypto/relay-signing.ts +53 -0
- package/src/web/src/lib/invite.ts +58 -0
- package/src/web/src/lib/storage/identity-store.ts +94 -0
- package/src/web/src/main.tsx +10 -0
- package/src/web/src/types/identity.ts +45 -0
- package/src/web/tsconfig.app.json +28 -0
- package/src/web/tsconfig.json +7 -0
- package/src/web/tsconfig.node.json +26 -0
- package/src/web/vite.config.ts +31 -0
- package/todo-security.md +92 -0
- package/tsconfig.json +23 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
- package/worker/bun.lock +237 -0
- package/worker/package.json +22 -0
- package/worker/schema.sql +96 -0
- package/worker/src/handlers/auth.ts +451 -0
- package/worker/src/handlers/subdomains.ts +376 -0
- package/worker/src/handlers/user.ts +98 -0
- package/worker/src/index.ts +70 -0
- package/worker/src/middleware/auth.ts +152 -0
- package/worker/src/services/cloudflare.ts +609 -0
- package/worker/src/types.ts +96 -0
- package/worker/tsconfig.json +15 -0
- package/worker/wrangler.toml +26 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handshake orchestration utilities for integration tests
|
|
3
|
+
*
|
|
4
|
+
* Provides high-level utilities to run complete handshake flows
|
|
5
|
+
* and verify the results.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
createClientHello,
|
|
10
|
+
processServerHello,
|
|
11
|
+
createClientAuth,
|
|
12
|
+
processServerAuth,
|
|
13
|
+
type X3DHClientState,
|
|
14
|
+
} from "../../handshake.js";
|
|
15
|
+
import {
|
|
16
|
+
HandshakeHandler,
|
|
17
|
+
type ProcessResult,
|
|
18
|
+
type EstablishedSession,
|
|
19
|
+
type HandshakeMessage,
|
|
20
|
+
} from "../../../handshake-handler.js";
|
|
21
|
+
import { AccessControlList } from "../../access-control.js";
|
|
22
|
+
import { createInviteToken } from "../../invites.js";
|
|
23
|
+
import type {
|
|
24
|
+
Identity,
|
|
25
|
+
SessionKeys,
|
|
26
|
+
AccessType,
|
|
27
|
+
X3DHResponseMessage,
|
|
28
|
+
X3DHResultMessage,
|
|
29
|
+
X3DHAuthMessage,
|
|
30
|
+
} from "../../../../../types/identity.js";
|
|
31
|
+
import { createMockRelay, MockRelay } from "./mock-relay.js";
|
|
32
|
+
import { toPublicIdentity } from "./test-identities.js";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Result of a complete handshake run
|
|
36
|
+
*/
|
|
37
|
+
export interface HandshakeRunResult {
|
|
38
|
+
/** Whether handshake succeeded */
|
|
39
|
+
success: boolean;
|
|
40
|
+
/** Client-side session keys (if successful) */
|
|
41
|
+
clientKeys?: SessionKeys;
|
|
42
|
+
/** Machine-side session (if successful) */
|
|
43
|
+
machineSession?: EstablishedSession;
|
|
44
|
+
/** Error message (if failed) */
|
|
45
|
+
error?: string;
|
|
46
|
+
/** Number of messages exchanged */
|
|
47
|
+
messageCount: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Authorization options for handshake
|
|
52
|
+
*/
|
|
53
|
+
export type AuthorizationOption =
|
|
54
|
+
| { type: "access_list" }
|
|
55
|
+
| { type: "invite"; accessType?: AccessType; sessionId?: string; validityMs?: number };
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run a complete 4-message X3DH handshake between client and machine
|
|
59
|
+
*
|
|
60
|
+
* This function orchestrates the full handshake:
|
|
61
|
+
* 1. Client sends ClientHello
|
|
62
|
+
* 2. Machine responds with ServerHello
|
|
63
|
+
* 3. Client sends ClientAuth
|
|
64
|
+
* 4. Machine responds with ServerAuth
|
|
65
|
+
*
|
|
66
|
+
* @param clientIdentity - Client's identity
|
|
67
|
+
* @param machineIdentity - Machine's identity
|
|
68
|
+
* @param accessList - Machine's access control list
|
|
69
|
+
* @param authorization - Authorization method to use
|
|
70
|
+
* @returns Handshake result with keys and session info
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const result = await runCompleteHandshake(
|
|
75
|
+
* clientIdentity,
|
|
76
|
+
* machineIdentity,
|
|
77
|
+
* acl,
|
|
78
|
+
* { type: "access_list" }
|
|
79
|
+
* );
|
|
80
|
+
*
|
|
81
|
+
* if (result.success) {
|
|
82
|
+
* // Both parties now have matching session keys
|
|
83
|
+
* expect(result.clientKeys).toBeDefined();
|
|
84
|
+
* expect(result.machineSession).toBeDefined();
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export async function runCompleteHandshake(
|
|
89
|
+
clientIdentity: Identity,
|
|
90
|
+
machineIdentity: Identity,
|
|
91
|
+
accessList: AccessControlList,
|
|
92
|
+
authorization: AuthorizationOption
|
|
93
|
+
): Promise<HandshakeRunResult> {
|
|
94
|
+
const relay = createMockRelay();
|
|
95
|
+
const handler = new HandshakeHandler({
|
|
96
|
+
identity: machineIdentity,
|
|
97
|
+
accessList,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
let clientState: X3DHClientState | null = null;
|
|
101
|
+
let clientSessionKeys: SessionKeys | null = null;
|
|
102
|
+
let machineSession: EstablishedSession | null = null;
|
|
103
|
+
|
|
104
|
+
// Set up machine handler
|
|
105
|
+
relay.onClientMessage(async (connId, msg) => {
|
|
106
|
+
const result = await handler.processMessage(connId, msg);
|
|
107
|
+
if (result.type === "reply") {
|
|
108
|
+
return result.message;
|
|
109
|
+
}
|
|
110
|
+
if (result.type === "established") {
|
|
111
|
+
machineSession = result.session;
|
|
112
|
+
// Also need to send the ServerAuth reply - extract from session
|
|
113
|
+
// Actually, the handler returns "established" which means we need to construct the reply
|
|
114
|
+
// Let's fix the handler or construct it here
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const connectionId = relay.generateConnectionId();
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Step 1: Client creates and sends ClientHello
|
|
124
|
+
const { state: initialState, message: clientHello } = createClientHello(
|
|
125
|
+
machineIdentity.id
|
|
126
|
+
);
|
|
127
|
+
clientState = initialState;
|
|
128
|
+
|
|
129
|
+
const clientHelloMsg: HandshakeMessage = {
|
|
130
|
+
type: "handshake",
|
|
131
|
+
phase: "client_hello",
|
|
132
|
+
data: clientHello,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Send ClientHello, get ServerHello
|
|
136
|
+
const serverHelloMsg = await relay.sendFromClient(connectionId, clientHelloMsg);
|
|
137
|
+
if (!serverHelloMsg) {
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
error: "No ServerHello received",
|
|
141
|
+
messageCount: relay.getMessageHistory().length,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Step 2: Client processes ServerHello
|
|
146
|
+
const serverHello = serverHelloMsg.data as X3DHResponseMessage;
|
|
147
|
+
const stateAfterServerHello = processServerHello(clientState, serverHello);
|
|
148
|
+
if (!stateAfterServerHello) {
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
error: "Failed to process ServerHello",
|
|
152
|
+
messageCount: relay.getMessageHistory().length,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
clientState = stateAfterServerHello;
|
|
156
|
+
|
|
157
|
+
// Step 3: Client creates and sends ClientAuth
|
|
158
|
+
let authData: X3DHAuthMessage["authorization"];
|
|
159
|
+
if (authorization.type === "access_list") {
|
|
160
|
+
authData = { type: "access_list" };
|
|
161
|
+
} else {
|
|
162
|
+
// Create invite token
|
|
163
|
+
const inviteToken = createInviteToken(machineIdentity, "wss://test.relay", {
|
|
164
|
+
accessType: authorization.accessType,
|
|
165
|
+
sessionId: authorization.sessionId,
|
|
166
|
+
validityMs: authorization.validityMs ?? 3600000,
|
|
167
|
+
});
|
|
168
|
+
authData = { type: "invite", inviteToken };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const { state: stateAfterAuth, message: clientAuth, sessionKeys } = createClientAuth(
|
|
172
|
+
clientState,
|
|
173
|
+
clientIdentity,
|
|
174
|
+
authData
|
|
175
|
+
);
|
|
176
|
+
clientState = stateAfterAuth;
|
|
177
|
+
clientSessionKeys = sessionKeys;
|
|
178
|
+
|
|
179
|
+
const clientAuthMsg: HandshakeMessage = {
|
|
180
|
+
type: "handshake",
|
|
181
|
+
phase: "client_auth",
|
|
182
|
+
data: clientAuth,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Send ClientAuth, get ServerAuth
|
|
186
|
+
// Note: For established sessions, the handler doesn't return a reply message
|
|
187
|
+
// We need to handle this differently - run the handler directly
|
|
188
|
+
const authResult = await handler.processMessage(connectionId, clientAuthMsg);
|
|
189
|
+
|
|
190
|
+
if (authResult.type === "error") {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
error: authResult.reason,
|
|
194
|
+
messageCount: relay.getMessageHistory().length + 1, // +1 for ClientAuth
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (authResult.type === "established") {
|
|
199
|
+
machineSession = authResult.session;
|
|
200
|
+
|
|
201
|
+
// For the client, we need to construct a ServerAuth to process
|
|
202
|
+
// But the handler already computed the session, we need to get the ServerAuth message
|
|
203
|
+
// Let's manually construct the response scenario
|
|
204
|
+
|
|
205
|
+
// Actually, let me re-think this. The handler.processMessage for ClientAuth
|
|
206
|
+
// should return "established" when successful, but we still need to verify
|
|
207
|
+
// the client can process it. Let's simulate what the relay would do:
|
|
208
|
+
// It would send the ServerAuth, so we need to construct it.
|
|
209
|
+
|
|
210
|
+
// The session keys in machineSession should match clientSessionKeys
|
|
211
|
+
// (with send/receive swapped)
|
|
212
|
+
return {
|
|
213
|
+
success: true,
|
|
214
|
+
clientKeys: clientSessionKeys,
|
|
215
|
+
machineSession,
|
|
216
|
+
messageCount: 4, // ClientHello, ServerHello, ClientAuth, ServerAuth
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (authResult.type === "reply") {
|
|
221
|
+
// This would be a ServerAuth with rejection
|
|
222
|
+
const serverAuth = authResult.message.data as X3DHResultMessage;
|
|
223
|
+
if (serverAuth.result.type === "rejected") {
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
error: `Rejected: ${serverAuth.result.reason}`,
|
|
227
|
+
messageCount: 4,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
success: false,
|
|
234
|
+
error: "Unexpected result type",
|
|
235
|
+
messageCount: relay.getMessageHistory().length,
|
|
236
|
+
};
|
|
237
|
+
} catch (error) {
|
|
238
|
+
return {
|
|
239
|
+
success: false,
|
|
240
|
+
error: error instanceof Error ? error.message : String(error),
|
|
241
|
+
messageCount: relay.getMessageHistory().length,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Verify that two sets of session keys are correctly paired
|
|
248
|
+
*
|
|
249
|
+
* Client's sendKey should equal machine's receiveKey and vice versa.
|
|
250
|
+
*
|
|
251
|
+
* @param clientKeys - Client's session keys
|
|
252
|
+
* @param machineKeys - Machine's session keys
|
|
253
|
+
* @returns True if keys are correctly paired
|
|
254
|
+
*/
|
|
255
|
+
export function verifyKeyPairing(
|
|
256
|
+
clientKeys: SessionKeys,
|
|
257
|
+
machineKeys: SessionKeys
|
|
258
|
+
): boolean {
|
|
259
|
+
// Client's sendKey should match machine's receiveKey
|
|
260
|
+
const sendMatchesReceive = arraysEqual(
|
|
261
|
+
clientKeys.sendKey,
|
|
262
|
+
machineKeys.receiveKey
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
// Client's receiveKey should match machine's sendKey
|
|
266
|
+
const receiveMatchesSend = arraysEqual(
|
|
267
|
+
clientKeys.receiveKey,
|
|
268
|
+
machineKeys.sendKey
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// Session IDs should match
|
|
272
|
+
const sessionIdsMatch = clientKeys.sessionId === machineKeys.sessionId;
|
|
273
|
+
|
|
274
|
+
return sendMatchesReceive && receiveMatchesSend && sessionIdsMatch;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Verify that session keys are unique across multiple handshakes
|
|
279
|
+
*
|
|
280
|
+
* @param keysSets - Array of session keys from different handshakes
|
|
281
|
+
* @returns True if all keys are unique
|
|
282
|
+
*/
|
|
283
|
+
export function verifyKeysUnique(keysSets: SessionKeys[]): boolean {
|
|
284
|
+
const seenSendKeys = new Set<string>();
|
|
285
|
+
const seenReceiveKeys = new Set<string>();
|
|
286
|
+
const seenSessionIds = new Set<string>();
|
|
287
|
+
|
|
288
|
+
for (const keys of keysSets) {
|
|
289
|
+
const sendKeyHex = Buffer.from(keys.sendKey).toString("hex");
|
|
290
|
+
const receiveKeyHex = Buffer.from(keys.receiveKey).toString("hex");
|
|
291
|
+
|
|
292
|
+
if (seenSendKeys.has(sendKeyHex)) return false;
|
|
293
|
+
if (seenReceiveKeys.has(receiveKeyHex)) return false;
|
|
294
|
+
if (seenSessionIds.has(keys.sessionId)) return false;
|
|
295
|
+
|
|
296
|
+
seenSendKeys.add(sendKeyHex);
|
|
297
|
+
seenReceiveKeys.add(receiveKeyHex);
|
|
298
|
+
seenSessionIds.add(keys.sessionId);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Create a pre-configured handshake test scenario
|
|
306
|
+
*
|
|
307
|
+
* Returns all necessary components for testing handshakes
|
|
308
|
+
*/
|
|
309
|
+
export function createHandshakeScenario(): {
|
|
310
|
+
relay: MockRelay;
|
|
311
|
+
clientIdentity: Identity;
|
|
312
|
+
machineIdentity: Identity;
|
|
313
|
+
accessList: AccessControlList;
|
|
314
|
+
handler: HandshakeHandler;
|
|
315
|
+
addClientToAccessList: (accessType?: AccessType, sessionId?: string) => void;
|
|
316
|
+
} {
|
|
317
|
+
// Import here to avoid circular dependencies
|
|
318
|
+
const { createTestIdentityPair } = require("./test-identities.js");
|
|
319
|
+
|
|
320
|
+
const { client, machine } = createTestIdentityPair();
|
|
321
|
+
const accessList = new AccessControlList();
|
|
322
|
+
const handler = new HandshakeHandler({
|
|
323
|
+
identity: machine,
|
|
324
|
+
accessList,
|
|
325
|
+
});
|
|
326
|
+
const relay = createMockRelay();
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
relay,
|
|
330
|
+
clientIdentity: client,
|
|
331
|
+
machineIdentity: machine,
|
|
332
|
+
accessList,
|
|
333
|
+
handler,
|
|
334
|
+
addClientToAccessList: (accessType?: AccessType, sessionId?: string) => {
|
|
335
|
+
accessList.addEntry(toPublicIdentity(client), accessType, sessionId);
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Compare two Uint8Arrays for equality
|
|
342
|
+
*/
|
|
343
|
+
function arraysEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
344
|
+
if (a.length !== b.length) return false;
|
|
345
|
+
for (let i = 0; i < a.length; i++) {
|
|
346
|
+
if (a[i] !== b[i]) return false;
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock relay for testing handshake flows
|
|
3
|
+
*
|
|
4
|
+
* This provides an in-memory message router that simulates the relay server.
|
|
5
|
+
* Messages are delivered synchronously for easy testing.
|
|
6
|
+
*
|
|
7
|
+
* The mock relay:
|
|
8
|
+
* - Routes messages between "client" and "machine" endpoints
|
|
9
|
+
* - Allows inspection of message history
|
|
10
|
+
* - Supports multiple concurrent connections
|
|
11
|
+
* - Can simulate delays and failures for error testing
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { HandshakeMessage } from "../../../handshake-handler.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Message record for history tracking
|
|
18
|
+
*/
|
|
19
|
+
export interface MessageRecord {
|
|
20
|
+
/** When message was sent */
|
|
21
|
+
timestamp: number;
|
|
22
|
+
/** Sender endpoint */
|
|
23
|
+
from: "client" | "machine";
|
|
24
|
+
/** Receiver endpoint */
|
|
25
|
+
to: "client" | "machine";
|
|
26
|
+
/** Connection ID */
|
|
27
|
+
connectionId: string;
|
|
28
|
+
/** Message content */
|
|
29
|
+
message: HandshakeMessage;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Message handler callback
|
|
34
|
+
*/
|
|
35
|
+
export type MessageHandler = (
|
|
36
|
+
connectionId: string,
|
|
37
|
+
message: HandshakeMessage
|
|
38
|
+
) => HandshakeMessage | undefined | Promise<HandshakeMessage | undefined> | void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Mock relay options
|
|
42
|
+
*/
|
|
43
|
+
export interface MockRelayOptions {
|
|
44
|
+
/** Simulate network delay in milliseconds */
|
|
45
|
+
latencyMs?: number;
|
|
46
|
+
/** Drop messages randomly (0-1 probability) */
|
|
47
|
+
dropRate?: number;
|
|
48
|
+
/** Tamper with messages (for security testing) */
|
|
49
|
+
tamperFn?: (message: HandshakeMessage) => HandshakeMessage;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Mock relay for testing handshake flows
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const relay = new MockRelay();
|
|
58
|
+
*
|
|
59
|
+
* relay.onClientMessage(async (connId, msg) => {
|
|
60
|
+
* // Process on machine side
|
|
61
|
+
* const result = await handler.processMessage(connId, msg);
|
|
62
|
+
* if (result.type === "reply") return result.message;
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* // Send ClientHello
|
|
66
|
+
* const response = await relay.sendFromClient("conn-1", clientHelloMessage);
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export class MockRelay {
|
|
70
|
+
private messageHistory: MessageRecord[] = [];
|
|
71
|
+
private clientHandler: MessageHandler | null = null;
|
|
72
|
+
private machineHandler: MessageHandler | null = null;
|
|
73
|
+
private options: MockRelayOptions;
|
|
74
|
+
private connectionIdCounter = 0;
|
|
75
|
+
|
|
76
|
+
constructor(options: MockRelayOptions = {}) {
|
|
77
|
+
this.options = options;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Register handler for messages arriving at the machine
|
|
82
|
+
*/
|
|
83
|
+
onClientMessage(handler: MessageHandler): void {
|
|
84
|
+
this.clientHandler = handler;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Register handler for messages arriving at the client
|
|
89
|
+
*/
|
|
90
|
+
onMachineMessage(handler: MessageHandler): void {
|
|
91
|
+
this.machineHandler = handler;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Send a message from client to machine
|
|
96
|
+
*
|
|
97
|
+
* @param connectionId - Connection identifier
|
|
98
|
+
* @param message - Handshake message to send
|
|
99
|
+
* @returns Response from machine (if any)
|
|
100
|
+
*/
|
|
101
|
+
async sendFromClient(
|
|
102
|
+
connectionId: string,
|
|
103
|
+
message: HandshakeMessage
|
|
104
|
+
): Promise<HandshakeMessage | undefined> {
|
|
105
|
+
// Record outgoing message
|
|
106
|
+
this.recordMessage("client", "machine", connectionId, message);
|
|
107
|
+
|
|
108
|
+
// Apply tampering if configured
|
|
109
|
+
const finalMessage = this.options.tamperFn
|
|
110
|
+
? this.options.tamperFn(message)
|
|
111
|
+
: message;
|
|
112
|
+
|
|
113
|
+
// Simulate latency
|
|
114
|
+
if (this.options.latencyMs) {
|
|
115
|
+
await this.delay(this.options.latencyMs);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check for drop
|
|
119
|
+
if (this.options.dropRate && Math.random() < this.options.dropRate) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Deliver to machine handler
|
|
124
|
+
if (!this.clientHandler) {
|
|
125
|
+
throw new Error("No machine handler registered");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const response = await this.clientHandler(connectionId, finalMessage);
|
|
129
|
+
|
|
130
|
+
if (response) {
|
|
131
|
+
this.recordMessage("machine", "client", connectionId, response);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return response ?? undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Send a message from machine to client
|
|
139
|
+
*
|
|
140
|
+
* @param connectionId - Connection identifier
|
|
141
|
+
* @param message - Handshake message to send
|
|
142
|
+
* @returns Response from client (if any)
|
|
143
|
+
*/
|
|
144
|
+
async sendFromMachine(
|
|
145
|
+
connectionId: string,
|
|
146
|
+
message: HandshakeMessage
|
|
147
|
+
): Promise<HandshakeMessage | undefined> {
|
|
148
|
+
// Record outgoing message
|
|
149
|
+
this.recordMessage("machine", "client", connectionId, message);
|
|
150
|
+
|
|
151
|
+
// Apply tampering if configured
|
|
152
|
+
const finalMessage = this.options.tamperFn
|
|
153
|
+
? this.options.tamperFn(message)
|
|
154
|
+
: message;
|
|
155
|
+
|
|
156
|
+
// Simulate latency
|
|
157
|
+
if (this.options.latencyMs) {
|
|
158
|
+
await this.delay(this.options.latencyMs);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check for drop
|
|
162
|
+
if (this.options.dropRate && Math.random() < this.options.dropRate) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Deliver to client handler
|
|
167
|
+
if (!this.machineHandler) {
|
|
168
|
+
throw new Error("No client handler registered");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const response = await this.machineHandler(connectionId, finalMessage);
|
|
172
|
+
|
|
173
|
+
if (response) {
|
|
174
|
+
this.recordMessage("client", "machine", connectionId, response);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return response ?? undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Generate a unique connection ID
|
|
182
|
+
*/
|
|
183
|
+
generateConnectionId(): string {
|
|
184
|
+
this.connectionIdCounter++;
|
|
185
|
+
return `conn-${this.connectionIdCounter}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get message history
|
|
190
|
+
*
|
|
191
|
+
* @param connectionId - Optional filter by connection ID
|
|
192
|
+
* @returns Array of message records
|
|
193
|
+
*/
|
|
194
|
+
getMessageHistory(connectionId?: string): MessageRecord[] {
|
|
195
|
+
if (connectionId) {
|
|
196
|
+
return this.messageHistory.filter((m) => m.connectionId === connectionId);
|
|
197
|
+
}
|
|
198
|
+
return [...this.messageHistory];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get messages by phase
|
|
203
|
+
*
|
|
204
|
+
* @param phase - Handshake phase to filter by
|
|
205
|
+
* @returns Array of message records with that phase
|
|
206
|
+
*/
|
|
207
|
+
getMessagesByPhase(
|
|
208
|
+
phase: HandshakeMessage["phase"]
|
|
209
|
+
): MessageRecord[] {
|
|
210
|
+
return this.messageHistory.filter((m) => m.message.phase === phase);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Clear message history
|
|
215
|
+
*/
|
|
216
|
+
clearHistory(): void {
|
|
217
|
+
this.messageHistory = [];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Reset the relay (clear handlers and history)
|
|
222
|
+
*/
|
|
223
|
+
reset(): void {
|
|
224
|
+
this.clientHandler = null;
|
|
225
|
+
this.machineHandler = null;
|
|
226
|
+
this.messageHistory = [];
|
|
227
|
+
this.connectionIdCounter = 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private recordMessage(
|
|
231
|
+
from: "client" | "machine",
|
|
232
|
+
to: "client" | "machine",
|
|
233
|
+
connectionId: string,
|
|
234
|
+
message: HandshakeMessage
|
|
235
|
+
): void {
|
|
236
|
+
this.messageHistory.push({
|
|
237
|
+
timestamp: Date.now(),
|
|
238
|
+
from,
|
|
239
|
+
to,
|
|
240
|
+
connectionId,
|
|
241
|
+
message,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private delay(ms: number): Promise<void> {
|
|
246
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Create a mock relay with default settings
|
|
252
|
+
*/
|
|
253
|
+
export function createMockRelay(options?: MockRelayOptions): MockRelay {
|
|
254
|
+
return new MockRelay(options);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Create a message tampering function for security tests
|
|
259
|
+
*
|
|
260
|
+
* @param modifications - Fields to modify in the message data
|
|
261
|
+
* @returns Tamper function
|
|
262
|
+
*/
|
|
263
|
+
export function createTamperFn(
|
|
264
|
+
modifications: Record<string, unknown>
|
|
265
|
+
): (msg: HandshakeMessage) => HandshakeMessage {
|
|
266
|
+
return (msg: HandshakeMessage) => ({
|
|
267
|
+
...msg,
|
|
268
|
+
data: {
|
|
269
|
+
...(msg.data as Record<string, unknown>),
|
|
270
|
+
...modifications,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Create a message that replays with a stale timestamp
|
|
277
|
+
*
|
|
278
|
+
* @param staleByMs - How stale the timestamp should be (default: 10 minutes)
|
|
279
|
+
* @returns Tamper function
|
|
280
|
+
*/
|
|
281
|
+
export function createStaleTimestampTamperFn(
|
|
282
|
+
staleByMs = 10 * 60 * 1000
|
|
283
|
+
): (msg: HandshakeMessage) => HandshakeMessage {
|
|
284
|
+
return (msg: HandshakeMessage) => ({
|
|
285
|
+
...msg,
|
|
286
|
+
data: {
|
|
287
|
+
...(msg.data as Record<string, unknown>),
|
|
288
|
+
timestamp: Date.now() - staleByMs,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|