|
| 1 | +# Connection links via a dedicated `#/connect` route |
| 2 | + |
| 3 | +Date: 2026-06-12 |
| 4 | + |
| 5 | +## Status |
| 6 | + |
| 7 | +Accepted |
| 8 | + |
| 9 | +## Context |
| 10 | + |
| 11 | +External applications want to deep-link into Graph Explorer with a connection |
| 12 | +already configured — for example, a console that lists Neptune clusters and |
| 13 | +offers an "Open in Graph Explorer" link. The link carries the endpoint and auth |
| 14 | +details as URL parameters. Graph Explorer stores all connections client-side and |
| 15 | +has no server, so the link is the only channel for this hand-off. |
| 16 | + |
| 17 | +Three decisions in this design are non-obvious and would otherwise invite "why |
| 18 | +is it like this?" later. |
| 19 | + |
| 20 | +## Decisions |
| 21 | + |
| 22 | +### A dedicated `#/connect` route, not an interstitial gate |
| 23 | + |
| 24 | +An earlier version mounted a `UrlConnectionGate` component high in the tree that |
| 25 | +read `window.location`, froze the resolved intent at mount with `useState`, and |
| 26 | +stripped the params via `history.replaceState`. That coupled a reactive |
| 27 | +computation to a one-shot lifecycle and put connection-handling logic in the app |
| 28 | +shell. |
| 29 | + |
| 30 | +Connection links are now a first-class route, `#/connect?graphDbUrl=…`. The |
| 31 | +route resolves the params once on entry and redirects (router `navigate`, with |
| 32 | +`replace`) to the graph canvas on completion. Because Graph Explorer uses a hash |
| 33 | +router, the parameters sit **after** the `#` like every other route — third-party |
| 34 | +integrators build the link the same way they would any in-app link, and |
| 35 | +`window.location.search` (everything before the `#`) is no longer a trap. The |
| 36 | +redirect leaves no `#/connect` entry in history, so refresh and back behave |
| 37 | +normally without any manual param stripping. |
| 38 | + |
| 39 | +### Auth posture is part of connection identity |
| 40 | + |
| 41 | +A link resolves to one of four intents against the existing connections: |
| 42 | +`none` (it targets the active connection — do nothing), `activate` (it matches an |
| 43 | +inactive connection — switch to it), `create` (no match — open a pre-filled |
| 44 | +form), or `invalid` (the link's `graphDbUrl` failed validation — warn and ignore |
| 45 | +it). A connection matches only when its `graphDbUrl`, `queryEngine`, **and auth |
| 46 | +posture** all agree, where auth posture is IAM on/off and, when on, the region |
| 47 | +and service type. |
| 48 | + |
| 49 | +Auth posture is identity-bearing because activating the wrong-auth connection |
| 50 | +would silently connect with credentials the link did not ask for. A link |
| 51 | +requesting IAM in `us-east-1` must not reuse a plaintext connection to the same |
| 52 | +endpoint, and vice versa. When posture differs, the link falls through to the |
| 53 | +`create` form rather than silently reusing a connection. |
| 54 | + |
| 55 | +### Switching to an existing connection needs no confirmation |
| 56 | + |
| 57 | +The `activate` path switches connections with no dialog. This matches how the |
| 58 | +connections list already works — clicking a connection there activates it on a |
| 59 | +single click, resetting the graph session, with no prompt. The link only ever |
| 60 | +activates a connection the user already created and validated, so there is |
| 61 | +nothing new to confirm. Adding a prompt here would guard an operation the rest |
| 62 | +of the app treats as routine. |
| 63 | + |
| 64 | +The `create` path keeps its friction: the pre-filled form is fully editable and |
| 65 | +the user must submit it. This is the deliberate trust gate for the untrusted |
| 66 | +endpoint details a link can carry — it is the only path that can introduce a new |
| 67 | +database, so it is the only one that asks the user to confirm. |
| 68 | + |
| 69 | +## Consequences |
| 70 | + |
| 71 | +- The contract other code and external integrators depend on is the parameter |
| 72 | + set (`graphDbUrl`, `queryEngine`, `awsRegion`, `serviceType`, `name`) and the |
| 73 | + four-intent model, both in `core/urlConnectionParams.ts`. Parameters are |
| 74 | + validated with zod; `graphDbUrl` must be an http(s) URL or the link is ignored. |
| 75 | +- A connection from a link always proxies through the same host that serves |
| 76 | + Graph Explorer. The proxy base URL is derived from `document.baseURI` rather |
| 77 | + than `window.location.origin`, so it keeps the path prefix of path-hosted |
| 78 | + deployments (e.g. a Neptune notebook at `/proxy/9250/explorer/` resolves the |
| 79 | + proxy to `/proxy/9250`). There is no parameter to target a different proxy |
| 80 | + host or to make a direct, non-proxy connection. (When the connection model |
| 81 | + drops the explicit proxy `url` in favor of always-relative requests — see |
| 82 | + PR #1773 — this derivation goes away and links inherit that behavior.) |
| 83 | +- A link can switch to or pre-fill a connection, but it can never create or |
| 84 | + connect to a new database without the user submitting the form. The proxy |
| 85 | + server's `PROXY_SERVER_ALLOWED_DB_ORIGINS` allowlist remains the unconditional |
| 86 | + backstop regardless of what a link requests. See |
| 87 | + [security reference](../references/security.md). |
| 88 | +- Parameters are plaintext, not an encoded token. This was deliberate: links are |
| 89 | + meant to be human-readable and constructible by any integrator. The trust gate |
| 90 | + is the create form plus the proxy allowlist, not obscurity. |
| 91 | +- Active-connection state is currently global. A separate workstream makes it |
| 92 | + per-tab; this route is written against the current global behavior and does |
| 93 | + not bake in cross-tab assumptions. |
| 94 | + |
| 95 | +## User-facing documentation |
| 96 | + |
| 97 | +[Connections feature → Connection Links](../features/connections.md#connection-links) |
| 98 | +documents the parameters, an example link, and the resolved behavior. |
0 commit comments