Skip to content

Latest commit

 

History

History
127 lines (106 loc) · 6.61 KB

internal-api.md

File metadata and controls

127 lines (106 loc) · 6.61 KB

Internal configuration API v1

Base URL: https://api-staging.eewbot.net. The read endpoints below are GET only, have no request body or query parameters, and require Cloudflare Access Service Token authentication. This is an internal contract for a future Java RemoteDestinationConfigSource / RevisionPoller; Java is unchanged in this phase.

Authentication and boundary

The client sends CF-Access-Client-Id and CF-Access-Client-Secret to Cloudflare Access over HTTPS. Access authenticates the token against a Service Auth policy that includes the existing common Core token (now authorized for both reads and cleanup writes). See setup in the README.

The Hono middleware reads the original runtime c.executionCtx.access using the installed Workers ExecutionContext['access'] type. Hono's portable type lacks this property, so a local intersection narrows that type without manufacturing context or copying data from HTTP headers. The middleware requires a nonblank ACCESS_AUD, an Access context, and strict equality of access.aud to that AUD. Missing context (including Hono calls with no ExecutionContext) fails closed. No JWT decoding, custom client-secret comparison or header-based identity is used.

The entire /internal subtree, its parent path, unknown versions/paths and all methods pass through authentication before routing or DB binding access. Authenticated non-GET read requests return 405 and Allow: GET; unknown GET paths return 404. The cleanup writer uses the same native Access middleware, application/AUD and Service Token. That common token can read and perform cleanup. Authentication is separate from routing so a future deployment can introduce distinct writer authorization. There are no test-only production routes, CORS grants, Cache API calls or request/snapshot debug logs.

Every Worker response in this subtree, including errors, carries Cache-Control: no-store. Do not cache snapshots in clients, proxies or Cloudflare Cache Rules, or log bodies/headers. Snapshots contain Discord webhook credentials. Edge responses generated by Cloudflare Access have Cloudflare's own status, format and headers; they may be redirects or 401/403 before Worker execution.

Requests and JSON responses

Request Success JSON
GET /internal/v1/meta {"apiVersion":1,"capabilities":["configRevision","deliverySnapshot"]}
GET /internal/v1/config/revision {"revision":0} (empty initial DB)
GET /internal/v1/config/snapshot {"revision":0,"destinations":[]} (empty initial DB)

apiVersion is the HTTP protocol version. It is independent of SQL migration versions, application versions and the changing configuration revision. Capabilities list only implemented operations. All success and Worker error bodies are JSON, except HEAD responses which have no body.

The revision is the existing D1 monotonically increasing configuration counter. It is a JSON integer, initially zero; transactional store mutations advance it, and read-only HTTP requests never change it. Revision is neither a timestamp nor a migration number. Store-level no-op updates need not advance it (see the D1 storage contract).

The snapshot directly uses the existing DestinationSnapshot and Destination types in src/db/destination-types.ts. Each destination contains all fields:

Field(s) JSON type / meaning
targetId, channelId Positive unsigned 64-bit Snowflake as canonical decimal string
threadId, guildId Snowflake string or explicit null
eewAlert, eewPrediction, eewDecimation, quakeInfo, tsunami JSON boolean
minIntensity Java numeric code: 0 unknown, 1–4 intensity 1–4, 5 intensity 5-lower, 6 intensity 5-upper, 7 intensity 6-lower, 8 intensity 6-upper, 9 intensity 7
lang Stored language string, e.g. ja_jp or en_us
webhookUrl Exact full stored URL, including its token/query, or explicit null
webhookId Snowflake string parsed by the existing store, or explicit null

Synthetic example (no real credentials):

{
  "revision": 1,
  "destinations": [{
    "targetId": "1234567890123456791",
    "channelId": "1234567890123456790",
    "threadId": "1234567890123456791",
    "guildId": "1234567890123456789",
    "eewAlert": true,
    "eewPrediction": false,
    "eewDecimation": true,
    "quakeInfo": false,
    "tsunami": true,
    "minIntensity": 4,
    "lang": "en_us",
    "webhookUrl": "https://discord.com/api/webhooks/1234567890123456792/synthetic-token",
    "webhookId": "1234567890123456792"
  }]
}

Consistency and future Core client

The API calls DestinationConfigSource.getRevision() / loadSnapshot() on D1DestinationStore. Handlers execute no SQL and do not change the store, schema or migrations. loadSnapshot() reads revision and destinations in one SQLite statement, so an empty or populated snapshot represents one committed DB state. Sorting is the store's target ID text order; clients should index by targetId.

A future Core client should discover protocol/capabilities, poll revision, and load a complete snapshot after detecting a change. Use the snapshot's own revision when replacing the in-memory configuration atomically: a preceding revision read may observe an earlier state. Keep Snowflakes as strings; never parse them as floating-point JSON numbers. Preserve nulls, booleans, language and Java intensity codes. Keep full webhook URLs confidential in memory. On any authentication/service/parse failure, do not replace a last known good snapshot with empty configuration; retry with backoff and report a sanitized error.

Worker errors

HTTP status JSON error Meaning
403 access_required No trusted native Access context
403 access_forbidden Context AUD differs from the configured application
503 access_not_configured Expected AUD absent or blank; all internal requests denied
503 storage_unavailable DB binding missing or storage/handler failure
405 method_not_allowed Authenticated request uses a method other than GET
404 not_found Authenticated GET targets an unknown internal path

Example: {"error":"storage_unavailable"}. Errors do not include exception details, SQL, JWTs, client secrets, snapshot data or webhook URLs. Native context and AUD checks happen before DB access. Meta requires authentication but no DB.

Production has neither ACCESS_AUD nor a DB binding added by this change, so its internal API stays closed. /health, /interactions and the Java Core continue using their existing behavior.