Files
UmbraTrans/web
umbra audio 08a44dbacd Wire the browser client to the real account and organization API
The pages under web/ were a static mockup with hardcoded names and counts. They
now call the real backend, and the areas with no backend are explicit empty
states rather than invented numbers.

Real: sign in, the mandatory first-login password change, sign out, the account
table with filters and lifecycle actions, organization create/rename/re-slug/
status, the audit log, and dashboard counts scoped to the caller. The project
workspace keeps its Studio identity and its real session wiring, but its
project, maintenance, and statistics areas now say which backend work is
pending instead of showing sample figures.

Removed the mock release-readiness pipeline and the fake production metrics
rather than restyling them.

The client depends on properties the server already enforces, and states them
where a future editor will look:

- No secret is sent by a client. Account provisioning has no password field;
  the service generates the one-time password and returns it once.
- That one-time password lives in exactly one variable, is cleared when the
  reveal panel closes and on pagehide, and is never written to storage, a
  cookie, the URL, the page title, or the console.
- Every unsafe request carries X-CSRF-Token read fresh from the readable
  cookie, and GET /api/health on page load bootstraps it because the
  middleware issues none on an unsafe method and login is exempt.
- Nothing is trusted from the browser. Controls a role cannot use are hidden,
  and the service re-checks each one.

Rendered markup is built by an el() helper that routes all server-supplied text
through textContent, with no innerHTML anywhere. The response CSP is
script-src 'self' and style-src 'self', so an inline script or style attribute
would be refused by the browser; there is none.

Verification, and its limits. The banned-pattern grep for inline script, inline
handlers, style attributes, innerHTML, storage, and console output comes back
empty. Replaying the exact request sequence the client performs against a real
uvicorn server confirmed all 86 contract assertions: every field the
JavaScript reads is present, the CSRF cookie is issued by the health probe, the
one-time password is absent from every later response, and the error codes the
UI branches on are the ones the API returns.

The client was NOT executed in a browser. No desktop browser is attached to
this session and no JavaScript runtime is installed on this host, so the
one-time-password reveal, the modal focus trap, and the role gating are
verified by reading and by the contract replay, not by driving a real browser.
That gap needs closing before this is trusted.

web/README.md replaces the mock description, including the run command and the
fact that this is not a production deployment: create_app still refuses to
start in production because the PostgreSQL adapter does not exist.
2026-09-26 20:13:46 -07:00
..

UmbraTrans web application

The browser client and the account/organization API it talks to.

This is not a mock. Every page calls the real backend under /api. The three routes below are served from the same origin as the API by umbratrans_web, because the session cookie is HttpOnly and the CSRF token travels as a header, and the application deliberately installs no CORS middleware. A separately hosted frontend could not call it at all.

What is real

Surface Backend
Sign in, forced first-login password change, sign out Real, Argon2id and server-side sessions
Accounts: list, filter, provision, suspend, reactivate, deactivate, revoke sessions Real
Organizations: list, create, rename, re-slug, suspend, reactivate, archive Real
Audit log Real
Dashboard counts Real, scoped to the caller
Project workspace contents, maintenance, statistics Not built yet

The unbuilt areas render as explicit empty states that name the missing backend work. No page displays a fabricated name, count, or metric.

Running it

In development the API serves this directory. From the repository root:

cd /home/umbra/src/UmbraTrans
UMBRA_ENV=development \
UMBRATRANS_REPOSITORY=json \
UMBRATRANS_REPOSITORY_PATH=.local/data/development/umbratrans-web/dev-accounts.json \
UMBRATRANS_STATIC_ROOT=/home/umbra/src/UmbraTrans/web \
UMBRATRANS_ALLOWED_HOSTS=127.0.0.1,localhost \
  /home/umbra/.local/bin/uv run python -m umbratrans_web serve \
    --host 127.0.0.1 --port 8400 --repository json

There is no first administrator until one is provisioned through the operator command, which reads the one-time password from a hidden prompt:

UMBRA_ENV=development \
  /home/umbra/.local/bin/uv run umbratrans-bootstrap-admin \
  --username Umbraadmin \
  --repository-path .local/data/development/umbratrans-web/dev-accounts.json

--repository json keeps accounts across restarts. --repository memory starts empty every time.

Routes

  • / — sign in, and the mandatory password change for a temporary-password account
  • /uadmin/ — control panel: dashboard, accounts, organizations, audit
  • /workspace.html — project workspace

Security properties the client relies on

  • No secret is ever sent by a client. POST /api/uadmin/accounts has no password field; the service generates the one-time password. The request schema refuses unknown fields, so a submitted password is rejected before a handler sees it.
  • A one-time password is shown exactly once, in the otp-modal panel. It is held in one variable, cleared when the panel closes and on pagehide, and is never written to localStorage, sessionStorage, a cookie, the URL, the page title, or the console.
  • Every unsafe request carries X-CSRF-Token, read fresh from the readable umbratrans_csrf cookie. GET /api/health on page load bootstraps that cookie, because the middleware does not issue one on an unsafe method and POST /api/auth/login is exempt.
  • No inline script, no inline style, no innerHTML. The server sends script-src 'self'; style-src 'self', so an inline <script> or a style= attribute would be refused by the browser. All server-supplied text is rendered with textContent through the el() helper in api.js.
  • Nothing is trusted from the browser. The UI hides controls a role cannot use, but the service re-checks every one of them; a forged identifier is refused with the same generic error as any other forbidden request.

Not a production deployment

The application fails closed in production: the PostgreSQL adapter does not exist, so create_app raises rather than starting against an unreviewed store. Serving this directory is therefore a development arrangement, and the reviewed migration in umbratrans_web/migrations/ has not been applied.

Systemd

umbratrans-web-prototype.service currently serves this directory with python3 -m http.server, which is static only and cannot reach the API. It exists so the port stays occupied and the public hostname keeps answering. See systemd/umbratrans-web-prototype.service.