From 3b16ff5046934467ecfa3d2b1f793e36ed3728d3 Mon Sep 17 00:00:00 2001 From: wilson Date: Tue, 14 Apr 2026 10:18:52 +0100 Subject: [PATCH] docs: [api] Add the 'todo-later.md' document to keep track of things we want to do, but later --- api/todo-later.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 api/todo-later.md diff --git a/api/todo-later.md b/api/todo-later.md new file mode 100644 index 0000000..3233697 --- /dev/null +++ b/api/todo-later.md @@ -0,0 +1,56 @@ +# 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"] +} +```