MONIMEGA
  • Blog
    • Politics
    • Software
    • Technology
    • Business
    • Design
    • Hardware
    • Health
    • Italy
    • Music
    • Sports
    • Strategy
    • World
  • Contatto
  • Galleria
  • Informazioni
  • Servizi
  • US justice department asks to unseal grand jury transcripts in Epstein case

    July 18, 2025
    Politics

    Move seeks to contain controversy that has engulfed Trump administration since it announced it would not release more files from sex trafficking case

    The US Department of Justice asked a federal court on Friday to unseal grand jury transcripts in Jeffrey Epstein’s case at the direction of Donald Trump amid a firestorm over the administration’s handling of records related to the wealthy financier.

    The move – coming a day after a Wall Street Journal story put a spotlight on Trump’s relationship with Epstein – seeks to contain a growing controversy that has engulfed the administration since it announced that it would not be releasing more government files from Epstein’s sex trafficking case.

    Continue reading…


    Source: World news | The Guardian.

  • How to Build a Telehealth App Using Stream Video and Chat SDK in React

    How to Build a Telehealth App Using Stream Video and Chat SDK in React

    July 18, 2025
    Software

    Remember when the COVID-19 pandemic moved everything online – doctor’s visits included – and staying home became the safest option?

    That moment kicked off a massive shift in how healthcare gets delivered.

    Telehealth became more than a workaround. It’s now a core part of modern care. As demand grows, developers are stepping up to build secure, real-time platforms that connect patients and providers from anywhere.

    In this article, you’ll learn how to build a telehealth application with Stream’s React Video and Chat SDKs. You’ll set up authentication, create video calls, enable messaging, and design a functional user interface that mimics real-world telehealth workflows.

    Let’s dive in.

    Outline

    • Introduction

    • Prerequisites

    • App Structure

    • Project Setup

    • Backend Setup

    • Frontend Setup

    • Stream Chat and Video Integration

    • Chat and Video Function (Frontend)

    • Project Demo

    • Conclusion

    Prerequisites

    Before you start this tutorial, make sure you have:

    • A basic understanding of React.

    • Node.js and npm/yarn installed on your computer

    • A free account with Stream

    • Familiarity with Stream SDKs

    • A basic understanding of Tailwind CSS for styling

    • Experience with VS Code and Postman (for testing APIs)

    App Structure

    Before diving into the code, it’s helpful to understand how the app is structured.

    # App Flow Structure
    
    - Landing Page  
      - Navigation  
        - Home  
        - About  
        - Sign Up  
          - Verify Account  
            - Log In  
              - Dashboard  
                - Stream Chat  
                - Stream Video  
                - Log Out
    

    Project Setup

    Before getting started, create two folders: “Frontend” to handle the client-side code and “Backend” for the server-side logic. This separation allows you to manage both parts of your application efficiently.

    Set Up React for the Frontend

    Once the folders are created, you can set up the React application in the Frontend folder.

    First, navigate to the Frontend directory using the command cd Frontend.

    Now you can initialize your React project. You’ll use Vite, a fast build tool for React applications.

    To create your React project, run the following command:

    npm create vite@latest [project name] -- --template react

    Next, navigate to your new project folder, using the command:

    cd [project name]

    Once there, install the required dependencies by running:

    npm install

    This command installs both the node_modules folder (which contains all your project’s packages) and the package-lock.json file (which records exact versions of installed packages).

    Next, you’ll need to install Tailwind CSS for styling. Follow the Tailwind Docs for step-by-step instructions.

    Then, it’s time to set up the website. Using React, you’ll create the home sign-in/log-in pages. Both will be nested together using React-router-dom.

    Here’s what the home page looks like:

    Telehealth Home Page

    Now, the user has a place to land whenever they visit the website.

    Let’s set up the backend.

    Backend Setup

    Installing Required Packages

    Before setting up your project’s backend, it’s important to define what your project needs to offer. This will help you install all the necessary packages in one go.

    Start by moving into the backend folder using the command: cd Backend

    Inside the Backend directory, initialize your Node.js project using npm install

    This will create a package.json file, which stores metadata and dependencies for your project.

    Next, install all the dependencies needed to build your backend. Run the following command:

    npm i bcryptjs cookie-parser cors dotenv express jsonwebtoken mongoose nodemailer validator nodemon

    Here’s a brief overview of what each package does:

    • bcryptjs: Encrypts user passwords for secure storage.

    • Cookie-parser: Handles cookies in your application.

    • CORS: Middleware that enables cross-origin requests – essential for frontend-backend communication.

    • dotenv: Loads environment variables from a .env file into process.env.

    • Express: The core framework for building your server and API routes.

    • jsonwebtoken: Generates and verifies JWT tokens for authentication.

    • Mongoose: Connects your app to a MongoDB database.

    • nodemailer: Handles sending emails from your application.

    • Validator: Validates user inputs like email, strings, and so on.

    • nodemon: Automatically restarts your server when changes are made to files.

    Once your packages are installed, create two key files in the backend directory: App.js, which contains your app logic, middleware, and route handlers, and server.js, responsible for initializing and configuring your server.

    Next, you have to update your package.json start script. Head to the package.json file in your backend directory and replace the default script:

    "scripts": {
        "test": "echo”Error: no test specified" && exit 1”
      }
    

    with this:

    "scripts": {
        "start": "nodemon server.js"
      }
    

    This setup allows you to run your server using nodemon, automatically reloading it when changes are made. This helps boost productivity during development.

    To check if your backend setup is correct, open the server.js file and add a test log: console.log (“Any of your Log Message”). Then, head to your terminal in the backend directory, and run npm start. You should see the log message in the terminal, confirming that your backend is running.

    Backend Server Testing

    App.js Setup

    In the App.js file, start by importing the packages you initially installed.

    const express = require("express");
    
    const cors = require("cors");
    
    const cookieParser = require("cookie-parser");
    
    const app = express();
    
    
    app.use(
    
      cors({
    
    origin: [
    
       "http://localhost:5173",
    
    ],
    
    credentials: true,
    
      })
    
    );
    
    app.use(express.json({ limit: "10kb" }));
    
    app.use(cookieParser());
    
    module.exports = app;
    

    Here’s what the code above does:

    The require statements import express, cors, and cookie-parser, which are essential for creating your backend server, handling cross-origin requests, and parsing cookies.

    The const app = express(); command sets up a new instance of an Express application.

    app.use(cors({ origin: ["http://localhost:5173"], credentials: true })); grants permission or allows requests from your frontend and enables cookie sharing between the frontend and backend of your application. This is important for authentication.

    The app.use(express.json({ limit: "10kb" })); command is a middleware function that ensures the server can process incoming JSON payloads and protects against overly large requests, which could be used in DoS attacks.

    The app.use(cookieParser()); command makes cookies available via req.cookies.

    Last, the module.exports = app; command allows the app to be imported in other files, especially in server.js, which is where the app will be started.

    Server.js Setup

    Once App.js is set up, the next step is to create and configure your server in a new file called server.js.

    Before doing that, ensure you have a MongoDB database set up. If you don’t have one yet, you can follow this video tutorial to set up a MongoDB database.

    After setting up MongoDB, you will receive a username and password. Copy the password, head to your backend directory, and create a .env file to store it.

    After you have stored the password, head back to complete your database setup.

    Next, click on the “Create Database User” button, then click on the choose connection method option. Since we are using Node.js for this project, choose the “Drivers” option. This gives you the database connection string (you should see it at No. 3).

    Database-String-Auth

    Then head to your .env and paste it there, and add auth immediately after you have “.net/”.

    Here’s what it looks like:

    mongodb+srv://<username>:<password>@cluster0.qrrtmhs.mongodb.net/auth?retryWrites=true&w=majority&appName=Cluster0

    Backend config.env file

    Lastly, whitelist your IP address. This ensures your backend can connect to MongoDB from your local machine or any environment during development.

    To allow your application to connect to the database:

    • Go to the “Network Access” section in the Security sidebar of your MongoDB dashboard.

    • Click on “ADD IP ADDRESS.”

    • Choose “Allow Access from Anywhere”, then click on Confirm.

    At this point, you can set up your server.js

    //server.js
    require("dotenv").config();
    const mongoose = require("mongoose");
    const dotenv = require("dotenv"); //to Manage our environment variable
    
    dotenv.config({ path: "./config.env" });
    // console.log(process.env.NODE_ENV);
    
    const app = require("./app");
    
    const db = process.env.DB;
    //connect the application to database using MongoDB
    
    mongoose
      .connect(db)
      .then(() => {
        console.log("DB connection Successful");
      })
      .catch((err) => {
        console.log(err);
      });
    
    const port = process.env.PORT || 3000;
    // console.log(process.env.PORT)
    
    app.listen(port, () => {
      console.log(`App running on port ${port}`);
    });
    

    The server.js file is responsible for handling all server-related functions and logic. From the code above, the server.js file loads the environment variables using dotenv, connects your backend to MongoDB using mongoose, and starts the Express server. It gets the database URL and port from the config.env file, connects to the database, then runs your application on the specified port (8000).

    If the specified port is not found, it falls back to port 3000 and a confirmation message is printed to the console indicating that the server is up and running on the specified port.

    server-js Telehealth App

    Connect the Database to MongoDB Compass

    First, download the MongoDB Compass app. (Go here to download and install: https://www.mongodb.com/try/download/compass). The MongoDB Compass app makes it easy for us to manage our data.

    Once the installation is complete, open the app and click on Click to add new connection. Go to your .env file, copy the connection string you initially got when setting up MongoDB, paste it in the URL section, and then click on “connect.” This setup helps you manage your data when you create and delete users.

    Mongo-DB-Compass

    Set up an Advanced Error Handling Method

    You’ll now create an advanced error-handling mechanism. To do so, create a utils folder in your backend, a catchAsync.js file in the utils folder, and add this code:

    //catchAsync.js
    module.exports = (fn) => {
      return (req, res, next) => {
        fn(req, res, next).catch(next);
      };
    };
    

    Next, create an appError.js file still in your utils folder. In the appError.js file, add the following command:

    class AppError extends Error {
      constructor(message, statusCode) {
        super(message);
    
        this.statusCode = statusCode;
        this.status = `${statusCode}`.startsWith("4") ? "fail" : "error";
        this.isOperational = true;
    
        Error.captureStackTrace(this, this.constructor);
      }
    }
    
    module.exports = AppError;
    

    The code above is helpful in tracking and tracing errors. It also provides you with the URL and file location where your error might be occurring, which helps with cleaner error handling and debugging.

    Next, let’s create a global error handler. Start by creating a new folder in the backend directory, and name it “controller”. In the controller folder, create your global error handling file. You can name it anything you like. In this example, it’s called globalErrorHandler.js.

    Your globalErrorHandler file will define several functions that handle specific error types, such as database issues, validation failures, or even JWT problems and return a nicely formatted error response for users. For the globalErrorHandler to work properly, you have to create a controller function.

    So, next, create an errorController.js file (still inside the controller folder). The errorController.js file responds to the user whenever an error is caught, sending the error in JSON format.

    globalErrorHandler.js:

    // globalErrorHandler.js
    const AppError = require("../utils/appError");
    
    const handleCastErrorDB = (err) => {
      const message = `Invalid ${err.path}: ${err.value}.`;
      return new AppError(message, 400);
    };
    
    const handleDuplicateFieldsDB = (err) => {
      const value = err.keyValue ? JSON.stringify(err.keyValue) : "duplicate field";
      const message = `Duplicate field value: ${value}. Please use another value!`;
      return new AppError(message, 400);
    };
    
    const handleValidationErrorDB = (err) => {
      const errors = Object.values(err.errors).map((el) => el.message);
      const message = `Invalid input: ${errors.join(". ")}`;
      return new AppError(message, 400);
    };
    
    const handleJWTError = () =>
      new AppError("Invalid token. Please log in again!", 401);
    const handleJWTExpiredError = () =>
      new AppError("Your token has expired! Please log in again.", 401);
    
    module.exports = {
      handleCastErrorDB,
      handleDuplicateFieldsDB,
      handleValidationErrorDB,
      handleJWTError,
      handleJWTExpiredError,
    };
    

    Here’s what the code above does:

    The const handleCastErrorDB = (err) =>.. section handles MongoDB CastError which usually happens when an invalid ID is passed, and returns a 400 Bad Request error response using your AppError class.

    The const handleDuplicateFieldsDB = (err) =>... checks and handles MongoDB duplicate key errors, such as trying to register an email or username that’s already taken and returns a 400 Bad Request error.

    The const handleValidationErrorDB = (err) =>... handles Mongoose validation errors (like required fields missing or wrong data types). It gathers all the individual validation error messages and combines them into one.

    The const handleJWTError = () => and const handleJWTExpiredError = () => handle errors which might occur as a result of invalid, tampered, or expired JWT tokens and return a 401 Unauthorized error response.

    The module.exports = {……}; section exports all the individual error handlers so they can be used in the errorController.js file.

    errorController.js:

    //errorController.js
    const errorHandlers = require("./globalErrorHandler");
    
    const {
      handleCastErrorDB,
      handleDuplicateFieldsDB,
      handleValidationErrorDB,
      handleJWTError,
      handleJWTExpiredError,
    } = errorHandlers;
    
    module.exports = (err, req, res, next) => {
      err.statusCode = err.statusCode || 500;
      err.status = err.status || "error";
    
      let error = { ...err, message: err.message };
    
      if (err.name === "CastError") error = handleCastErrorDB(err);
      if (err.code === 11000) error = handleDuplicateFieldsDB(err);
      if (err.name === "ValidationError") error = handleValidationErrorDB(err);
      if (err.name === "JsonWebTokenError") error = handleJWTError();
      if (err.name === "TokenExpiredError") error = handleJWTExpiredError();
    
      res.status(error.statusCode).json({
        status: error.status,
        message: error.message,
        ...(process.env.NODE_ENV === "production" && { error, stack: err.stack }),
      });
    };
    

    To ensure your error-handling function works properly, head to your App.js and add the command:

    //import command
    const globalErrorHandler = require("./controller/errorController");
    const AppError = require("./utils/appError");
    
    //Catch unknown routes
    app.all("/{*any}", (req, res, next) => {
      next(new AppError(`Can't find ${req.originalUrl} on this server!`, 404)); });
    
    app.use(globalErrorHandler);
    

    This ensures that all errors are properly handled and sends the error response to the user.

    Create User Model

    To build a user model, create a new folder in the backend directory and name it “model.” Inside the model folder, create a new file named userModel.js.

    The userModel.js file is built essentially for user authentication and security. It provides a validation-rich schema for managing users using Mongoose, which maps how user data is structured in the MongoDB database. It includes validations, password hashing, and methods to compare user passwords securely.

    //userModel.js
    const mongoose = require("mongoose");
    const validator = require("validator");
    const bcrypt = require("bcryptjs");
    
    const userSchema = new mongoose.Schema(
      {
        username: {type: String, required: [true, "Please provide username"], trim: true, minlength: 3, maxlength: 30, index: true,},
        email: {type: String, required: [true, "Please Provide an email"], unique: true, lowercase: true, validate: [validator.isEmail, "Please provide a valid email"],},
        password: {type: String, required: [true, "Please provide a Password"], minlength: 8, select: false,},
        passwordConfirm: {type: String, required: [true, "Please confirm your Password"],
         validate: {validator: function (el) {return el === this.password;},
            message: "Passwords do not match",
          },
        },
        isVerified: {type: Boolean, default: false,}, otp: String,
        otpExpires: Date,
         resetPasswordOTP: String,
          resetPasswordOTPExpires: Date,
        createdAt: {type: Date, default: Date.now,},}, { timestamps: true });
    
    // Hash password before saving
    userSchema.pre("save", async function (next) {
      if (!this.isModified("password")) return next();
    
      this.password = await bcrypt.hash(this.password, 12);
      this.passwordConfirm = undefined; // Remove passwordConfirm before saving
      next();
    });
    
    const User = mongoose.model("User", userSchema);
    module.exports = User;
    

    Auth Controller

    Now, you can create logic that regulates your user’s authentication process. This authentication logic consists of the sign-up, sign-in (log-in), OTP, and so on.

    To do so, first head to your controller folder and create a new file. Call it authController.js because it handles the authentication flow of your project.

    After you’ve created the file, you’ll create your sign-up function.

    //import
    const User = require("../model/userModel");
    const AppError = require("../utils/appError");
    const catchAsync = require("../utils/catchAsync");
    const generateOtp = require("../utils/generateOtp");
    const jwt = require("jsonwebtoken");
    const sendEmail = require("../utils/email")
    
    exports.signup = catchAsync(async (req, res, next) => {
      const { email, password, passwordConfirm, username } = req.body;
    
      const existingUser = await User.findOne({ email });
    
      if (existingUser) return next(new AppError("Email already registered", 400));
    
      const otp = generateOtp();
    
      const otpExpires = Date.now() + 24 60 60 * 1000; //when thhe otp will expire (1 day)
    
      const newUser = await User.create({
        username,
        email,
        password,
        passwordConfirm,
        otp,
        otpExpires,
      });
    
      //configure email sending functionality
      try {
        await sendEmail({
          email: newUser.email,
          subject: "OTP for email Verification",
          html: `<h1>Your OTP is : ${otp}</h1>`,
        });
    
        createSendToken(newUser, 200, res, "Registration successful");
      } catch (error) {
        console.error("Email send error:", error);
        await User.findByIdAndDelete(newUser.id);
        return next(
          new AppError("There is an error sending the email. Try again", 500)
        );
      }
    });
    

    const { email, password, passwordConfirm, username } = req.body; extracts the necessary registration details from the incoming request: email, password, password confirmation, and username during user sign-up.

    const existingUser = await User.findOne({ email }); checks the database to see if a user already exists with the given email. If yes, it sends an error response using your AppError utility.

    const otp = generateOtp(); generates the OTP, and const otpExpires = Date.now()….. is used to set the OTP to expire at a specified time or day.

    With const newUser = await User.create({…});, the new user is saved in MongoDB with their credentials and the OTP info, with the password automatically hashed.

    await sendEmail({…}); sends an email to the user. This email contains their sign-in OTP. If the email is sent successfully, createSendToken(newUser, 200, res, "Registration successful"); (which is a utility function) generates a JWT token and sends it in the response with a success message.

    If the email fails to send or something goes wrong, await User.findByIdAndDelete(newUser.id); deletes the user from the database to keep things clean, and an error message of There is an error sending the email. Try again", 500 is returned.

    Generate OTP

    To ensure that your users’ OTP is successfully sent to them, in the utils folder, create a new file and name it generateOtp.js. Then add the code:

    module.exports = () => {
      return Math.floor(1000 + Math.random() * 9000).toString();
    };
    

    The code above is a function that generates a user’s random 4-digit OTP and returns it as a string.

    After completing the code above, go to your authController.js and ensure you import the generateOtp.js in the import section.

    Create User Token

    Next, the user sign-in token will be created, and it will be assigned to the user upon sign-in.

    const signToken = (userId) => {
      return jwt.sign({ id: userId }, process.env.JWT_SECRET, {
        expiresIn: process.env.JWT_EXPIRES_IN || "90d",
      });
    };
    
    //function to create the token
    const createSendToken = (user, statusCode, res, message) => {
      const token = signToken(user._id);
    
      //function to generate the cookie
      const cookieOptions = {
        expires: new Date(
          Date.now() + process.env.JWT_COOKIE_EXPIRES_IN 24 60 60 1000
        ),
    
        httponly: true,
        secure: process.env.NODE_ENV === "production", //only secure in production
        sameSite: process.env.NODE_ENV === "production" ? "none" : "Lax",
      };
    
      res.cookie("token", token, cookieOptions);
    
      user.password = undefined;
      user.passwordConfirm = undefined;
      user.otp = undefined;
    

    Before the code above can work perfectly, create a JWT in your .env file.

    //.env
    JWT_SECRET = kaklsdolrnnhjfsnlsoijfbwhjsioennbandksd;
    JWT_EXPIRES_IN = 90d
    JWT_COOKIE_EXPIRES_IN = 90
    

    The code above is how the .env file should look. Your JWT_SECRET can be anything, just as you can see in the code.

    Note: The user token creation logic should run before the sign-in logic. So in that case, the signToken and createSendToken logic should be placed at the top before the signup logic.

    Send Email

    Next, you need to configure your email sending functionality so you can automatically send the user’s OTP to their email whenever they sign in. To configure the email, head to the utils folder, create a new file, and give it a name. In this example, the name is email.js.

    In email.js, we will send emails using the nodemailer package and Gmail as a provider.

    //email.js
    const nodemailer = require('nodemailer');
    
    const sendEmail = async (options) => {
      const transporter = nodemailer.createTransport({
        service: 'Gmail',
        auth: {
          user: process.env.HOST_EMAIL,
          pass: process.env.EMAIL_PASS
        }
      })
    
      //defining email option and structure
    
      const mailOptions = {
        from: `"{HOST Name}" <{HOST Email} >`,
        to: options.email,
        subject: options.subject,
        html: options.html,
      };
      await transporter.sendMail(mailOptions);
    };
    
    module.exports = sendEmail;
    

    From the code above, the const nodemailer = require('nodemailer'); command imports the nodemailer package. This is a popular Node.js library for sending emails.

    The const transporter = nodemailer.createTransport({…..}) is an email transporter. Since we will be using the Gmail service provider, service will be assigned to Gmail and auth pulls your Gmail address and password from the .env file where it’s stored.

    Note: The password is not your actual Gmail password but rather your Gmail app password. You can see how you can get your Gmail password here.

    Once you’ve successfully gotten your Gmail app password, store it in your .env file.

    Route Creation

    At this point, you have finished setting up your project’s signup function. Next, you need to test whether your signup works properly using Postman. But before that, let’s set up and define a route where the signup function will be executed.

    To set up your route, create a new folder in your backend directory named “routes” and a file named userRouter.js.

    const express = require("express");
    const {signup} = require(“../controller/authController”);
    
    const router = express.Router();
    router.post("/signup", signup);
    
    module.exports = router;
    

    Next, go to your App.js file and add the router to it.

    const userRouter = require("./routes/userRouters"); //Route import statement
    app.use("/api/v1/users", userRouter) //common route for all auth, i.e sign up, log in, forget password, etc.
    

    After setting up your routes, you can test your signup to see if it works. This is a post request, and the route URL will be http://localhost:8000/api/v1/users/signup.

    New user Sign up Testing

    The image above shows that the signup function works perfectly with a statusCode of 200 and an OTP code being sent to the user’s email.

    Congratulations on reaching this point! You can check your MongoDB database to see if the user is displayed there.

    Mongo-DB-Compass-User-Display

    From the image above, you can see that the user details are obtained and the password is in an encrypted form, which ensures the user credentials are safe.

    Create a Verify Account Controller Function

    In this section, you’ll create a Verify Account controller function. This function verifies a user’s account using the OTP code sent to their email address.

    First, go to your authController.js file and add:

    exports.verifyAccount = catchAsync(async (req, res, next) => {
      const { email, otp } = req.body;
    
      if (!email || !otp) {
        return next(new AppError("Email and OTP are required", 400));
      }
    
      const user = await User.findOne({ email });
    
      if (!user) {
        return next(new AppError("No user found with this email", 404));
      }
    
      if (user.otp !== otp) {
        return next(new AppError("Invalid OTP", 400));
      }
    
      if (Date.now() > user.otpExpires) {
        return next(
          new AppError("OTP has expired. Please request a new OTP.", 400)
        );
      }
    
      user.isVerified = true;
      user.otp = undefined;
      user.otpExpires = undefined;
    
      await user.save({ validateBeforeSave: false });
    
      // ✅ Optionally return a response without logging in
      res.status(200).json({
        status: "success",
        message: "Email has been verified",
      });
    });
    

    Next, create a middleware function to authenticate the currently logged-in user.

    In your backend directory, create a new folder called middlewares. Inside the middlewares folder, create a file named isAuthenticated.js.

    Add the following code:

    //isAuthenticated.js
    const jwt = require("jsonwebtoken");
    const catchAsync = require("../utils/catchAsync");
    const AppError = require("../utils/appError");
    const User = require("../model/userModel");
    
    const isAuthenticated = catchAsync(async (req, res, next) => {
      let token;
    
      // 1. Retrieve token from cookies or Authorization header
      if (req.cookies?.token) {
        token = req.cookies.token;
      } else if (req.headers.authorization?.startsWith("Bearer")) {
        token = req.headers.authorization.split(" ")[1];
      }
    
      if (!token) {
        return next(
          new AppError(
            "You are not logged in. Please log in to access this resource.",
            401
          )
        );
      }
    
      // 2. Verify token
      let decoded;
      try {
        decoded = jwt.verify(token, process.env.JWT_SECRET);
      } catch (err) {
        return next(
          new AppError("Invalid or expired token. Please log in again.", 401)
        );
      }
    
    // 3. Confirm user still exists in database
      const currentUser = await User.findById(decoded._id);
      if (!currentUser) {
        return next(
          new AppError("User linked to this token no longer exists.", 401)
        );
      }
    
      // 4. Attach user info to request
      req.user = currentUser;
      req.user = {
        id: currentUser.id,
        name: currentUser.name,
      };
    
      next();
    });
    
    module.exports = isAuthenticated;
    ```
    Now, go to your `userRouter.js` file and add the route for the verify account function:
    ```
    const { verifyAccount} = require("../controller/authController");
    const isAuthenticated = require("../middlewares/isAuthenticated");
    router.post("/verify", isAuthenticated, verifyAccount);
    

    Here is what these two sets of code are doing:

    When a user sends a request to the /verify route, the isAuthenticated middleware runs first. It checks whether a valid token exists in the cookie or authorization header. If no token is found, it throws an error: You are not logged in. Please log in to access this resource.

    If a token is found, it verifies the token and checks if the associated user still exists. If not, another error is thrown: "User linked to this token no longer exists."

    If the user exists and the token is valid, their details are attached to the request (req.user). The request then proceeds to the verifyAccount controller, which handles OTP verification.

    You can test this endpoint using Postman with a POST request to: http://localhost:8000/api/v1/users/verify

    Verify-Account

    The image above shows that the verify token function is working well, and a status code of 200 is displayed.

    Login Function

    If you’ve reached this point, you’ve successfully signed up and verified a user’s account.

    Now it’s time to create the login function, which allows a verified user to access their account. Here’s how you can do that:

    Go to your authController.js file and create your login function by adding the following:

    exports.login = catchAsync(async (req, res, next) => {
      const { email, password } = req.body;
    
      // 1. Validate email & password presence
      if (!email || !password) {
        return next(new AppError("Please provide email and password", 400));
      }
    
      // 2. Check if user exists and include password
      const user = await User.findOne({ email }).select("+password");
      if (!user || !(await user.correctPassword(password, user.password))) {
        return next(new AppError("Incorrect email or password", 401));
      }
    
      // 3. Create JWT token
      const token = signToken(user._id);
    
      // 4. Configure cookie options
      const cookieOptions = {
        expires: new Date(
          Date.now() +
            (parseInt(process.env.JWT_COOKIE_EXPIRES_IN, 10) || 90) 24 60 60 1000
        ),
        httpOnly: true,
        // secure: process.env.NODE_ENV === "production",
        // sameSite: process.env.NODE_ENV === "production" ?
        //  "None" : "Lax",
    
        //set to false during or for local HTTP and cross-origin
        secure: false,
        sameSite: "Lax",
      };
    
      // 5. Send cookie
      res.cookie("token", token, cookieOptions);
    
    });
    

    if (!email || !password) {…} checks if the user actually provided both an email and a password. If not, it returns the error: Please provide email and password", 400.

    const user = await User.findOne({ email }).select("+password"); searches the database for a user with the provided email and explicitly includes the password field, since it’s normally hidden by default in the schema.

    if (!user || !(await user.correctPassword(…))) {…} checks if the user exists and if the password entered matches the one stored in the database (after hashing comparison). If either is wrong, it throws: Incorrect email or password.

    The line signToken(user._id) generates a JWT using the user’s unique ID. The cookieOptions object configures how the cookie behaves – it sets the cookie to expire after a specific number of days defined in the .env file, marks it as httpOnly to prevent JavaScript access for security, sets secure to false since the app is currently in development, and uses sameSite: "Lax" to allow cross-origin requests during local testing.

    Finally, res.cookie(...) sends the token as a cookie attached to the HTTP response, enabling the client to store the token for authentication purposes.

    From the code above, you may have noticed that the password stored in the database is hashed for security reasons. This means it looks completely different from the user’s password when logging in. So, even if a user types in the correct password, it won’t match the stored hash directly through a simple comparison.

    To fix this, you need to compare the entered password with the hashed one using the bcryptjs package.

    Head over to your userModel.js file and create a method that handles password comparison. This method will take the plain text password provided by the user and compare it to the hashed password stored in the database.

    //userModel.js
    //create a method responsible for comparing the password stored in the database
    
    userSchema.methods.correctPassword = async function (password, userPassword) {
      return await bcrypt.compare(password, userPassword);
    };
    

    This correctPassword method uses bcrypt.compare(), which internally hashes the plain password and checks if it matches the stored hashed version. This ensures that login validation works correctly and securely, even though the actual password is not stored in plain text.

    Next, add the Login functionality to the userRouter.js file.

    const {login} = require("../controller/authController");
    const isAuthenticated = require("../middlewares/isAuthenticated");
    
    router.post("/login", login);
    

    You can test this endpoint using Postman with a POST request to: http://localhost:8000/api/v1/users/login

    Logout Function

    At this point, you can implement the logout function to end a user’s session securely. To do this, navigate to your authController.js file and add the following function:

    //creating a log out function
    exports.logout = catchAsync(async (req, res, next) => {
      res.cookie("token", "loggedout", {
        expires: new Date(0),
        httpOnly: true,
        secure: process.env.NODE_ENV === "production",
      });
    
      res.status(200).json({
        status: "success",
        message: "Logged out successfully",
      });
    });
    

    This function works by overwriting the authentication cookie named token with the value "loggedout" and setting its expiration time to the past using new Date(0). This effectively invalidates the cookie and removes it from the browser.

    The httpOnly: true flag ensures that the cookie cannot be accessed via JavaScript, which protects it from XSS attacks, while the secure flag ensures that the cookie is only sent over HTTPS in a production environment. Once the cookie is cleared, a success response is returned with the message “Logged out successfully” to confirm the action.

    Next, add the logout functionality to your route:

    const {logout} = require("../controller/authController");
    const isAuthenticated = require("../middlewares/isAuthenticated");
    
    router.post("/logout", logout);
    

    Then, head to Postman to test your logout function and see if it works.

    Frontend Setup

    Now that your backend is up and running, you can integrate it into your frontend application.

    First, navigate to the frontend directory using the command cd Frontend.

    Create a new folder in the src folder where your authentication-related files will live. Depending on your preference or app structure, you can name it something like auth or pages. Then, create a new file called NewUser. js. This file will handle user signup functionality.

    import axios from 'axios';
    import React, { useState } from 'react';
    import { Link, useNavigate } from 'react-router-dom';
    import { Loader } from 'lucide-react';
    import { useDispatch } from 'react-redux';
    import { setAuthUser, setPendingEmail } from '../../../../store/authSlice';
    
    const API_URL = import.meta.env.VITE_API_URL;
    
    function NewUser() {
      const dispatch = useDispatch();
      const navigate = useNavigate();
    
      const [loading, setLoading] = useState(false);
    
      const [formData, setFormData] = useState({
        username: '',
        email: '',
        password: '',
        passwordConfirm: '',
      });
    
      const handleChange = (e) => {
        const { name, value } = e.target;
        setFormData((prev) => ({ ...prev, [name]: value }));
      };
    
      const submitHandler = async (e) => {
        e.preventDefault();
        setLoading(true);
        try {
          const response = await axios.post(`${API_URL}/users/signup`, formData, {
            withCredentials: true,
          });
          const user = response.data.data.user;
          dispatch(setAuthUser(user));
          dispatch(setPendingEmail(formData.email)); // Save email for OTP
          navigate('/verifyAcct'); // Navigate to OTP verification page
        } catch (error) {
          alert(error.response?.data?.message || 'Signup failed');
        } finally {
          setLoading(false);
        }
      };
    
      return (
    <div>
    // visit the frontend Github repository to see the remaining code for the OTP Verification
    
    https://github.com/Derekvibe/Telehealth_Frontend/blob/main/src/pages/Auth/Join/NewUser.jsx
        </div>
      );
    }
    
    export default NewUser;
    

    The code above renders a signup form with fields for username, email, password and passwordConfirm. When the user submits the form, the frontend sends a POST request to the backend’s /users/signup endpoint using Axios. The withCredentials: true option ensures cookies like the auth token are properly set by the backend.

    If the signup is successful, the user data is dispatched into Redux using setAuthUser(), and their email is saved with setPendingEmail() so it can be used during OTP verification. Then, the user is redirected to the /verifyAcct route, where they can enter their OTP.

    Frontend-Sign-Up

    OTP Verification Page

    The OTP verification page is the next step in the user authentication process. Once a user signs up, they are redirected to enter the 4-digit OTP sent to their email. This verifies their account before allowing login access.

    import React, { useState, useRef, useEffect } from 'react';
    import { useSelector, useDispatch } from 'react-redux';
    import { useNavigate, Link } from 'react-router-dom';
    import axios from 'axios';
    import { clearPendingEmail } from '../../../../store/authSlice';
    
    const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000/api'; // adjust as needed
    
    function VerifyAcct() {
      const [code, setCode] = useState(['', '', '', '']);
      const [loading, setLoading] = useState(false);
      const [resendLoading, setResendLoading] = useState(false);
      const [timer, setTimer] = useState(60);
    
      const inputsRef = useRef([]);
      const dispatch = useDispatch();
      const navigate = useNavigate();
      const email = useSelector((state) => state.auth.pendingEmail);
    
      useEffect(() => {
        let interval;
        if (timer > 0) {
          interval = setInterval(() => setTimer((prev) => prev - 1), 1000);
        }
        return () => clearInterval(interval);
      }, [timer]);
    
      const handleChange = (value, index) => {
        if (!/^d*$/.test(value)) return;
        const newCode = [...code];
    // visit the frontend Github repository to see the remaining code for the OTP Verification
    https://github.com/Derekvibe/Telehealth_Frontend/blob/main/src/pages/Auth/login/VerifyAcct.jsx
    }
    export default VerifyAcct;
    

    Here’s what the code does:

    The OTP is stored as an array of 4 characters ([‘ ‘, ‘ ‘, ‘ ‘, ‘ ‘]). Each box only accepts digits, and focus automatically moves to the next input as the user types in the digit. The focus returns to the previous input box if the user presses the backspace button on an empty box.

    When the OTP has been added and the form is submitted, the 4-digit code is joined into a string and an HTTP POST request is made to the backend /user/verify/ endpoint along with the stored email and OTP. If the verification is successful, the user is alerted and redirected to the login page, and if not, an error is shown.

    Frontend-OTP

    Log In

    Now you can create the login interface for your application. First, create a Login.jsx file and input the code:

    //Login.Jsx
    
    import React, { useState } from 'react';
    import { Link, useNavigate } from 'react-router-dom';
    import axios from 'axios';
    
    const API_URL = import.meta.env.VITE_API_URL || 'https://telehealth-backend-2m1f.onrender.com/api/v1';
    
    function Join() {
      const [formData, setFormData] = useState({ email: '', password: '' });
      const [loading, setLoading] = useState(false);
      const [error, setError] = useState('');
      const navigate = useNavigate();
    
      const handleChange = (e) => {
        setFormData({ ...formData, [e.target.name]: e.target.value });
      };
    
      const handleLogin = async (e) => {
        e.preventDefault();
        setLoading(true);
        setError('');
    
        try {
          const res = await axios.post(`${API_URL}/users/login`, formData, {
            withCredentials: true,
          });
    
          if (res.data.status === 'success') {
            const { token, user, streamToken } = res.data;
    
            // Save to localStorage
            localStorage.setItem('authToken', token);
            localStorage.setItem('user', JSON.stringify(user));
            localStorage.setItem('streamToken', streamToken);
    
            navigate('/dashboard');
          }
        } catch (err) {
          console.error(err);
          setError(
            err.response?.data?.message || 'Something went wrong. Please try again.'
          );
        } finally {
          setLoading(false);
        }
      };
    
      return (
    <div>
    {// visit the frontend Github repository to see the remaining code for the OTP Verification
    https://github.com/Derekvibe/Telehealth_Frontend/blob/main/src/pages/Auth/login/Login.jsx
     </div>
    );
    }
    

    The Export default Join; component allows a registered and verified user to log into your application using their email and password. It handles form submission, talks to the backend, and securely stores user data if login is successful.

    handleChange() updates the email or password field as the user types.

    handleLogin() is triggered when the login form is submitted. When the login button is triggered, it sends a Post request to /users/login with the form data, which includes {withCredentials: true} to enable cookie handling.

    If login is successful, it extracts the JWT token, user data, and Stream Chat token from the response and stores them in the localStorage so the user stays logged in across sessions. Then it redirects the user to the dashboard page using navigate(‘/dashboard’).

    Frontend-Login

    Set Up the Frontend Route

    Just as you set up the backend route, you have to do the same for the frontend.

    Head to App.jsx. Before adding the route, make sure you have installed the react-router-dom package. If not, run this command in the frontend terminal:

    npm install react-router-dom

    Then, add the command to your App.jsx file:

    import React from 'react';
    import { createBrowserRouter, RouterProvider } from 'react-router-dom';
    import HomeIndex from './pages/Home/HomeIndex';
    import Hero from './pages/Home/Hero';
    
    //Authentication Section
    import NewUser from './pages/Auth/Join/NewUser';
    import Login from './pages/Auth/login/Login'
    import VerifyAcct from './pages/Auth/login/VerifyAcct';
    
    // Dashboard
    import Dashboard from './pages/Dashboard/Dashboard';
    import VideoStream from './components/VideoStream';
    
    const router = createBrowserRouter([
      {
        path: '/',
        element: <HomeIndex />,
        children: [
          { index: true, element: <Hero /> }
        ],
      },
    
      {
        path: 'signup',
        element: <NewUser />,
        children: [
          { index: true, element: <NewUser /> }
        ],
      },
    
      {
        path: 'login',
        element: <Login />,
        children: [
          {index:true, element:<Login />}
        ]
      },
    
    ]);
    {// visit the frontend Github repository to see the remaining code for the OTP Verification
    https://github.com/Derekvibe/Telehealth_Frontend/blob/main/src/App.jsx}
    
    function App() {
      return (
        <div className='border border-red-700 w-full min-w-[100vw] min-h-[100vh]'>
          <RouterProvider router={router} />
        </div>
      );
    }
    
    export default App;
    

    Stream Chat and Video Integration

    Before proceeding to the dashboard, let’s integrate the Stream Chat and Video functionality into the project.

    First, create a free Stream account, start a new project in your dashboard, and get your APP KEY and API_SECRET.

    STREAM_API_KEY=your_app_key
    STREAM_API_SECRET=your_api_secret
    

    Watch the Stream Chat React Quick Start Guide to see how you can set it up.

    Next, store your Stream APP KEY and API_SECRET in your .env.

    Install Stream Packages (Frontend)

    Now, install the Stream Chat and Video packages in your terminal.

    npm install stream-chat stream-chat-react
    npm install @stream-io/video-react-sdk
    npm install @stream-io/stream-chat-css
    

    Stream Token Handler

    First, create a new file in your frontend Src directory and name it. In this example, it’s StreamContext.jsx. This file sets up a context to fetch and manage the Stream Chat token on login and includes logout functionality.

    import React, { createContext, useContext, useEffect, useState } from "react";
    import axios from "axios";
    
    const API_URL = import.meta.env.VITE_API_URL || 'https://telehealth-backend-2m1f.onrender.com/api/v1';
    
    // 1. Create the context
    const StreamContext = createContext();
    
    // 2. Provider component
    export const StreamProvider = ({ children }) => {
      const [user, setUser] = useState(null);
      const [token, setToken] = useState(null);
    
      useEffect(() => {
        const fetchToken = async () => {
          try {
            const res = await axios.get(`${API_URL}/stream/get-token`, {
              withCredentials: true,
            });
    
            if (res.data?.user && res.data?.token) {
              setUser(res.data.user);
              setToken(res.data.token);
              console.log("Stream user/token:", res.data);
            } else {
              console.error("Token or user missing in response:", res.data);
            }
          } catch (error) {
            console.error("Error fetching Stream token:", error);
          }
        };
    
        fetchToken();
      }, []);
    
      //Log out Functionality
      const logout = async () => {
        try {
          await axios.post(`${API_URL}/users/logout`, {},
            {
              withCredentials: true
            });
    
          // Clear localStorage
          localStorage.removeItem('authToken');
          localStorage.removeItem('user');
          localStorage.removeItem('streamToken');
    
          // Clear context
          setUser(null);
          setToken(null);
        } catch (error) {
          console.error("Logout failed", error);
        }
      };
    
      // Expose Logout with capital L
      return (
        <StreamContext.Provider value={{ user, token, Logout:logout }}>
          {children}
        </StreamContext.Provider>
      );
    };
    
    // 3. Custom hook for easy access
    export const useStream = () => useContext(StreamContext);
    

    The code above creates a StreamContext using React’s Context API. In the useEffect section, it makes a GET request to /stream/get-token to fetch the authenticated user and their Stream token. Then it stores them in user and token states. It also provides the user/token through the context so that any component that needs it can make use of it.

    Finally, it adds a Logout method that hits the logout endpoint and clears all stored auth data from localStorage.

    Next, open your main.jsx and wrap your entire application with the StreamProvider so all child components can access the Stream context.

    // main.jsx
    import { createRoot } from 'react-dom/client';
    import { StrictMode } from 'react';
    import App from './App';
    import { StreamProvider } from './components/StreamContext';
    
    createRoot(document.getElementById('root')).render(
      <StrictMode>
        <StreamProvider>
          <App />
        </StreamProvider>
      </StrictMode>
    );
    

    Set Up Stream API

    After successfully creating the streamContent, the next step is to set up the Stream API. This will be the endpoint from which the user ID and user Stream token can be generated and fetched during login.

    To set it up, navigate to your backend directory by running cd Backend in your terminal. Then install the Stream package using the command:

    npm install getstream
    npm install stream-chat stream-chat-react
    

    Open your .env file and add your Stream API KEY and API_SECRET:

    STREAM_API_KEY=your_app_key
    STREAM_API_SECRET=your_api_secret
    

    Next, open your authController.js and create your Stream Chat logic:

    //Initialize the Stream Client
    const {StreamChat} = require("stream-chat");
    const streamClient = StreamChat.getInstance(
      process.env.STREAM_API_KEY,
      process.env.STREAM_API_SECRET
    );
    
    // Modifies the `createSendToken to include `streamToken`
    const createSendToken = (user, statusCode, res, message) => {
    ……
    const streamToken = streamClient.createToken(user._id.toString());
    
      //structure of the cookie response when sent to the user
      res.status(statusCode).json({
        status: "success",
        message,
        token,
        streamToken,
        data: {
          user: {
            id: user._id.toString(),
            name: user.username,
          },
        },
      });
    };
    
    //login functionality
    exports.login = catchAsync(async (req, res, next) => {
     {…………………..}
    
    // Generate Stream token
      await streamClient.upsertUser({
        id: user._id.toString(),
        name: user.username,
      });
      const streamToken = streamClient.createToken(user._id.toString());
    
    user.password = undefined;
    
      res.status(200).json({
        status: "success",
        message: "Login successful",
        token,
        user: {
          id: user._id.toString(),
          name: user.username,
        },
        streamToken,
      });
    

    streamRoutes Endpoint

    Next, create an endpoint from which the Stream token can be called. To do this, go to your routes folder and create a new file called streamRoutes.js. In streamRoutes.js, add the command:

    const express = require("express");
    const { StreamChat } = require("stream-chat");
    
    const protect = require("../middlewares/protect");
    
    const router = express.Router();
    
    const apiKey = process.env.STREAM_API_KEY;
    const apiSecret = process.env.STREAM_API_SECRET;
    
    if (!apiKey || !apiSecret) {
      throw new Error(
        "Missing Stream credentials. Check your environment variables."
      );
    }
    
    const streamClient = StreamChat.getInstance(apiKey, apiSecret);
    
    router.get("/get-token", protect, async (req, res) => {
      try {
        const { id, username } = req.user || {};
        console.log(req.user.id, "User");
        // TRY LOGGING THE ID AND NAME FROM YOUR REQUEST FIRST
    
        if (!id || !username) {
          return res.status(400).json({ error: "Invalid user data" });
        }
    
        // const userId = _id.toString();
        const user = { id, username };
    
        // Ensure user exists in Stream backend
        await streamClient.upsertUser(user);
    
        // Add user to my_general_chat channel
        const channel = streamClient.channel("messaging", "my_general_chat");
        await channel.addMembers([id]);
    
    
        // Generate token
        const token = streamClient.createToken(id);
        res.status(200).json({ token, user });
      } catch (error) {
        console.error("Stream token generation error:", error);
        res.status(500).json({ error: "Failed to generate Stream token" });
      }
    });
    
    /**
     * @route   POST /api/stream/token
     * @desc    Generate a Stream token for any userId from request body (no auth)
     * @access  Public
     */
    router.post("/token", async (req, res) => {
      try {
        const { userId, name } = req.body;
    
        if (!userId) {
          return res.status(400).json({ error: "userId is required" });
        }
    
        const userName = name || "Anonymous";
        const user = { id: userId, name: userName };
    
        await streamClient.upsertUser(user);
    
        // Add user to my_general_chat channel
        const channel = streamClient.channel("messaging", "my_general_chat");
        await channel.addMembers([userId]);
    
    
        const token = streamClient.createToken(userId);
    
        res.status(200).json({
          token,
          user: {
            id: userId,
            name: name,
            role: "admin",
            image: `https://getstream.io/random_png/?name=${name}`,
          },
        });
      } catch (error) {
        console.error("Public token generation error:", error);
        res.status(500).json({ error: "Failed to generate token" });
      }
    });
    
    module.exports = router;
    

    User Logout Endpoint

    Go to your authController.js and create a functionality that handles logging out the user:

    exports.logout = catchAsync(async (req, res, next) => {
      res.cookie("token", "loggedout", {
        expires: new Date(0),
        httpOnly: true,
        secure: process.env.NODE_ENV === "production",
      });
    
      res.status(200).json({
        status: "success",
        message: "Logged out successfully",
      });
    });
    

    Then register your logout route to your userRouters.js:

    const express = require("express");
    const {logout}= require("../controller/authController");
    const isAuthenticated = require("../middlewares/isAuthenticated");
    
    
    router.post("/logout", isAuthenticated, logout);
    
    module.exports = router;
    

    Chat and Video Function (Frontend)

    After setting up your backend Stream API, the last task is setting up chat and video in your frontend application.

    Dashboard.jsx

    Create a new file Dashboard.jsx in your frontend directory. This is where you will set up your Stream and video function.

    import React, { useState, useEffect } from "react";
    import axios from "axios";
    import {
      Chat,
      Channel,
      ChannelHeader,
      MessageInput,
      MessageList,
      Thread,
      Window,
      useCreateChatClient,
    } from "stream-chat-react";
    import "stream-chat-react/dist/css/v2/index.css";
    import { useStream } from "../../components/StreamContext";
    import VideoStream from "../../components/VideoStream";
    import { useNavigate } from "react-router-dom";
    
    
    
    const apiKey = import.meta.env.VITE_STREAM_API_KEY;
    
    const API_URL = import.meta.env.VITE_API_URL || 'https://telehealth-backend-2m1f.onrender.com/api/v1';
    
    function App() {
      const [channel, setChannel] = useState(null);
      const [clientReady, setClientReady] = useState(false);
      const navigate = useNavigate();
    
      // const ChatComponent = () => {
        const { user, token, Logout } = useStream();
    
        // Always call the hook
        const chatClient = useCreateChatClient({
          apiKey,
          tokenOrProvider: token,
          userData: user?.id ? { id: user.id } : undefined,
        });
    
      // Debug: See when user/token is ready
      useEffect(() => {
        console.log("Stream user:", user);
        console.log("Stream token:", token);
      }, [user, token]);
    
        // Connect user to Stream
        useEffect(() => {
          const connectUser = async () => {
            if (!chatClient || !user || !token || !user?.id) {
              console.warn("Missing chat setup data:", { chatClient, token, user });
              return;
            }
    
    
            try {
              await chatClient.connectUser(
                {
                  id: user.id,
                  name: user.name || "Anonymous",
                  image:
                    user.image ||
                    `https://getstream.io/random_png/?name=${user.name || "user"}`,
                },
                token
              );
    
              const newChannel = chatClient.channel("messaging", "my_general_chat", {
                name: "General Chat",
                members: [user.id],
              });
    
              await newChannel.watch();
              setChannel(newChannel);
              setClientReady(true);
            } catch (err) {
              console.error("Error connecting user:", err);
            }
          };
    
          connectUser();
        }, [chatClient, user, token]);
    
        const handleVideoCallClick = () => {
          navigate("/videoCall");
      };
    
      const handleLogout = async () => {
        await Logout();
        navigate("/login");
      }
    
      if (!user || !token) {
        return <div className="text-red-600">User or token not ready.</div>;
      }
    
      if (!clientReady || !channel) return <div>Loading chat...</div>;
    
    
      return (
    { checkout the github repo}
                <ChannelHeader />
                <MessageList />
                <MessageInput />
              </Window>
              <Thread />
            </Channel>
          </Chat>
    
    
          </div>
    
        );
      }
    
    export default App;
    

    Video Setup

    You’ll now set up the video function for your frontend. To do so, create a new file VideoStream.jsx and add the command:

    import React, { useEffect, useState } from "react";
    import { StreamVideoClient } from "@stream-io/video-client";
    import { StreamVideo, StreamCall } from "@stream-io/video-react-sdk";
    import { useNavigate } from "react-router-dom";
    
    
    import { useStream } from "./StreamContext";
    import { MyUILayout } from "./MyUILayout";
    
    
    const apiKey = import.meta.env.VITE_STREAM_API_KEY;
    
    function VideoStream() {
    
      const [client, setClient] = useState(null);
        const [call, setCall] = useState(null);
      const { user, token } = useStream();
      const navigate = useNavigate();
    
      useEffect(() => {
    
        let clientInstance;
        let callInstance;
    
    
        const setup = async () => {
          if (!apiKey || !user || !token) return;
    
          clientInstance = new StreamVideoClient({ apiKey, user, token });
    
          callInstance = clientInstance.call("default", user.id); // Use user.id as callId
    
    
          await callInstance.join({ create: true });
    
          setClient(clientInstance);
          setCall(callInstance);
        };
    
        setup();
    
        return () => {
          if (callInstance) callInstance.leave();
          if (clientInstance) clientInstance.disconnectUser();
    
        };
      }, [user, token]);
    
      const handleLeaveCall = async () => {
        if (call) await call.leave();
        if (client) await client.disconnectUser();
    
        setCall(null);
        setClient(null);
    
        navigate("/dashboard"); // or any other route
      };
    
      if (!apiKey) return <div>Missing Stream API Key</div>;
    
      if (!client || !call)
        return (
      <div className="flex items-center justify-center h-screen text-xl font-semibold">
        Connecting to the video call...
      </div>
        );
    
      return (
        <div className="relative h-screen w-full p-2 sm:p-4 bg-gray-50">
          <StreamVideo client={client}>
            <StreamCall call={call}>
              <MyUILayout />
            </StreamCall>
          </StreamVideo>
    
          <button
            onClick={handleLeaveCall}
            className="absolute top-2 right-2 sm:top-4 sm:right-4 bg-red-600 text-white text-sm sm:text-base px-3 sm:px-4 py-1.5 sm:py-2 rounded-lg shadow hover:bg-red-700 transition"
          >
            Leave Call
          </button>
        </div>
        );
      }
    
    export default VideoStream;
    
    //MYUILayout.jsx
    import React from 'react';
    import {
      useCall,
      useCallStateHooks,
      CallingState,
    } from '@stream-io/video-react-sdk';
    
    export function MyUILayout() {
      const call = useCall();
      const { useCallCallingState, useParticipantCount } = useCallStateHooks();
      const callingState = useCallCallingState();
      const participantCount = useParticipantCount();
    
      if (callingState !== CallingState.JOINED) {
        return <div>Joining call...</div>;
      }
    
      return (
        <div style={{ padding: '1rem', fontSize: '1.2rem' }}>
          ✅ Call "<strong>{call?.id}</strong>" has <strong>{participantCount}</strong> participants.
        </div>
      );
    }
    

    Project Demo

    Telehealth Final Project Demo

    Congratulations! You have successfully integrated Stream’s chat and video function into your application.

    Conclusion

    And that’s a wrap!

    You’ve built a telehealth app with secure video, real-time chat, and user authentication – all powered by Stream’s Chat and Video SDKs.

    This foundation gives you the flexibility to expand further with features like appointment scheduling, patient history, or HIPPA-compliant file sharing.

    You can find the frontend and backend applications on GitHub. The frontend app is hosted using the Vercel hosting service, and the backend is hosted on Render.

    Check out the repository of the application.

    Happy coding! 🚀


    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More.

  • Apple Sues the YouTuber Who Leaked iOS 26

    July 18, 2025
    Technology

    Leaks are a constant part of big product news cycles, particularly for companies like Apple. Online soothsayers like Jon Prosser and Bloomberg’s Mark Gurman have long predicted the content of upcoming Apple announcements, citing anonymous sources from within the company to glean glimpses of what’s next. They have been correct often enough to become a real pain for the Cupertino company.

    Now, Apple has seized upon an opportunity to fight back against leaks. In a complaint filed Thursday in US federal court for the Northern District of California, Apple is accusing prominent leaker Jon Prosser of allegedly scheming to “break into an Apple development iPhone, steal Apple’s trade secrets, and profit from the theft.”

    The suit alleges that, along with a co-conspirator, Prosser, who makes videos on the YouTube channel Front Page Tech, deliberately took advantage of an Apple employee named Ethan Lipnik who had access to a developer iPhone running the as-of-yet unreleased software that would become iOS 26. The suit alleges that the other defendant, Michael Ramacciotti, who was staying at Lipnik’s home at the time, waited for Lipnik to leave before accessing the development phone and showing details of the unreleased software to Prosser over a video call. Prosser then allegedly used information obtained in that early peek at iOS in videos that he posted on his YouTube channel well before Apple officially announced the updates.

    The suit also alleges that Ramacciotti claims Prosser masterminded the whole plan, promising Ramacciotti he would “find out a way for [Mr. Ramacciotti] to get payment.”

    Prosser has denied any wrongdoing and says the details of the suit are incorrect. In public posts on X, Prosser has maintained that this was not how things went down on his end.

    “The details that Apple was given are just not accurate,” Prosser wrote to me via a direct message on X. “I had no knowledge of how the info was obtained. He never told me he ‘needed money’ and I absolutely did not instruct him to act this out.”

    Prosser doesn’t deny that he did reveal the information gleaned from Ramacciotti. He highlighted the details about the unreleased update in a series of videos earlier this year, including one in which he called the news, “the biggest iOS leak ever.”

    Despite the question of how he actually acquired the information, the aftermath has been messy. Ethan Lipnik, the Apple employee who had the development phone that Ramacciotti is alleged to have accessed, was fired by the company. According to the complaint, Apple ended Lipnik’s employment “for failing to follow Apple’s policies designed to protect its confidential information, including development devices and unreleased software and features.”

    Apple has not responded to requests for comment. Ethan Lipnik has also not responded to a request for comment.

    “It’s quite a significant lawsuit,” says Anshel Sag, principal analyst at the tech research firm Moor Insights & Strategy, in an email. “But I also believe that there is quite a disparity between what Prosser is saying and what Apple alleges, especially since the employee seemed to not be aware of what was going on.”

    The biggest problem being alleged here, Sag says, is that while the data was taken from a development device that should have been better protected, how that data was acquired and where it came from should have been vetted before the details were put out into the world.

    “Ultimately, every company fights leaks, especially Apple, but with this happening entirely in the US the company has a lot more power and laws to support its efforts,” Sag says. Many Apple leaks have historically come from sources outside the US, such as from within its manufacturing and supply chain partners in Asia. Because the complaint focuses on events that allegedly took place in California, Apple can argue in federal court that two US laws—the Defend Trade Secrets Act and the Computer Fraud And Abuse Act—have been violated.

    Prosser says he didn’t even find out about the lawsuit until reading a MacRumors story about the filing.

    “I feel awful that Ethan was terminated over this,” Prosser says. “I wish he had shared with Apple what had occurred, and I wish that Apple would have connected with me for more answers—I would have gladly chatted with them.”


    Source: Wired.

  • Questions About Death, Answered: Funeral Planning, End-of-Life Decisions and More

    July 18, 2025
    Health

    From end-of-life care to burials and beyond, we tackled your questions on the topic.


    Source: NYT > Well.

  • Your Joyspan is the Key to Aging Well

    July 18, 2025
    Health

    A long life doesn’t matter much if you don’t enjoy it.


    Source: NYT > Well.

  • Getting Creative With Versal Letters

    July 18, 2025
    Software

    A while back, our man Geoff Graham treated us to a refresher on the CSS initial-letter property, but how can you style drop and initial caps to reflect a brand’s visual identity and help to tell its stories?

    Here’s how I do it in CSS by combining ::first-letter and initial-letter with other unexpected properties, including border-image, and clip-path.

    Showing three screenshots of Patty Meltt's website, including a homepage a videos page, and a discography, all side-by-side.
    Patty Meltt is an up-and-coming country music sensation.

    My brief: Patty Meltt is an up-and-coming country music sensation, and she needed a website to launch her new album. She wanted it to be distinctive-looking and memorable, so she called Stuff & Nonsense. Patty’s not real, but the challenges of designing and developing sites like hers are.

    First, a drop cap recap. Chris Coyier wrote about drop caps several years ago. They are a decorative letter at the beginning of a paragraph, often spanning several lines of text. It’s a typographic flourish found in illuminated manuscripts and traditional book design, where it adds visual interest and helps guide a reader’s eye to where they should begin.

    Study manuscripts from the Middle Ages onwards, and you’ll find hand-decorated illuminated capitals. The artists who made these initial letters were fabulously called “illuminators.” These medieval versals went beyond showing someone where to start reading; historiated letters also illustrated the stories, which was especially useful since most people in the Middle Ages couldn’t read.

    The first letter is a P that is four lines tall, bold, and colored light gray.
    A basic drop cap

    On the web, drop caps can improve readability and reflect a brand’s visual identity.

    A brief refresher on properties and values

    In CSS, drop caps are created using the ::first-letter pseudo-element in combination with initial-letter. As you might expect, ::first-letter targets the very first letter of a block of text, enabling you to style it independently from the rest of a paragraph. The first number sets how many lines tall the letter appears, and the second controls its baseline alignment — that is, which line of text the bottom of the cap sits on.

    p::first-letter {
      -webkit-initial-letter: 3 3;
      initial-letter: 3 3;
    }

    Because browser support still varies, it’s common to include both the unprefixed and -webkit- prefixed properties for maximum compatibility. And speaking of browser support, it’s also sensible to wrap the initial-letter property inside an @supports CSS at-rule so we can check for browser support and provide a fallback, if needed:

    @supports (initial-letter:2) or (-webkit-initial-letter:2) {
      p::first-letter {
        -webkit-initial-letter: 3 3;
        initial-letter: 3 3;
      }
    }

    The initial-letter property automatically calculates the font size to match the number of lines a drop cap spans. On its own, this can make for quite a first impression. However, drop caps really start to come to life when you combine initial-letter with other CSS properties.

    Tip: Interactive examples from this article are available in my lab.

    Shadows

    The first example is a single shadow and the second uses two shadows to create a 3D effect.
    Text shadows applied to first letters (live demo)

    When I want to lift a drop cap off the page, I can add a single text-shadow. Shadows can be colourful and don’t have to be black. I created a full live demo you can check out.

    p::first-letter {
      /* ... *//
      text-shadow: 6px 6px 0 #e6d5b3;
    }

    But why use just one shadow when two hard-edged shadows will turn a cap into a classic graphic typographic element?

    p::first-letter {
      /* ... */
      text-shadow: 
        -6px -6px 0 #7d6975, 
        6px 6px 0 #e6d5b3;
    }
    Examples showing unstyled, single text shadow, and two text shadows (live demo)

    Strokes

    A text shadow applied to a first letter (live demo)

    The text-stroke property — shorthand for text-stroke-width and text-stroke-color — adds an outline to the centre of the text shape. It’s a Baseline feature and is now widely available. I can make the cap text transparent or colour it to match the page background.

    p::first-letter {
      /* ... */
      text-stroke: 5px #e6d5b3;
    }

    Backgrounds

    Solid and gradient backgrounds applied to first letters (live demo)

    Adding a background is a simple way to start making a cap more decorative. I could start by adding a solid background-color.

    p::first-letter {
      /* ... */
      background-color: #97838f;
    }

    To add a lighting effect, I could apply a conical, linear, or radial gradient background image (here’s a demo):

    p::first-letter {
      /* ... */
      background-color: #e6d5b3;
      background-image: linear-gradient(135deg,#c8b9c2 0%, #7d6975 50%);
    }

    And even an image URL to use a bitmap or vector image as a background (and here’s that demo):

    p::first-letter {
      /* ... */
      background-color: #e6d5b3;
      background-image: url(...);
      background-size: cover;
    }
    Background images and a background clipped to text

    Things become even more interesting by clipping a bitmap, gradient, or vector background image to the text while setting its colour to transparent. Now, the image will only appear inside the text space (demo).

    p::first-letter {
      /* ... */
      background-clip: text;
      color: transparent;
    }

    Borders

    Two examples of borders applied to first letters, one square and one rounded

    You might think borders are boring, but there’s plenty you can do to make them look interesting. I could start by applying a solid border to surround the cap box (demo).

    p::first-letter {
      /* ... */
      border: 5px solid #e6d5b3;
    }

    Then, I could apply border-radius to slightly round all its corners (demo).

    p::first-letter {
      /* ... */
      border-radius: 1rem;
    }

    Or, I might round individual corners for a more interesting look (demo):

    p::first-letter {
      /* ... */
      border-top-left-radius: 3rem;
      border-bottom-right-radius: 3rem;
    }
    A border radius applied to the first letter, where the top-left and bottom-right edges are rounded (live demo)

    And then there’s the border-image property, a powerful, yet often overlooked CSS tool. By slicing, repeating, and outsetting images, you can create intricate borders and decorative drop caps with minimal code.

    Initial letter with a thick gradient border.
    A CSS border image applied to a first letter (live demo)

    You can insert a bitmap or vector format image, or drop a CSS gradient into the border space:

    p::first-letter {
      /* ... */
      border-style: solid;
      border-width: 10px;
      border-image: conic-gradient(...) 1;
    }

    Clipping

    The first example is an arrow-shaped background pointing toward the right. The second example is a background cut out like a sunburst.
    Clipping first letters

    The clip-path property lets you define a custom shape that controls which parts of an element are visible and which are hidden. Instead of always showing a rectangular box, you can use clip-path to crop elements into circles, polygons, or even complex shapes defined with SVG paths. It’s an effective way to create visual effects like this right-facing arrow. Clipping the drop cap into an arrow shape isn’t just decorative — it reinforces direction and hierarchy, literally pointing readers to where the story begins. Here’s a demo of the following example.

    p::first-letter {
      /* ... */
      padding-inline: 1rem 2rem;
      background-color: #e6d5b3;
      clip-path: polygon(...);
    }

    Or a glossy sticker shape cap, made by combining clip-path with a gradient background image and a text shadow (demo).

    Transforms

    Two examples of transforming first letters, one rotated (demo) and one scaled (demo)

    You can transform a drop cap independently from the rest of a paragraph by rotating, scaling, skewing, or translating it to make it feel more dynamic:

    p::first-letter {
      /* ... */
      margin-inline-end: 2.5em;
      transform: skew(20deg, 0deg);
    }
    Two examples of skewed first letters, angling each one so that they are slanted backward towards the left.

    And with a little trial and error to arrive at the correct values, you could even flow the remaining paragraph text around the cap using the shape-outside property (demo):

    p::first-letter {
      /* ... */
      display: block;
      float: left;
      shape-outside: polygon(0 0, 0 200px, 250px 600px);
      shape-margin: 50px;
      transform: skew(20deg, 0deg) translateX(-60px);
    }

    Drop caps don’t just help guide a reader’s eye to where they should begin; they also set the tone for what follows. A well-designed drop cap adds visual interest at the start of a block of text, drawing attention in a way that feels intentional and designed. Because it’s often the first element the reader sees, caps can carry a lot of visual weight, making them powerful tools for expressing a brand’s identity.

    Designing for Patty Meltt

    Patty Meltt wanted a website packed with design details. Every element added to a design is an opportunity to be expressive, and that includes her drop caps.

    Her biography page is presentable, but we felt a focus on where someone should start reading was lacking.

    Patty Meltt’s biography without a drop cap

    From the selection of designs I showed her, she felt the sticker-style cap best suited her brand.

    Showing all letters of the alphabet styled in the artist’s preferred way, with a circular background shaped like a sun with sharp angles.

    To implement it, first, I added a cursive typeface which matches her branding and contrasts with the rest of her typographic design:

    p::first-letter {
      font-family: "Lobster Two", sans-serif;
      font-weight: 700;
    }

    I changed the cap colour to match the page background and added a semi-transparent text shadow:

    p::first-letter {
      /* ... */
      color: #140F0A;
      text-shadow: 6px 6px 0 rgba(163,148, 117, .8);
    }

    Next, I clipped the cap box to a visible area shaped like a sticker:

    p::first-letter {
      /* ... */
      clip-path: polygon(...);
    }

    …before applying two background images — a noise-filled SVG and a radial gradient — that I blended using a background-blend-mode:

    p::first-letter {
      /* ... */
      background-image: url(img/cap-noise.svg), 
      radial-gradient(circle, #e6d5b3 0%, #cdaa65 100%);
      background-blend-mode: soft-light, normal;
    }
    Patty Meltt’s biography with a stylsh new drop cap (demo)

    The result is a drop cap that’s as stylish as cut-off jeans and a pair of gator-skinned boots.

    Conclusion

    Styling drop caps isn’t just about decoration — it’s about setting a tone, drawing readers in, and using every detail to express a brand’s voice. CSS has the tools to go beyond the default: gradients, textures, borders, and even complex shapes all help transform first letters into statements. So don’t waste the opportunities that drop caps give you. Make ’em sing.


    Getting Creative With Versal Letters originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.


    Source: CSS-Tricks.

  • How China’s Patriotic ‘Honkers’ Became the Nation’s Elite Cyberspies

    How China’s Patriotic ‘Honkers’ Became the Nation’s Elite Cyberspies

    July 18, 2025
    Technology

    In the summer of 2005, Tan Dailin was a 20-year-old grad student at Sichuan University of Science and Engineering when he came to the attention of the People’s Liberation Army of China.

    Tan was part of a burgeoning hacker community known as the Honkers—teens and twentysomethings in late-’90s and early-’00s China who formed groups like the Green Army and Evil Octal and launched patriotic cyberattacks against Western targets they deemed disrespectful to China. The attacks were low-sophistication—mostly website defacements and denial-of-service operations targeting entities in the US, Taiwan, and Japan—but the Honkers advanced their skills over time, and Tan documented his escapades in blog posts. After publishing about hacking targets in Japan, the PLA came calling.

    Tan and his university friends were encouraged to participate in a PLA-affiliated hacking contest and won first place. The PLA invited them to an intense, monthlong hacker training camp, and within weeks Tan and his friends were building hacking tools, studying network infiltration techniques, and conducting simulated attacks.

    The subsequent timeline of events is unclear, but Tan, who went by the hacker handles Wicked Rose and Withered Rose, then launched his own hacking group—the Network Crack Program Hacker (NCPH). The group quickly gained notoriety for winning hacking contests and developing hacking tools. They created the GinWui rootkit, one of China’s first homegrown remote-access backdoors and then, experts believe, used it and dozens of zero-day exploits they wrote in a series of “unprecedented” hacks against US companies and government entities over the spring and summer of 2006. They did this on behalf of the PLA, according to Adam Kozy, who tracked Tan and other Chinese hackers for years as a former FBI analyst who now heads the SinaCyber consulting firm, focused on China.

    Tan revealed online at the time that he and his team were being paid about $250 a month for their hacking, though he didn’t say who paid or what they hacked. The pay increased to $1,000 a month after their summer hacking spree, according to a 2007 report by former threat intelligence firm VeriSign iDefense.

    At some point, Tan switched teams and began contracting for the Ministry of State Security (MSS), China’s civilian intelligence agency, as part of its notorious hacking group known as APT 41. And in 2020, when Tan was 36, the US Justice Department announced indictments against him and other alleged APT 41 members for hacking more than 100 targets, including US government systems, health care organizations, and telecoms.

    Tan’s path to APT 41 isn’t unique. He’s just one of many former Honkers who began their careers as self-directed patriotic hackers before being absorbed by the state into its massive spying apparatus.

    Not a lot has been written about the Honkers and their critical role in China’s APT operations, outside of congressional testimony Kozy gave in 2022. But a new report, published this month by Eugenio Benincasa, senior cyberdefense researcher at the Center for Security Studies at ETH Zürich university in Switzerland, expands on Kozy’s work to track the Honkers’ early days and how this group of skilled youths became some of China’s most prolific cyberspies.

    “This is not just about [Honkers] creating a hacker culture that was implicitly aligned with national security goals,” Benincasa says, “but also the personal relations they created [that] we still see reflected in the APTs today.”

    Early Days

    The Honker community largely began when China joined the internet in 1994, and a network connecting universities and research centers across the country for knowledge-sharing put Chinese students online before the rest of the country. Like US hackers, the Honkers were self-taught tech enthusiasts who flocked to electronic bulletin boards (dial-up forums) to share programming and computer hacking tips. They soon formed groups like Xfocus, China Eagle Union, and The Honker Union of China and came to be known as Red Hackers or Honkers, a name derived from the Mandarin word “hong,” for red, and “heike,” for dark visitor—the Chinese term for hacker.

    The groups were self-governing with loosely formed hierarchies and even had codes of ethics shaped by influential members like Taiwanese hacker Lin Zhenglong (known by his handle “coolfire”). Lin believed hacking skills should be cultivated only to strengthen cyberdefenses—to learn the ways of hackers in order to thwart them—and wrote an influential hacking manual “to raise awareness about the importance of computer security, not to teach people how to crack passwords.”

    There were no simulated environments for hackers to build their skills at the time, so Honkers often resorted to hacking real networks. Lin didn’t oppose this—hacking wasn’t illegal in China except against government, defense, or scientific research networks—but he published a set of ethical guidelines advising hackers to avoid government systems or causing permanent damage and to restore systems to their original condition after Honkers finished hacking them.

    But these guidelines soon fell away, following a series of incidents involving foreign affronts to China. In 1998, a wave of violence in Indonesia broke out against ethnic Chinese there, and outraged Honker groups responded with coordinated website defacements and denial-of-service attacks against Indonesian government targets. The next year, after Taiwanese president Lee Teng-hui announced his Two-States Theory challenging the Communist Party’s One China doctrine, the Honkers defaced Taiwanese government sites with patriotic messages asserting the existence of a unified China.

    In 2000, after participants at a conference in Japan denied facts around the Nanjing Massacre, in which an estimated 300,000 Chinese were killed during Japan’s 1930’s occupation of the city, Honkers circulated a list of more than 300 Japanese government and corporate sites, along with email addresses of Japanese officials, and prompted members to target them.

    The so-called patriotic cyberwars gave the Honkers a common cause that forged an identity unique from Western hacking groups, which the Honkers had emulated until then. Where Western hackers were primarily motivated by curiosity, intellectual challenge, and bragging rights, the Honkers bonded over their common cause to help China “rise up.” In the words of a China Eagle Union pledge, the Honkers vowed “to put the interests of the Chinese nation above everything else.”

    The patriotic wars put China’s Honkers on the map and inspired more to join them. Honker Union swelled to an estimated 80,000 members, Green Army to 3,000. Most were just enthusiasts and adventure seekers, but a subset stood out for leadership and hacking skills. A particularly influential group among these, whom Benincasa calls the Red 40, would go on to found or join many of China’s top cybersecurity and tech firms and become integral to the state’s cyberspy machine.

    There’s no evidence that the government directed the patriotic hacking operations, says Benincasa, but their activity aligned with state interests, and they drew government attention. A retired People’s Liberation Army rear admiral and former professor at the PLA National Defense University praised their patriotism. The public also appeared to support it. A report claimed that 84 percent of internet users in China favored the patriotic hacking.

    But in April 2001, this began to change after a Chinese fighter jet clipped a US reconnaissance plane midair off the coast of Hainan and sparked an international incident. The collision killed the Chinese pilot and forced the US plane to land on Hainan, where the Chinese military seized the aircraft and held the crew for more than a week. The incident stoked nationalist sentiments among US and Chinese hackers alike, and both sides lobbed cyberattacks against the other country’s systems.

    The Chinese government grew concerned over its lack of control of the Honkers and feared they could become a liability and escalate tensions. The Chinese Communist Party’s official newspaper likened the hacking to “web terrorism,” and the head of the Internet Society of China issued a statement through China’s official state media condemning it as well. The retired PLA rear admiral who previously praised the groups now warned they were a threat to international relations.

    The Honkers got the message, but with their patriotic mission shelved, the groups now became less cohesive. There were leadership clashes and disagreements over direction and priorities—some wanted to turn professional and launch cybersecurity companies to defend China’s systems against attack; others wanted to go rogue and sell malicious tools. The former left to join tech firms like Baidu, Alibaba, and Huawei or cybersecurity firms like Venustech and Topsec. Some became entrepreneurs and launched their own security firms, like NSFocus and Knownsec, which became leaders in vulnerability research and threat intelligence. Some, however, shifted to cybercrime. And others, like Tan, became contract hackers for the PLA and MSS or founded firms that served these operations.

    Honker Recruitment

    According to Benincasa, the PLA and MSS began hiring Honkers around 2003, but the recruitment became more structured and earnest following the 2006 hackings attributed to NCPH and Tan. The recruitment expanded during and after the 2008 Beijing Olympics and was likely helped in 2009 with the passage of China’s Criminal Law Amendment VII, which criminalized unauthorized intrusions into any network as well as the distribution of hacking tools.

    Hacker forums began to shutter, and some Honkers got arrested. Word spread that Tan was among them. According to Kozy, Tan faced seven and a half years in prison, though it’s unclear whether he served any time. Kozy believes he cut a deal and began work for the MSS. In 2011, it appears he launched an antivirus firm named Anvisoft, which may have served as a front for his MSS work.

    Former Honkers Zeng Xiaoyong (envymask) and Zhou Shuai (coldface) also became contractors for the PLA and MSS and worked on operations conducted by APT 41, APT 17, and APT 27, according to Benincasa. Some worked through shell companies, others worked through legitimate firms who acted as intermediaries to the intelligence services.

    Topsec and Venustech were two firms alleged to have assisted these efforts. Topsec employed a number of former Honkers, including the founder of the Honker Union of China, and Topsec’s founder once acknowledged in an interview that the PLA directed his company. In 2015, Topsec was linked to state-sponsored cyber operations, including the Anthem Insurance breach in the US.

    Over the years, many tools used by China APT groups were built by Honkers, and the PLA and MSS mined them for vulnerability research and exploit development. In 1999, Huang Xin (glacier), a member of Green Army, released “Glacier,” a remote-access trojan. The next year, he and Yang Yong (coolc) from XFocus released X-Scan, a tool to scan networks for vulnerabilities that is still used by hackers in China today. In 2003, two members of Honker Union released HTRAN, a tool to hide an attacker’s location by rerouting their traffic through proxy computers, which has been used by China’s APTs. Tan and fellow NCPH member Zhou Jibing (whg) are believed to have created the PlugX backdoor in 2008, which has been used by more than 10 Chinese APTs. According to Benincasa, Zhou developed it even further to produce ShadowPad, which has been used by APT 41 and others.

    Over the years, leaks and US indictments against former Honkers have exposed their alleged post-Honker spy careers, as well as China’s use of for-profit firms for state hacking operations. The latter include i-Soon and Integrity Tech, both launched by former Honkers.

    Wu Haibo (shutdown), formerly of Green Army and 0x557, launched i-Soon in 2010. And last year, someone leaked internal i-Soon files and chat logs, exposing the company’s espionage work on behalf of the MSS and MPS. In March this year, eight i-Soon employees and two MPS officers were indicted by the US for hacking operations that targeted US government agencies, Asian foreign ministries, dissidents, and media outlets.

    Integrity Tech, founded in 2010 by former Green Army member Cai Jingjing (cbird), was sanctioned by the US this year over ties to global infrastructure hacks.

    This year, the US also indicted former Green Army members Zhou and Wu for conducting state hacking operations and sanctioned Zhou over links to APT 27. In addition to engaging in state-sponsored hacking, he allegedly also ran a data-leak service selling some of the stolen data to customers, including intelligence agencies.

    This isn’t unlike early-generation US hackers who also transitioned to become cybersecurity company founders and also got recruited by the National Security Agency and Central Intelligence Agency or hired by contractors to perform hacking operations for US operations. But unlike the US, China’s whole-of-society intelligence authorities have compelled some Chinese citizens and companies to collaborate with the state in conducting espionage, Kozy notes.

    “I think that China from the beginning just thought, ‘We can co-opt [the Honkers] for state interests.’” Kozy says. “And … because a lot of these young guys had patriotic leanings to begin with, they were kind of pressed into service by saying, ‘Hey you’re going to be doing a lot of really good things for the country.’ Also, many of them started to realize they could get rich doing it.”


    Source: Wired.

  • Dennis Lehtonen Documents a Pair of Immense Icebergs Paying a Visit to a Small Greenland Village

    Dennis Lehtonen Documents a Pair of Immense Icebergs Paying a Visit to a Small Greenland Village

    July 18, 2025
    Design

    Dennis Lehtonen Documents a Pair of Immense Icebergs Paying a Visit to a Small Greenland Village

    From rocky outcrops overlooking modest, brightly painted houses, photographer Dennis Lehtonen captures an astonishing nordic phenomenon. Innaarsuit, Greenland, which sits more than 430 miles north of the Arctic Circle, sets the stage for a series of images highlighting dramatic visitors to the area’s waterways.

    Lehtonen moved to Greenland in April 2023 to work in a local fish factory. Fascinated by polar regions, he previously spent time in Lapland, the northernmost region of Finland. The minimal light pollution meant fantastic opportunities for photographing aurora borealis, which remains a steering interest as he continues to explore the snowy far reaches of the Northern Hemisphere.

    a small house in Greenland with a giant iceberg in the background

    When he arrived in Innaarsuit, he heard about an incident in 2018 when the village of around 160 Inuit residents had to be fully evacuated due to a giant iceberg settling near the shore. Estimated to have been around 100 meters high, its presence threatened people’s safety due to the dangers of pieces breaking off and causing waves large enough to hit some of the coastal houses. While inherently a tense situation, it was also astonishing to see, and Lehtonen couldn’t help being curious “what it would look like to have a skyscraper made of ice on your backyard.”

    A couple of weeks ago, Lehtonen and locals spotted an iceberg floating a few miles away, and even from the distance, he could tell it was large. Days later, it—actually a pair—slid into Innaarsuit, dwarfing the fishing village’s modest wooden houses.

    The municipality was warned to be careful when on the coast and not to travel in large groups. Fragments occasionally broke off as the iceberg moved, creating a reverberating sound akin to thunder. Many locals also documented the phenomenon, despite being more accustomed to icebergs. “They would also tell me that this is the highest they have ever seen an iceberg rise above the houses,” Lehtonen says. “So it was definitely a special event.”

    Interested in eventually documenting remote regions of Alaska, northern Canada, and Siberia, Lehtonen is happy to continue exploring Greenland for now, as it still stokes his curiosity. “The (nomadic) lifestyle doesn’t seem to make sense to most other people, but I feel like it creates the best memories,” he tells Colossal. Find more on his website and Instagram.

    a small house in Greenland with a giant iceberg in the background
    two giant icebergs seen from the air in a small town in Greenland
    two wooden houses in Greenland with a giant iceberg in the background
    a small house in Greenland with a giant iceberg in the background

    Do stories and artists like this matter to you? Become a Colossal Member today and support independent arts publishing for as little as $7 per month. The article Dennis Lehtonen Documents a Pair of Immense Icebergs Paying a Visit to a Small Greenland Village appeared first on Colossal.


    Source: Colossal.

  • 2025.29: What It Takes to Change the Web

    2025.29: What It Takes to Change the Web

    July 18, 2025
    Strategy
    A drawing of Google and the Ad Supported Web
    (Stratechery)

    Welcome back to This Week in Stratechery!

    As a reminder, each week, every Friday, we’re sending out this overview of content in the Stratechery bundle; highlighted links are free for everyone. Additionally, you have complete control over what we send to you. If you don’t want to receive This Week in Stratechery emails (there is no podcast), please uncheck the box in your delivery settings.

    On that note, here were a few of our favorites this week.

    1. The Economic Future of the Web. One reason why I love writing is that it, more than any other medium, forces me to challenge and validate my initial reactions to a piece of news, and this week provided a perfect example. My initial reaction to Cloudflare’s Content Independence Day, when they blocked AI crawlers by default, was discomfort at the advantage this provided Google, along with the audacious exercise of power on Cloudflare’s part. When I thought about it more deeply, however, I realized that realizing vision I painted in May’s The Agentic Web and Original Sin would actually require audacious action like Cloudflare’s. — Ben Thompson
    2. Greatest of All Talk in Las Vegas. One would think that basketball news slows down in the summer, but in fact, the news continues well into July, it just gets much nerdier. NBA Summer League is the epicenter of the nerdery, and Greatest of All Talk has been on the scene in Las Vegas for the past week. For those interested in a scouting report of number one pick Cooper Flagg, Ben Golliver and I spent 45 minutes talking through his performance on an episode Sunday. As for everything else—speculation surrounding LeBron’s future, whether the league will expand to Las Vegas and Seattle, misgivings with the current CBA among NBA players, and a mid-summer league “Chesstival” hosted by Magnus Carlsen and Derrick Rose (I told you it was nerdy)—Ben and I covered the scene from top to bottom on two more episodes later in week. Whether you come for Cooper Flagg or chess, GOAT is a great listen for the weekend.  — Andrew Sharp
    3. How We Cool Computers. Since the dawn of computing, engineers have been struggling to keep computers from overheating. This week’s episode of Asianometry traces that history, beginning with an era of vacuum tubes that were literally cooled with air conditioning, and arriving at the Gen AI era, when Nvidia’s H100 and B200 accelerators now emit 700 to 1,000 watts of heat apiece and companies all over the world are experimenting with hybrid methods to cool chips. Altogether the episode was a great look at the history underlying a challenge that most people never think about, and one that will be central to the future of AI infrastructure, energy costs, and the tension between performance and efficiency that has defined computing from the beginning. Check out the YouTube video here, or if you’re on the move this weekend, all Stratechery subscribers can listen to the episode as a podcast. — AS

    Stratechery Articles and Updates

    • Google and Windsurf, Stinky Deals, Chesterton’s Fence and the Silicon Valley Ecosystem — Windsurf’s founders and IP are going to Google in the latest stinky deal that is downstream of regulator’s recklessly messing the startup ecosystem.
    • Cognition Buys Windsurf, Nvidia Can Sell to China, Grok 4 and Kimi — Cognition rescues Windsurf, Nvidia can sell H20s to China, and Grok 4 and Kimi K2 point to future avenues of model improvement
    • Cloudflare’s Content Independence Day, Google’s Advantage, Monetizing AI — Cloudflare is unilaterally blocking AI crawlers unless they are willing to pay

    Dithering with Ben Thompson and Daring Fireball’s John Gruber

    • Tech’s Chesterton’s Fence
    • Beeper and RCS

    Asianometry with Jon Yu

    • Chilling Chips: A Computer Cooling Chronicle
    • AT&T’s UNIX PC Failure

    Sharp China with Andrew Sharp and Sinocism’s Bill Bishop

    • Nvidia Can Sell H20 Chips to China Again; Trump Softening on China?; A Flurry of Xi Activity; Yang Hansen at NBA Summer League

    Greatest of All Talk with Andrew Sharp and WaPo’s Ben Golliver

    • Live from the Chesstival with Derrick Rose and Magnus Carlsen, News and Notes from Vegas, More on the Bucks, Jazz and Yang Hansen
    • An Embiid Profile and LeBron Uncertainty, Expansion May Have to Wait, Live Reactions to Lillard’s Blazers Homecoming

    Sharp Tech with Andrew Sharp and Ben Thompson

    • Windsurf Madness, Big Tech Wins as the Silicon Valley Ecosystem Erodes, Cloudflare Wants to Fix the Internet Economy

    This week’s Stratechery video is on Tech Philosophy and AI Opportunity.


    Source: Stratechery by Ben Thompson.

  • FISHER & bbyclose – Blackberries (Extended Mix)

    FISHER & bbyclose – Blackberries (Extended Mix)

    July 18, 2025
    Music

    Source: YouTube.

Previous Page
1 … 892 893 894 895 896 … 911
Next Page
MONIMEGA
  • Instagram
  • Facebook
  • Twitter
Gestisci Consenso
Per fornire le migliori esperienze, utilizziamo tecnologie come i cookie per memorizzare e/o accedere alle informazioni del dispositivo. Il consenso a queste tecnologie ci permetterà di elaborare dati come il comportamento di navigazione o ID unici su questo sito. Non acconsentire o ritirare il consenso può influire negativamente su alcune caratteristiche e funzioni.
Funzionale Always active
L'archiviazione tecnica o l'accesso sono strettamente necessari al fine legittimo di consentire l'uso di un servizio specifico esplicitamente richiesto dall'abbonato o dall'utente, o al solo scopo di effettuare la trasmissione di una comunicazione su una rete di comunicazione elettronica.
Preferenze
L'archiviazione tecnica o l'accesso sono necessari per lo scopo legittimo di memorizzare le preferenze che non sono richieste dall'abbonato o dall'utente.
Statistiche
L'archiviazione tecnica o l'accesso che viene utilizzato esclusivamente per scopi statistici. L'archiviazione tecnica o l'accesso che viene utilizzato esclusivamente per scopi statistici anonimi. Senza un mandato di comparizione, una conformità volontaria da parte del vostro Fornitore di Servizi Internet, o ulteriori registrazioni da parte di terzi, le informazioni memorizzate o recuperate per questo scopo da sole non possono di solito essere utilizzate per l'identificazione.
Marketing
L'archiviazione tecnica o l'accesso sono necessari per creare profili di utenti per inviare pubblicità, o per tracciare l'utente su un sito web o su diversi siti web per scopi di marketing simili.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Visualizza le preferenze
  • {title}
  • {title}
  • {title}