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,319 @@
|
|
|
1
|
+
# Gateway Worker Specification
|
|
2
|
+
|
|
3
|
+
> **Status: SPECIFICATION ONLY - NOT YET IMPLEMENTED**
|
|
4
|
+
>
|
|
5
|
+
> This document describes the planned Gateway Worker for subdomain routing and
|
|
6
|
+
> authentication. The current implementation only includes the API Worker
|
|
7
|
+
> (`api.gitspace.sh`) for user/subdomain management. The Gateway Worker
|
|
8
|
+
> described here is Phase 2 of the platform architecture.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
A Cloudflare Worker that sits in front of all user subdomains (`*.{user}.gitspace.sh`), providing:
|
|
13
|
+
- Authentication via gitspace.sh GitHub OAuth
|
|
14
|
+
- Authorization for shared services
|
|
15
|
+
- Routing to appropriate Cloudflare Tunnels
|
|
16
|
+
|
|
17
|
+
## Why Not Cloudflare Access?
|
|
18
|
+
|
|
19
|
+
Cloudflare Access:
|
|
20
|
+
- Uses its own identity providers (separate OAuth flows)
|
|
21
|
+
- Policies configured per-app in dashboard, not programmatically
|
|
22
|
+
- Can't query our D1 database for ACL/invites
|
|
23
|
+
- Can't validate our signed tokens
|
|
24
|
+
|
|
25
|
+
We need:
|
|
26
|
+
- Single identity across all gitspace.sh subdomains
|
|
27
|
+
- Programmatic access control via our API
|
|
28
|
+
- Custom authorization logic (invites, ACL, port sharing)
|
|
29
|
+
- Portable session (one login works everywhere)
|
|
30
|
+
|
|
31
|
+
## Architecture
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
35
|
+
│ Cloudflare Edge │
|
|
36
|
+
│ │
|
|
37
|
+
│ Request: app.username.gitspace.sh │
|
|
38
|
+
│ │ │
|
|
39
|
+
│ ▼ │
|
|
40
|
+
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
41
|
+
│ │ Gateway Worker │ │
|
|
42
|
+
│ │ │ │
|
|
43
|
+
│ │ 1. Parse: owner=username, service=app │ │
|
|
44
|
+
│ │ 2. Validate session cookie (JWT signed by gitspace.sh) │ │
|
|
45
|
+
│ │ 3. Check authorization (D1 query) │ │
|
|
46
|
+
│ │ 4. Route to tunnel │ │
|
|
47
|
+
│ │ │ │
|
|
48
|
+
│ └────────────────────────────────────────────────────────────┘ │
|
|
49
|
+
│ │ │
|
|
50
|
+
│ ▼ │
|
|
51
|
+
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
52
|
+
│ │ Cloudflare Tunnel → User's Machine │ │
|
|
53
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
54
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## URL Structure
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
{service}.{owner}.gitspace.sh
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
username.gitspace.sh → relay (default, terminal access)
|
|
64
|
+
app.username.gitspace.sh → localhost:3000
|
|
65
|
+
api.username.gitspace.sh → localhost:8080
|
|
66
|
+
preview.username.gitspace.sh → localhost:5173
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Data Model
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
-- Services (ports exposed under a subdomain)
|
|
73
|
+
CREATE TABLE services (
|
|
74
|
+
id TEXT PRIMARY KEY,
|
|
75
|
+
subdomain_id TEXT REFERENCES subdomains(id),
|
|
76
|
+
name TEXT NOT NULL, -- 'app', 'api', '' (root = relay)
|
|
77
|
+
local_port INTEGER NOT NULL, -- 3000, 8080, 4480 (relay)
|
|
78
|
+
visibility TEXT DEFAULT 'owner', -- 'public', 'owner', 'shared'
|
|
79
|
+
created_at INTEGER,
|
|
80
|
+
updated_at INTEGER,
|
|
81
|
+
|
|
82
|
+
UNIQUE(subdomain_id, name)
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
-- Service access grants (for visibility='shared')
|
|
86
|
+
CREATE TABLE service_access (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
service_id TEXT REFERENCES services(id),
|
|
89
|
+
user_id TEXT REFERENCES users(id), -- GitHub-authenticated user
|
|
90
|
+
invite_token_hash TEXT, -- Alternative: invite-based access
|
|
91
|
+
expires_at INTEGER,
|
|
92
|
+
created_at INTEGER
|
|
93
|
+
);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Authentication
|
|
97
|
+
|
|
98
|
+
### Session Token (JWT)
|
|
99
|
+
|
|
100
|
+
Issued by gitspace.sh after GitHub OAuth:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
interface SessionToken {
|
|
104
|
+
sub: string; // gitspace.sh user ID
|
|
105
|
+
github_id: string; // GitHub user ID
|
|
106
|
+
github_username: string;
|
|
107
|
+
iat: number;
|
|
108
|
+
exp: number;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Cookie settings:
|
|
113
|
+
```
|
|
114
|
+
__gitspace_session=<jwt>
|
|
115
|
+
Domain=.gitspace.sh // Works for all subdomains
|
|
116
|
+
HttpOnly=true
|
|
117
|
+
Secure=true
|
|
118
|
+
SameSite=Lax
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Auth Flow
|
|
122
|
+
|
|
123
|
+
**First visit (no session):**
|
|
124
|
+
1. User visits `app.username.gitspace.sh`
|
|
125
|
+
2. Gateway Worker: no valid session cookie
|
|
126
|
+
3. Redirect → `gitspace.sh/login?redirect=https://app.username.gitspace.sh`
|
|
127
|
+
4. User authenticates with GitHub
|
|
128
|
+
5. gitspace.sh sets session cookie on `.gitspace.sh`
|
|
129
|
+
6. Redirect back to original URL
|
|
130
|
+
7. Gateway Worker validates session, checks authorization
|
|
131
|
+
|
|
132
|
+
**Return visit (has session):**
|
|
133
|
+
1. User visits `app.username.gitspace.sh`
|
|
134
|
+
2. Gateway Worker: session cookie present
|
|
135
|
+
3. Validate JWT signature and expiry
|
|
136
|
+
4. Query D1: does this user have access?
|
|
137
|
+
5. Forward to tunnel
|
|
138
|
+
|
|
139
|
+
**Invite link (no login required):**
|
|
140
|
+
1. User visits `app.username.gitspace.sh?invite=xxx`
|
|
141
|
+
2. Gateway Worker: validate invite signature (Ed25519)
|
|
142
|
+
3. Valid → forward to tunnel
|
|
143
|
+
4. Optionally prompt to "claim" by logging in
|
|
144
|
+
|
|
145
|
+
## Authorization Logic
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
async function checkAccess(
|
|
149
|
+
user: SessionUser | null,
|
|
150
|
+
service: Service,
|
|
151
|
+
inviteToken: string | null
|
|
152
|
+
): Promise<boolean> {
|
|
153
|
+
// Public services: anyone
|
|
154
|
+
if (service.visibility === 'public') {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Valid invite token: allow
|
|
159
|
+
if (inviteToken && await validateInvite(inviteToken, service)) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Must be logged in from here
|
|
164
|
+
if (!user) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Owner: always has access to their own services
|
|
169
|
+
const subdomain = await getSubdomain(service.subdomain_id);
|
|
170
|
+
if (subdomain.user_id === user.id) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Shared: check access grants
|
|
175
|
+
if (service.visibility === 'shared') {
|
|
176
|
+
const grant = await getAccessGrant(service.id, user.id);
|
|
177
|
+
return grant !== null && (grant.expires_at === null || grant.expires_at > Date.now());
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Gateway Worker Implementation
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// worker-gateway/src/index.ts
|
|
188
|
+
export default {
|
|
189
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
190
|
+
const url = new URL(request.url);
|
|
191
|
+
const host = url.hostname;
|
|
192
|
+
|
|
193
|
+
// Parse subdomain: app.username.gitspace.sh
|
|
194
|
+
const parsed = parseHost(host);
|
|
195
|
+
if (!parsed) {
|
|
196
|
+
return new Response('Invalid hostname', { status: 400 });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { owner, serviceName } = parsed;
|
|
200
|
+
|
|
201
|
+
// Lookup service in D1
|
|
202
|
+
const service = await env.DB.prepare(`
|
|
203
|
+
SELECT s.*, sub.user_id as owner_user_id, sub.tunnel_id
|
|
204
|
+
FROM services s
|
|
205
|
+
JOIN subdomains sub ON s.subdomain_id = sub.id
|
|
206
|
+
WHERE sub.subdomain = ? AND s.name = ?
|
|
207
|
+
`).bind(owner, serviceName || '').first();
|
|
208
|
+
|
|
209
|
+
if (!service) {
|
|
210
|
+
return new Response('Service not found', { status: 404 });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Get session from cookie
|
|
214
|
+
const sessionToken = getSessionCookie(request);
|
|
215
|
+
const user = sessionToken ? await validateSession(sessionToken, env) : null;
|
|
216
|
+
|
|
217
|
+
// Get invite from query param
|
|
218
|
+
const inviteToken = url.searchParams.get('invite');
|
|
219
|
+
|
|
220
|
+
// Check authorization
|
|
221
|
+
const hasAccess = await checkAccess(user, service, inviteToken);
|
|
222
|
+
|
|
223
|
+
if (!hasAccess) {
|
|
224
|
+
// No session? Redirect to login
|
|
225
|
+
if (!user) {
|
|
226
|
+
const loginUrl = new URL('https://gitspace.sh/login');
|
|
227
|
+
loginUrl.searchParams.set('redirect', request.url);
|
|
228
|
+
return Response.redirect(loginUrl.toString(), 302);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Has session but not authorized
|
|
232
|
+
return new Response('Access denied', { status: 403 });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Forward to tunnel
|
|
236
|
+
// The tunnel is already configured with ingress rules
|
|
237
|
+
// We just need to pass through the request
|
|
238
|
+
return fetch(request);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## CLI Commands
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# Share a port as a service
|
|
247
|
+
gssh share port 3000 --as app
|
|
248
|
+
# Creates: app.username.gitspace.sh → localhost:3000
|
|
249
|
+
|
|
250
|
+
# Share with specific user
|
|
251
|
+
gssh share port 3000 --as app --with alice
|
|
252
|
+
# Adds alice to service_access
|
|
253
|
+
|
|
254
|
+
# Make public (no auth required)
|
|
255
|
+
gssh share port 3000 --as app --public
|
|
256
|
+
|
|
257
|
+
# List shared services
|
|
258
|
+
gssh share list
|
|
259
|
+
# app.username.gitspace.sh → :3000 (owner-only)
|
|
260
|
+
# api.username.gitspace.sh → :8080 (shared: alice, bob)
|
|
261
|
+
# preview.username.gitspace.sh → :5173 (public)
|
|
262
|
+
|
|
263
|
+
# Stop sharing
|
|
264
|
+
gssh share remove app
|
|
265
|
+
|
|
266
|
+
# Grant access to existing service
|
|
267
|
+
gssh share grant app --to bob
|
|
268
|
+
|
|
269
|
+
# Revoke access
|
|
270
|
+
gssh share revoke app --from bob
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Tunnel Configuration
|
|
274
|
+
|
|
275
|
+
When a user shares a port, the tunnel ingress is updated:
|
|
276
|
+
|
|
277
|
+
```yaml
|
|
278
|
+
ingress:
|
|
279
|
+
- hostname: username.gitspace.sh
|
|
280
|
+
service: http://localhost:4480 # relay
|
|
281
|
+
- hostname: "*.username.gitspace.sh"
|
|
282
|
+
service: http://localhost:4480 # default to relay
|
|
283
|
+
- hostname: app.username.gitspace.sh
|
|
284
|
+
service: http://localhost:3000 # specific service
|
|
285
|
+
- service: http_status:404
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The Gateway Worker handles auth BEFORE the request reaches the tunnel.
|
|
289
|
+
|
|
290
|
+
## Migration Path
|
|
291
|
+
|
|
292
|
+
### Phase 1 (Current)
|
|
293
|
+
- Relay-level auth only (signed messages + challenge-response)
|
|
294
|
+
- No Gateway Worker
|
|
295
|
+
|
|
296
|
+
### Phase 2 (This Spec)
|
|
297
|
+
- Deploy Gateway Worker
|
|
298
|
+
- Session cookies via gitspace.sh OAuth
|
|
299
|
+
- Owner-only access (prove you own the GitHub account)
|
|
300
|
+
|
|
301
|
+
### Phase 3 (Future)
|
|
302
|
+
- Invite system for sharing with others
|
|
303
|
+
- Service-level access grants
|
|
304
|
+
- Port sharing CLI commands
|
|
305
|
+
|
|
306
|
+
## Security Considerations
|
|
307
|
+
|
|
308
|
+
1. **Session tokens** - Signed by gitspace.sh, validated at edge
|
|
309
|
+
2. **Invite tokens** - Ed25519 signed by service owner, time-limited
|
|
310
|
+
3. **Cookie scope** - `.gitspace.sh` allows SSO across all subdomains
|
|
311
|
+
4. **HTTPS only** - All traffic through Cloudflare, TLS enforced
|
|
312
|
+
5. **No secrets in Worker** - Only public keys for signature validation
|
|
313
|
+
|
|
314
|
+
## Open Questions
|
|
315
|
+
|
|
316
|
+
1. **Wildcard routing** - How to handle `*.username.gitspace.sh` efficiently?
|
|
317
|
+
2. **WebSocket support** - Gateway Worker must handle WS upgrade
|
|
318
|
+
3. **Rate limiting** - Per-user or per-service limits?
|
|
319
|
+
4. **Audit logging** - Track access for security/debugging?
|