已开启
[EPIC] Add the authenticated serverless WebUI frontend #29
urandon创建于  8月4日
urandon
urandon成员
8月4日 创建

Parent architecture

  • System architecture: #1
  • Product implementation epic: #6
  • Canonical-session contracts: #20#27
  • Cloud foundation: #12

Outcome

Deliver a small authenticated WebUI as the second Sessionless frontend. A user signs in with Telegram, Sessionless resolves that external identity to its internal user and tenant memberships, and the browser can access only canonical sessions authorized for the selected tenant.

The WebUI is a frontend/projection over Sessionless canonical state. It does not import Telegram chat history, make Telegram authoritative for product sessions, or introduce a WebUI-owned conversation store.

Decision summary

  • Frontend: SvelteKit with adapter-static.
  • Backend-for-frontend: Go HTTP service.
  • Production runtime: one private Yandex Serverless Container containing the Go BFF and embedded fingerprinted Svelte assets; no Python and no Node.js runtime.
  • Authentication: Telegram OpenID Connect Authorization Code flow with PKCE, server-side code exchange, state and nonce verification.
  • Authorization: internal Sessionless user/tenant/session membership, never a browser-supplied tenant_id.
  • Session: opaque first-party cookie backed by revocable, TTL-controlled YDB records; no provider token in browser storage.
  • Data access: frontend-neutral application services from the canonical-session track; UI handlers do not issue ad-hoc YDB queries.
  • Updates: bounded polling with sequence cursors and ETags for MVP; no WebSocket/SSE requirement.
  • Deployment hostname: web.dev.sessionless.triborg.dev in the delegated Yandex DNS zone.
  • Scale: zero provisioned instances in dev; explicit low concurrency and budget bounds.

Why this shape

Svelte's official static adapter emits deployable static files, so SSR is unnecessary for this authenticated application shell: SvelteKit static generation.

Yandex Serverless Containers accept an arbitrary AMD64 containerized HTTP server, scale instances for requests and allow zero provisioned instances. A static Svelte build embedded in the Go binary gives one immutable artifact and one same-origin security boundary: Serverless Containers, runtime, limits.

Telegram now documents a standard OIDC Authorization Code flow with PKCE and JWKS validation. This is preferred over the archived iframe login widget: Log In With Telegram.

System diagram

flowchart LR
    Browser["Browser<br/>static Svelte UI"] --> Gateway["API Gateway<br/>web.dev.sessionless.triborg.dev"]
    Gateway --> BFF["Private Go web-bff<br/>embedded UI + JSON API"]
    BFF --> OIDC["Telegram OIDC<br/>auth/code + token + JWKS"]
    BFF --> YDB["YDB Serverless<br/>auth sessions + canonical metadata"]
    BFF --> Storage["Object Storage<br/>payloads and attachments"]
    BFF --> App["Canonical application services<br/>sessions/events/runs/quota"]
    App --> YDB
    App --> Storage

Authentication and tenant resolution

sequenceDiagram
    actor U as User
    participant W as Svelte WebUI
    participant B as Go BFF
    participant T as Telegram OIDC
    participant D as YDB

    U->>W: Sign in with Telegram
    W->>B: GET /auth/telegram/start
    B->>D: Store hashed state, PKCE verifier, nonce and expiry
    B-->>U: Redirect to Telegram authorization endpoint
    U->>T: Approve login
    T-->>B: Authorization code + state
    B->>D: Consume one-time login challenge
    B->>T: Exchange code using PKCE
    T-->>B: ID token
    B->>B: Verify signature, iss, aud, exp, nonce
    B->>D: Resolve Telegram subject to internal user and memberships
    alt no existing authorized membership
        B-->>U: 403; instruct user to initialize via the bot
    else authorized membership
        B->>D: Store hashed opaque web session
        B-->>U: Secure HttpOnly SameSite cookie
    end
    U->>B: Request/select tenant
    B->>D: Validate membership and load tenant-scoped state
    B-->>U: Authorized canonical data

Rules:

  1. The OIDC sub identifies a Telegram external identity; it is not a tenant_id.
  2. The server maps the subject to an existing internal user and active tenant membership. Web login does not silently create a tenant.
  3. A requested tenant switch is treated only as a selector and is accepted after a server-side membership check.
  4. After identity resolution, every canonical read/write carries the resolved internal tenant_id.
  5. Unknown, suspended or membership-revoked identities fail closed.
  6. The OIDC client secret is stored in Lockbox. ID/access tokens never enter browser storage, logs, Terraform state or canonical events.

Web session and CSRF contract

  • Generate at least 256 bits of random session material; store only its digest.
  • Use a __Host- Secure, HttpOnly, SameSite=Lax cookie with Path=/ and no Domain attribute.
  • Default idle expiry: 12 hours; absolute expiry: 7 days; both configurable and enforced server-side.
  • Rotate the session after login and tenant switch; revoke it on logout or membership/security-version change.
  • Protect every mutation with exact Origin validation plus a session-bound CSRF token.
  • Cache Telegram JWKS with bounded expiry and fail closed if an unknown key cannot be refreshed.
  • Redact authorization codes, cookies, provider tokens and upload URLs from logs.
  • Audit login success/failure, logout, tenant switch, session revoke and privileged mutations without storing provider token payloads.

