- Voice enhancements, user management, payment method
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
function sendMailgunMessage(string $to, string $subject, string $text, string $html = ''): array {
|
||||
$key = trim((string)getConfigVal('mailgun.api_key', ''));
|
||||
$domain = trim((string)getConfigVal('mailgun.domain', ''));
|
||||
$from = trim((string)getConfigVal('mailgun.from', ''));
|
||||
$base = rtrim(trim((string)getConfigVal('mailgun.api_base', '')) ?: 'https://api.mailgun.net/v3', '/');
|
||||
if ($key === '' || $domain === '' || $from === '') {
|
||||
throw new RuntimeException('Mailgun is not configured');
|
||||
}
|
||||
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) throw new InvalidArgumentException('Invalid recipient email');
|
||||
if (!function_exists('curl_init')) throw new RuntimeException('PHP cURL extension is required');
|
||||
|
||||
$fields = ['from' => $from, 'to' => $to, 'subject' => $subject, 'text' => $text];
|
||||
if ($html !== '') $fields['html'] = $html;
|
||||
$curl = curl_init($base . '/' . rawurlencode($domain) . '/messages');
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_USERPWD => 'api:' . $key,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $fields,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 20,
|
||||
]);
|
||||
$body = curl_exec($curl);
|
||||
$status = (int)curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
|
||||
$error = curl_error($curl);
|
||||
curl_close($curl);
|
||||
if ($body === false || $status < 200 || $status >= 300) {
|
||||
throw new RuntimeException('Mailgun request failed: ' . ($error ?: 'HTTP ' . $status));
|
||||
}
|
||||
return json_decode((string)$body, true) ?: ['success' => true];
|
||||
}
|
||||
|
||||
function issueEmailToken(array $user, string $email, string $purpose, int $ttl = 900): array {
|
||||
$token = bin2hex(random_bytes(24));
|
||||
$code = (string)random_int(100000, 999999);
|
||||
$db = getDB();
|
||||
$db->prepare("UPDATE email_tokens SET used_at=? WHERE user_id=? AND purpose=? AND used_at IS NULL")
|
||||
->execute([time(), $user['id'], $purpose]);
|
||||
$db->prepare("INSERT INTO email_tokens
|
||||
(user_id,email,purpose,token_hash,code_hash,expires_at,created_at) VALUES (?,?,?,?,?,?,?)")
|
||||
->execute([
|
||||
$user['id'], strtolower($email), $purpose, hash('sha256', $token),
|
||||
password_hash($code, PASSWORD_DEFAULT), time() + $ttl, time(),
|
||||
]);
|
||||
return ['token' => $token, 'code' => $code, 'expires_at' => time() + $ttl];
|
||||
}
|
||||
|
||||
function sendVerificationEmail(array $user, string $email): void {
|
||||
$issued = issueEmailToken($user, $email, 'verify_email', 1800);
|
||||
$url = appBaseUrl() . '/index.html?verify=' . rawurlencode($issued['token']);
|
||||
$title = (string)getConfigVal('ui.app_title', 'CyberChat');
|
||||
$text = "Verify your $title email.\n\nCode: {$issued['code']}\nVerification link: $url\n\nThis expires in 30 minutes.";
|
||||
$html = '<p>Verify your ' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . ' email.</p>'
|
||||
. '<p>Your code is <strong style="font-size:20px">' . $issued['code'] . '</strong></p>'
|
||||
. '<p><a href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '">Open verification screen</a></p>'
|
||||
. '<p>This expires in 30 minutes.</p>';
|
||||
sendMailgunMessage($email, 'Verify your ' . $title . ' email', $text, $html);
|
||||
}
|
||||
|
||||
function consumeEmailVerification(array $user, string $token = '', string $code = ''): bool {
|
||||
$stmt = getDB()->prepare("SELECT * FROM email_tokens
|
||||
WHERE user_id=? AND purpose='verify_email' AND used_at IS NULL AND expires_at>? ORDER BY id DESC LIMIT 1");
|
||||
$stmt->execute([$user['id'], time()]);
|
||||
$row = $stmt->fetch();
|
||||
if (!$row || (int)($row['attempts'] ?? 0) >= 5) return false;
|
||||
$valid = ($token !== '' && hash_equals($row['token_hash'], hash('sha256', $token)))
|
||||
|| ($code !== '' && password_verify($code, $row['code_hash']));
|
||||
if (!$valid) {
|
||||
getDB()->prepare("UPDATE email_tokens SET attempts=attempts+1 WHERE id=?")->execute([$row['id']]);
|
||||
return false;
|
||||
}
|
||||
$db = getDB();
|
||||
$db->beginTransaction();
|
||||
$db->prepare("UPDATE email_tokens SET used_at=? WHERE id=?")->execute([time(), $row['id']]);
|
||||
$db->prepare("UPDATE users SET email=?,email_verified_at=? WHERE id=?")
|
||||
->execute([$row['email'], time(), $user['id']]);
|
||||
$db->commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
function sendTwoFactorCode(array $user, string $challengeId, string $code): void {
|
||||
$title = (string)getConfigVal('ui.app_title', 'CyberChat');
|
||||
$url = appBaseUrl() . '/index.html?login_challenge=' . rawurlencode($challengeId);
|
||||
sendMailgunMessage(
|
||||
(string)$user['email'],
|
||||
$title . ' login code',
|
||||
"Your login code is $code.\n\nOpen the authentication screen: $url\n\nIt expires in 10 minutes.",
|
||||
'<p>Your login code is <strong style="font-size:22px">' . $code . '</strong>.</p>'
|
||||
. '<p><a href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '">Open authentication screen</a></p>'
|
||||
. '<p>It expires in 10 minutes.</p>'
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user