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
@@ -3,18 +3,18 @@
|
||||
restyled into the redesigned design language (see components/home/* and
|
||||
HomeView).
|
||||
|
||||
Per the earlier decision: OTP-based flow ported structurally, but the
|
||||
SMS-send step is stubbed — no SMS provider exists. What's real vs stubbed,
|
||||
all carried over unchanged from the previous version:
|
||||
- Step 1 (mobile number) is checked against the REAL, already-existing
|
||||
GET /api/customers endpoint (client-side match on `mobile`) — no fake
|
||||
endpoint invented. A non-match shows legacy's own
|
||||
"اطلاعات کاربری در سیستم وجود ندارد" text, not a new message.
|
||||
- Step 2 (OTP code) is NOT verified — any 4-digit code is accepted, since
|
||||
there is no real code to check against. On "verify", the session store
|
||||
logs in as the matched real customer, so downstream features (balance,
|
||||
ledger, favourites-as-logged-in) work against real seeded data.
|
||||
- `token.fireBaseToken` doesn't exist in this port (Firebase deferred).
|
||||
The flow is REAL. These notes used to say it was stubbed, and that stale text
|
||||
misled a reader as recently as 2026-10-12, so it is corrected rather than
|
||||
deleted:
|
||||
- Step 1 posts the number to the server, which looks it up itself. It no
|
||||
longer pulls the customer list into the client to match on `mobile` — that
|
||||
was the login leak (D8). An unregistered number is answered with 404 and
|
||||
the server's Persian message is shown; owner ruling 2026-10-12, because
|
||||
the earlier opaque answer walked the person to the code box to wait for an
|
||||
SMS that was never going to arrive.
|
||||
- Step 2 IS verified by the server: hashed code, two-minute expiry, attempt
|
||||
limit (D6). Accepting any 4-digit code is long gone.
|
||||
- `token.fireBaseToken` still does not exist in this port (PUSH-01).
|
||||
- `useOtpToken()` (WebOTP autofill) was already dead in legacy itself.
|
||||
-->
|
||||
<template>
|
||||
@@ -140,9 +140,15 @@ const sendPhone = async () => {
|
||||
body.defaultFocus = true
|
||||
})
|
||||
} catch (e: unknown) {
|
||||
// 429 = the send throttle (too soon, or too many in the window). Its message
|
||||
// is already Persian and user-facing, so show it rather than a generic one.
|
||||
const text = e instanceof Error && /\(429\)/.test(e.message) ? extractServerMessage(e) : null
|
||||
// Both of these carry a Persian, user-facing `message` from the server, so
|
||||
// show it instead of a generic one:
|
||||
// 429 — the send throttle (too soon, or too many in the window);
|
||||
// 404 — the number is not registered (owner ruling 2026-10-12). Without
|
||||
// this branch the person is told "خطا در ارسال کد"، which is the
|
||||
// very confusion the 404 was added to remove: they would still not
|
||||
// know whether the SMS was late or the number was never on file.
|
||||
const text =
|
||||
e instanceof Error && /\((?:429|404)\)/.test(e.message) ? extractServerMessage(e) : null
|
||||
toast.error(text ?? 'خطا در ارسال کد. لطفاً دوباره تلاش کنید.')
|
||||
} finally {
|
||||
body.loading = false
|
||||
|
||||
Reference in New Issue
Block a user