Skip to content

Replace Node UI token bootstrap with dashboard sessions#1428

Open
Jurij89 wants to merge 93 commits into
mainfrom
codex/node-ui-auth-bootstrap-session
Open

Replace Node UI token bootstrap with dashboard sessions#1428
Jurij89 wants to merge 93 commits into
mainfrom
codex/node-ui-auth-bootstrap-session

Conversation

@Jurij89

@Jurij89 Jurij89 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Removes the browser-visible window.__DKG_TOKEN__ bootstrap from production /ui and Vite dev serving, so the static SPA no longer receives a reusable daemon bearer token.
  • Adds server-side dashboard sessions for browser access: opaque HttpOnly SameSite=Strict cookies, loopback bootstrap, token exchange, logout/status/CSRF endpoints, and CSRF checks for unsafe dashboard API requests.
  • Moves Node UI fetch/SSE/e2e helpers to same-origin session credentials, updates the devnet smoke and Playwright coverage, and adds a CI static bundle contract that fails if the built UI contains the legacy token global.

Related

  • None

Files changed

File What
packages/cli/src/daemon/dashboard-session.ts New dashboard session store, cookie helpers, loopback/exchange/logout/status/CSRF routes.
packages/cli/src/auth.ts Allows protected routes to authenticate with a valid dashboard session while preserving bearer-token precedence and explicit auth failures.
packages/cli/src/daemon/lifecycle.ts Wires dashboard session routes before the auth guard and stops passing daemon tokens to Node UI static serving.
packages/node-ui/src/api.ts, packages/node-ui/vite.config.ts Removes HTML token injection and serves secret-free static UI with security/cache headers.
packages/node-ui/src/ui/** Centralizes browser API calls through session-aware apiFetch, removes EventSource query tokens, and avoids sending dashboard credentials to external RPC URLs.
packages/node-ui/e2e/**, packages/node-ui/test/**, packages/cli/test/** Updates unit/e2e helpers and adds regression coverage for no browser bearer token, session-authenticated API access, CSRF, and SSE without query tokens.
scripts/devnet-test-node-ui-smoke.sh Upgrades the smoke from "HTML exists" to auth/session/security contract checks against a real devnet.
.github/workflows/ci.yml Adds a static build:ui contract check for no __DKG_TOKEN__ and valid referenced assets.
agent-docs/** Adds the requested upgrade plan, team prompt, execution todo, and durable memory notes.

Test plan

  • pnpm --filter @origintrail-official/dkg exec vitest run --config vitest.unit.config.ts test/auth.test.ts test/dashboard-session.test.ts passed: 44 tests.
  • pnpm --filter @origintrail-official/dkg-node-ui exec vitest run test/api-routes.test.ts test/ui-compat.test.ts test/ui-api-pure.test.ts test/pca-api.test.ts test/web3-clients.test.ts test/use-current-agent.test.ts test/openclaw-bridge.test.ts --no-file-parallelism passed: 296 passed, 38 skipped.
  • pnpm --filter @origintrail-official/dkg-node-ui exec tsc --noEmit passed.
  • pnpm --filter @origintrail-official/dkg exec tsc --noEmit passed.
  • pnpm --filter @origintrail-official/dkg-node-ui run build:ui passed; existing chunk-size / mock-subgraph export warnings only.
  • Static bundle contract against packages/node-ui/dist-ui/index.html passed: no __DKG_TOKEN__, referenced assets present.
  • pnpm --filter @origintrail-official/dkg run build passed.
  • scripts/devnet-test-node-ui-smoke.sh against a one-node WSL devnet passed: PASS=11 FAIL=0.
  • PLAYWRIGHT_DEVNET_TIMEOUT_MS=300000 PW_HEADLESS=1 pnpm exec playwright test e2e/specs/auth-session.spec.ts passed on the standard four-node real-node devnet: 1 passed.

Comment thread packages/node-ui/src/ui/api.ts Outdated
Comment thread packages/node-ui/src/ui/web3/clients.ts Outdated
Comment thread packages/cli/src/auth.ts Outdated
Comment thread packages/node-ui/src/ui/api.ts Outdated
Comment thread packages/cli/src/daemon/dashboard-session.ts
Comment thread packages/cli/src/daemon/dashboard-session.ts
Comment thread packages/node-ui/e2e/specs/auth-session.spec.ts
Comment thread packages/cli/src/daemon/dashboard-session.ts Outdated
Comment thread packages/node-ui/src/ui/dashboardSessionClient.ts Outdated
Comment thread packages/node-ui/src/ui/dashboardSessionClient.ts Outdated
Comment thread agent-docs/todo.md Outdated
Comment thread packages/cli/src/auth.ts Outdated
Comment thread packages/node-ui/src/api.ts
Comment thread packages/cli/src/daemon/dashboard-session.ts Outdated
Comment thread packages/cli/src/daemon/lifecycle.ts Outdated
Comment thread packages/cli/src/daemon/handle-request.ts Outdated
Comment thread packages/node-ui/src/ui/hooks/useNodeEvents.ts Outdated
Comment thread scripts/devnet-test-node-ui-smoke.sh Outdated
Comment thread packages/cli/src/daemon/routes/context.ts Outdated
Comment thread packages/cli/src/daemon/dashboard-session.ts Outdated
Comment thread packages/node-ui/src/ui/dashboardSessionClient.ts Outdated
Comment thread packages/cli/src/daemon/dashboard-session.ts
Comment thread packages/cli/src/daemon/dashboard-session.ts
Comment thread packages/cli/src/auth.ts
Comment thread packages/cli/src/daemon/lifecycle.ts Outdated
Comment thread packages/cli/src/daemon/lifecycle.ts Outdated
Comment thread packages/node-ui/src/ui/hooks/useNodeEvents.ts
Comment thread packages/cli/src/daemon/dashboard-session-cookie.ts
Comment thread packages/cli/src/daemon/dashboard-session.ts Outdated
Comment thread packages/node-ui/src/ui/hooks.ts
…wups

Refine dashboard login follow-up boundaries
// widening the api.ts surface.
async function post<T = unknown>(path: string, body: unknown): Promise<T> {
const res = await fetch(path, {
const res = await apiFetch(path, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Bug: Ontology install helper breaks the new daemonFetch path contract

What's wrong
The PR introduces a typed same-origin fetch boundary for dashboard-session requests, but this changed call passes a plain string into that boundary. That weakens the contract and is likely to break TypeScript compilation for ontology install.

Example
post('/api/sub-graph/create', ...) is valid at runtime, but inside post the literal has been widened to string, so apiFetch(path, ...) no longer satisfies the new DaemonPath contract.

Suggested direction
Keep the typed same-origin boundary by using DaemonPath at the helper boundary or validating with daemonPath.

For Agents
In packages/node-ui/src/ui/lib/ontologyInstall.ts, change the helper to accept DaemonPath or wrap the argument with daemonPath(path) before calling apiFetch. Preserve the same-origin path guard and type-check this file.

try {
const { ensureDashboardCredentials } = await import('../daemon/dashboard-credentials.js');
return await ensureDashboardCredentials();
} catch (err: any) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: Init dashboard credential failure path is untested

What's wrong
The new init flow intentionally catches dashboard credential creation failures so dkg init can continue, but the added init tests only cover success and auth-disabled paths. A regression that rethrows here would break first-run setup when a stale or corrupt credential file exists, and the current init test suite would not catch it.

Example
A focused regression test can create an invalid dashboard.credentials.json, call ensureDashboardCredentialsForInit(true), and assert it resolves null plus logs the reset-password guidance instead of throwing.

Suggested direction
Add an init-specific regression test for invalid or unreadable dashboard credentials so the best-effort contract is pinned.

For Agents
Add coverage in packages/cli/test/init-dashboard-credentials.test.ts for the catch path in ensureDashboardCredentialsForInit: seed an invalid credential file under DKG_HOME, call the helper with auth enabled, assert no throw, null result, and warning guidance. Preserve existing create/rerun/auth-disabled behavior.

} catch (err) {
if (!res.headersSent) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end(err instanceof Error ? err.stack : String(err));
// Run CLI-owned setup side effects after the node config home is known and
// before wallets / daemon startup. Best-effort so adapter setup remains
// recoverable and daemon/UI-driven setup can omit terminal-only side effects.
const afterConfigBootstrap = deps.afterConfigBootstrap ?? deps.ensureDashboardCredentials;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: Post-config setup hook handling is duplicated across setup orchestrators

What's wrong
The PR adds the same optional lifecycle hook, deprecated dashboard-specific fallback, and try/catch best-effort execution to multiple setup flows. That spreads one policy across packages and makes the general adapter setup APIs carry dashboard credential terminology they otherwise tried to avoid.

Example
Changing the post-config hook failure policy or removing the deprecated ensureDashboardCredentials fallback currently requires coordinated edits in OpenClaw, Hermes, and MCP setup code.

Suggested direction
Centralize the hook selection and best-effort execution behind a shared helper or setup lifecycle abstraction. Keep adapter-facing deps generic and contain the deprecated dashboard-specific compatibility shim at the CLI boundary where possible.

Confidence note
This assumes the three setup paths are expected to keep identical lifecycle semantics; if the divergence is intentional, the shared helper should make those differences explicit.

For Agents
Look at packages/adapter-openclaw/src/setup.ts, packages/adapter-hermes/src/setup.ts, and packages/cli/src/mcp-setup.ts. Introduce one shared post-config setup hook runner or a common setup lifecycle dependency type. Preserve dry-run skipping, best-effort error handling, and legacy JS fallback behavior, then update the existing setup hook tests to target the shared contract once.

* omits it. Required setup work should stay explicit instead of being hidden
* behind this optional hook.
*/
afterConfigBootstrap?: PostConfigSetupHook;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: A generic post-config hook turns setup into a side-effect injection point

