230 lines
6.8 KiB
PHP
230 lines
6.8 KiB
PHP
<?php
|
|
// File: backend/auth/otp/providers.php
|
|
// Encapsulates external OTP gateway API calls for Kazumi, Intaleq, and Nabeh.
|
|
|
|
/**
|
|
* Send SMS OTP via Kazumi SMS Gateway (Egypt)
|
|
*
|
|
* @param string $receiver Recipient phone number (e.g. +2010xxxxxxxx)
|
|
* @param string $otp 3-digit verification code
|
|
* @return bool True if OTP was sent successfully
|
|
*/
|
|
function sendKazumiSms(string $receiver, string $otp): bool {
|
|
$username = getenv('SMS_USERNAME');
|
|
$password = getenv('SMS_PASSWORD_EGYPT');
|
|
$sender = getenv('SMS_SENDER');
|
|
|
|
if (!$username || !$password || !$sender) {
|
|
error_log("⚠️ [Kazumi OTP] Missing credentials in environment variables.");
|
|
return false;
|
|
}
|
|
|
|
$message = "Siro app code is " . $otp;
|
|
$apiUrl = 'https://sms.kazumi.me/api/sms/send-sms';
|
|
|
|
$payload = [
|
|
'username' => $username,
|
|
'password' => $password,
|
|
'language' => 'e',
|
|
'sender' => $sender,
|
|
'receiver' => $receiver,
|
|
'message' => $message
|
|
];
|
|
|
|
$response = curlCall("POST", $apiUrl, json_encode($payload), [
|
|
"Content-Type: application/json"
|
|
]);
|
|
|
|
if ($response) {
|
|
$decoded = json_decode($response, true);
|
|
if (isset($decoded['message']) && $decoded['message'] === 'Success') {
|
|
return true;
|
|
}
|
|
error_log("❌ [Kazumi OTP] API returned failure response: " . $response);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Retrieve Nabeh JWT Bearer Token, caching it in Redis for 24 hours.
|
|
*
|
|
* @return string|null The Bearer token, or null on failure.
|
|
*/
|
|
function getNabehBearerToken(): ?string {
|
|
global $redis;
|
|
|
|
// 1. Try to read cached token from Redis (TTL 24 hours)
|
|
if ($redis) {
|
|
try {
|
|
$cachedToken = $redis->get('nabeh_bearer_token');
|
|
if ($cachedToken) {
|
|
return $cachedToken;
|
|
}
|
|
} catch (Exception $e) {
|
|
error_log("⚠️ [Nabeh Auth Redis] Error reading token: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// 2. Token not cached, authenticate via Nabeh Login API
|
|
$email = getenv('NABEH_EMAIL');
|
|
$password = getenv('NABEH_PASSWORD');
|
|
|
|
if (!$email || !$password) {
|
|
error_log("⚠️ [Nabeh Auth] Missing NABEH_EMAIL or NABEH_PASSWORD environment variables.");
|
|
return null;
|
|
}
|
|
|
|
$apiUrl = 'https://nabeh.intaleqapp.com/api/auth/login';
|
|
$payload = [
|
|
'email' => $email,
|
|
'password' => $password
|
|
];
|
|
|
|
$response = curlCall("POST", $apiUrl, json_encode($payload), [
|
|
'Content-Type: application/json'
|
|
]);
|
|
|
|
if ($response) {
|
|
$decoded = json_decode($response, true);
|
|
$token = $decoded['token'] ?? $decoded['message']['token'] ?? $decoded['jwt'] ?? $decoded['access_token'] ?? null;
|
|
|
|
if ($token) {
|
|
// Cache token in Redis for 24 hours (86400 seconds)
|
|
if ($redis) {
|
|
try {
|
|
$redis->setex('nabeh_bearer_token', 86400, $token);
|
|
} catch (Exception $e) {
|
|
error_log("⚠️ [Nabeh Auth Redis Cache Save] Error saving token: " . $e->getMessage());
|
|
}
|
|
}
|
|
return $token;
|
|
}
|
|
error_log("❌ [Nabeh Auth] Failed to extract token from login response: " . $response);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Send OTP via Nabeh JWT Auth Gateway (WhatsApp, Voice, etc.)
|
|
*
|
|
* @param string $receiver Recipient phone number
|
|
* @param string $otp 3-digit verification code
|
|
* @param string $method text | voice | image | whatsapp
|
|
* @return bool True if OTP was sent successfully
|
|
*/
|
|
function sendNabehOtp(string $receiver, string $otp, string $method = 'text'): bool {
|
|
$bearerToken = getNabehBearerToken();
|
|
if (!$bearerToken) {
|
|
error_log("⚠️ [Nabeh OTP] Failed to obtain dynamic JWT Bearer token.");
|
|
return false;
|
|
}
|
|
|
|
// Strip symbols for Nabeh endpoint
|
|
$phoneRaw = preg_replace('/\D+/', '', $receiver);
|
|
|
|
// Map method/type
|
|
$type = 'text';
|
|
if ($method === 'voice') {
|
|
$type = 'voice';
|
|
} elseif ($method === 'image') {
|
|
$type = 'image';
|
|
}
|
|
// elseif ($method === 'flash_call') {
|
|
// $type = 'flash_call';
|
|
// }
|
|
|
|
$apiUrl = 'https://nabeh.intaleqapp.com/api/otp/send';
|
|
$payload = [
|
|
'phone' => $phoneRaw,
|
|
'type' => $type,
|
|
'code' => $otp
|
|
];
|
|
|
|
$response = curlCall("POST", $apiUrl, json_encode($payload), [
|
|
'Content-Type: application/json',
|
|
"Authorization: Bearer $bearerToken"
|
|
]);
|
|
|
|
if ($response) {
|
|
$decoded = json_decode($response, true);
|
|
if ($decoded && ($decoded['success'] ?? false)) {
|
|
return true;
|
|
}
|
|
error_log("❌ [Nabeh OTP] API returned failure response: " . $response);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Send OTP via Intaleq Static OTP Gateway (using body app_key parameter)
|
|
*
|
|
* @param string $receiver Recipient phone number
|
|
* @param string $otp 3-digit verification code
|
|
* @param string $method whatsapp | sms | voice | flash_call
|
|
* @return bool True if OTP was sent successfully
|
|
*/
|
|
function sendIntaleqOtp(string $receiver, string $otp, string $method = 'whatsapp'): bool {
|
|
$appKey = getenv('NABEH_OTP_APP_KEY');
|
|
|
|
if (!$appKey) {
|
|
error_log("⚠️ [Intaleq OTP] Missing NABEH_OTP_APP_KEY in environment.");
|
|
return false;
|
|
}
|
|
|
|
// Normalize receiver to start with +
|
|
$phoneWithPlus = (strpos($receiver, '+') === 0) ? $receiver : '+' . $receiver;
|
|
|
|
$apiUrl = 'https://otp.intaleqapp.com/api/request-otp.php';
|
|
$payload = [
|
|
'phone' => $phoneWithPlus,
|
|
'app_key' => $appKey,
|
|
'device_type' => 'android',
|
|
'method' => $method,
|
|
'code' => $otp
|
|
];
|
|
|
|
$response = curlCall("POST", $apiUrl, json_encode($payload), [
|
|
'Content-Type: application/json'
|
|
]);
|
|
|
|
if ($response) {
|
|
$decoded = json_decode($response, true);
|
|
if ($decoded && ($decoded['success'] ?? false)) {
|
|
return true;
|
|
}
|
|
error_log("❌ [Intaleq OTP] API returned failure response: " . $response);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Generic cURL execution helper
|
|
*/
|
|
function curlCall(string $method, string $url, string $data, array $headers): ?string {
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_CUSTOMREQUEST => $method,
|
|
CURLOPT_POSTFIELDS => $data,
|
|
CURLOPT_HTTPHEADER => $headers,
|
|
CURLOPT_TIMEOUT => 15,
|
|
CURLOPT_CONNECTTIMEOUT => 5
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
$error = curl_error($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
if ($error) {
|
|
error_log("⚠️ [OTP cURL] Error calling $url: $error");
|
|
return null;
|
|
}
|
|
|
|
if ($httpCode !== 200) {
|
|
error_log("⚠️ [OTP cURL] Non-200 HTTP code $httpCode from $url. Response: $response");
|
|
}
|
|
|
|
return $response;
|
|
}
|