fix(auth): tell the user when the number is not registered (D28)
Reported by the owner: a number that is not in the system produced no error at all. The app advanced to the code box and then nothing happened, and there was no way to tell apart "the SMS is late", "the gateway is down" and "this number was never registered" — the only one of the three the person can actually do something about. Step 1 now says which it is: - unregistered number -> 404 «این شماره در سیستم ثبت نشده است…» - SMS send failed -> 503 «ارسال پیام کوتاه … ممکن نیست…» - sales expert, account unusable -> 403, its own message and both apps surface the server's Persian message for a 404 exactly as they already did for a 429. Without that second half the person would still have seen the generic «خطا در ارسال کد», which is the confusion being removed. This restores the reference's behaviour (firstOrFail() in ApiMobileController::userAuth:43 returns 404); the opaque answer was our deviation, not this. The anti-enumeration protection it was there for is deliberately reduced at step 1 on the owner's instruction — the cost was being paid by real users and by support. Still standing: the per-mobile and per-IP send throttles, which the reference never had, so enumeration is rate-bounded rather than free, and verifyOtp keeps one message for every failure reason. ISSUES.md SEC-03's "no enumeration" claim is corrected rather than left to go stale. Two more instances of the same shape turned up while fixing it: A failed SMS send also returned success, because SmsService never throws (D20), so a dead gateway produced the identical dead end. D20 exists so a failed SMS cannot break an operation — an order must still be placed — but in a login the SMS IS the operation, so it now fails loudly. The local-dev path keys off SmsService's own `sms-not-configured` result rather than re-testing the config: with D26's default relay driver, smsConfigured() is true on any machine with an external IPv4, so a config test would have called a dev box configured and then failed its login. The new test caught precisely that on its first run. Three states return no trading account for a sales expert who does exist. Under the new 404 each would have claimed the number was not registered — false, and it would send support hunting a registration problem that is not there. Also corrects both apps' LoginView header comment, which still described this flow as stubbed: no SMS provider, any 4-digit code accepted, step 1 matching the customer list in the client (that was the login leak, D8). All three had been untrue for weeks and the stale text misled a reader during this session. requestOtp had NO test coverage: the 28-test customer-auth suite stayed green through a change to its response for every unregistered number, so its greenness was not treated as evidence. request-otp.spec.ts adds 9 cases, including that the dev fallback does not apply in production. api: 295/295 tests, 27 suites, typecheck clean; both apps vue-tsc clean. اپ: اگر شماره شما در سیستم ثبت نشده باشد، همان را میگوید و دیگر بیدلیل به صفحه کد نمیرود. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
89e2d7937e
commit
c518f9995b
@@ -1418,3 +1418,36 @@ and product lists and a live balance on the app profile, all working whenever
|
||||
a provider is connected regardless of the feature switches; and the order
|
||||
status that posts the temporary document (پیشفاکتور) is an admin-chosen rule
|
||||
(`documents.triggerStatus`, default «تحویل مسئول فروش»).
|
||||
|
||||
---
|
||||
|
||||
## D28 — ورود اپ باید خطای واقعی را بگوید (۲۰۲۶/۱۰/۱۲)
|
||||
|
||||
**گزارش مالک:** «اگر کاربری در اپ نبود خطا نمیدادی کاربر یافت نشد میبردیش توی
|
||||
صفحه ارسال کد و واقعا نمیشد فهمید خطا از پروژه هست یا نه پیامک نمیاد یا نه واقعا
|
||||
نیست میخوام خطا بدی کاربر یافت نشد»
|
||||
|
||||
**تصمیم:** مرحله اول ورود باید علت را بگوید.
|
||||
|
||||
| حالت | قبل | بعد |
|
||||
|---|---|---|
|
||||
| شماره ثبت نشده | `{ sent: true }` — کاربر به صفحه کد میرفت | **۴۰۴** «این شماره در سیستم ثبت نشده است…» |
|
||||
| پیامک فرستاده نشد (درگاه خراب) | `{ sent: true }` | **۵۰۳** «ارسال پیام کوتاه در حال حاضر ممکن نیست…» |
|
||||
| کارشناس فروش با حساب معاملاتی غیرفعال | `{ sent: true }` | **۴۰۳** «حساب شما غیرفعال است…» |
|
||||
| کارشناس فروش بدون پروفایل | `{ sent: true }` | **۴۰۳** «حساب شما کامل نیست…» |
|
||||
|
||||
**نسبت با مرجع:** این همان کاری است که مرجع میکرد (`firstOrFail()` در
|
||||
`ApiMobileController::userAuth:43` ⇒ ۴۰۴). پاسخ مبهم، انحراف ما بود نه این.
|
||||
|
||||
**معاوضه امنیتی، صریح:** پاسخ مبهم برای این بود که کسی از بیرون نتواند بفهمد چه
|
||||
شمارههایی در سیستم ثبتاند. این محافظت در مرحله اول کاهش یافت، **عمداً و به
|
||||
دستور مالک**، چون هزینهاش را کاربران واقعی و پشتیبانی میدادند. چیزی که سر جایش
|
||||
ماند: سقف تعداد ارسال برای هر شماره و هر IP (`assertSendAllowed`) که مرجع
|
||||
نداشت — پس شمارشکردن شمارهها محدود است نه آزاد؛ و مرحله دوم (`verifyOtp`) که
|
||||
برای همه علتهای شکست یک پیام واحد میدهد.
|
||||
|
||||
**چرا D20 اینجا نقض نمیشود:** D20 میگوید پیامک ناموفق نباید یک *عملیات* را
|
||||
خراب کند — سفارش باید ثبت شود حتی اگر پیامکش نرود. در ورود، پیامک **خودِ
|
||||
عملیات** است، پس سکوت از چیزی محافظت نمیکند.
|
||||
|
||||
ثبت شده در `ISSUES.md` بهعنوان AUTH-01/02/03 و DOC-01.
|
||||
|
||||
Reference in New Issue
Block a user