56 lines
3.7 KiB
Markdown
56 lines
3.7 KiB
Markdown
# 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"]
|
|
}
|
|
```
|