Files
jakach-login/app-code/api/login/send_reset_link.php
T
2026-05-15 10:20:47 +02:00

291 lines
15 KiB
PHP

<?php
include "../utils/security.php";
secure_session_start();
require_same_origin_request();
require_csrf_token();
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'message' => 'Invalid request method.']);
exit;
}
include "../../config/config.php";
include "../utils/get_location.php";
$username=$_SESSION["username"] ?? "";
$conn = new mysqli($DB_SERVERNAME, $DB_USERNAME, $DB_PASSWORD, $DB_DATABASE);
check_rate_limit($conn, 'send_reset_link', 3, 60 * 60, $username);
if ($username === "") {
echo json_encode(['success' => false, 'message' => 'Missing username.']);
exit;
}
$sql="SELECT id, email, telegram_id FROM users WHERE username = ?;";
$mail="";
$id="";
$telegram_id="";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 's', $username);
mysqli_stmt_execute($stmt);
mysqli_stmt_store_result($stmt);
mysqli_stmt_bind_result($stmt,$id, $mail,$telegram_id);
mysqli_stmt_fetch($stmt);
$user_found = mysqli_stmt_num_rows($stmt) === 1;
mysqli_stmt_close($stmt);
if (!$user_found) {
echo json_encode(['success' => true, 'message' => 'If the account has reset methods configured, a reset link has been sent.']);
exit;
}
//send telegram message
$device = $_SERVER['HTTP_USER_AGENT'] ?? "";
$forwarded_for = $_SERVER["HTTP_X_FORWARDED_FOR"] ?? $_SERVER["REMOTE_ADDR"] ?? "";
$ip=trim(explode(",",$forwarded_for)[0]);
$location=get_location_from_ip($ip);
$date=date('Y-m-d H:i:s');
$token=bin2hex(random_bytes(128));
$token_hash=auth_token_hash($token);
$link="https://auth.jakach.ch/login/reset_pw.php?token=$token";
$tg_device = str_replace(['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'], ['\\_', '\\*', '\\[', '\\]', '\\(', '\\)', '\\~', '\\`', '\\>', '\\#', '\\+', '\\-', '\\=', '\\|', '\\{', '\\}', '\\.', '\\!'], $device);
$tg_username = str_replace(['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'], ['\\_', '\\*', '\\[', '\\]', '\\(', '\\)', '\\~', '\\`', '\\>', '\\#', '\\+', '\\-', '\\=', '\\|', '\\{', '\\}', '\\.', '\\!'], $_SESSION["username"]);
$tg_ip = str_replace(['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'], ['\\_', '\\*', '\\[', '\\]', '\\(', '\\)', '\\~', '\\`', '\\>', '\\#', '\\+', '\\-', '\\=', '\\|', '\\{', '\\}', '\\.', '\\!'], $ip);
$tg_location = str_replace(['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'], ['\\_', '\\*', '\\[', '\\]', '\\(', '\\)', '\\~', '\\`', '\\>', '\\#', '\\+', '\\-', '\\=', '\\|', '\\{', '\\}', '\\.', '\\!'], ($location["country"] ?? "").", ".($location["state"] ?? "").", ".($location["city"] ?? ""));
$message = "*Password reset token*\n\n"
. "You have requested the reset of your password here is your reset link.\n\n"
. "*Link*: [click here]($link)\n\n"
. "*Details of this request:*\n"
. "• *Date&Time*: $date\n"
. "• *Device&Browser*: $tg_device\n"
. "*Location*: $tg_location\n"
. "• *Account*: $tg_username\n"
. "• *IP*: $tg_ip\n\n"
."If this was you, you can reset your password. If this was not you somebody else tried to reset your password!\n"
. "*Thank you for using Jakach login!*";
// Telegram API URL
$url = "https://api.telegram.org/$TELEGRAM_BOT_API/sendMessage";
$message_data = [
'chat_id' => $telegram_id,
'text' => $message,
'parse_mode' => 'Markdown', // Use Markdown for formatting
];
// Use cURL to send the request
$ch = curl_init();
// Construct the GET request URL
$query_string = http_build_query($message_data); // Converts the array to URL-encoded query string
$get_url = $url . '?' . $query_string; // Append query string to the base URL
curl_setopt($ch, CURLOPT_URL, $get_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Still retrieve the response if needed
curl_exec($ch);
curl_close($ch);
//send mail
if(!empty($mail)){
$loc=$location["country"].", ".$location["state"].", ".$location["city"];
$html_username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8');
$html_device = htmlspecialchars($device, ENT_QUOTES, 'UTF-8');
$html_ip = htmlspecialchars($ip, ENT_QUOTES, 'UTF-8');
$html_loc = htmlspecialchars($loc, ENT_QUOTES, 'UTF-8');
$html_mail = htmlspecialchars($mail, ENT_QUOTES, 'UTF-8');
$html_link = htmlspecialchars($link, ENT_QUOTES, 'UTF-8');
$content = '
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="x-apple-disable-message-reformatting" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="color-scheme" content="light dark" />
<meta name="supported-color-schemes" content="light dark" />
<title></title>
<style type="text/css" rel="stylesheet" media="all">
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
body { width:100% !important; height:100%; margin:0; -webkit-text-size-adjust:none; }
a { color:#3869D4; }
a img { border:none; }
td { word-break:break-word; }
.preheader { display:none !important; visibility:hidden; mso-hide:all; font-size:1px; line-height:1px; max-height:0; max-width:0; opacity:0; overflow:hidden; }
body, td, th { font-family:"Nunito Sans",Helvetica,Arial,sans-serif; }
h1 { margin-top:0; color:#333333; font-size:22px; font-weight:bold; text-align:left; }
h2 { margin-top:0; color:#333333; font-size:16px; font-weight:bold; text-align:left; }
h3 { margin-top:0; color:#333333; font-size:14px; font-weight:bold; text-align:left; }
td, th { font-size:16px; }
p, ul, ol, blockquote { margin:.4em 0 1.1875em; font-size:16px; line-height:1.625; }
p.sub { font-size:13px; }
.align-right { text-align:right; }
.align-left { text-align:left; }
.align-center { text-align:center; }
.u-margin-bottom-none { margin-bottom:0; }
.button { background-color:#3869D4; border-top:10px solid #3869D4; border-right:18px solid #3869D4; border-bottom:10px solid #3869D4; border-left:18px solid #3869D4; display:inline-block; color:#FFF; text-decoration:none; border-radius:3px; box-shadow:0 2px 3px rgba(0,0,0,0.16); -webkit-text-size-adjust:none; box-sizing:border-box; }
.button--green { background-color:#22BC66; border-top:10px solid #22BC66; border-right:18px solid #22BC66; border-bottom:10px solid #22BC66; border-left:18px solid #22BC66; }
.button--red { background-color:#FF6136; border-top:10px solid #FF6136; border-right:18px solid #FF6136; border-bottom:10px solid #FF6136; border-left:18px solid #FF6136; }
@media only screen and (max-width:500px) { .button { width:100% !important; text-align:center !important; } }
.attributes { margin:0 0 21px; }
.attributes_content { background-color:#F4F4F7; padding:16px; }
.attributes_item { padding:0; }
.related { width:100%; margin:0; padding:25px 0 0 0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
.related_item { padding:10px 0; color:#CBCCCF; font-size:15px; line-height:18px; }
.related_item-title { display:block; margin:.5em 0 0; }
.related_item-thumb { display:block; padding-bottom:10px; }
.related_heading { border-top:1px solid #CBCCCF; text-align:center; padding:25px 0 10px; }
.discount { width:100%; margin:0; padding:24px; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; background-color:#F4F4F7; border:2px dashed #CBCCCF; }
.discount_heading { text-align:center; }
.discount_body { text-align:center; font-size:15px; }
.social { width:auto; }
.social td { padding:0; width:auto; }
.social_icon { height:20px; margin:0 8px 10px 8px; padding:0; }
.purchase { width:100%; margin:0; padding:35px 0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
.purchase_content { width:100%; margin:0; padding:25px 0 0 0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
.purchase_item { padding:10px 0; color:#51545E; font-size:15px; line-height:18px; }
.purchase_heading { padding-bottom:8px; border-bottom:1px solid #EAEAEC; }
.purchase_heading p { margin:0; color:#85878E; font-size:12px; }
.purchase_footer { padding-top:15px; border-top:1px solid #EAEAEC; }
.purchase_total { margin:0; text-align:right; font-weight:bold; color:#333333; }
.purchase_total--label { padding:0 15px 0 0; }
body { background-color:#F2F4F6; color:#51545E; }
p { color:#51545E; }
.email-wrapper { width:100%; margin:0; padding:0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; background-color:#F2F4F6; }
.email-content { width:100%; margin:0; padding:0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
.email-masthead { padding:25px 0; text-align:center; }
.email-masthead_logo { width:94px; }
.email-masthead_name { font-size:16px; font-weight:bold; color:#A8AAAF; text-decoration:none; text-shadow:0 1px 0 white; }
.email-body { width:100%; margin:0; padding:0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
.email-body_inner { width:570px; margin:0 auto; padding:0; -premailer-width:570px; -premailer-cellpadding:0; -premailer-cellspacing:0; background-color:#FFFFFF; }
.email-footer { width:570px; margin:0 auto; padding:0; -premailer-width:570px; -premailer-cellpadding:0; -premailer-cellspacing:0; text-align:center; }
.email-footer p { color:#A8AAAF; }
.body-action { width:100%; margin:30px auto; padding:0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; text-align:center; }
.body-sub { margin-top:25px; padding-top:25px; border-top:1px solid #EAEAEC; }
.content-cell { padding:45px; }
@media only screen and (max-width:600px) { .email-body_inner, .email-footer { width:100% !important; } }
@media (prefers-color-scheme:dark) { body,.email-body,.email-body_inner,.email-content,.email-wrapper,.email-masthead,.email-footer { background-color:#333333 !important; color:#FFF !important; } p,ul,ol,blockquote,h1,h2,h3,span,.purchase_item { color:#FFF !important; } .attributes_content,.discount { background-color:#222 !important; } .email-masthead_name { text-shadow:none !important; } }
:root { color-scheme:light dark; supported-color-schemes:light dark; }
</style>
</head>
<body>
<span class="preheader">Use this link to reset your password. The link is only valid for 12 hours.</span>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="https://auth.jakach.ch" class="f-fallback email-masthead_name">Jakach Login</a>
</td>
</tr>
<tr>
<td class="email-body" width="570" cellpadding="0" cellspacing="0">
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Hi '.$html_username.',</h1>
<p>You recently requested to reset your password for your Jakach login account. Use the button below to reset it. <strong>This password reset is only valid for the next 12 hours.</strong></p>
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="'.$html_link.'" class="f-fallback button button--green" target="_blank">Reset your password</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p><strong>Request Details:</strong></p>
<ul>
<li><strong>Date & Time:</strong> '.$date.'</li>
<li><strong>Device & Browser:</strong> '.$html_device.'</li>
<li><strong>Account:</strong> '.$html_mail.'</li>
<li><strong>IP Address:</strong> '.$html_ip.'</li>
<li><strong>Location:</strong> '.$html_loc.'</li>
</ul>
<p>Thanks,<br>The Jakach login team</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If you are having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">'.$html_link.'</p>
</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">Jakach.ch<br>CH-Switzerland.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
';
$message = [
"personalizations" => [
[
"to" => [
[
"email" => $mail
]
]
]
],
"from" => [
"email" => $SENDGRID_MAIL
],
"subject" => "Jakach login password reset",
"content" => [
[
"type" => "text/html",
"value" => $content
]
]
];
$url = "https://api.sendgrid.com/v3/mail/send";
// Initialize cURL
$ch = curl_init($url);
// Set cURL options
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $SENDGRID_KEY",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($message));
// Execute the cURL request
curl_exec($ch);
curl_close($ch);
}
//insert the token into our db
$valid_until=time()+(12 * 60 * 60);
$sql="INSERT INTO reset_tokens (auth_token, user_id,valid_until) VALUES (?,?,?);";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 'sii', $token_hash,$id,$valid_until);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
echo json_encode(['success' => true, 'message' => 'If the account has reset methods configured, a reset link has been sent.']);
?>