Pre-tenant auth lookup is an explicit exception to tenant-first keys: login challenge and session keys use a random digest prefix to avoid hot ranges. After the session resolves a membership, all product access is tenant-scoped.

Minimal YDB additions

  • web_login_challenges(shard, state_hash): PKCE verifier, nonce, redirect target, created/expires/consumed timestamps; short TTL.
  • web_auth_sessions(shard, session_hash): user, active tenant, authentication provider/subject reference, membership security version, issued/seen/idle/absolute/revoked timestamps; TTL.
  • Reuse the canonical external-identity and tenant-membership records defined by #20/#21; do not create a second user directory.
  • Maintain bounded user/session administration paths; never scan sessions or tenant payloads to authenticate a cookie.

MVP WebUI surface

  • Telegram sign-in, sign-out and access-denied recovery.
  • Current identity and authorized tenant selection.
  • Compute connection/quota status.
  • Canonical session list, create/new, select, archive/unarchive and bounded history.
  • Text composer and run status/result refresh.
  • Image/file attachment upload through tenant-bound short-lived upload intents.
  • Empty/error/loading/retry states and responsive desktop/mobile layout.
  • No summary/search (#28), billing UI, provider administration, collaborative role editor or production admin console.

The browser does not proxy large uploads through the container. Serverless Container HTTP requests are limited to 3.5 MB, so the BFF issues a short-lived tenant-bound Object Storage upload intent and validates the resulting object before committing an event: container limits, Object Storage Presign API. Prefer the IAM-authenticated Yandex Presign API from the container metadata identity; do not add a static S3 key unless a verified API limitation forces a separately reviewed fallback.

API boundary

Initial same-origin routes:

GET  /auth/telegram/start
GET  /auth/telegram/callback
POST /auth/logout
GET  /api/web/v1/me
GET  /api/web/v1/tenants
POST /api/web/v1/active-tenant
GET  /api/web/v1/sessions
POST /api/web/v1/sessions
GET  /api/web/v1/sessions/{session_id}/events?after_seq=
POST /api/web/v1/sessions/{session_id}/archive
POST /api/web/v1/sessions/{session_id}/messages
POST /api/web/v1/uploads
POST /api/web/v1/uploads/{upload_id}/commit
GET  /api/web/v1/runs/{run_id}

Handlers call frontend-neutral ports introduced by #20–#26. Resource IDs are opaque selectors; authorization is rechecked for every request.

Deployment

  • Add a dedicated web-bff service account with only container invocation runtime, YDB table access, Lockbox payload access, logging and bounded Object Storage permissions required by the implemented routes.
  • Add one private web-bff Serverless Container and a dedicated API Gateway/custom certificate for web.dev.sessionless.triborg.dev.
  • Build the Svelte frontend with pinned Node/package-manager versions, then copy the static output into the Go build and serve it with go:embed.
  • Use immutable AMD64 image digests, zero prepared instances in dev and explicit maximum instance/concurrency settings.
  • Serve fingerprinted assets with long immutable caching; serve HTML and auth responses with no-store.
  • Apply CSP, HSTS, X-Content-Type-Options, Referrer-Policy and frame restrictions. Allow only the origins required by the selected Telegram OIDC redirect flow and Object Storage uploads.
  • Terraform owns cloud resources, DNS, certificates, service accounts and secret references. Application migrations own YDB tables.
  • Cloud deployment must remain inside the existing 100 RUB/month development budget envelope; any need for prepared instances is a separate approval.

Local and cloud verification

  • Local Go test OIDC provider with fixed JWKS and explicit APP_ENV=local guard; it must refuse to run in cloud modes.
  • Svelte dev server proxies same-origin API requests to the Go BFF.
  • YDB Local covers login challenge consumption, session rotation/revocation, tenant switching and two-tenant negatives.
  • Browser tests use Playwright for login, tenant isolation, session/history, mutation CSRF and logout.
  • Cloud-dev uses real Telegram OIDC, managed certificate, immutable image digest and synthetic tenant fixtures.
  • CI runs Go format/vet/race tests, frontend type/lint/unit tests, browser tests, production build, container build and Terraform validation.

Decomposition

Order Issue Outcome Estimate Depends on Gate
1 WEB-01 #30 Auth, authorization, API contracts and threat model 3 SP / 2d #20 Contract
2 WEB-02 #31 Go Telegram OIDC BFF and YDB sessions 8 SP / 5d #30, #21 Identity
3 WEB-03 #32 Canonical Web API, uploads and bounded refresh 8 SP / 5d #31, #24, #26 Product API
4 WEB-04 #33 Static Svelte canonical-session UI 8 SP / 5d #30–#32 UI
5 WEB-05 #34 Yandex serverless deployment and domain 5 SP / 3d #12, #31, #33 Cloud
6 WEB-06 #35 Tenant-isolation/cloud E2E and runbooks 5 SP / 3d #32–#34 WebUI gate

Nominal estimate: 37 SP / 23 engineering days, before reserve.

gantt
    title Authenticated serverless WebUI track
    dateFormat  YYYY-MM-DD
    axisFormat  %d %b
    excludes    weekends

    section Contracts and identity
    WEB-01 contracts and threat model (#30) :crit, w1, 2026-08-05, 2d
    WEB-02 Go OIDC BFF and sessions (#31)   :crit, w2, after w1, 5d

    section Product and UI
    WEB-03 canonical Web API (#32)          :crit, w3, after w2, 5d
    WEB-04 Svelte UI (#33)                  :crit, w4, after w3, 5d

    section Cloud and gate
    WEB-05 serverless deployment (#34)      :w5, after w4, 3d
    WEB-06 isolation/cloud E2E (#35)        :crit, w6, after w5, 3d
    WebUI gate                              :milestone, webgate, after w6, 0d

The dates show a single-engineer dependency baseline. WEB-04 can build against the WEB-01 contract fixtures while WEB-02/03 are in progress; WEB-05 Terraform work can begin after #12 independently of the final UI bundle.

Dependency direction

flowchart TD
    S20["#20 canonical contracts"] --> W1["WEB-01 contracts"]
    W1 --> W2["WEB-02 auth BFF"]
    S21["#21 canonical schema"] --> W2
    W2 --> W3["WEB-03 canonical Web API"]
    S24["#24 stateless context"] --> W3
    S26["#26 listing/history API"] --> W3
    W1 --> W4["WEB-04 Svelte UI"]
    W2 --> W4
    W3 --> W4
    C12["#12 cloud foundation"] --> W5["WEB-05 deployment"]
    W2 --> W5
    W4 --> W5
    W3 --> W6["WEB-06 E2E"]
    W4 --> W6
    W5 --> W6
    W6 --> S27["#27 cross-frontend gate"]
    W6 --> R14["#14 release hardening"]

WEB-01 and the UI shell may start before Telegram ingress #17. The WebUI track does not depend on the external Telegram update edge #17/#18: Telegram OIDC is a separate browser identity channel. Live Telegram message delivery remains a later integration/release gate.

Acceptance criteria

  • A Telegram-authenticated user can access only tenants granted by server-side membership.
  • A forged tenant_id, session ID, OIDC callback, CSRF token or upload key cannot cross tenant boundaries.
  • Unknown Telegram identities do not receive an implicit tenant.
  • Browser storage contains no Telegram token, OIDC client secret or Sessionless bearer token.
  • Session cookies are revocable, TTL-bound, rotated and tested against replay/fixation.
  • Two tenants and two users pass negative API and browser tests.
  • The UI lists and opens canonical sessions without importing Telegram history.
  • Text and attachment requests become canonical events/runs through frontend-neutral services.
  • The production image contains a Go runtime and static assets only; no Python or Node runtime.
  • The cloud-dev container scales to zero and is deployed by Terraform on an immutable digest.
  • Public English documentation covers login registration, local development, cloud deployment, rollback, session revocation and incident response.

Non-goals

  • Replacing Telegram as a transport frontend.
  • SSR, SEO-oriented public pages or a general-purpose Node application server.
  • Password authentication, email login or a Sessionless-owned credential database.
  • Trusting a client-provided tenant, role, Telegram username or display name.
  • Search/summarization, billing, full administration or production multi-region deployment.
likedislike
urandonurandon成员
8月4日 修改了issue 的描述
urandon
urandon成员
16 天前 评论:

Web epic status rebaseline — 2026-08-25

WEB-01 through WEB-04 (#30–#33) and the canonical local gate #27 are complete. The remaining Web critical path is now only:

flowchart LR
    C34["#34 apply Web foundation/runtime\nimmutable explicit-intent image"] --> C35["#35 real Telegram OIDC\ntwo-tenant cloud/browser E2E"] --> R14["#14 release hardening"]

#34 has implementation/Terraform/publication support in main, but its live foundation apply, Web secret loading, DNS/certificate/container evidence and rollback smoke require operator-owned Yandex backend/tfvars/deployment-lock inputs. Image publication is no longer an implicit effect of every green main CI run; #71 requires an explicit deployment/publication intent.

#35 repeats the already-closed local canonical/isolation proof against real Telegram OIDC and cloud resources. It no longer blocks #27, which is closed; it remains a cloud release gate for #14.

Architecture remains unchanged: one private Go BFF with embedded static Svelte assets; Node is build-only; Python is absent. No subscription-worker credential is added to the Web container. The new attached-worker path is tracked separately by #72 and joins Web only through authenticated resource/status APIs after its owner/security contracts exist.

likedislike
urandonurandon成员
15 天前 添加了label:webui
urandonurandon成员
15 天前 添加了label:epicmvp
urandonurandon成员
15 天前 关联了里程碑:MVP — Authenticated WebUI (#29)
urandonurandon成员
4 天前 关联了pull request:[DEVX/Docs] Turn the README into a visual product showcase