diff --git a/app/Controllers/Abha.php b/app/Controllers/Abha.php index 15cd4a3..42d2498 100644 --- a/app/Controllers/Abha.php +++ b/app/Controllers/Abha.php @@ -2764,6 +2764,13 @@ class Abha extends BaseController ?? $account['phone'] ?? '' )); + $aadhaarMaskedMobile = $this->maskAbhaMobile((string) ( + $data['aadhaar_masked_mobile'] + ?? $data['aadhaarMaskedMobile'] + ?? $account['aadhaar_masked_mobile'] + ?? $account['aadhaarMaskedMobile'] + ?? '' + )); return $this->response->setJSON([ 'ok' => 1, 'status' => $status, @@ -2772,11 +2779,13 @@ class Abha extends BaseController 'auth_methods' => array_values(array_unique(array_map(static fn($method): string => strtoupper(trim((string) $method)), $authMethods))), 'blocked_auth_methods' => is_array($blockedMethods) ? array_values($blockedMethods) : [], 'masked_mobile' => $maskedMobile, + 'aadhaar_masked_mobile' => $aadhaarMaskedMobile, 'account' => [ 'name' => trim((string) ($account['name'] ?? $account['fullName'] ?? $account['full_name'] ?? $data['name'] ?? $data['fullName'] ?? $data['full_name'] ?? '')), 'abha_number' => trim((string) ($account['ABHANumber'] ?? $account['abhaNumber'] ?? $account['abha_id'] ?? $account['healthIdNumber'] ?? $data['ABHANumber'] ?? $data['healthIdNumber'] ?? $data['abha_id'] ?? '')), 'abha_address' => trim((string) ($account['abhaAddress'] ?? $account['preferredAddress'] ?? $account['preferredAbhaAddress'] ?? $account['abha_address'] ?? $data['abhaAddress'] ?? $data['preferredAddress'] ?? $data['preferredAbhaAddress'] ?? $data['abha_address'] ?? '')), 'masked_mobile' => $maskedMobile, + 'aadhaar_masked_mobile' => $aadhaarMaskedMobile, ], ]); } @@ -2784,7 +2793,11 @@ class Abha extends BaseController private function maskAbhaMobile(string $value): string { $value = trim($value); - if ($value === '' || preg_match('/\*/', $value) === 1) { + if ($value === '' || preg_match('/\d/', $value) !== 1) { + return ''; + } + + if (preg_match('/\*/', $value) === 1) { return $value; } @@ -2833,8 +2846,22 @@ class Abha extends BaseController } $data = is_array($result['data'] ?? null) ? $result['data'] : []; - $maskedMobile = trim((string) ($data['masked_mobile'] ?? $data['maskedMobile'] ?? $data['mobile'] ?? '')); - $deliveryMessage = trim((string) ($result['message'] ?? $data['message'] ?? '')); + $maskedMobile = trim((string) ( + $result['masked_mobile'] + ?? $result['maskedMobile'] + ?? $data['masked_mobile'] + ?? $data['maskedMobile'] + ?? $data['mobile'] + ?? $data['mobile_number'] + ?? '' + )); + $deliveryMessage = trim((string) ( + $result['message'] + ?? $data['message'] + ?? $result['details']['message'] + ?? $data['details']['message'] + ?? '' + )); if ($maskedMobile === '' && preg_match('/(\d{4})(?!.*\d)/', $deliveryMessage, $mobileEnding) === 1) { $maskedMobile = '******' . $mobileEnding[1]; } diff --git a/app/Libraries/Abdm/EAtriaBridgeConnector.php b/app/Libraries/Abdm/EAtriaBridgeConnector.php index 0c3be6d..f1664e6 100644 --- a/app/Libraries/Abdm/EAtriaBridgeConnector.php +++ b/app/Libraries/Abdm/EAtriaBridgeConnector.php @@ -656,8 +656,19 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface ?? $firstAccount['masked_mobile'] ?? $firstAccount['maskedMobile'] ?? $firstAccount['mobile'] ?? $firstAccount['mobile_number'] ?? $firstAccount['phone'] ?? $firstAccount['phone_number'] ?? '' )); - if ($maskedMobile !== '') { + if ($maskedMobile !== '' && preg_match('/\d/', $maskedMobile) === 1) { $result['masked_mobile'] = $maskedMobile; + } else { + unset($result['masked_mobile'], $result['maskedMobile']); + } + + $aadhaarMaskedMobile = trim((string) ( + $result['aadhaar_masked_mobile'] ?? $result['aadhaarMaskedMobile'] + ?? $data['aadhaar_masked_mobile'] ?? $data['aadhaarMaskedMobile'] + ?? $firstAccount['aadhaar_masked_mobile'] ?? $firstAccount['aadhaarMaskedMobile'] ?? '' + )); + if ($aadhaarMaskedMobile !== '' && preg_match('/\d/', $aadhaarMaskedMobile) === 1) { + $result['aadhaar_masked_mobile'] = $aadhaarMaskedMobile; } // HMS callers expect a status-like field; treat successful search as VALID when absent. diff --git a/app/Views/partials/abha_verify_modal.php b/app/Views/partials/abha_verify_modal.php index e56dac1..bed3cee 100644 --- a/app/Views/partials/abha_verify_modal.php +++ b/app/Views/partials/abha_verify_modal.php @@ -161,8 +161,21 @@ window.AbhaVerifyModal = (function () { var label = authLabel(method); var disabled = blocked.indexOf(method) !== -1; var checked = availableMethods[0] === method; - var mobile = response.masked_mobile || (response.account && response.account.masked_mobile) || ''; - var destination = mobile ? ' (' + escapeHtml(mobile) + ')' : ''; + var targetMobile = ''; + if (method === 'MOBILE_OTP') { + var m = response.masked_mobile || (response.account && response.account.masked_mobile) || ''; + if (typeof m === 'string' && /\d/.test(m)) { + targetMobile = m; + } + } else if (method === 'AADHAAR_OTP') { + // Aadhaar mobile is registered with UIDAI and not exposed by ABDM during search. + // Only show if the Bridge explicitly returned an Aadhaar-specific masked mobile. + var am = response.aadhaar_masked_mobile || (response.account && response.account.aadhaar_masked_mobile) || ''; + if (typeof am === 'string' && /\d/.test(am)) { + targetMobile = am; + } + } + var destination = targetMobile ? ' (' + escapeHtml(targetMobile) + ')' : ''; html += ''; }); $('#abhaVerifyAuthOptions').html(html || '
This ABHA account did not return a supported OTP method. The Bridge must return MOBILE_OTP or AADHAAR_OTP.
'); @@ -200,7 +213,10 @@ window.AbhaVerifyModal = (function () { lookupResponse.txn_id = response.txn_id || lookupResponse.txn_id; lookupResponse.auth_method = method; var label = authLabel(method); - $('#abhaVerifyOtpHint').text('OTP sent using ' + label.title + (response.masked_mobile ? ' (' + response.masked_mobile + ')' : '') + '.'); + var otpDest = (response.masked_mobile && /\d/.test(response.masked_mobile)) + ? ' (' + escapeHtml(response.masked_mobile) + ')' + : ''; + $('#abhaVerifyOtpHint').text('OTP sent using ' + label.title + otpDest + '.'); $('#abhaVerifyOtp').val(''); showStep(3); startResendTimer(response.resend_after); diff --git a/docs/abdm/ABHA_IDENTIFIER_LOGIN_BRIDGE_CONTRACT.md b/docs/abdm/ABHA_IDENTIFIER_LOGIN_BRIDGE_CONTRACT.md index 2b7b12f..9339def 100644 --- a/docs/abdm/ABHA_IDENTIFIER_LOGIN_BRIDGE_CONTRACT.md +++ b/docs/abdm/ABHA_IDENTIFIER_LOGIN_BRIDGE_CONTRACT.md @@ -47,6 +47,7 @@ Required success response: "authMethods": ["MOBILE_OTP", "AADHAAR_OTP"], "blockedAuthMethods": [], "maskedMobile": "******1369", + "aadhaar_masked_mobile": null, "accounts": [ { "name": "Ashish Soni", @@ -60,7 +61,11 @@ Required success response: } ``` -The search transaction must remain valid for the next OTP request. Do not replace it with a new mobile-search transaction. +Rules: +- When ABDM returns `mobile: "************"` (all asterisks), the Bridge sanitizes `mobile`, `masked_mobile`, and `maskedMobile` to `null`. +- If the patient has a previously verified record with genuine digits, the Bridge returns `maskedMobile: "******XXXX"`. Genuine ABDM numbers (e.g. `78*****379`) are passed through. +- `aadhaar_masked_mobile` is explicitly `null` during search (compliant with UIDAI/ABDM guidelines preventing harvesting). Aadhaar mobile is resolved only upon OTP dispatch. +- The search transaction must remain valid for the next OTP request. Do not replace it with a new mobile-search transaction. ## 2. Request Account-Bound OTP