payfrit-api/api/auth/sendLoginOTP.php
John Mizerek 08ef54976f Port Twilio SMS integration from CFML to PHP
Add sendSMS() to helpers.php using Twilio REST API with cURL,
credentials loaded from config/twilio.json. Wire into sendOTP,
loginOTP, and sendLoginOTP endpoints, replacing TODO stubs.
SMS is auto-skipped on dev environments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 16:02:34 -07:00

88 lines
2.4 KiB
PHP

<?php
require_once __DIR__ . '/../helpers.php';
runAuth();
/*
Send OTP Code for Portal Login
POST: { "Email": "user@example.com" } or { "Phone": "3105551234" } or { "Identifier": "..." }
Returns: { OK: true } always (don't reveal if account exists)
*/
$data = readJsonBody();
$identifier = trim($data['Identifier'] ?? $data['Email'] ?? $data['Phone'] ?? '');
if (empty($identifier)) {
apiAbort(['OK' => false, 'ERROR' => 'missing_identifier', 'MESSAGE' => 'Email or phone is required']);
}
$isPhone = isPhoneNumber($identifier);
$email = '';
$phone = '';
if ($isPhone) {
$phone = normalizePhone($identifier);
} else {
$email = $identifier;
}
$genericResponse = ['OK' => true, 'MESSAGE' => 'If an account exists, a code has been sent.'];
try {
if (!empty($email)) {
$user = queryOne(
"SELECT ID, FirstName, ContactNumber FROM Users WHERE EmailAddress = ? AND IsActive = 1 LIMIT 1",
[$email]
);
} else {
$user = queryOne(
"SELECT ID, FirstName, ContactNumber FROM Users WHERE ContactNumber = ? AND IsActive = 1 LIMIT 1",
[$phone]
);
}
// Always return OK to not reveal if account exists
if (!$user) {
jsonResponse($genericResponse);
}
$uid = (int) $user['ID'];
// Rate limit: max 3 codes per user in last 10 minutes
$rateCheck = queryOne(
"SELECT COUNT(*) AS cnt FROM OTPCodes WHERE UserID = ? AND CreatedAt > DATE_SUB(NOW(), INTERVAL 10 MINUTE)",
[$uid]
);
if (((int) ($rateCheck['cnt'] ?? 0)) >= 3) {
jsonResponse($genericResponse);
}
$code = random_int(100000, 999999);
$dev = isDev();
// Store with 10-minute expiry
queryTimed(
"INSERT INTO OTPCodes (UserID, Code, ExpiresAt) VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 10 MINUTE))",
[$uid, $code]
);
// Send OTP via SMS or email (skip on dev)
if (!$dev) {
if (!empty($phone) && !empty($user['ContactNumber'])) {
sendSMS("+1" . $user['ContactNumber'], "Your Payfrit login code is: {$code}. It expires in 10 minutes.");
} else {
// TODO: Email sending
}
}
$resp = $genericResponse;
if ($dev) {
$resp['DEV_OTP'] = $code;
}
jsonResponse($resp);
} catch (\Exception $e) {
// Swallow errors — always return generic OK
error_log("sendLoginOTP error for {$email}: " . $e->getMessage());
jsonResponse($genericResponse);
}