A small AWS Lambda that sits between the OpenChat frontend and the Google Translate API. It exists so that:
- the Google API key never ships in the client bundle
- access is enforced server-side via an OpenChat-issued JWT (diamond members and platform moderators only)
- translations are cached, so repeated text is only ever paid for once
- usage is rate-limited per user with a global daily spend kill switch
POST /translate
Headers:
x-auth-jwt: <JWT issued by the OpenChat local_user_index access_token_v2 endpoint,
claim_type "Translate", ES256, claims { user_id, exp }>
Body:
{ "texts": ["Bonjour", ...], "target": "en" } # 1-128 texts, <= 20k chars total
200 -> { "translations": ["Hello", ...] } # same order as texts
400 -> invalid body / target locale
401 -> missing/invalid/expired token (client should refetch its token and retry once)
413 -> request too large
429 -> per-user rate limit hit
502 -> Google call failed
503 -> global daily budget exhausted
- Auth: verifies the ES256 signature against the OpenChat public key
(mirrors the
jwtlibrary in the open-chat repo), checksclaim_typeand expiry. Theuser_idclaim keys the rate limits. - Cache: DynamoDB, keyed on
sha256(target, text)— content-addressed, so edited/deleted messages need no invalidation and identical text dedupes globally. Entries expire after 30 days via DynamoDB TTL. - Rate limits (DynamoDB atomic counters, all configurable via template
parameters):
- per-user requests/minute (default 30)
- per-user translated chars/day, cache misses only (default 100k)
- global chars/day kill switch (default 2.5M ≈ $50/day at Google's $20/M)
Prerequisites: sam CLI, cargo-lambda, an AWS profile.
-
Store the Google API key (restrict it to the Cloud Translation API in the Google console first):
aws ssm put-parameter \ --name /openchat/translation-proxy/google-api-key \ --type SecureString \ --value <the-key> -
Get the OpenChat public key from the user_index canister metrics (
oc_public_key). -
Build and deploy:
sam build sam deploy --guided # first time; pass OcPublicKeyPem when promptedGotcha: when passing the PEM via
--parameter-overrides, sam splits on whitespace even inside a shell-quoted string, silently truncating the value at the first space. Wrap the value in embedded double quotes and escape the newlines:sam deploy ... --parameter-overrides 'OcPublicKeyPem="-----BEGIN PUBLIC KEY-----\nMFkw...\n-----END PUBLIC KEY-----"'To test against a local OpenChat replica, deploy with the local user_index's
oc_public_keyinstead, then restore the mainnet key afterwards (same command, different PEM). -
The stack outputs
FunctionUrl— setOC_TRANSLATE_PROXY_URLto it in the OpenChat frontend.env(and theGH_TRANSLATE_PROXY_URLsecret for the android release workflow).
- The Lambda has no fixed egress IP, so the Google key can't be IP-restricted without adding a VPC + NAT gateway. API-restricting the key (Cloud Translation only) plus the rate limits above is the v1 posture; revisit if the key ever leaks.
- Old Google API keys baked into previously shipped OpenChat client bundles must be revoked once this proxy is live.
- There is no per-IP pre-auth rate limit in the Lambda itself (an invocation has already happened by the time we could check). If unauthenticated junk traffic ever becomes a cost problem, put AWS WAF or CloudFront in front of the function URL.