8.1 KiB
造物工坊 · Foundry Core
共享后端的第一版提供 Google 登录。各项目通过同一个 HTTPS API 登录,在私有 PostgreSQL 的认证库中维护独立账号和会话。项目业务库可分别配置独立的数据库和角色。
服务端配置
- 在 Google Cloud 创建一个 Web application OAuth 客户端。授权重定向 URI 填写
https://<API 域名>/auth/google/callback。所有项目信任同一个客户端,因此共享 Google 同意页面的应用名称。 - 在服务器私网准备 PostgreSQL 13 或以上版本。在认证库执行一次
db/001-auth.sql(使用psql -v ON_ERROR_STOP=1),给运行账号授予这四张表所需的查询及增删改权限。 - 按
.env.example注入运行环境变量。每个项目配置一个固定的前端回调地址,本地开发可用 localhost。将 Google 客户端密钥和数据库连接串存入服务器的密钥管理设施。 - 由 Gitea Actions 在
ces-control上使用 Go 1.25 构建并检查。部署工作流仅处理main上影响后端或部署的改动,将二进制启动在服务器 Docker 私网,由 Traefik 提供 HTTPS。通过GET /healthz检查进程,通过GET /readyz检查认证库。 - 代理避免记录 Google 回调的查询参数及认证请求头。设置入口限流,并定时删除三张临时表(
oauth_attempts、login_grants、project_sessions)中expires_at <= now()的记录。
Actions 部署配置
在仓库的 Actions Secrets 配置 GOOGLE_CLIENT_ID 和 GOOGLE_CLIENT_SECRET,在 Actions Variables 配置 FOUNDRY_PROJECTS。变量值为 JSON 对象,例如 {"prompt":"http://localhost:17321/auth/callback"}。部署读取服务器上预先配置的 /opt/endoleap-infra/foundry/database.env,OAuth 值由 Actions 在启动容器时注入。
Google OAuth 的授权回调地址为 https://api.foundry.entroia.com/auth/google/callback。工作流会验证数据库就绪、匿名请求返回 401 和 HTTPS 就绪。前端接入后,可在浏览器用 Google 测试账号验证完整登录、回调、兑换和会话。
前端接入
- 在浏览器用 Web Crypto 生成 32 字节随机值,编码为无填充 Base64URL,作为 verifier 暂存在当前 tab 的 sessionStorage。对 verifier 字符串做 SHA-256 并用相同编码得到 challenge。
- 顶层导航到
GET /projects/<project>/auth/google?challenge=<challenge>。Google 回到共享 API 后,API 将浏览器重定向至已配置的项目回调。 - 回调页从 URL fragment 读取
code或error,用 history.replaceState 清掉 fragment。收到 code 时,向POST /projects/<project>/auth/exchange发送X-Foundry-Code和X-Foundry-Verifier请求头,随后清理暂存的 verifier。code 有效期一分钟,仅可使用一次。 - 将返回的八小时会话凭证保存在内存中,使用
Authorization: Bearer <access_token>调用GET /projects/<project>/auth/me。通过POST /projects/<project>/auth/logout撤销会话并清理本地凭证。刷新页面后,内存凭证需要通过重新登录获取。
同一个 Google 账号首次进入不同项目时,会分别创建用户 ID。会话绑定其签发项目。Google 账号的标识 sub 用于认领同一项目的既有账号,邮箱只作为资料字段。当前注册模式为开放注册。
验收
用同一个 Google 测试账号分别登录两个项目,确认用户 ID 不同。用 A 的会话请求 B 的 me 应返回 401。验证兑换码重放被拒绝,并确认退出 A 后 B 的会话仍有效。
Runtime HTTPS trust
The Go backend needs an operating-system CA bundle for Google and Slack HTTPS
requests. The node:24-bookworm-slim runtime does not include that trust store;
Node's own successful HTTPS checks do not validate Go's trust configuration.
deploy/release.sh mounts the deployment host's
/etc/ssl/certs/ca-certificates.crt read-only and sets SSL_CERT_FILE to that
path. The deployment fails before switching traffic if the host bundle is
missing or unreadable in the candidate. Keep TLS certificate verification
enabled and maintain the deployment host's trusted CA package.
Codex execution
When enabled, POST /projects/{project}/codex/exec accepts a JSON object
containing only a nonempty prompt string and returns {"result":"…"}.
Use Authorization: Bearer <token> with a dedicated 32-byte random token
encoded as 64 hex characters. Ordinary OAuth sessions cannot execute Codex.
Missing, unknown and revoked tokens return 401. A valid token for a different
project returns 403. Requests are limited to 32 KiB, CLI output to 512 KiB,
and each execution to 90 seconds. The worker accepts one execution at a time
and returns 429 when busy.
On the deployment host, provision /opt/endoleap-infra/foundry/codex with:
grants/projects.jsoncontaining a JSON object mapping lowercase hex SHA-256 digests of the decoded token bytes to configured project names. Use mode 0700 for the directory and 0600 for the file, readable by UID 1000. Rotate or revoke tokens by atomically replacing the file within the mounted directory. A revocation applies to subsequent requests.homeowned by UID 1000 with an isolated Codex login inauth.json. Keep this home dedicated to the worker. Supply the login only and keep user configuration, MCP servers, skills, and other workload files out of it.
With that directory configured, the next normal Foundry deployment starts the
worker using the Linux binary built by ces-control and mounts the worker socket
and grants directory read-only. To replace an existing worker, stop and remove
its container, then run bash deploy/codex-worker.sh /absolute/path/to/foundry
with the desired ces-control build. The worker
runs the installed Codex CLI with fixed read-only, no-approval arguments and
disables command execution tools. It receives prompts via stdin and returns
the final agent message. The worker has no published port and Foundry receives
neither the Codex home nor the Docker socket. Keep both containers on the same
host for Unix socket access.
Board Canvas snapshots and slugs
Apply db/003-canvas.sql through the normal migration runner before starting this
version. GET /projects/{project}/workspaces/{workspace}/materials/{board}/canvas
returns {version, document} and a quoted ETag. An untouched board returns
version 1 and an empty schema-1 graph. PATCH takes {document: <full snapshot>}
and the quoted If-Match; it atomically replaces the snapshot and returns
{version} with the new ETag. Canvas and material metadata have separate
versions. GET uses existing material read permissions; PATCH requires editor or
admin. Missing versions return 428, stale versions 412 version_conflict, invalid
graphs 400 invalid_canvas, and bodies over 5 MiB (UTF-8 bytes) 413. PNG/JPEG/WebP/
GIF data URLs up to 2 MiB decoded and HTTPS image URLs are supported. Temporary
node selection/drag/measurement state and mirrored styles are discarded.
Workspace and material create/get/list responses include slug. Create and
metadata PATCH accept an optional slug (1–100 lowercase ASCII letters/digits,
with single hyphens between segments; UUID-form custom values are reserved).
Workspace slugs are unique within a project, material slugs within a workspace;
duplicates return 409. Defaults use the ASCII name/title and UUID, falling back
to a generic prefix for names without ASCII letters/digits. Existing records are
backfilled. Renaming leaves slugs stable unless explicitly supplied. Workspace
and material path parameters accept either ID or slug, with unchanged permissions.
Canvas uses its board material's slug; there is no separate Canvas identity.
Slugs enable frontend SEO URLs but do not make private content public or add
search-engine metadata. Changing a custom slug does not retain an old alias.
Frontend follow-up: allowlist the Canvas proxy route and set its body limit to at least 5 MiB; load the server snapshot, offer migration of old local drafts, serialize autosaves and handle 412 explicitly. Restore node style dimensions from width/height and render edges as Bézier curves. These frontend changes are outside this Core delivery; local drafts alone are not database persistence.