What's wrong
The hook is intentionally broad, optional, and best-effort, while the actual behavior is specific dashboard credential provisioning. This leaks CLI-owned side effects into adapter setup through an abstraction that does not describe what it does. It also duplicates the same fallback and warning pattern in multiple setup orchestrators, which is the kind of hook-based growth that tends to become permanent setup debt.

Example
Dashboard credential provisioning is the only new use case, but the adapter API now exposes a generic post-config lifecycle hook plus a deprecated alias. The same hook fallback and catch/log behavior is repeated across Hermes, OpenClaw, and MCP setup.

Suggested direction
Make the dependency explicit to the behavior being added, or centralize a typed setup-step runner reused by Hermes/OpenClaw/MCP. Avoid a generic lifecycle hook that can hide arbitrary work and duplicated error handling.

Confidence note
This is a structural concern from the diff shape; the exact public compatibility constraints for the adapter setup dependency objects may need maintainer confirmation.

For Agents
Review the setup dependency surfaces in Hermes, OpenClaw, and MCP. Preserve the ordering: config bootstrap before wallet creation/daemon start, dry-run skip, and best-effort credential provisioning. Replace the generic hook with an explicit named setup concern or a shared post-config step runner so future setup side effects do not accumulate behind an untyped lifecycle slot.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: Avoid spreading a generic post-config hook across setup flows

What's wrong
The new afterConfigBootstrap abstraction is generic, optional, duplicated, and currently only exists to run dashboard credential provisioning. It hides a feature-specific side effect behind a lifecycle hook and repeats the same control-flow policy in multiple setup implementations.

Example
Adding another CLI-owned setup side effect after config bootstrap would require deciding whether it goes through the generic hook, then auditing Hermes, OpenClaw, and MCP to keep the same ordering/dry-run/best-effort behavior.

Suggested direction
Prefer an explicit, shared setup lifecycle helper or an explicit dashboardCredentialSetup dependency with one runner for dry-run/deprecated-alias/best-effort handling. That would keep adapter setup order readable without giving every setup path an open-ended side-effect slot.

For Agents
Inspect packages/adapter-hermes/src/setup.ts, packages/adapter-openclaw/src/setup.ts, packages/cli/src/mcp-setup.ts, and the CLI setup wrappers. Preserve dry-run skipping, wallet-before-daemon ordering, and best-effort dashboard credential creation. Replace the repeated generic hook with a shared setup lifecycle helper or an explicit dashboard-credential dependency composed in the CLI-owned setup layer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants