Swap the Bootstrap CDN for a self-hosted Tailwind CSS build, mirroring
the myice setup: standalone Tailwind CLI v3.4.17, a one-off build-css.sh
script, and a gitignored generated style.css.
- Add tailwind.config.js, style-input.css, build-css.sh at repo root
- Rewrite templates/index.html: drop Bootstrap link/script, link
/style.css, convert all classes to Tailwind utilities; showAlert now
uses a class lookup so the JIT scanner detects dynamic classes
- Add /style.css FileResponse route in main.py; drop cdn.jsdelivr.net
from script-src/style-src in the CSP
- Flip test assertion to assert cdn.jsdelivr.net is absent from CSP
The generated style.css and tailwindcss binary are gitignored; run
./build-css.sh to regenerate (or --watch for dev).
The OIDC implicit-flow authorize request carried no state parameter and
the nonce was generated with weak Math.random() and never validated
client- or server-side, enabling login CSRF / forced authentication /
replay (AUTHZ-VULN-05).
Generate state and nonce with the Web Crypto CSPRNG, send state in the
authorize request, and validate both state and the id_token nonce claim
in the callback before storing tokens. Forward the nonce to /validate-
token so the backend also binds the id_token to the login flow.
Fixes the forged-callback fragment attack where a planted id_token with
a mismatched nonce was accepted, stored, and used by the SPA.