GLOW Companion (optional, accessible front door)
The companion is the optional Flask service that sits at the edges of the Hybrid architecture in golden.md (Option C) and SPEC.md section 11. It makes a good thing nicer; it never makes a working thing necessary.
If this service is ever down, learners can still register and complete the entire workshop on GitHub through the issue-form front door, and facilitators can still read the admin-issue dashboard. The companion is non-critical by design.
What it provides
- An accessible registration landing page that writes to the owned roster.
- A facilitator cohort dashboard that reads the owned roster and derived progress.
- A redacted
roster.jsonendpoint for facilitator tooling.
What it deliberately does not do
- It does not invent a parallel store of record. The only state it touches is the owned roster JSON, through the same contract the provisioning subsystem uses.
- It does not provision repositories directly. Provisioning is the GitHub-native subsystem's job (see ../admin/OWNED_PROVISIONING.md).
Accessibility (WCAG 2.2 AA)
Every template is authored screen-reader-first and keyboard-operable:
- Semantic landmarks (
header,nav,main,footer) and a visible skip link. - Correct heading order and labeled form controls with hint and error associations
via
aria-describedbyandaria-invalid. - An error summary with in-page links to each invalid field.
- Live-region announcements for flash messages (
role="status",aria-live). - Visible focus styles and no keyboard traps.
- No information conveyed by color alone; no emoji.
- A data table with a
caption,scope-d headers, and an intro sentence.
Run a manual screen reader pass (NVDA, JAWS, VoiceOver) before any cohort, per the project accessibility testing guidance.
Security (OWASP-aware)
- Output is auto-escaped by Jinja2.
- Per-session CSRF tokens protect every state-changing POST.
- Facilitator actions require sign-in; the token is compared in constant time.
- Input is validated (GitHub handle regex, cohort, path enum) before any write.
- Best-effort per-IP rate limiting on registration and sign-in.
- Strict security headers: a restrictive
Content-Security-Policy(no inline scripts),X-Content-Type-Options,X-Frame-Options, andReferrer-Policy. - Secrets come only from the environment; cookies are
HttpOnly,SameSite=Lax, andSecureby default.
Configuration
All configuration is environment-driven:
| Variable | Purpose | Default |
|---|---|---|
COMPANION_SECRET_KEY |
Flask session signing key | random per process (set this in production) |
COMPANION_ROSTER_PATH |
Path to the owned roster.json |
roster.json |
COMPANION_FACILITATOR_TOKEN |
Facilitator sign-in token | empty (dashboard locked until set) |
COMPANION_SECURE_COOKIES |
Set 0 only for local HTTP testing |
1 |
COMPANION_RATE_LIMIT_MAX |
Requests per window per IP | 10 |
COMPANION_RATE_LIMIT_WINDOW |
Rate-limit window in seconds | 300 |
PORT |
Port for the built-in dev server | 5000 |
Run locally
cd companion
python -m venv .venv
.venv/bin/pip install -r requirements.txt # Windows: .venv\Scripts\pip install -r requirements.txt
export COMPANION_SECRET_KEY="dev-only-not-a-real-secret"
export COMPANION_FACILITATOR_TOKEN="dev-token"
export COMPANION_SECURE_COOKIES=0
export COMPANION_ROSTER_PATH="../admin/examples/roster.example.json"
.venv/bin/python app.py
# Visit http://127.0.0.1:5000/
For production, serve app:app behind a WSGI server (for example gunicorn) and a
TLS-terminating reverse proxy, set a strong COMPANION_SECRET_KEY, and point
COMPANION_ROSTER_PATH at a working copy of the admin roster that a separate job
commits back to the admin repository.
Test
cd companion
.venv/bin/python -m unittest discover -s tests
The suite covers routing, CSRF enforcement, input validation, facilitator auth, rate limiting, roster redaction, and the accessible page shell.
Routes
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | / |
none | Redirects to /register |
| GET, POST | /register |
none | Accessible registration form; writes the roster |
| GET | /register/success |
none | Confirmation and next steps |
| GET, POST | /login |
none | Facilitator sign-in |
| POST | /logout |
session | Sign out |
| GET | /dashboard |
facilitator | Cohort dashboard |
| GET | /roster.json |
facilitator | Redacted roster JSON |
| GET | /healthz |
none | Liveness probe |