fix(abdm): isolate Aadhaar mobile from ABHA registered mobile and sanitize asterisk placeholders in verification flow

This commit is contained in:
DevSoftTech Server 2026-09-12 00:35:51 +05:30
parent fd492a2aa2
commit 7c573403b2
4 changed files with 67 additions and 8 deletions

View File

@ -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];
}

View File

@ -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.

View File

@ -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 += '<label class="abha-auth-option' + (disabled ? ' opacity-50' : '') + '"><span class="d-flex align-items-center gap-3"><input class="form-check-input mt-0" type="radio" name="abhaVerifyAuth" value="' + method + '" ' + (checked ? 'checked' : '') + ' ' + (disabled ? 'disabled' : '') + '><i class="bi ' + label.icon + ' fs-4 text-primary"></i><span><strong>' + label.title + destination + '</strong><small class="d-block text-muted">' + label.detail + '</small></span></span></label>';
});
$('#abhaVerifyAuthOptions').html(html || '<div class="alert alert-warning mb-0">This ABHA account did not return a supported OTP method. The Bridge must return MOBILE_OTP or AADHAAR_OTP.</div>');
@ -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);

View File

@ -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