# Callback response page

The HTML the browser renders after Keycloak redirects to the loopback URL, produced by `src/oauth/renderHTML.ts`.

## Approach

Visually match Deque's billing-service frontend (palette, typography, centered layout) so developers recognise the page as Deque-owned, but ship no external assets — everything is inline. The logo lives at `assets/logo.png` and is embedded as a base64 data URI via the generated `src/oauth/logo.generated.ts`; regenerate with `node scripts/encode-logo.js` after replacing the PNG.

## Security considerations

**No external stylesheets or images.** billing-service pulls Roboto from Google Fonts. The callback page deliberately does not. Loading any external resource from `http://127.0.0.1:<port>/callback?code=...&state=...` causes the browser to send a `Referer` header containing the full callback URL — leaking the auth code to the third-party origin. A `<meta name="referrer" content="no-referrer">` would suppress the header, but relying on it is brittle; avoiding external references entirely is defense in depth. The `Roboto → Helvetica → Arial` fallback renders indistinguishably on developer machines.

**Content Security Policy.** Every response sets:

```
Content-Security-Policy: default-src 'none'; img-src data:; style-src 'sha256-<digest>'
X-Content-Type-Options: nosniff
```

`default-src 'none'` blocks everything by default. `img-src data:` allows only the inlined logo. `style-src 'sha256-...'` allows only a `<style>` block whose contents match a digest computed at module load — stricter than `'unsafe-inline'`, and any drift between the rendered CSS and the committed hash causes the browser to refuse the stylesheet. Inline `style=""` attributes are avoided entirely (they require separate `style-src-attr` / `'unsafe-hashes'` machinery). The `renderHTML` test suite asserts the hash matches the rendered `<style>` contents to prevent silent drift.

**Auth code not echoed.** The success page deliberately does not render the received `code` in the HTML (RFC 8252 §8.1 interception mitigation).

**XSS escape.** The only untrusted input rendered into the page is `error_description` from the IdP. It is HTML-escaped before interpolation; a regression test feeds `<script>alert(1)</script>` and asserts the escaped form.
