diff --git a/app-code/account/index.php b/app-code/account/index.php index 148f248..5e3dc2a 100644 --- a/app-code/account/index.php +++ b/app-code/account/index.php @@ -17,6 +17,7 @@ if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) { + @@ -39,19 +40,22 @@ if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) { @@ -78,6 +82,17 @@ if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) { + + +
+ + +
+ +
+ + +
@@ -119,15 +134,16 @@ if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) {

Using a passkey you can login with e.g. your fingerprint. If this is enabled you can still login using your password but using a passkey is faster.

-
+
+

You can get a message via telegram whenever somebody logs in to your account

+
+ + +
+
@@ -188,9 +204,12 @@ if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) { // Fill input fields document.getElementById('name').value = user.name; + document.getElementById('user_token').value = user.user_token; document.getElementById('email').value = user.email; document.getElementById('telegram').value = user.telegram_id; document.getElementById('twofa-switch').checked = user.twofa_enabled; + document.getElementById('message-switch').checked = user.login_message; + document.getElementById('last_login').value = user.last_login; }) .catch(error => { @@ -316,6 +335,43 @@ if (!isset($_SESSION["logged_in"]) || $_SESSION["logged_in"] !== true) { showErrorModal('Failed to send request. Please try again later.'); } }); + + const switchElement2 = document.getElementById('message-switch'); + + // Add an event listener for when the switch is changed + switchElement2.addEventListener('change', async function () { + // Get the current state of the switch + const isEnabled = switchElement2.checked; + if(document.getElementById('telegram').value.length!=0){ + try { + // Send the state to the backend using a POST request + const response = await fetch('/api/account/update_message.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + enable_message: isEnabled, // Send the new state of 2FA + }), + }); + + // Check if the response is successful + const result = await response.json(); + if (response.ok) { + // Handle success + showSuccessModal(result.message || (isEnabled ? 'Login messages enabled successfully.' : 'Login messages disabled successfully.')); + } else { + // Handle error + showErrorModal('Error: ' + (result.message || 'An error occurred while updating login messages.')); + } + } catch (error) { + console.error('Error:', error); + showErrorModal('Failed to send request. Please try again later.'); + } + }else{ + showErrorModal("Please configure your Telegram ID first."); + } + }); }); // Function to show error modal diff --git a/app-code/api/account/get_user_data.php b/app-code/api/account/get_user_data.php index aac923e..bc4d6e9 100644 --- a/app-code/api/account/get_user_data.php +++ b/app-code/api/account/get_user_data.php @@ -15,16 +15,18 @@ include "../../config/config.php"; $conn = new mysqli($DB_SERVERNAME, $DB_USERNAME, $DB_PASSWORD, $DB_DATABASE); $username=$_SESSION["username"]; -$sql="SELECT id, email, telegram_id, auth_method_enabled_2fa FROM users WHERE username = ?"; +$sql="SELECT id, email, telegram_id, auth_method_enabled_2fa, user_token, login_message FROM users WHERE username = ?"; $id=0; $email=""; $telegram_id=""; $twofa_enabled=""; +$user_token=""; +$login_message=0; $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,$email,$telegram_id,$twofa_enabled); +mysqli_stmt_bind_result($stmt, $id,$email,$telegram_id,$twofa_enabled,$user_token,$login_message); mysqli_stmt_fetch($stmt); $_SESSION["id"]=$id; @@ -33,7 +35,10 @@ $user_data = [ "name" => $username, "email" => $email, "telegram_id" => $telegram_id, - "twofa_enabled" => $twofa_enabled + "twofa_enabled" => $twofa_enabled, + "user_token"=>$user_token, + "last_login"=>$_SESSION["last_login"], + "login_message"=>$login_message ]; // Send JSON response diff --git a/app-code/api/account/update_message.php b/app-code/api/account/update_message.php new file mode 100644 index 0000000..31e2f0a --- /dev/null +++ b/app-code/api/account/update_message.php @@ -0,0 +1,67 @@ + false, + 'message' => 'Not logged in' + ]); + exit(); +} + +// Include database configuration +include "../../config/config.php"; + +// Create a new database connection +$conn = new mysqli($DB_SERVERNAME, $DB_USERNAME, $DB_PASSWORD, $DB_DATABASE); + +// Check for database connection errors +if ($conn->connect_error) { + echo json_encode([ + 'success' => false, + 'message' => 'Database connection failed: ' . $conn->connect_error + ]); + exit(); +} + +// Get the logged-in user's ID and username from the session +$id = $_SESSION["id"]; +$username = $_SESSION["username"]; + +// Get the raw POST data (JSON) +$data = json_decode(file_get_contents("php://input")); +if($data->enable_message==true){ + $sql="UPDATE users SET login_message=1 WHERE id = ?"; + if ($update_stmt = $conn->prepare($sql)) { + $update_stmt->bind_param("i", $id); + if ($update_stmt->execute()) { + echo json_encode(['success' => true, 'message' => 'Login messages enabled.']); + } else { + echo json_encode(['success' => false, 'message' => 'Failed to enable login messages.']); + } + $update_stmt->close(); + } else { + echo json_encode(['success' => false, 'message' => 'Database error.']); + } +} + +if($data->enable_message==false){ + //create 2fa secret key + $sql="UPDATE users SET login_message=0 WHERE id = ?"; + if ($update_stmt = $conn->prepare($sql)) { + $update_stmt->bind_param("i",$id); + if ($update_stmt->execute()) { + echo json_encode(['success' => true, 'message' => 'Login messages disabled.']); + } else { + echo json_encode(['success' => false, 'message' => 'Failed to disable login messages.']); + } + $update_stmt->close(); + } else { + echo json_encode(['success' => false, 'message' => 'Database error.']); + } +} + +?> + diff --git a/app-code/api/login/redirect.php b/app-code/api/login/redirect.php index 613dcf5..29e536c 100644 --- a/app-code/api/login/redirect.php +++ b/app-code/api/login/redirect.php @@ -1,6 +1,33 @@ 'done', - 'redirect' => '' + 'redirect' => '/account/' ]; } + //update last login + $ip=$_SERVER["REMOTE_ADDR"]; + $date=date('Y-m-d H:i:s'); + $last_login_msg=$date." from ".$ip; + $sql="UPDATE users SET last_login = ? WHERE id = ?"; + $stmt = mysqli_prepare($conn, $sql); + mysqli_stmt_bind_param($stmt, 'si', $last_login_msg,$user_id); + mysqli_stmt_execute($stmt); + mysqli_stmt_close($stmt); + //send login message + if($_SESSION["login_message"] && $_SESSION["logged_in"]!==true){ + $device = $_SERVER['HTTP_USER_AGENT']; + $location=get_location_from_ip($ip); + $message = "⚠️ *Login Warning*\n\n" + . "We noticed a login attempt with your account.\n\n" + . "*Date&Time*: $date\n" + . "*Device&Browser*: $device\n" + . "*Location*: ".$location["country"].", ".$location["state"].", ".$location["city"]."\n" + . "*Account*: ".$_SESSION["username"]."\n" + . "*IP*: $ip\n\n" + . "If this was you, you can ignore this message. If not, please secure your account immediately."; + + // Telegram API URL + $url = "https://api.telegram.org/$TELEGRAM_BOT_API/sendMessage"; + + // Data to be sent in the POST request + $telegram_id=$_SESSION["telegram_id"]; + $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); + + } + + $_SESSION["logged_in"]=true; echo(json_encode($data)); } else{ @@ -60,7 +133,7 @@ else{ $username=$_SESSION["username"]; $_SESSION["needs_auth"]=false; $_SESSION["logged_in"]=false; - $sql="SELECT auth_method_required_pw, auth_method_required_2fa, auth_method_required_passkey, id, user_token FROM users WHERE username = ?"; + $sql="SELECT auth_method_required_pw, auth_method_required_2fa, auth_method_required_passkey, id, user_token,last_login, login_message,telegram_id FROM users WHERE username = ?"; $stmt = mysqli_prepare($conn, $sql); mysqli_stmt_bind_param($stmt, 's', $username); mysqli_stmt_execute($stmt); @@ -69,8 +142,11 @@ else{ $mfa=0; $passkey=0; $user_token=""; + $last_login=""; + $login_message=0; + $telegram_id=""; if(mysqli_stmt_num_rows($stmt) == 1){ - mysqli_stmt_bind_result($stmt, $pw,$mfa,$passkey,$user_id,$user_token); + mysqli_stmt_bind_result($stmt, $pw,$mfa,$passkey,$user_id,$user_token,$last_login,$login_message,$telegram_id); mysqli_stmt_fetch($stmt); $_SESSION["pw_required"] = $pw; $_SESSION["pw_authenticated"] = ($pw == 0) ? 1 : 0; // If $pw is 0, set pw_authenticated to 1 @@ -80,6 +156,9 @@ else{ $_SESSION["passkey_authenticated"] = ($passkey == 0) ? 1 : 0; $_SESSION["id"]=$user_id; $_SESSION["user_token"]=$user_token; + $_SESSION["last_login"]=$last_login; + $_SESSION["telegram_id"]=$telegram_id; + $_SESSION["login_message"]=$login_message; $data=[ 'message' => 'prepared_start_auth', 'redirect' => '/login/' diff --git a/app-code/api/login/reset_pw.php b/app-code/api/login/reset_pw.php new file mode 100644 index 0000000..3981fcd --- /dev/null +++ b/app-code/api/login/reset_pw.php @@ -0,0 +1,70 @@ + 'error', 'message' => 'Missing required fields.']); + exit; + } + include "../../config/config.php"; + + // Create a new database connection + $conn = new mysqli($DB_SERVERNAME, $DB_USERNAME, $DB_PASSWORD, $DB_DATABASE); + + $token = $_POST['token']; + $user_id=""; + $valid_until=0; + $password = $_POST['password']; + $confirmPassword = $_POST['confirm_password']; + $sql="SELECT user_id, valid_until FROM reset_tokens WHERE auth_token=?;"; + $stmt = mysqli_prepare($conn, $sql); + mysqli_stmt_bind_param($stmt, 's', $token); + mysqli_stmt_execute($stmt); + mysqli_stmt_store_result($stmt); + mysqli_stmt_bind_result($stmt, $user_id,$valid_until); + mysqli_stmt_fetch($stmt); + if(mysqli_stmt_num_rows($stmt) > 0 && time()<$valid_until){ + mysqli_stmt_close($stmt); + // Check if passwords match + if ($password !== $confirmPassword) { + echo json_encode(['status' => 'error', 'message' => 'Passwords do not match.']); + exit; + } + if (strlen($password)<12) { + echo json_encode(['status' => 'error', 'message' => 'Password must be at least 12 characters.']); + exit; + } + + $new_pepper=bin2hex(random_bytes(32)); + // Hash the password / a salt is added automaticly + $hashed_password = password_hash($password.$new_pepper, PASSWORD_BCRYPT); + + // Update the password in the database + $update_sql = "UPDATE users SET password = ?, pepper = ? WHERE id = ?"; + if ($update_stmt = $conn->prepare($update_sql)) { + $update_stmt->bind_param("ssi", $hashed_password, $new_pepper, $user_id); + if ($update_stmt->execute()) { + echo json_encode(['status' => 'success','success' => true, 'message' => 'Password updated successfully.']); + } else { + echo json_encode(['success' => false, 'message' => 'Failed to update password.']); + } + $update_stmt->close(); + } else { + echo json_encode(['success' => false, 'message' => 'Database error.']); + } + }else { + mysqli_stmt_close($stmt); + echo json_encode(['success' => false, 'message' => 'Ivalid auth token']); + } + //remove token + $sql="DELETE FROM reset_tokens WHERE auth_token = ?;"; + $stmt = mysqli_prepare($conn, $sql); + mysqli_stmt_bind_param($stmt, 's', $token); + mysqli_stmt_execute($stmt); + mysqli_stmt_close($stmt); + +} else { + // If it's not a POST request, show error + echo json_encode(['status' => 'error', 'message' => 'Invalid request method.']); +} +?> + diff --git a/app-code/api/login/send_reset_link.php b/app-code/api/login/send_reset_link.php new file mode 100644 index 0000000..0821ae5 --- /dev/null +++ b/app-code/api/login/send_reset_link.php @@ -0,0 +1,192 @@ + $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)){ + $content = " + + + + + + Password Reset + + + + +
+

Password Reset Request

+

Hi $mail,

+

You have requested a password reset link. Here it is:

+

Click here to reset your password

+

If you did not request this, please ignore this email. If you did, you can reset your password using the link above.

+ +

Request Details:

+ + +

If this was you, you can reset your password. If this was not you, someone else may have tried to reset your password.

+ + +
+ + + +"; + + + $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()+8600; +$sql="INSERT INTO reset_tokens (auth_token, user_id,valid_until) VALUES (?,?,?);"; +$stmt = mysqli_prepare($conn, $sql); +mysqli_stmt_bind_param($stmt, 'sii', $token,$id,$valid_until); +mysqli_stmt_execute($stmt); +mysqli_stmt_close($stmt); + +?> diff --git a/app-code/assets/image.png b/app-code/assets/image.png new file mode 100644 index 0000000..1377001 Binary files /dev/null and b/app-code/assets/image.png differ diff --git a/app-code/favicon.ico b/app-code/favicon.ico new file mode 100644 index 0000000..6c939ef Binary files /dev/null and b/app-code/favicon.ico differ diff --git a/app-code/install/create_db.php b/app-code/install/create_db.php index 66c6034..18a91b2 100644 --- a/app-code/install/create_db.php +++ b/app-code/install/create_db.php @@ -62,6 +62,8 @@ username VARCHAR(255) NOT NULL UNIQUE, public_key TEXT DEFAULT '', credential_id VARBINARY(255), + last_login VARCHAR(255), + login_message INT, user_token VARCHAR(128), counter INT DEFAULT 0, 2fa VARCHAR(255), @@ -114,6 +116,25 @@ '; } + $sql="CREATE TABLE IF NOT EXISTS reset_tokens ( + id INT AUTO_INCREMENT PRIMARY KEY, + auth_token VARCHAR(256), + user_id INT, + valid_until INT + );"; + + + if ($conn->query($sql) === TRUE) { + echo '
'; + } else { + $success=0; + echo '
'; + } + if($success!==1){ diff --git a/app-code/login/pw.php b/app-code/login/pw.php index 9136bab..6ba6f83 100644 --- a/app-code/login/pw.php +++ b/app-code/login/pw.php @@ -32,6 +32,7 @@ echo('Use passkey instead'); } ?> +
Forgott password
@@ -56,8 +57,32 @@ + + + + + + diff --git a/docker-compose.yml b/docker-compose.yml index 07b32ad..f69599b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,7 @@ services: ipv4_address: 192.168.5.3 ports: - "444:443" + - "80:443" depends_on: - jakach-login-db volumes: