-
Who's Online 0 Members, 0 Anonymous, 86 Guests (See full list)
- There are no registered users currently online
All Activity
- Today
-
Nile Web Design & Digital Marketing Dubai is a leading web design Dubai, known for delivering high-quality, innovative, and SEO-friendly website solutions tailored to meet your business goals. With a strong focus on user experience, functionality, and modern design trends, Nile helps businesses establish a strong digital presence and stay ahead in a competitive online market. Their expert team combines creativity and technical expertise to create responsive, engaging websites that drive traffic and conversions. Whether you need a new website or a redesign, Nile ensures every project reflects your brand identity and supports your growth. Partner with Nile for reliable, result-driven web design services.
-
johnabbat54 joined the community
-
automatically logout deleted user with ajax no refresh
Psycho replied to ssscriptties's topic in PHP Coding Help
I'd like to add my two cents on this as well. Having a process that automatically logs a user out is a nice to have feature. Ensuring that all service calls check the current status and permissions of the user making a request is a must have feature. You specifically asked about "users when they delete the accounts they're logged into", but that should also include other users that may be logged on who are deleted by a different user. The former would be a fairly trivial task, but the latter would require some type of polling or websocket functionality (as gizmola stated) which, in my opinion, adds unnecessary complexity. If you have all your other value add features then, sure, add that ability. But you would still need to add server-side validation for every request anyway. For an edge case scenario where a user is "deleted" while they are logged in I would be OK with some unhandled errors in the UI as long as I was confident their calls were not being accepted/completed. Not saying there shouldn't be error handling - only that it is not as important as blocking the requests. I would suggest the following: Create a single process/function that validates that a user is "Active" (or whatever that means for your application) and returns the permissions they have (assuming there are distinct permission) Every page load should run that common process. If the user is not active or does not have the requisite permissions for the page being loaded, redirect them to an appropriate error page I assume you have various AJAX driven features. All back-end AJAX calls should call the same common process and if the user is not active or does not have the requisite permissions for the process being called, have the AJAX response return an appropriate error. The client-side implementation will need to check for such errors and react accordingly (I'd redirect to the same error pages as noted above). -
automatically logout deleted user with ajax no refresh
gizmola replied to ssscriptties's topic in PHP Coding Help
mac_gyver as usual provided you with a clear answer. HTTP protocol is request/response. Without some other streaming protocol, once a client has received a response, the tcp connection(s) required to get all the assets for the page, and the building of that page are close and the rendering of the page and any interactivity is entirely client side. New requests can be initiated, or you can have some javascript (ajax) that makes requests using javascript that can then be used to update the page without having an entirely new HTTP request (GET/POST/PUT/DELETE). There are ways to have a client poll ajax calls, or alternatively to use websocket protocol. You often see websockets used to provide more real time functionality. Regardless, for every Request sent to the server, checking for authorization of the client must be done. In other words, it should not matter if someone has their browser open to your site, as a logged in user who has now had their account deleted/suspended etc. All that matters is that the deletion/suspension/logout is enforced on the CURRENT HTTP request. -
OketraSuperpower joined the community
-
automatically logout deleted user with ajax no refresh
mac_gyver replied to ssscriptties's topic in PHP Coding Help
the code for every page (http request) must enforce what the current user can do or see on that page. if you do what i wrote in one of your recent threads - the code performing the admin actions will find that the current user is either not logged in, doesn't exist, or no longer has a role that allows access to the code on that page and the user will be prevented from performing any action. -
from my last post I figured out how to logout users when they delete the accounts they're logged into and checking the sessions they're using, but it isn't automatic and needs a page refresh which means the user has time to delete other users on the admin page. I want to make it so the moment the account is deleted they're logged out without refresh... is that possible? this is the current code: <?php function pdo_connect_mysql() { $DATABASE_HOST = 'localhost'; $DATABASE_USER = 'root'; $DATABASE_PASS = ''; $DATABASE_NAME = 'phpticket'; try { return new PDO('mysql:host=' . $DATABASE_HOST . ';dbname=' . $DATABASE_NAME . ';charset=utf8', $DATABASE_USER, $DATABASE_PASS); } catch (PDOException $exception) { exit('Failed to connect to database!'); } } function getUser($email) { global $conn; if (empty($email)) { return null; } $stmt = $conn->prepare("SELECT id, username, email, role FROM users WHERE email = ?"); $stmt->bind_param("s", $email); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { $userData = $result->fetch_assoc(); $stmt->close(); $user = new stdClass(); $user->id = $userData['id']; $user->username = $userData['username']; $user->email = $userData['email']; $user->role = $userData['role']; $user->isActive = true; return $user; } $stmt->close(); return null; } ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ALnasser | Ticketing System</title> <link href="style.css" rel="stylesheet" type="text/css"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css"> </head> <body> <nav class="navtop"> <div> <img src="alnasser_nobg.png"><h1><a href="index.php" style="color:white;font-size:25px;font-weight: normal;">AlNasser Help Desk</a></h1> <a href="index.php"><i class="fas fa-ticket-alt"></i>Tickets</a> </div> </nav> </body> </html> <?php include 'functions.php'; include "config.php"; $currentUser = getUser($_SESSION['email']); if (!$currentUser || !$currentUser->isActive) { session_destroy(); setcookie('remember_token', '', time() - 3600, "/"); setcookie('email', '', time() - 3600, "/"); session_start(); $_SESSION['login_error'] = 'Session has expired. Please log in again.'; $_SESSION['active_form'] = 'login'; header("Location: login&signup.php"); exit(); } <?php $host = "localhost"; $user = "root"; $password = ""; $database = "phpticket"; $conn = new mysqli($host, $user, $password, $database); if ($conn->connect_error) { die("Connection failed ". $conn->connect_error); }
-
Great answer from Barand to your specific question. As for your initial question, start with your entities, and the relationships between them. You mentioned: A DJ Organizations Events Playlists I'm unclear if this means that an event could have multiple playlists, or just one. Implied entities are: artist album song/track So you want to start with the entities and determine which attributes they require. Every entity will become a table, and every table needs a primary key, which unless you have expertise and a strong reason not to, should be auto incremented unsigned "integer" types. You want to use the smallest reasonable type. Some "lookup" tables, you will know in advance will never have more than a handful of rows. Use a tinyint type. Use the smallest type you can get away with. Organizations is a good example here, where you can use a (with mysql for example) a smallint, which unsigned means you could have up to 64k rows in it. With little chance of ever having anything close to that number of orgs, stay with the 2 byte primary key instead of making everything and integer or worse yet a bigint. Once you have the entities ready, then relate them together, by determining the type of relationship needed (one to one, one to many, many to many) and at that point add foreign keys and add ables as needed. There are many ERD design tools that can help with the design process.
-
@Strider64 my friend, lose the closing PHP tags -- as per https://www.php-fig.org/per/coding-style/. These days I'd recommend that you use mkcert for local development, and not have a configuration variable to get around the use of https only cookie settings. It's just inviting a mistake to be made. I don't know if you've started to make use of docker, but DDEV is a really nice wrapper for setting up docker based PHP development environments, and it integrates mkcert, so you don't even have to invest any time in figuring out mkcert yourself, as they've integrated that into DDEV. It's also a cli tool, which I like.
-
How to Properly Add SEO Anchor Text in HTML for a Keyword Like 'kedi'?
gizmola replied to zohaib999999's topic in HTML Help
Usually we would not allow promotion like yours, but in this case it's on topic, and also a helpful example, as the forum utilizes the very techniques your article covers. - Yesterday
-
Samuel8819 joined the community
-
I personally have `setcookie` setup up like this: // Set a secure cookie with the login token setcookie('login_token', $token, [ 'expires' => strtotime('+6 months'), 'path' => '/', 'domain' => $cookieDomain, // Adjusted for environment 'secure' => $cookieSecure, // Adjusted for environment 'httponly' => true, 'samesite' => 'Lax' ]); as it is easier to debut in my opinion. Here's my full login script for my personal website: <?php // Include the configuration file and autoload file from the composer. require_once __DIR__ . '/../config/clearwebconfig.php'; require_once "vendor/autoload.php"; // Import the ErrorHandler and Database classes from the PhotoTech namespace. use clearwebconcepts\{ ErrorHandler, Database, LoginRepository as Login }; // Create an ErrorHandler instance $errorHandler = new ErrorHandler(); // Set the exception handler to use the ErrorHandler instance set_exception_handler([$errorHandler, 'handleException']); // Create a Database instance and establish a connection $database = new Database(); $pdo = $database->createPDO(); // Create a LoginRepository instance with the database connection $login = new Login($pdo); $checkStatus = new Login($pdo); // Start session if not already started if (session_status() == PHP_SESSION_NONE) { session_start(); } // Redirect to dashboard if the user is already logged in if ($login->check_login_token()) { header('Location: dashboard.php'); exit(); } // Generate a CSRF token if it doesn't exist and store it in the session if (!isset($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } // Detect environment $isLocal = in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1']); $cookieDomain = $isLocal ? '' : DOMAIN; $cookieSecure = !$isLocal; // Set to true on remote server // Process the login form submission if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Check if the submitted CSRF token matches the one stored in the session if (hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { // Sanitize the username and password input $username = strip_tags($_POST['username']); $password = $_POST['password']; // Verify the user's credentials if ($login->verify_credentials($username, $password)) { // Generate a secure login token $token = bin2hex(random_bytes(32)); // Store the login token in the database $login->store_token_in_database($_SESSION['user_id'], $token); // Set a secure cookie with the login token setcookie('login_token', $token, [ 'expires' => strtotime('+6 months'), 'path' => '/', 'domain' => $cookieDomain, // Adjusted for environment 'secure' => $cookieSecure, // Adjusted for environment 'httponly' => true, 'samesite' => 'Lax' ]); // Store the login token in the session $_SESSION['login_token'] = $token; // Redirect the user to the dashboard header('Location: dashboard.php'); exit; } else { // Log error message for invalid username or password $error = 'Invalid username or password'; error_log("Login error: " . $error); } } else { // Display an error message $error = 'Invalid CSRF token'; error_log("Login error: " . $error); $error = 'An error occurred. Please try again.'; } } // Generate a random nonce value $nonce = base64_encode(random_bytes(16)); ?>
-
Your form for entering new events would contain a dropdown listing organisations for the user to choose. The value of each dropdown option would br the organisation's id. This id is posted (with the other event data) for insertion into the event resord.
- Last week
-
I'm a bit rusty in the database department and would appreciate a quick review and any pointers. I'm planning to set up a database for a DJ (that colloquially spins records). The DJ does work with multiple organizations. Each organization sponsors several events per year. Each event has a separate 'playlist' depending on the audience. From this skeletal overview, I'm thinking that I should establish a primary key/foreign key relationship (to avoid future complication?). I'm certain that referencing an auto increment ID as the foreign key associated with the 180 character organization name will save valuable disk space, but how do I reference it when I'm adding a new event that the XYZ org is sponsoring? Any other refresher pointers appreciated.
-
How to Properly Add SEO Anchor Text in HTML for a Keyword Like 'kedi'?
gizmola replied to zohaib999999's topic in HTML Help
If the link has a "rel" attribute equal to "nofollow" that tells search engines that they should not follow the link. So yes, that will effect SEO. This article explains "nofollow" and other values for the "rel" attribute that are important for SEO. In summary, "nofollow" tells search engines to ignore the link. -
Hi everyone, I'm working on a project that involves pet-related content, specifically about male cats. I want to add an anchor text in HTML that links to my website with the word kedi (which means "cat" in Turkish). Here’s what I’m trying to achieve: I want to link the word kedi with my url (Mentioned in anchor text) Is this the correct and SEO-friendly way to do it? Also, does adding rel="nofollow" or target="_blank" impact SEO in such internal linking cases? Any tips would be appreciated
-
zohaib999999 joined the community
-
Which is a bad fix. What you did was make your site dramatically less secure, by allowing people to create cookies without going through https:// which is a really bad idea. Is this an issue that only comes up in development, perhaps because you don't have a local cert installed? When you have a problem you really have to do a better job of describing the environment under which you had a problem. 99% of the time, if you had working code and it stops working, there is an explanation for that having to do with some environmental change. One tip: on your register/login script, as with any other pure PHP scripts, you should remove the ending PHP tag. I believe that someone else explained to you on another thread, that using session variables to handle bad login attempts and lockouts is another really bad idea. People wanting to brute force won't accept a session cookie, so all that logic will have no effect on those people or their automated brute force scripting. You have to log bad attempts using some sort of persistence (typically a table related to your user table) which include the datetime/timestamp and the IP address. You can then lock out an account for a period of time, as well as locking out IP addresses that might be trying a range of different email/password combinations. You want to prevent both.
-
for anybody wondering I fixed it I changed setcookie('email', $email, time() + (60 * 60 * 24 * 30), "/", "", true, true); to setcookie('email', $email, time() + (60 * 60 * 24 * 30), "/", "", false, true);
-
my code was working jus fine yesterday but when I woke up today and tried it out it wouldn't create cookies, and I'm wondering why? <?php session_start(); require_once 'config.php'; if (!isset($_SESSION['email']) && isset($_COOKIE['email'], $_COOKIE['remember_token'])) { $email = $_COOKIE['email']; $token = $_COOKIE['remember_token']; $stmt = $conn->prepare("SELECT u.*, rt.token FROM users u INNER JOIN remember_tokens rt ON u.id = rt.user_id WHERE u.email = ? AND rt.token = ? AND rt.expires_at > NOW()"); $stmt->bind_param("ss", $email, $token); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { $user = $result->fetch_assoc(); // Set session variables $_SESSION['username'] = $user['username']; $_SESSION['email'] = $user['email']; $_SESSION['role'] = $user['role']; $_SESSION['location'] = $user['location']; $_SESSION['used_remember_me'] = true; $newToken = bin2hex(random_bytes(32)); $expiresAt = date('Y-m-d H:i:s', time() + (60 * 60 * 24 * 30)); $updateStmt = $conn->prepare("UPDATE remember_tokens SET token = ?, expires_at = ? WHERE user_id = ?"); $updateStmt->bind_param("ssi", $newToken, $expiresAt, $user['id']); $updateStmt->execute(); $updateStmt->close(); setcookie('remember_token', $newToken, time() + (60 * 60 * 24 * 30), "/", "", true, true); if ($user['role'] === 'admin') { header("Location: admin.php"); } else { header("Location: index.php"); } exit(); } else { setcookie('remember_token', '', time() - 3600, "/"); setcookie('email', '', time() - 3600, "/"); } $stmt->close(); } $errors = [ 'login' => $_SESSION['login_error'] ?? '', 'register' => $_SESSION['register_error'] ?? '' ]; $successMessage = $_SESSION['register_success'] ?? ''; $activeForm = $_SESSION['active_form'] ?? 'login'; $loginAttempts = $_SESSION['login_attempts'] ?? 0; $lockoutTime = $_SESSION['lockout_time'] ?? 0; unset($_SESSION['login_error'], $_SESSION['register_error'], $_SESSION['register_success'], $_SESSION['active_form']); function showError($error) { return !empty($error) ? "<p class='error-message'>" . htmlspecialchars($error) . "</p>" : ""; } function showSuccess($message) { return !empty($message) ? "<p class='success-message'>" . htmlspecialchars($message) . "</p>" : ""; } function isActiveForm($formName, $activeForm) { return $formName === $activeForm ? 'active' : ''; } $currentTime = time(); $remainingLockoutTime = 0; $isLocked = false; if ($loginAttempts >= 3) { if (($currentTime - $lockoutTime) < 40) { $isLocked = true; $remainingLockoutTime = 40 - ($currentTime - $lockoutTime); } else { $_SESSION['login_attempts'] = 0; $_SESSION['lockout_time'] = 0; } } ?> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f5f5f5; } .container { display: flex; flex-direction: column; justify-content: center; align-items: center; min-height: 100vh; width: 100%; padding: 20px; box-sizing: border-box; } .form-box { width: 100%; max-width: 450px; padding: 30px; background: #0061af; border-radius: 10px; display: none; margin: 10px 0; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .form-box.active { display: block; } .logo-container { text-align: center; margin-bottom: 20px; } .logo-container img { width: 120px; height: auto; } h2 { font-size: 28px; text-align: center; margin-bottom: 20px; color: white; } input, select { width: 100%; padding: 12px; border: none; outline: none; font-size: 16px; margin-bottom: 20px; border-radius: 6px; background-color: rgba(255, 255, 255, 0.9); } button { display: flex; align-items: center; justify-content: center; background-color: #f3f7fe; color: #3b82f6; border: none; cursor: pointer; border-radius: 8px; width: 100%; height: 45px; transition: 0.3s; text-decoration: none; font-size: 16px; font-weight: 600; margin-bottom: 15px; } button:hover { background-color: #3b82f6; box-shadow: 0 0 0 5px #3b83f65f; color: #fff; } .error-message { padding: 12px; background: #f8d7da; border-radius: 6px; color: #a42834; text-align: center; margin-bottom: 20px; } .success-message { padding: 12px; background: #d4edda; border-radius: 6px; color: #155724; text-align: center; margin-bottom: 20px; } .form-footer { text-align: center; color: white; margin-top: 15px; } .form-footer a { color: #aad4ff; text-decoration: none; } .form-footer a:hover { text-decoration: underline; } .sso-button { background-color: #0078d4 !important; color: white !important; } .sso-button:hover { background-color: #106ebe !important; box-shadow: 0 0 0 5px rgba(0, 120, 212, 0.3) !important; } .divider { display: flex; align-items: center; margin: 20px 0; color: white; } .divider::before, .divider::after { content: ""; flex: 1; border-bottom: 1px solid rgba(255, 255, 255, 0.3); } .divider-text { padding: 0 10px; } ::-webkit-scrollbar { width: 10px; } ::-webkit-scrollbar-track { background: #f1f1f1; } ::-webkit-scrollbar-thumb { background: #0061af; } ::-webkit-scrollbar-thumb:hover { background: #0363b1; } #countdown { padding: 12px; background: #ffeeba; border-radius: 6px; color: #856404; text-align: center; margin-bottom: 20px; font-weight: bold; } .remember-me { display: flex; align-items: center; margin-bottom: 20px; color: white; } .remember-me input { width: auto; margin-right: 10px; margin-bottom: 0; } </style> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ALnasser | Ticketing System</title> <link rel="icon" type="image/x-icon" href="alnasser.png"> <link href="style.css" rel="stylesheet" type="text/css"> </head> <body> <div class="container"> <div class="form-box <?= isActiveForm('login', $activeForm); ?>" id="login-form"> <form action="login_register.php" method="post"> <center><img width="30%" height="auto" src="alnasser_nobg.png" alt="ALnasser Logo"></center> <h2>Login</h2> <?= showError($errors['login']); ?> <button type="button" class="sso-button" onclick="window.location.href='windows_login.php'"> Sign in with Windows Domain Account </button> <div class="divider"><span class="divider-text">OR</span></div> <input type="email" name="email" placeholder="Email" required> <input type="password" name="password" placeholder="Password" required> <div class="remember-me"> <input type="checkbox" id="remember_me" name="remember_me"> <label for="remember_me">Remember me for 30 days</label> </div> <?php if ($isLocked): ?> <div id="countdown">Too many failed attempts. Please try again in <span id="time"></span> seconds.</div> <button type="submit" name="login" disabled style="cursor: not-allowed; background-color: #ccc;">Login</button> <?php else: ?> <button type="submit" name="login">Login</button> <?php endif; ?> <p class="form-footer">Don't have an account? <a href="#" onclick="showForm('register-form')">Register</a></p> </form> </div> <div class="form-box <?= isActiveForm('register', $activeForm); ?>" id="register-form"> <form action="login_register.php" method="post"> <center><img width="30%" height="auto" src="alnasser_nobg.png" alt="ALnasser Logo"></center> <h2>Register</h2> <?= showError($errors['register']); ?> <?= showSuccess($successMessage); ?> <input type="text" name="username" placeholder="Username" required> <input type="email" name="email" placeholder="Email" pattern="[a-zA-Z0-9._%+-]+@alnasser\.eg$" required> <input type="password" name="password" placeholder="Password" required> <select name="role" required> <option value="">--Select Role--</option> <option value="user">User</option> <option value="admin">Admin</option> <option value="technician">Technician</option> </select> <select name="location" required> <option value="">--Select Location--</option> <option value="Asiout">Asiout</option> <option value="Zizinia">Zizinia</option> <option value="Aswan">Aswan</option> <option value="Helwan">Helwan</option> <option value="Menia">Menia</option> <option value="Mokattam">Mokattam</option> <option value="Arcadia">Arcadia</option> <option value="October">October</option> <option value="Tagamoa">Tagamoa</option> <option value="Maadi">Maadi</option> <option value="Heliopolis">Heliopolis</option> <option value="Nasr city">Nasr city</option> <option value="Obour">Obour</option> <option value="Qena">Qena</option> <option value="Smouha">Smouha</option> <option value="Haram">Haram</option> <option value="Sohag1">Sohag1</option> <option value="Bani Suef">Bani Suef</option> <option value="Mohandseen">Mohandseen</option> <option value="Tanta">Tanta</option> <option value="Mahalla">Mahalla</option> <option value="Zaqaziq">Zaqaziq</option> <option value="Shebeen">Shebeen</option> <option value="Qusseya">Qusseya</option> <option value="Mansoura2">Mansoura2</option> <option value="Luxor">Luxor</option> <option value="Damanhor">Damanhor</option> <option value="Hadayek">Hadayek</option> <option value="Agami">Agami</option> <option value="Suez">Suez</option> <option value="Fisal">Fisal</option> <option value="ismailia">ismailia</option> <option value="Mansoura 3">Mansoura 3</option> <option value="Abas el3qad">Abas el3qad</option> <option value="mohy eldeen">mohy eldeen</option> <option value="Sohag2">Sohag2</option> <option value="Zaharaa El-Maadi">Zaharaa El-Maadi</option> <option value="Gesr Al-Suez">Gesr Al-Suez</option> <option value="Shoubra">Shoubra</option> <option value="Fayoum">Fayoum</option> <option value="Hurghada">Hurghada</option> <option value="Sharm ElSheikh">Sharm ElSheikh</option> <option value="Mashaal">Mashaal</option> <option value="Victoria">Victoria</option> <option value="Al Rehab">Al Rehab</option> <option value="Madinaty">Madinaty</option> <option value="Mall of Egypt">Mall of Egypt</option> <option value="Gardenia">Gardenia</option> <option value="Tanta 2">Tanta 2</option> <option value="Port Said">Port Said</option> <option value="Town Center Mall">Town Center Mall</option> <option value="Office">Office</option> <option value="Online">Online</option> </select> <button type="submit" name="register">Register</button> <p class="form-footer">Already have an account? <a href="#" onclick="showForm('login-form')">Login</a></p> </form> </div> </div> <script src="script.js"></script> <script> <?php if ($isLocked): ?> let remainingTime = <?= $remainingLockoutTime ?>; const countdownElement = document.getElementById('time'); function updateCountdown() { if (remainingTime > 0) { countdownElement.textContent = remainingTime; remainingTime--; setTimeout(updateCountdown, 1000); } else { window.location.reload(); } } updateCountdown(); <?php endif; ?> function showForm(formId) { document.querySelectorAll('.form-box').forEach(box => box.classList.remove('active')); document.getElementById(formId).classList.add('active'); } window.onload = function() { const activeFormId = '<?= htmlspecialchars($activeForm) ?>-form'; showForm(activeFormId); }; </script> </body> </html> <?php session_start(); require_once 'config.php'; if (isset($_POST['register'])) { $username = trim($_POST['username']); $email = trim($_POST['email']); $password_raw = $_POST['password']; $role = $_POST['role']; $location = $_POST['location']; if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) { $_SESSION['register_error'] = 'Username can only contain letters, numbers, and underscores.'; $_SESSION['active_form'] = 'register'; header("Location: login&signup.php"); exit(); } if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $_SESSION['register_error'] = 'Invalid email format.'; $_SESSION['active_form'] = 'register'; header("Location: login&signup.php"); exit(); } if (!preg_match('/@alnasser\.eg$/', $email)) { $_SESSION['register_error'] = 'Only @alnasser.eg email addresses are allowed.'; $_SESSION['active_form'] = 'register'; header("Location: login&signup.php"); exit(); } if (strlen($password_raw) < 8 || !preg_match('/[A-Za-z]/', $password_raw) || !preg_match('/[0-9]/', $password_raw) || !preg_match('/[^A-Za-z0-9]/', $password_raw)) { $_SESSION['register_error'] = 'Password must be at least 8 characters long and include letters, numbers, and symbols.'; $_SESSION['active_form'] = 'register'; header("Location: login&signup.php"); exit(); } $password_hashed = password_hash($password_raw, PASSWORD_DEFAULT); $stmt = $conn->prepare("SELECT email FROM users WHERE email = ?"); $stmt->bind_param("s", $email); $stmt->execute(); $checkEmail = $stmt->get_result(); if ($checkEmail->num_rows > 0) { $_SESSION['register_error'] = 'Email is already registered.'; $_SESSION['active_form'] = 'register'; } else { $stmt = $conn->prepare("INSERT INTO users (username, email, password, role, location) VALUES (?, ?, ?, ?, ?)"); $stmt->bind_param("sssss", $username, $email, $password_hashed, $role, $location); if ($stmt->execute()) { $_SESSION['active_form'] = 'login'; $_SESSION['register_success'] = 'Registration successful! Please login.'; } else { error_log("Registration failed: " . $stmt->error); $_SESSION['register_error'] = 'Registration failed. Please try again.'; $_SESSION['active_form'] = 'register'; } } $stmt->close(); $conn->close(); header("Location: login&signup.php"); exit(); } if (isset($_POST['login'])) { $email = trim($_POST['email']); $password = $_POST['password']; $loginAttempts = $_SESSION['login_attempts'] ?? 0; $lockoutTime = $_SESSION['lockout_time'] ?? 0; $currentTime = time(); if ($loginAttempts >= 3 && ($currentTime - $lockoutTime < 40)) { $_SESSION['login_error'] = 'Account locked due to too many failed attempts. Please wait.'; $_SESSION['active_form'] = 'login'; header("Location: login&signup.php"); exit(); } if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $_SESSION['login_error'] = 'Invalid email format.'; $_SESSION['active_form'] = 'login'; header("Location: login&signup.php"); exit(); } if (!preg_match('/@alnasser\.eg$/', $email)) { $_SESSION['login_error'] = 'Only @alnasser.eg email addresses are allowed.'; $_SESSION['active_form'] = 'login'; header("Location: login&signup.php"); exit(); } $stmt = $conn->prepare("SELECT * FROM users WHERE email = ?"); $stmt->bind_param("s", $email); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { $user = $result->fetch_assoc(); if (password_verify($password, $user['password'])) { $_SESSION['username'] = $user['username']; $_SESSION['email'] = $user['email']; $_SESSION['role'] = $user['role']; $_SESSION['location'] = $user['location']; $_SESSION['login_attempts'] = 0; $_SESSION['lockout_time'] = 0; if (!empty($_POST['remember_me'])) { $token = bin2hex(random_bytes(32)); $expiresAt = date('Y-m-d H:i:s', time() + (60 * 60 * 24 * 30)); // 30 days $cleanupStmt = $conn->prepare("DELETE FROM remember_tokens WHERE user_id = ?"); $cleanupStmt->bind_param("i", $user['id']); $cleanupStmt->execute(); $cleanupStmt->close(); $tokenStmt = $conn->prepare("INSERT INTO remember_tokens (user_id, token, expires_at, created_at) VALUES (?, ?, ?, NOW())"); $tokenStmt->bind_param("iss", $user['id'], $token, $expiresAt); if ($tokenStmt->execute()) { setcookie('email', $email, time() + (60 * 60 * 24 * 30), "/", "", true, true); setcookie('remember_token', $token, time() + (60 * 60 * 24 * 30), "/", "", true, true); $_SESSION['used_remember_me'] = true; } else { error_log("Failed to store remember token: " . $tokenStmt->error); } $tokenStmt->close(); } else { setcookie('remember_token', '', time() - 3600, "/"); setcookie('email', '', time() - 3600, "/"); $cleanupStmt = $conn->prepare("DELETE FROM remember_tokens WHERE user_id = ?"); $cleanupStmt->bind_param("i", $user['id']); $cleanupStmt->execute(); $cleanupStmt->close(); $_SESSION['used_remember_me'] = false; } $stmt->close(); $conn->close(); if ($user['role'] === 'admin') { header("Location: admin.php"); } else { header("Location: index.php"); } exit(); } else { $_SESSION['login_error'] = 'Incorrect email or password.'; $_SESSION['active_form'] = 'login'; $_SESSION['login_attempts'] = $loginAttempts + 1; if ($_SESSION['login_attempts'] >= 3) { $_SESSION['lockout_time'] = $currentTime; } } } else { $_SESSION['login_error'] = 'Incorrect email or password.'; $_SESSION['active_form'] = 'login'; $_SESSION['login_attempts'] = $loginAttempts + 1; if ($_SESSION['login_attempts'] >= 3) { $_SESSION['lockout_time'] = $currentTime; } } $stmt->close(); $conn->close(); header("Location: login&signup.php"); exit(); } ?>
-
remember me login system and how to make it work with cookies
mac_gyver replied to ssscriptties's topic in PHP Coding Help
here are some implementation practices - the form processing code and form should be on the same page. by putting them on separate pages, you are creating a lot of extra code. by only validating one input at a time and not having the form fields 'sticky', you are providing a poor User eXperience (UX). by storing the 'login_attempts' and 'lockout_time' in session variables, a nefarious user/bot can get unlimited new login attempts by simply not propagating the session id cookie between requests. you must store this data persistently on the server in a database table. the only user related value you should store in a session variable upon successful login is the user id (autoincrement primary index.) you should query on each page request to get any other user data, so that any changes made to the user data will take effect on the very next page request, without requiring the user to log out and back in again. the way a 'remember me' operation should be implemented is that if the remember me checkbox is checked, at the point of successfully verifying the user's credentials, generate a unique token, store that in a cookie and in a database 'remember me' table that also includes the user id, and the current datatime, for a determining token expiration. on any page request, if the remember me token cookie is set, query to find a matching row in the remember me table. if there is a row and the token is not timed out, use the user id from that row to set the session variable that identifies who the logged in user is. the rest of the code then uses this value in the session variable, just like it was set in the login form processing code. the registration process, unless being performed by an administrator, which your code is not doing, should not include the role. the role should not be something that the user can decide when they register. modern php (8+) uses exceptions for database statement errors by default - connection, query, prepare, and execute. any discrete logic you currently have testing the result of these statements should be removed since it will never get executed upon an error. both the username and email must be unique or you should only use the email and forget about a separate username. the correct way of determining if a unique value already exists in a database table is to define the column(s) as a unique index, just attempt to insert the data, and detect in the exception catch logic for the insert query if a duplicate index error (number) occurred. any form processing code should keep for the form data as a set, in an array variable, then operate on elements in this array variable throughout the rest of the code. i.e. don't write out a line of code copying every $_POST variable to a discrete variable. you need to trim ALL the user supplied inputs, mainly so that you can detect if all white-space characters were entered, before validating the data. you need to use an array to hold user/validation errors, and validate all the inputs at once, storing the errors in the array using the field name as the array index. after the end of the validation logic, if there are no errors (the array will be empty), use the submitted form data. in the login validation logic, all you really care about is that the required inputs are are not empty strings, after being trimmed. by providing additional feedback to a nefarious user/bot, you are helping narrow down the values they need to try.- 1 reply
-
- 1
-
-
- cookies
- remember me
-
(and 1 more)
Tagged with:
-
I want to make it so when the email and password and remember_me cookies expire the user is logged out but only if they originally clicked remember me, if they didn't nothing will happen. how do I go about doing that? when I enter index and the cookies expired if I clicked remember me before it then it redirects to login page. if you didn't click remember me, you don't redirect anywhere and no cookies are there. also want to make the cookie password into a hashed password or token. how can I do this? how do I alter my already written code to do this? <?php session_start(); require_once 'config.php'; if (!isset($_SESSION['email']) && isset($_COOKIE['email'], $_COOKIE['password'], $_COOKIE['remember_me'])) { $email = $_COOKIE['email']; $password = $_COOKIE['password']; $stmt = $conn->prepare("SELECT * FROM users WHERE email = ?"); $stmt->bind_param("s", $email); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { $user = $result->fetch_assoc(); if (password_verify($password, $user['password'])) { $_SESSION['username'] = $user['username']; $_SESSION['email'] = $user['email']; $_SESSION['role'] = $user['role']; $_SESSION['location'] = $user['location']; if ($user['role'] === 'admin') { header("Location: admin.php"); } else { header("Location: index.php"); } exit(); } } setcookie('remember_me', '', time() - 3600, "/"); setcookie('email', '', time() - 3600, "/"); setcookie('password', '', time() - 3600, "/"); $stmt->close(); } $errors = [ 'login' => $_SESSION['login_error'] ?? '', 'register' => $_SESSION['register_error'] ?? '' ]; $successMessage = $_SESSION['register_success'] ?? ''; $activeForm = $_SESSION['active_form'] ?? 'login'; $loginAttempts = $_SESSION['login_attempts'] ?? 0; $lockoutTime = $_SESSION['lockout_time'] ?? 0; unset($_SESSION['login_error'], $_SESSION['register_error'], $_SESSION['register_success'], $_SESSION['active_form']); function showError($error) { return !empty($error) ? "<p class='error-message'>" . htmlspecialchars($error) . "</p>" : ""; } function showSuccess($message) { return !empty($message) ? "<p class='success-message'>" . htmlspecialchars($message) . "</p>" : ""; } function isActiveForm($formName, $activeForm) { return $formName === $activeForm ? 'active' : ''; } $currentTime = time(); $remainingLockoutTime = 0; $isLocked = false; if ($loginAttempts >= 3) { if (($currentTime - $lockoutTime) < 40) { $isLocked = true; $remainingLockoutTime = 40 - ($currentTime - $lockoutTime); } else { $_SESSION['login_attempts'] = 0; $_SESSION['lockout_time'] = 0; } } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ALnasser | Ticketing System</title> <link rel="icon" type="image/x-icon" href="alnasser.png"> <link href="style.css" rel="stylesheet" type="text/css"> </head> <body> <div class="container"> <div class="form-box <?= isActiveForm('login', $activeForm); ?>" id="login-form"> <form action="login_register.php" method="post"> <center><img width="30%" height="auto" src="alnasser_nobg.png" alt="ALnasser Logo"></center> <h2>Login</h2> <?= showError($errors['login']); ?> <button type="button" class="sso-button" onclick="window.location.href='windows_login.php'"> Sign in with Windows Domain Account </button> <div class="divider"><span class="divider-text">OR</span></div> <input type="email" name="email" placeholder="Email" required> <input type="password" name="password" placeholder="Password" required> <div class="remember-me"> <input type="checkbox" id="remember_me" name="remember_me"> <label for="remember">Remember me for 30 days</label> </div> <?php if ($isLocked): ?> <div id="countdown">Too many failed attempts. Please try again in <span id="time"></span> seconds.</div> <button type="submit" name="login" disabled style="cursor: not-allowed; background-color: #ccc;">Login</button> <?php else: ?> <button type="submit" name="login">Login</button> <?php endif; ?> <p class="form-footer">Don't have an account? <a href="#" onclick="showForm('register-form')">Register</a></p> </form> </div> <div class="form-box <?= isActiveForm('register', $activeForm); ?>" id="register-form"> <form action="login_register.php" method="post"> <center><img width="30%" height="auto" src="alnasser_nobg.png" alt="ALnasser Logo"></center> <h2>Register</h2> <?= showError($errors['register']); ?> <?= showSuccess($successMessage); ?> <input type="text" name="username" placeholder="Username" required> <input type="email" name="email" placeholder="Email" pattern="[a-zA-Z0-9._%+-]+@alnasser\.eg$" required> <input type="password" name="password" placeholder="Password" required> <select name="role" required> <option value="">--Select Role--</option> <option value="user">User</option> <option value="admin">Admin</option> <option value="technician">Technician</option> </select> <select name="location" required> <option value="">--Select Location--</option> <option value="Asiout">Asiout</option> <option value="Zizinia">Zizinia</option> <option value="Aswan">Aswan</option> <option value="Helwan">Helwan</option> <option value="Menia">Menia</option> <option value="Mokattam">Mokattam</option> <option value="Arcadia">Arcadia</option> <option value="October">October</option> <option value="Tagamoa">Tagamoa</option> <option value="Maadi">Maadi</option> <option value="Heliopolis">Heliopolis</option> <option value="Nasr city">Nasr city</option> <option value="Obour">Obour</option> <option value="Qena">Qena</option> <option value="Smouha">Smouha</option> <option value="Haram">Haram</option> <option value="Sohag1">Sohag1</option> <option value="Bani Suef">Bani Suef</option> <option value="Mohandseen">Mohandseen</option> <option value="Tanta">Tanta</option> <option value="Mahalla">Mahalla</option> <option value="Zaqaziq">Zaqaziq</option> <option value="Shebeen">Shebeen</option> <option value="Qusseya">Qusseya</option> <option value="Mansoura2">Mansoura2</option> <option value="Luxor">Luxor</option> <option value="Damanhor">Damanhor</option> <option value="Hadayek">Hadayek</option> <option value="Agami">Agami</option> <option value="Suez">Suez</option> <option value="Fisal">Fisal</option> <option value="ismailia">ismailia</option> <option value="Mansoura 3">Mansoura 3</option> <option value="Abas el3qad">Abas el3qad</option> <option value="mohy eldeen">mohy eldeen</option> <option value="Sohag2">Sohag2</option> <option value="Zaharaa El-Maadi">Zaharaa El-Maadi</option> <option value="Gesr Al-Suez">Gesr Al-Suez</option> <option value="Shoubra">Shoubra</option> <option value="Fayoum">Fayoum</option> <option value="Hurghada">Hurghada</option> <option value="Sharm ElSheikh">Sharm ElSheikh</option> <option value="Mashaal">Mashaal</option> <option value="Victoria">Victoria</option> <option value="Al Rehab">Al Rehab</option> <option value="Madinaty">Madinaty</option> <option value="Mall of Egypt">Mall of Egypt</option> <option value="Gardenia">Gardenia</option> <option value="Tanta 2">Tanta 2</option> <option value="Port Said">Port Said</option> <option value="Town Center Mall">Town Center Mall</option> <option value="Office">Office</option> <option value="Online">Online</option> </select> <button type="submit" name="register">Register</button> <p class="form-footer">Already have an account? <a href="#" onclick="showForm('login-form')">Login</a></p> </form> </div> </div> <script src="script.js"></script> <script> <?php if ($isLocked): ?> let remainingTime = <?= $remainingLockoutTime ?>; const countdownElement = document.getElementById('time'); function updateCountdown() { if (remainingTime > 0) { countdownElement.textContent = remainingTime; remainingTime--; setTimeout(updateCountdown, 1000); } else { window.location.reload(); } } updateCountdown(); <?php endif; ?> function showForm(formId) { document.querySelectorAll('.form-box').forEach(box => box.classList.remove('active')); document.getElementById(formId).classList.add('active'); } window.onload = function() { const activeFormId = '<?= htmlspecialchars($activeForm) ?>-form'; showForm(activeFormId); }; </script> </body> </html> <?php session_start(); require_once 'config.php'; if (isset($_POST['register'])) { $username = trim($_POST['username']); $email = trim($_POST['email']); $password_raw = $_POST['password']; $role = $_POST['role']; $location = $_POST['location']; if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) { $_SESSION['register_error'] = 'Username can only contain letters, numbers, and underscores.'; $_SESSION['active_form'] = 'register'; header("Location: login&signup.php"); exit(); } if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $_SESSION['register_error'] = 'Invalid email format.'; $_SESSION['active_form'] = 'register'; header("Location: login&signup.php"); exit(); } if (!preg_match('/@alnasser\.eg$/', $email)) { $_SESSION['register_error'] = 'Only @alnasser.eg email addresses are allowed.'; $_SESSION['active_form'] = 'register'; header("Location: login&signup.php"); exit(); } if (strlen($password_raw) < 8 || !preg_match('/[A-Za-z]/', $password_raw) || !preg_match('/[0-9]/', $password_raw) || !preg_match('/[^A-Za-z0-9]/', $password_raw)) { $_SESSION['register_error'] = 'Password must be at least 8 characters long and include letters, numbers, and symbols.'; $_SESSION['active_form'] = 'register'; header("Location: login&signup.php"); exit(); } $password_hashed = password_hash($password_raw, PASSWORD_DEFAULT); $stmt = $conn->prepare("SELECT email FROM users WHERE email = ?"); $stmt->bind_param("s", $email); $stmt->execute(); $checkEmail = $stmt->get_result(); if ($checkEmail->num_rows > 0) { $_SESSION['register_error'] = 'Email is already registered.'; $_SESSION['active_form'] = 'register'; } else { $stmt = $conn->prepare("INSERT INTO users (username, email, password, role, location) VALUES (?, ?, ?, ?, ?)"); $stmt->bind_param("sssss", $username, $email, $password_hashed, $role, $location); if ($stmt->execute()) { $_SESSION['active_form'] = 'login'; $_SESSION['register_success'] = 'Registration successful! Please login.'; } else { error_log("Registration failed: " . $stmt->error); $_SESSION['register_error'] = 'Registration failed. Please try again.'; $_SESSION['active_form'] = 'register'; } } $stmt->close(); $conn->close(); header("Location: login&signup.php"); exit(); } if (isset($_POST['login'])) { $email = trim($_POST['email']); $password = $_POST['password']; $loginAttempts = $_SESSION['login_attempts'] ?? 0; $lockoutTime = $_SESSION['lockout_time'] ?? 0; $currentTime = time(); if ($loginAttempts >= 3 && ($currentTime - $lockoutTime < 40)) { $_SESSION['login_error'] = 'Account locked due to too many failed attempts. Please wait.'; $_SESSION['active_form'] = 'login'; header("Location: login&signup.php"); exit(); } if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $_SESSION['login_error'] = 'Invalid email format.'; $_SESSION['active_form'] = 'login'; header("Location: login&signup.php"); exit(); } if (!preg_match('/@alnasser\.eg$/', $email)) { $_SESSION['login_error'] = 'Only @alnasser.eg email addresses are allowed.'; $_SESSION['active_form'] = 'login'; header("Location: login&signup.php"); exit(); } $stmt = $conn->prepare("SELECT * FROM users WHERE email = ?"); $stmt->bind_param("s", $email); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { $user = $result->fetch_assoc(); if (password_verify($password, $user['password'])) { $_SESSION['username'] = $user['username']; $_SESSION['email'] = $user['email']; $_SESSION['role'] = $user['role']; $_SESSION['location'] = $user['location']; $_SESSION['login_attempts'] = 0; $_SESSION['lockout_time'] = 0; if (!empty($_POST['remember_me'])) { setcookie('remember_me', '1', time() + (60 * 60 * 24 * 30), "/"); setcookie('email', $_POST['email'], time() + (60* 60 * 24 * 30), "/"); setcookie('password', $_POST['password'], time() + (60* 60 * 24 * 30), "/"); } else { setcookie('remember_me', '', time() - 3600, "/"); setcookie('email', '', time() - 3600, "/"); setcookie('password', '', time() - 3600, "/"); } $stmt->close(); $conn->close(); if ($user['role'] === 'admin') { header("Location: admin.php"); } else { header("Location: index.php"); } exit(); } else { $_SESSION['login_error'] = 'Incorrect email or password.'; $_SESSION['active_form'] = 'login'; $_SESSION['login_attempts'] = $loginAttempts + 1; if ($_SESSION['login_attempts'] >= 3) { $_SESSION['lockout_time'] = $currentTime; } } } else { $_SESSION['login_error'] = 'Incorrect email or password.'; $_SESSION['active_form'] = 'login'; $_SESSION['login_attempts'] = $loginAttempts + 1; if ($_SESSION['login_attempts'] >= 3) { $_SESSION['lockout_time'] = $currentTime; } } $stmt->close(); $conn->close(); header("Location: login&signup.php"); exit(); }
- 1 reply
-
- cookies
- remember me
-
(and 1 more)
Tagged with:
-
BangkokTechExperts joined the community
-
C++ OOP is more complicated, so you should not be having issues picking up PHP OOP. For example, PHP OOP doesn't have templates/ operator overloading or multiple inheritance. The best examples of how to apply OOP are those you find in some of the better known component libraries, and in particular those associated with Symfony and Laravel. For everyday use, you want to learn about the Design Patterns described in the Gang of Four book. You don't have to buy this book to learn about these OOP design patterns but many people do, and it's a common text book from my understanding. There are similar books specific to PHP, but I can't personally vouch for any of them. One of the most important OOP design patterns is the Dependency Injection pattern (sometimes called "Inversion of Control"). There are a number of well regarded frameworks that fundamentally are Dependency Injection frameworks. Spring (for Java) was one of the first I was aware of, and for PHP Symfony and Laravel are both DI frameworks, as are any number of other frameworks, given the advantages of the pattern. You want to read about Dependency Injection. There's an article here, that talks about DI and has some examples: https://php-di.org/doc/understanding-di.html Coming from C++, you should already have a good handle on inheritance, methods, constructors, variable scoping, static variables and methods, etc. You want to learn about PHP Interfaces and more recent PHP additions like traits. For free video material, there are any number of tutorials and free courses that cover PHP OOP. I have frequently recommended this channel, and many experienced PHP developers seem to agree with me, that he does a good job covering the syntax and providing examples.
-
Hello developers , I am learning PHP and I have an obstacle at OOP, when I read about it on any website like w3school I understand most of it but I don't know how to apply on it , and when I went to move towards in PHP I see that this thing need deep knowledge about OOP and I move in this cycle , NOTE: I already learned OOP with C++ in college so I just read about OOP in PHP and I don't see full course on youtube because I don't want to hear the same principle again , if what I am doing is wrong please tell me . Thank You.
- Earlier
-
@mac_gyver yes, I had discovered that, but it wasn't the actual fix. The real issue was that my test for is_file was flawed; it didn't drill down to actually test the specified file. if (is_file($subDir)) { //needed to become if (is_file($directory.'/'.$subDir)) { After both adjustments, everything is running better than expected!
-
the above line is missing any { }, so the only line of code that gets executed for an is_dir() is the - echo '<strong>'.$directory .'</strong> <br>'; all the rest of the lines get executed regardless of what $directory is. i recommend that you always format your code so that you can see when it is actually doing.
-
I thought this made sense, but it wouldn't work until I removed lines that I thought would provide extra validation: $dir = 'rootFolder'; $directories = scandir($dir); foreach($directories as $directory){ if($directory=='.' or $directory=='..' ){ echo 'dot'; }else{ if(is_dir($directory)) echo '<strong>'.$directory .'</strong> <br>'; $filePath = scandir($directory); foreach($filePath as $subDir){ if($subDir=='.' or $subDir=='..' ){ echo 'dot2 <br>'; }else { // if (is_file($subDir)) { //Only provided PHP files but NO IMAGES echo $subDir . "<br>"; } } // } } } } I'm just feeling down one level of a director to see what I've get. It seemed like a simple exercise until I tried to list the files. Obviously, of it's not a directory it MUST be a file, but why did quantifying it as a file not recognize my files as a file??
-
Hello From Emmanuel Katto Uganda
WebDesignerUganda replied to emmanuelkatto's topic in Introductions
Hi Emmanuel, Welcome to the PHP Freaks community! We’re excited to have passionate developers like you join the conversation. At Trophy Developers, we specialize in web design and development in Uganda, and it's always great to connect with fellow professionals who share a deep interest in PHP and building user-focused applications. Your experience across diverse projects sounds impressive, and we’re especially glad to see your enthusiasm for continuous learning and collaboration. If you're ever exploring areas like progressive web apps (PWAs), clean architecture, or performance optimization, we'd be happy to share insights from our work too. Looking forward to learning from your contributions and growing alongside you in this community!