mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-05 02:45:28 +00:00
195 lines
13 KiB
Markdown
195 lines
13 KiB
Markdown
# Integrations
|
|
|
|
Last updated: dev@df2fad2 | 2026-07-12
|
|
|
|
## Scope
|
|
|
|
This spec covers external integration surfaces in:
|
|
|
|
- `routes/codex_routes.py`;
|
|
- `integrations/codex/*` and `integrations/claude/*`;
|
|
- `routes/api_token_routes.py` and bearer-token handling in `app.py`;
|
|
- `routes/auth_routes.py` integration CRUD/test routes;
|
|
- `src/integrations.py` and `data/integrations.json`;
|
|
- `routes/webhook_routes.py` and `src/webhook_manager.py`;
|
|
- task webhook generation/triggering in `routes/task_routes.py`, `app.py`, and `static/js/tasks.js`;
|
|
- companion/mobile pairing in `companion/routes.py` and `companion/pairing.py`;
|
|
- provider OAuth/device-flow endpoint links in `routes/copilot_routes.py`, `routes/chatgpt_subscription_routes.py`, `routes/device_flow.py`, and `ProviderAuthSession` rows;
|
|
- integration UI surfaces in `static/js/settings.js` and `static/js/admin.js`;
|
|
- database models `ApiToken` and `Webhook`.
|
|
|
|
The SQLAlchemy `Integration` model exists in `core/database.py`, but current Settings generic integration CRUD uses `src/integrations.py` and `data/integrations.json`.
|
|
|
|
## Scoped Agent Runtime
|
|
|
|
`/api/codex/*` is the canonical scoped HTTP surface for external coding agents. Claude Code uses the same runtime endpoints; `/api/claude/plugin.zip` only delivers the Claude skill bundle.
|
|
|
|
`routes.codex_routes` owns:
|
|
|
|
- `/api/codex/capabilities`;
|
|
- todos list/manage through `do_manage_notes()`;
|
|
- email list/read/draft/send;
|
|
- memory list/add/delete;
|
|
- calendar list/create/delete;
|
|
- document list/read/create/delete;
|
|
- Cookbook task/server/output/cached-model/preset/serve/adopt/stop controls.
|
|
|
|
`_scope_owner()` owns scope checks and token-owner resolution. `_as_owner()` temporarily runs borrowed route handlers as the scoped owner and restores request state afterward. Borrowed email, memory, calendar, and document route handlers own their domain behavior; Codex routes only adapt them behind scoped access.
|
|
|
|
Runtime behavior:
|
|
|
|
- missing scopes return 403;
|
|
- invalid payloads return 400;
|
|
- unavailable borrowed route surfaces return 503;
|
|
- capabilities expose scope-derived booleans and partial availability flags;
|
|
- email send and destructive actions remain described as confirmation-required behavior in bundled agent instructions.
|
|
- Cookbook adopt/stop paths validate stored remote SSH host and port before interpolating them into SSH commands.
|
|
|
|
The local integration skill/helper files require `ODYSSEUS_URL` and `ODYSSEUS_API_TOKEN`. They must use `/api/codex/*` and must not bypass Settings/token scopes through SSH, Docker, direct DB access, local files, MCP internals, or app imports. Helper scripts refuse non-`/api/codex/*` paths.
|
|
|
|
## Bundle Distribution
|
|
|
|
`/api/codex/plugin.zip` ships the Codex plugin tree from `integrations/codex/`. `/api/claude/plugin.zip` ships only the Claude `skills/` subtree from `integrations/claude/skills/`. These routes require an authenticated browser/user request and do not embed an API token.
|
|
|
|
Setup instructions are duplicated in integration READMEs and `static/js/settings.js`; they need to stay aligned with live route surfaces and `/api/codex/capabilities`.
|
|
|
|
## API Tokens
|
|
|
|
`routes.api_token_routes` owns token profiles, allowed scopes, scope normalization, token creation/update/revocation, and profile metadata shown in Settings. Partial updates preserve existing scopes unless new scopes are supplied, owner checks apply to update/delete, and write scopes auto-include their read scope where applicable.
|
|
|
|
`app.py` owns bearer-token validation. It accepts `Bearer ody_...`, checks a bcrypt hash through a prefix cache, updates `last_used_at` asynchronously, and stamps:
|
|
|
|
- `request.state.current_user = "api"`;
|
|
- `request.state.api_token = True`;
|
|
- `request.state.api_token_owner`;
|
|
- `request.state.api_token_scopes`.
|
|
|
|
The raw token is returned only on creation. Stored state is hash, prefix, owner, scopes, active flag, and timestamps. Token create/update/delete invalidates the auth middleware cache. Companion pairing also mints chat-scoped `ApiToken` rows and invalidates that cache.
|
|
|
|
Current API-token consumers include:
|
|
|
|
- `/api/codex/*` scoped agent routes;
|
|
- `/api/v1/chat` synchronous external chat;
|
|
- `/api/models` catalog reads for `chat`-scoped token owners;
|
|
- companion read endpoints;
|
|
- selected session and owner-attribution helpers described in `auth-security.md`.
|
|
|
|
The Cookbook scoped-agent surface currently exposes `cookbook:read` and `cookbook:launch` in Settings and checks them in Codex routes; those scope names must stay reconciled with `routes.api_token_routes.ALLOWED_SCOPES`.
|
|
|
|
## Generic API Integrations
|
|
|
|
`src.integrations` owns generic API integration presets, `data/integrations.json`, API-key encryption/decryption, secret masking, plaintext-key migration, enabled integration prompt text, and `execute_api_call()`.
|
|
|
|
`routes.auth_routes` owns admin-only HTTP CRUD/test routes for these integrations. Presets are public metadata. The ntfy test route is special: it publishes a real test notification to the configured reminder topic instead of only probing server health.
|
|
|
|
`api_call` is the agent/tool execution path for configured integrations. It is blocked for non-admin/public users by tool security, accepts only relative paths, uses the admin-configured base URL/auth settings, and returns truncated external responses to the model, including a sentinel when long JSON lists are shortened. Admin-authored integration descriptions are prompt context; external responses remain untrusted data.
|
|
|
|
`execute_api_call()` normalizes base URLs to HTTP(S) scheme, hostname, and
|
|
path-only values, rejects request paths that are not relative absolute paths
|
|
(`/...`) or that carry schemes/fragments, treats `/` as the base URL without
|
|
appending an extra slash, and checks the final URL through `src.url_safety`.
|
|
Link-local/metadata targets are always rejected; setting
|
|
`INTEGRATION_API_BLOCK_PRIVATE_IPS=true` also rejects loopback/RFC1918/private
|
|
addresses for operators who do not need LAN integrations.
|
|
|
|
Current call sites include:
|
|
|
|
- `src.agent_loop` injecting enabled integration descriptions;
|
|
- `src.tool_implementations.do_api_call()`;
|
|
- task scheduler discovery/check-ins;
|
|
- note reminder delivery through ntfy integrations and the generic webhook reminder channel.
|
|
|
|
## Webhooks And External Chat
|
|
|
|
Outgoing webhooks are admin-managed `Webhook` rows. `routes.webhook_routes` owns CRUD/test/toggle/delete and `/api/v1/chat`. `src.webhook_manager` owns allowed event validation, public URL validation, delivery-time URL revalidation, DNS-rebinding-safe pinned-IP delivery, HMAC signing, fire-and-forget delivery, in-flight task references, and delivery status/error persistence. Sanitized delivery errors redact IPv6-style address details.
|
|
|
|
Allowed outgoing events are:
|
|
|
|
- `session.created`;
|
|
- `chat.message`;
|
|
- `chat.completed`;
|
|
- `webhook.test`.
|
|
|
|
Current webhook event emitters include session creation, chat message/completion paths, and `/api/v1/chat` completion.
|
|
|
|
`/api/v1/chat` is an inbound external chat endpoint. It requires a `chat` API token, checks session ownership before resume, can create a session from a direct API key, and otherwise falls back to the first owner-visible enabled model endpoint. Token-supplied direct `base_url` values use public-URL validation; configured endpoints remain admin-trusted. Logs and delivery/error text that include endpoint URLs should pass through URL redaction helpers before persistence or diagnostics.
|
|
|
|
## Task Webhooks And Event Triggers
|
|
|
|
Task webhook triggers are separate inbound webhooks. `app.py` exempts only `/api/tasks/{task_id}/webhook/{token}` from normal auth so external callers can trigger tasks without cookies. `routes.task_routes` owns token generation/regeneration and validates task id, token, and active status before queueing a run.
|
|
|
|
`static/js/tasks.js` displays the live task webhook URL. `scripts/odysseus-webhook` is stale against this route shape and is not authoritative.
|
|
|
|
Event-triggered tasks use `src.event_bus`; task execution and scheduling ownership lives in `calendar-tasks-notes.md`.
|
|
|
|
## Companion Pairing
|
|
|
|
`companion.routes` owns companion/mobile HTTP routes:
|
|
|
|
- `/api/companion/ping`;
|
|
- `/api/companion/info`;
|
|
- `/api/companion/models`;
|
|
- `/api/companion/pair`.
|
|
|
|
Read endpoints accept session or bearer-token callers and resolve the effective owner for visible rows. Model responses omit API keys. Pairing `GET` renders the admin form; pairing `POST` is admin-cookie only, mints a normal chat-scoped API token, invalidates the auth token cache, and returns a host/port/token payload as HTML or JSON.
|
|
|
|
`companion.pairing` owns LAN host detection, pairing payload shape, token minting, and optional QR generation. QR rendering depends on optional `qrcode`; if unavailable or failing, pairing still returns the text payload.
|
|
|
|
## Unified Settings Surface
|
|
|
|
The Settings Integrations view aggregates several subsystem surfaces:
|
|
|
|
- generic API integrations;
|
|
- Codex/Claude agent token setup;
|
|
- CalDAV, CardDAV, email accounts including Google Workspace/.edu OAuth connect flows, MCP/OAuth links, provider device-flow links, and agent tokens.
|
|
- provider-auth backed model endpoints such as ChatGPT Subscription and Copilot, where device-flow credentials live in provider auth rows rather than endpoint API-key fields.
|
|
|
|
Vault and companion/mobile setup are separate settings/route surfaces today, not entries in the unified add-integration list.
|
|
|
|
This spec owns the cross-integration framing and agent/token/webhook surfaces. Domain internals stay with their subsystem specs: calendar, email/contacts, shell-MCP, vault/auth, and settings-admin.
|
|
|
|
## Degraded And Compatibility Behavior
|
|
|
|
- 403 from scoped APIs means a settings/scope restriction.
|
|
- 503 from Codex borrowed routes means the domain route surface is unavailable.
|
|
- Missing or corrupt `data/integrations.json` loads as an empty list; non-object rows are ignored.
|
|
- Plaintext generic integration API keys migrate to encrypted storage on load.
|
|
- Webhook delivery has no retry/backoff queue; the persisted state is last status or sanitized last error.
|
|
- Webhook URLs are validated at create and delivery time, redirects are disabled,
|
|
and delivery connects to the IP set validated immediately before the request.
|
|
- Companion LAN detection is best-effort and falls back to local host/port defaults.
|
|
- `ODYSSEUS_URL` must be reachable from the external coding agent; no Docker/native URL rewrite is performed.
|
|
|
|
## Security And Provenance
|
|
|
|
- API-token routes must either enforce a relevant scope or document an explicit exception.
|
|
- Codex/Claude plugin zips must not expose secrets beyond source instructions and helper files.
|
|
- Webhook list responses expose `has_secret`, not the secret value.
|
|
- Webhook secrets are encrypted when an API key manager is available; plaintext fallback is legacy/degraded behavior.
|
|
- Outgoing webhook signatures use `X-Odysseus-Signature`.
|
|
- Generic integration API keys are encrypted at rest and masked in API responses.
|
|
- Generic integration base URLs are admin-configured and not the same public-only policy as webhook URLs.
|
|
- `api_call` output and remote integration responses are untrusted model context.
|
|
- Pairing payloads expose the raw chat token once through HTML/JSON/QR; persisted token storage is hash/prefix only.
|
|
|
|
## Testing Notes
|
|
|
|
Current targeted coverage includes API-token CRUD basics, chat-scoped `/api/models` token access, companion pairing/read-only owner scoping, webhook SSRF validation, webhook auth-exempt source checks, webhook CLI token masking, integration-store shape/encryption migration, Google email OAuth route/helper behavior, Cookbook API-token scopes, Cookbook adopt SSH host validation, and `/api/v1/chat` base-url/fallback owner scoping.
|
|
|
|
The integration audit also ran the targeted venv subset covering those areas with 52 passing tests and one warning.
|
|
|
|
## Current Gaps
|
|
|
|
- Codex/Claude scoped routes, owner restoration, degraded 503 behavior, plugin zip contents, and helper-script path refusal need focused regression tests.
|
|
- Token profile/update behavior and Settings agent-token scope toggles need direct coverage.
|
|
- Codex Cookbook scopes need continued Settings, route-check, and `ALLOWED_SCOPES` regression coverage.
|
|
- Generic integration HTTP CRUD/test routes, `execute_api_call()` auth modes, response shaping, and frontend Settings/Admin flows need direct coverage.
|
|
- `do_manage_tokens()` does not match `/api/tokens` semantics for `ody_` prefix, owner, scopes, and cache invalidation.
|
|
- `do_manage_webhooks()` bypasses route behavior and does not cover signing-secret parity.
|
|
- Companion read endpoints should either require `chat` scope or be documented as an explicit scope-policy exception.
|
|
- Decide whether webhook secret plaintext fallback should remain accepted when the API key manager is unavailable.
|
|
- Decide whether generic integration base URLs should stay LAN-capable by
|
|
default or make `INTEGRATION_API_BLOCK_PRIVATE_IPS=true` the default.
|
|
- Decide whether admin-authored integration descriptions and `api_call` results need a shared untrusted-context wrapper.
|
|
- The dormant SQLAlchemy `Integration` model should be removed, migrated into use, or documented as legacy.
|
|
- `scripts/odysseus-webhook` still emits the removed `/api/webhook/{token}` path.
|