With DEBUG=0 and Kavenegar unconfigured, POST /api/auth/otp/request returns a
hard 503, so the only way to sign in was for someone with admin access to hand-
create an OTPCode row. That made every frontend developer's login depend on one
person being available.
TEST_LOGIN_PHONES is the pattern real apps use for exactly this (Firebase Auth's
fictional numbers, Apple's App Store demo account): a short allowlist of
phone:code pairs that skip SMS delivery entirely. Unlike DEV_MASTER_OTP it is
not "any phone with one code" and it is not tied to DEBUG -- each number carries
its own code, and everything else takes the normal OTP path untouched.
The code never appears in an API response, so knowing the number is not enough
to log in, and the existing per-phone login attempt limit still applies, so the
code cannot be brute-forced. Malformed configuration raises ImproperlyConfigured
at startup rather than being skipped silently, and /api/health reports how many
test numbers are open (count only) so they cannot be forgotten on a server that
has real users.
The code is capped at six digits because LoginRequestSerializer caps otp at
max_length=6; a longer code would have been rejected before reaching the check
and looked like the feature was broken.
The guest path the frontend uses today is untouched, and leaving the variable
empty reproduces the previous behaviour exactly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
خطاهای این پروژه خاموشاند: نامِ اشتباهِ collection یا فیلترِ نخورده نتیجهی
خالی میدهد نه استثنا، و دستیار فقط بدتر جواب میدهد. این تستها نگهبانِ
همانجاهایند.
TextbookLayoutTests بررسی میکند رشتهی هر فصل بینِ ingest و retrieval یکی
باشد و بازهی صفحهها پیوسته بماند. IngestBookResumeTests مطمئن میشود
صفحهی بیمسئله دوبار پول نمیگیرد، صفحهی خطادار در اجرای بعد دوباره
سراغش میروند، و upsertِ شکستخورده rollback میشود.
LessonContextResolutionTests نگهبانِ نرمالسازیِ «پایه نهم» → «ریاضی نهم»
است و اینکه مسیرِ gradeId و مسیرِ lessonId به یک collection برسند.
GradeAwarePromptTests جلوی برگشتنِ لحنِ دبستانی برای پایههای بالا را
میگیرد. FollowUpStaysGroundedTests پیامِ دومِ یک گفتگو را هم مستند نگه
میدارد.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TadrisinoTokenAuthentication ran one SELECT on accounts_user for every
authenticated request. Off by default (AUTH_USER_CACHE_TTL=0) so behaviour
is unchanged until a deployment opts in.
accounts/authentication.py
- _get_request_user(): when AUTH_USER_CACHE_TTL>0, cache all concrete
field values under auth:user:<id> and rebuild a full User instance
(so save(update_fields=[...]) stays safe); otherwise load from DB as
before. invalidate_user_cache() / _load_user_or_fail() helpers.
accounts/receivers.py (new)
- post_save/post_delete on User -> invalidate_user_cache(pk); covers
every in-app write path (SetUserGrade, login, refresh, last_activity).
accounts/apps.py
- AccountsConfig.ready() imports receivers.
accounts/tests.py
- AuthUserRowCacheTests: disabled -> loads every request; enabled ->
loads once then cache; User.save() invalidates; SetUserGradeView still
persists and the next request sees the new grade.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>