# TODO (but later) - Tasks to get round to The following are suggested improvements to the system which require more thought or triage. Do NOT start work on these. ## User onboarding flow **4. Add `onboarding_complete` state to the user or derive it.** After verifying their email, the user still needs to add at least one `LearnableLanguage` before the app can serve them content. There are two options: (a) add a boolean `onboarding_complete` column to `users` that is set when the first language is added, or (b) derive it at runtime (`len(learnable_languages) > 0`). Option (b) is simpler and avoids a migration, but it makes the concept implicit. Either way, the BFF `/user_profile` response must expose this state so the frontend knows which screen to show. **5. Protected routes must enforce both `is_email_verified` and onboarding completion.** Currently `verify_token` in `app/auth.py` only checks the JWT signature and expiry. Routes like `/vocab`, `/flashcards`, and `/bff/articles` should not be reachable by a user who has not verified their email or not completed onboarding. Add a `require_onboarding_complete` dependency (similar to the existing `require_admin`) that does a lightweight DB lookup and returns `403` with a structured error body (`{"code": "EMAIL_NOT_VERIFIED"}` / `{"code": "ONBOARDING_INCOMPLETE"}`) so the frontend can route appropriately. **6. Add a `POST /auth/resend-verification` endpoint.** Users frequently miss or lose the initial verification email. Without a resend endpoint they are permanently locked out if the first email is lost. This endpoint should be unauthenticated (the user has no token yet), accept `{"email": "..."}`, and always return `200` regardless of whether the email exists (to avoid user enumeration). Rate-limit it tightly (e.g. 3 requests per hour per IP). **7. Add password strength validation at registration.** `POST /auth/register` currently accepts any non-empty string. Add a minimum length (12 characters is a reasonable baseline) at the Pydantic model layer in `app/routers/auth.py`. This is one line using a `@field_validator` and is much cheaper to add now than after users have weak passwords in the DB. **8. Rate-limit the `register` and `login` endpoints.** Neither endpoint has any rate limiting. `login` in particular is vulnerable to credential-stuffing. Add `slowapi` (already commonly paired with FastAPI) and apply per-IP limits: e.g. 5 requests/minute on `login`, 10 requests/hour on `register`. ## Enqueued or stashed articles Just because a `TranslatedArticle` exists doesn't mean it should be accessible to every language learner learning that language. One article could be accessible to many language learners. Not every language learner could "earn" access to that article at the same time. E.g. consider that we ran a three-piece non-fiction evergreen set of articles on the history of the croissant, for French learners. We might have several versions of this series, one at each proficiency level - and a learner who has both B1, B2 selected shouldn't receive both. To model this we might have, a data structure that looks like: ```json // This is extremely speculative, purely illustrative { "user_id": "alice-user-uuid", "article_id": "croissant-piece-part-2-uuid", "available_after": "2026-04-01:00:00Z" } ``` We may want to release the first piece to the user no the day they sign up, then a further piece in the 24h after that. Similarly, we may generate news summarise a day ahead, to be released the following morning at 7am. That data might look like: ```json { "article_id": "mondays-french-briefing-uuid", "available_after": "2026-04-01T07:00Z", "for_language": "fr", "for_proficiencies": ["B1"] } ```