# CORS and origins

Which origins may call the Filudi API from a browser and frame a game, how a site's domains join that list, and what a game origin may do.

A browser call reaches the API only from an origin the API allows. The same list is the `frame-ancestors` of
every game bundle: an origin that may call the API may frame a game, and no other.

## The list

The allowed origins are the union of:

- `https://<domain>` for every entry of `domains` of every approved site. A pending site has no origin, and a
  proposed domain (`pendingDomains`) joins the list once staff approves it.
- The hub origins the API is started with (`HUB_URLS`): filudi.com, the playground and the local ports of the
  monorepo, `http://localhost:4321` and `http://localhost:5173`.
- The console origins (`CONSOLE_URLS`), for `/v1/console/*` and `/v1/admin/*` only, with credentials.

Each API pod reloads the site domains every 60 seconds. A domain approved in the back office works within a
minute, on every pod, without a redeploy.

`localhost` is never a site domain. For local work, run the API from the monorepo, which allows the two local
ports above, or add your port to `HUB_URLS` on that API.

## Credentials and the console

`/v1/console/*` and `/v1/admin/*` answer the console origins with `Access-Control-Allow-Credentials: true`,
so the `filudi_console` cookie travels with `credentials: 'include'`. Every non-GET request there must carry an
`Origin` from that list; otherwise 403 `forbidden`. A browser sets `Origin` on its own. A `curl` adds it by hand.

## Game origins

Each live game runs on its own origin, `https://<slug>.<play domain>`. That origin gets CORS on the five
routes a game token opens (`GET /v1/sdk/players/me`, `/v1/sdk/saves/{gameId}`, `POST /v1/sdk/sessions`,
`/v1/sdk/leaderboards/{gameId}/*`, `POST /v1/sdk/events`) and on nothing else. A game cannot read a fresh
guest token, a sign-in answer, a rating or a feedback route from its frame.

A bundle answers with a Content Security Policy that lets it fetch its own origin and the API only, with no
remote script and no frame. `frame-ancestors` lists the allowed origins, so your page frames a game only once
your domain is approved.

## Server routes

`/v1/server/*` sends no CORS headers at all. A browser cannot call it, which is the point: the server key stays
in your backend. Call these routes from your servers and hand the browser a player token
(`POST /v1/server/players/{playerId}/tokens`) or a game token (`POST /v1/server/game-tokens`) over your own
authenticated channel.

## Your page's policy

Your pages send a CSP of their own. The one that lets a frame load and the page call the API:

```
frame-src https://play.filudi.com https://*.<play domain>
connect-src 'self' https://api.filudi.com
img-src 'self' data: https://play.filudi.com
media-src https://play.filudi.com
```

Never serve a bundle from your own origin: the frame has `allow-same-origin`, so a bundle on your origin could
read your `localStorage`, where the player's token sits. `GameFrame` throws on a bundle from the page's origin.
