IoT

ESP e Cookies: Login com Usuários e Categorias

Eletrogate 29 de junho de 2023

Introdução

Na área de Internet das Coisas (IoT), o ESP32 tem se destacado como um microcontrolador de baixo custo e com Wi-Fi integrado, capaz de criar servidores web e se comunicar com outros dispositivos através da rede. Neste post será explorado uma das funcionalidades do ESP32, que é a capacidade de criar sistemas de login web, permitindo que múltiplos usuários tenham acesso em diferentes categorias hierárquicas. Será feito o uso de cookies no ESP32 para autenticação de usuários em um sistema de login web, utilizando a biblioteca ESPAsyncWebServer para criar o servidor web e a biblioteca Preferences para armazenar as informações de cada usuário. Além disso, vamos implementar um sistema de log para armazenar informações sobre as atividades dos usuários. Este projeto é uma excelente oportunidade para aprender habilidades úteis em projetos de IoT que envolvem a interação com usuários através de uma interface web.


Materiais Necessários para o Projeto com ESP e Cookies: Login com Usuários e Categorias

Para este post, serão utilizados os seguintes materiais:


Sobre o projeto

O ESP32 é um microcontrolador de baixo custo com Wi-Fi integrado, que se tornou popular na área de Internet das Coisas (IoT). Uma das funcionalidades mais interessantes do ESP32 é a capacidade de criar servidores web, permitindo que ele se comunique com outros dispositivos através da rede. Neste projeto, será demonstrado o uso de cookies no ESP32 usando a biblioteca ESPAsyncWebServer para criar o servidor web e a biblioteca Preferences para armazenar as informações de cada usuário, criando assim um sistema de login web que permite o acesso de múltiplos usuários em diferentes categorias hierárquicas.

O sistema de login web desenvolvido utilizará cookies para autenticação, permitindo que os usuários permaneçam autenticados durante a sessão, mesmo que a página seja atualizada ou o usuário feche o navegador. Neste sistema web, haverá páginas HTML em que cada categoria de usuário tem acesso liberado somente para as páginas cadastradas no código do ESP32. As páginas web serão construídas com auxílio do framework Bootstrap. As categorias de usuários incluem Administrador, Avançado e Comum. A categoria de usuário Administrador também terá acesso a uma página para visualizar os usuários que estão logados no momento e também a uma página de logs do sistema.

As credenciais de acesso serão um nome de usuário e uma senha, que estarão armazenadas na Flash do ESP32 juntamente com a categoria do tipo de usuário, através da biblioteca Preferences. Os cookies serão gerados no ESP32 por uma função criada que gera uma sequência aleatória de determinada quantidade de caracteres que incluem números, letras e símbolos. Com o cookie gerado, ele será armazenado na memória RAM, pois assim, quando o ESP32 for desligado, todos os usuários que estavam logados terão que fazer login novamente, já que a memória RAM é volátil.

O webserver assíncrono será criado com a biblioteca ESPAsyncWebServer, incluindo a implementação de um sistema de log que armazena na SPIFFS o comportamento de cada usuário e registra informações como data e hora de login e logout do usuário, nome de usuário que realizou o login, duração da sessão de login, eventos de falha de login, incluindo tentativas de login mal sucedidas e erros de senha, informações sobre ações realizadas pelo usuário no sistema após o login, como acesso a páginas web, e eventos de erro, como tentativas de acesso não autorizado. O usuário de categoria Administrador poderá acessar esses dados através de uma página web.

Com este projeto, será possível aprender como utilizar cookies no ESP32, criar um sistema de login web para múltiplos usuários em diferentes categorias hierárquicas e implementar um sistema de log para armazenar informações sobre as atividades dos usuários. Essas habilidades podem ser úteis em vários projetos de IoT que envolvem a interação com usuários através de uma interface web.


Estrutura dos Arquivos

Os arquivos que conterão a programação, os arquivos web e o arquivo de log estarão na seguinte estrutura de arquivos:

  • software (pasta que contém todos os arquivos)
    • data (pasta que contém todos os arquivos que serão transferidos para a SPIFFS)
      • adm: (pasta que contém as páginas web que podem ser acessadas somente pelo Usuário categoria Administrador)
        • ferramentas.html (página web que contém as ferramentas que podem ser acessadas somente pelo Usuário categoria Administrador)
        • home.html (página web que mostra a página inicial que pode ser acessado somente pelo Usuário categoria Administrador)
        • log.csv (arquivo CSV que armazena os logs que pode ser acessado somente pelo Usuário categoria Administrador)
        • minha-conta.html (página web que mostra as informações do usuário que pode ser acessado somente pelo Usuário categoria Administrador)
      • asts: (pasta que é usada para armazenar os ativos de um projeto web, como imagens, arquivos de estilo (CSS), arquivos de script (JavaScript), fontes, entre outros recursos)
        • btsp: (pasta que contém os arquivos necessários para utilizar a biblioteca Bootstrap em um projeto web)
          • css: (contém os arquivos CSS necessários para implementar o design responsivo do Bootstrap em um site)
            • btsp.min.css (versão otimizada e minimizada do arquivo CSS principal do Bootstrap)
          • js: (contém os arquivos JavaScript necessários para adicionar interatividade e funcionalidade aos componentes do Bootstrap em um site)
            • btsp.min.js (versão otimizada e minimizada do arquivo JS principal do Bootstrap)
        • img: (pasta que contém imagens referentes ao projeto web)
          • favicon.png (imagem de ícone do site)
        • js: (pasta que contém os arquivos JS personalizados para um projeto específico, além de scripts que não pertencem diretamente ao Bootstrap)
          • bs-init.js (arquivo JavaScript que é usado para inicializar os componentes do Bootstrap de forma personalizada)
          • javascript.js (arquivo JavaScript que é usado para adicionar funcionalidades às páginas web)
      • avd: (pasta que contém as páginas web que podem ser acessadas somente pelo Usuário categoria Avançado)
        • ferramentas.html (página web que contém as ferramentas que podem ser acessadas somente pelo Usuário categoria Avançado)
        • home.html (página web que mostra a página inicial que pode ser acessado somente pelo Usuário categoria Avançado)
        • minha-conta.html (página web que mostra as informações do usuário que pode ser acessado somente pelo Usuário categoria Avançado)
      • com: (pasta que contém as páginas web que podem ser acessadas somente pelo Usuário categoria Comum)
        • ferramentas.html (página web que contém as ferramentas que podem ser acessadas somente pelo Usuário categoria Comum)
        • home.html (página web que mostra as informações do usuário que pode ser acessado somente pelo Usuário categoria Comum)
        • minha-conta.html (página web que mostra as informações do usuário que pode ser acessado somente pelo Usuário categoria Comum)
      • 403.html (página web que exibe à qualquer usuário quando o mesmo acessa uma página que sua categoria não tem autorização para acessar)
      • 404.html (página web que exibe à qualquer usuário quando o mesmo acessa uma página que não existe)
      • login.html (página web que exibe à qualquer usuário quando o mesmo tem de fazer login, possibilitando a inserção de suas credenciais)
    • api.ino (arquivo arduino que armazena funções de callback que são acessadas pelo cliente no servidor, sendo normalmente de obter dados, como, por exemplo, obter horário atual, obter nome do usuário, entre outros)
    • credenciais.h (arquivo.h — header file — que armazena a senha e o SSID WiFi em variáveis constantes para que o ESP32 se conecte)
    • pages.ino (arquivo arduino que armazena funções de callback que são acessadas pelo cliente no servidor, sendo normalmente de apenas obter página html, como, por exemplo, obter página de login, obter página de ferramentas, entre outros)
    • software.ino (arquivo arduino principal, que contém o void setup, void loop, entre outras funções)
    • webLibrary.ino (arquivo arduino que armazena funções de callback que são acessadas pelo cliente no servidor, sendo normalmente de apenas obter os arquivos da biblioteca bootstrap e de scripts)

Implementando as Páginas Web

Sumário

Arquivo /software/data/adm/ferramentas.html

Esta página será onde estará disponível as ferramentas de Logger, Usuários Ativos e as funções gerais de demonstração com acesso à todas categorias de usuários.

<!DOCTYPE html>
<html lang="pt-br">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Sistema Web ESP32</title>
    <link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon.png">
    <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
</head>

<body>
    <nav class="navbar navbar-light navbar-expand-md sticky-top py-3" id="navbarPage" style="background: #ffffff;">
        <div class="container"><a class="navbar-brand d-flex align-items-center" href="#"><span class="bs-icon-sm bs-icon-rounded d-flex justify-content-center align-items-center me-2 bs-icon"><img src="/assets/img/favicon.png"></span><span>Sistema Web ESP32</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-2"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
            <div class="collapse navbar-collapse" id="navcol-2">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item"><a class="nav-link" href="/adm/home.html">Home</a></li>
                    <li class="nav-item"><a class="nav-link active" href="#">Ferramentas</a></li>
                    <li class="nav-item"><a class="nav-link" href="/adm/minha-conta.html">Meus Dados</a></li>
                </ul><button class="btn btn-primary ms-md-2" id="logout" type="button">Sair</button>
            </div>
            <div style="margin-left: 10px;cursor: help;margin-top: 10px;"><span data-bs-toggle="tooltip" data-bss-tooltip="" id="horarioAtual" title="Horário Atual">Horário Atual</span></div>
        </div>
    </nav>
    <div class="container py-4 py-xl-5">
        <div class="row mb-5">
            <div class="col-md-8 col-xl-6 text-center mx-auto">
                <h2>Acessar Funções do Sistema</h2>
                <p>Nesta seção, você encontrará informações sobre as funções do sistema que podem ser acessadas por sua categoria de usuário.</p>
            </div>
        </div>
        <div class="row gy-4 row-cols-1 row-cols-md-2 row-cols-xl-3 justify-content-center">
            <div class="col"><a href="#visualizar-dados" style="color: inherit;text-decoration: inherit;">
                    <div class="card">
                        <div class="card-body p-4">
                            <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center" style="width: 48px;height: 48px;background: rgb(13,110,253);border-radius: 8px;font-size: 24px;color: rgb(255,255,255);margin-bottom: 16px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-clipboard-data">
                                    <path d="M4 11a1 1 0 1 1 2 0v1a1 1 0 1 1-2 0v-1zm6-4a1 1 0 1 1 2 0v5a1 1 0 1 1-2 0V7zM7 9a1 1 0 0 1 2 0v3a1 1 0 1 1-2 0V9z"></path>
                                    <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"></path>
                                    <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"></path>
                                </svg></div>
                            <h4 class="card-title">Visualizar dados sensores</h4>
                            <p class="card-text">(Funcionalidade demonstrativa) Exibe os dados coletados pelos sensores conectados ao sistema, permitindo uma visualização clara e em tempo real das informações. Isso permite que o usuário tenha uma noção completa do estado do sistema e possa tomar decisões informadas com base nesses dados.</p>
                        </div>
                    </div>
                </a></div>
            <div class="col"><a href="#dados-sistema" style="color: inherit;text-decoration: inherit;">
                    <div class="card">
                        <div class="card-body p-4">
                            <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center" style="width: 48px;height: 48px;background: rgb(13,110,253);border-radius: 8px;font-size: 24px;color: rgb(255,255,255);margin-bottom: 16px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-hdd-network">
                                    <path d="M4.5 5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zM3 4.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"></path>
                                    <path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2H8.5v3a1.5 1.5 0 0 1 1.5 1.5h5.5a.5.5 0 0 1 0 1H10A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5H.5a.5.5 0 0 1 0-1H6A1.5 1.5 0 0 1 7.5 10V7H2a2 2 0 0 1-2-2V4zm1 0v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1zm6 7.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5z"></path>
                                </svg></div>
                            <h4 class="card-title">Configurar a Rede</h4>
                            <p class="card-text">(Funcionalidade demonstrativa) Os usuários podem configurar as configurações da rede, incluindo endereço IP, máscara de sub-rede e gateway padrão. Isso permite que o sistema se conecte à rede corretamente e, assim, possa se comunicar com outros dispositivos conectados à mesma rede.</p>
                        </div>
                    </div>
                </a></div>
            <div class="col"><a href="#enviar-comandos" style="color: inherit;text-decoration: inherit;">
                    <div class="card">
                        <div class="card-body p-4">
                            <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center" style="width: 48px;height: 48px;background: rgb(13,110,253);border-radius: 8px;font-size: 24px;color: rgb(255,255,255);margin-bottom: 16px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-send">
                                    <path fill-rule="evenodd" d="M15.854.146a.5.5 0 0 1 .11.54l-5.819 14.547a.75.75 0 0 1-1.329.124l-3.178-4.995L.643 7.184a.75.75 0 0 1 .124-1.33L15.314.037a.5.5 0 0 1 .54.11ZM6.636 10.07l2.761 4.338L14.13 2.576 6.636 10.07Zm6.787-8.201L1.591 6.602l4.339 2.76 7.494-7.493Z"></path>
                                </svg></div>
                            <h4 class="card-title">Enviar comandos ao ESP32</h4>
                            <p class="card-text">(Funcionalidade demonstrativa) Permite ao usuário enviar comandos para o ESP32, o que pode ser útil em situações em que é necessário acionar ou desativar dispositivos conectados ao sistema, por exemplo. Isso permite que o usuário controle o sistema de forma eficaz e conveniente.</p>
                        </div>
                    </div>
                </a></div>
            <div class="col"><a href="#visualizar-estatistica" style="color: inherit;text-decoration: inherit;">
                    <div class="card">
                        <div class="card-body p-4">
                            <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center" style="width: 48px;height: 48px;background: rgb(13,110,253);border-radius: 8px;font-size: 24px;color: rgb(255,255,255);margin-bottom: 16px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-bar-chart-line">
                                    <path d="M11 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12h.5a.5.5 0 0 1 0 1H.5a.5.5 0 0 1 0-1H1v-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3h1V7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7h1V2zm1 12h2V2h-2v12zm-3 0V7H7v7h2zm-5 0v-3H2v3h2z"></path>
                                </svg></div>
                            <h4 class="card-title">Monitorar estatísticas</h4>
                            <p class="card-text">(Funcionalidade demonstrativa) O usuário pode monitorar as estatísticas do sistema.</p>
                        </div>
                    </div>
                </a></div>
        </div>
        <div class="row" style="margin-bottom: 12px;margin-top: 12px;">
            <div class="col">
                <div class="card">
                    <div class="card-body p-4">
                        <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center" style="width: 48px;height: 48px;background: rgb(13,110,253);border-radius: 8px;font-size: 24px;color: rgb(255,255,255);margin-bottom: 16px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-person-lines-fill">
                                <path d="M6 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-5 6s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zM11 3.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zm.5 2.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4zm2 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2zm0 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2z"></path>
                            </svg></div>
                        <h4 class="card-title">Usuários Ativos</h4>
                        <p class="card-text">Aqui você pode ver os usuários que estão atualmente conectados na sua aplicação, juntamente com a data/hora de login e o tempo de login de cada usuário. Essa informação é útil para monitorar a atividade dos usuários e identificar possíveis problemas de performance.</p>
                        <div class="table-responsive" style="overflow-y: scroll;max-height: 235px;margin-top: 5px;">
                            <table class="table">
                                <thead>
                                    <tr>
                                        <th>Nome de Usuário</th>
                                        <th>Data/Hora de Login</th>
                                        <th>Tempo de Login (HH:MM:SS)</th>
                                    </tr>
                                </thead>
                                <tbody id="bodyTableUsuariosAtivos"></tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="row" style="margin-top: 12px;margin-bottom: 12px;">
            <div class="col">
                <div class="card">
                    <div class="card-body p-4">
                        <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center" style="width: 48px;height: 48px;background: rgb(13,110,253);border-radius: 8px;font-size: 24px;color: rgb(255,255,255);margin-bottom: 16px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-journal-text">
                                <path d="M5 10.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"></path>
                                <path d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2z"></path>
                                <path d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1z"></path>
                            </svg></div>
                        <h4 class="card-title">Logger do Sistema</h4>
                        <p class="card-text">Nesta seção, você pode visualizar e fazer o download de informações detalhadas sobre os eventos que ocorreram no sistema, incluindo a data/hora em que cada evento ocorreu, a descrição do evento, e detalhes adicionais sobre o evento, quando disponíveis. Essa informação pode ajudá-lo a identificar e solucionar problemas no sistema, bem como monitorar a sua operação em geral.</p><a class="btn btn-primary btn-sm" role="button" data-bs-toggle="tooltip" data-bss-tooltip="" href="/adm/log.csv" download="" title="Ao abrir o arquivo CSV em seu aplicativo de planilha, selecione o caractere delimitador que separa valores em seu arquivo de texto como TABULAÇÃO."><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-download" style="margin-right: 5px;">
                                <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"></path>
                                <path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"></path>
                            </svg>Baixar Arquivo de Logs</a>
                        <div class="table-responsive" style="overflow-y: scroll;max-height: 235px;margin-top: 5px;">
                            <table class="table">
                                <thead>
                                    <tr>
                                        <th>Data/Hora</th>
                                        <th>Evento</th>
                                        <th>Descrição</th>
                                        <th>Detalhes</th>
                                    </tr>
                                </thead>
                                <tbody id="bodyTableLoggerSistema"></tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
    <script src="/assets/js/bs-init.js"></script>
    <script src="/assets/js/javascript.js"></script>
</body>

</html>

O <!DOCTYPE html> é uma declaração do tipo de documento, indicando que o documento é um HTML5.

Na seção head, existem várias tags que descrevem o conteúdo da página e especificam informações como o título (<title>), o conjunto de caracteres utilizado (<meta charset="utf-8">), o tamanho da tela (<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">) e os links para os arquivos de estilo (<link>).

Dentro do body, há um nav que contém a barra de navegação superior da página, com links para as páginas principais do site e um botão de logout.

Abaixo do nav, há um div com a classe container py-4 py-xl-5, que contém o conteúdo principal da página. Este div contém quatro divs. O primeiro div contém um título e um parágrafo explicando o objetivo da seção.

O segundo div contém cards que descrevem as funções apenas de demonstração do sistema que podem ser acessadas pelo usuário, como Visualizar dados de sensores, Configurar a Rede, Enviar comandos ao ESP32, e Monitorar estatísticas. Estes cards, são na realidade, outro div, em que para cada função há um. Cada um deles, contém um link para acessar a função e uma descrição dela. As funções são organizadas em colunas responsivas usando as classes de grid do Bootstrap.

O terceiro div é um container para um cartão (div com classe card) que exibe informações sobre usuários ativos no sistema web ESP32.

No topo do card há um ícone, que é uma representação visual para enfatizar o propósito do card. Ele é criado com um elemento SVG.

O card tem um cabeçalho (card-title) com o texto “Usuários Ativos”, um corpo (card-text) com uma descrição sobre a utilidade da informação apresentada e uma tabela (table) que exibe informações sobre os usuários conectados, incluindo as colunas com informações de nome, data/hora de login e tempo de login.

O div que contém a tabela tem uma classe "table-responsive" para garantir que ela seja exibida corretamente em diferentes tamanhos de tela e uma altura máxima (max-height) de 235 pixels, para que, caso haja muitos dados, a tabela não fique muito grande. Além disso, a propriedade "overflow-y: scroll" permite a rolagem vertical para visualizar todos os dados na tabela, se necessário.

Por fim, o tbody da tabela tem o ID "bodyTableUsuariosAtivos", que através deste ID, é preenchida a tabela dinamicamente com os dados dos usuários ativos obtidos através de uma requisição HTTP GET.

O quarto div é um container para um cartão (div com classe card) que exibe informações detalhadas sobre os eventos que ocorreram em um sistema e que podem ajudar aos usuários de categoria Administrador a identificarem e solucionarem problemas no sistema.

No topo do card há um ícone, que é uma representação visual para enfatizar o propósito do card. Ele é criado com um elemento SVG.

O card tem um cabeçalho (card-title) com o texto “Logger do Sistema”, um corpo (card-text) com uma descrição sobre a utilidade da informação apresentada.

Há também um botão de download que é usado para baixar o arquivo CSV que contém informações sobre os eventos do sistema (logs). O botão é um elemento a com a classe btn btn-primary btn-sm e um atributo href que aponta para o arquivo CSV (/adm/log.csv). O botão tem um ícone SVG  e um texto de descrição.

Ainda neste card (Logger do Sistema), há uma tabela (table) que exibe informações armazenadas do logger sobre as ações realizadas pelos usuários conectado, incluindo as colunas com informações de Data/Hora, Evento, Descrição e Detalhes.

O div que contém a tabela tem uma classe "table-responsive" para garantir que ela seja exibida corretamente em diferentes tamanhos de tela e uma altura máxima (max-height) de 235 pixels, para que, caso haja muitos dados, a tabela não fique muito grande. Além disso, a propriedade "overflow-y: scroll" permite a rolagem vertical para visualizar todos os dados na tabela, se necessário.

Por fim, o tbody da tabela tem o ID "bodyTableLoggerSistema", que através deste ID, é preenchida a tabela dinamicamente com os dados do logger do sistema obtidos através de uma requisição HTTP GET.

Após o div que contém os quatros div, é inserido os arquivos de scripts da biblioteca bootstrap também o arquivo de script criado para o sistema (“/assets/js/javascript.js”).

Arquivo /software/data/adm/home.html

<!DOCTYPE html>
<html lang="pt-br">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Sistema Web ESP32</title>
    <link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon.png">
    <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
</head>

<body>
    <nav class="navbar navbar-light navbar-expand-md sticky-top py-3" id="navbarPage" style="background: #ffffff;">
        <div class="container"><a class="navbar-brand d-flex align-items-center" href="#"><span
                    class="bs-icon-sm bs-icon-rounded d-flex justify-content-center align-items-center me-2 bs-icon"><img
                        src="/assets/img/favicon.png"></span><span>Sistema Web ESP32</span></a><button
                data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-2"><span
                    class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
            <div class="collapse navbar-collapse" id="navcol-2">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item"><a class="nav-link active" href="#">Home</a></li>
                    <li class="nav-item"><a class="nav-link" href="/adm/ferramentas.html">Ferramentas</a></li>
                    <li class="nav-item"><a class="nav-link" href="/adm/minha-conta.html">Meus Dados</a></li>
                </ul><a class="btn btn-primary ms-md-2" role="button" id="logout" href="#">Sair</a>
            </div>
            <div style="margin-left: 10px;cursor: help;margin-top: 10px;"><span data-bs-toggle="tooltip"
                    data-bss-tooltip="" id="horarioAtual" title="Horário Atual">Horário Atual</span></div>
        </div>
    </nav>
    <section class="py-4 py-xl-5">
        <div class="container">
            <div class="text-center p-4 p-lg-5">
                <p class="fw-bold text-primary mb-2">Sistema Web ESP32</p>
                <h1 class="fw-bold mb-4">Olá, <span id="nomeUser"></span>!</h1><span>Usuário tipo <span
                        id="nomeTipoUser"></span>.</span>
            </div>
        </div>
    </section>
    <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
    <script src="/assets/js/bs-init.js"></script>
    <script src="/assets/js/javascript.js"></script>
</body>

</html>

A primeira linha, <!DOCTYPE html>, indica que este é um documento HTML5.

A linha <html lang="pt-br"> define a linguagem da página como português do Brasil.

O elemento <head> é onde se encontra informações sobre a página, como o título (<title>), a codificação de caracteres (<meta charset>), e um conjunto de instruções que se referem a como a página será exibida em diferentes dispositivos (<meta name="viewport">). Além disso, há links para diferentes recursos que a página usa, como um ícone (<link rel="icon">) e folhas de estilo (<link rel="stylesheet">).

O conteúdo principal da página é o elemento <body>, que contém o conteúdo visível para o usuário. Aqui há uma barra de navegação (<nav>) que contém links para diferentes seções da página, incluindo uma seção de ferramentas (<a href="/adm/ferramentas.html">Ferramentas</a>) e uma seção para ver informações do usuário (<a href="/adm/minha-conta.html">Meus Dados</a>), além de um botão de logout.

O elemento <section> define uma seção da página com um título (<h1>) e algumas informações de texto. Há também alguns elementos que usam o atributo id, como <span id="nomeUser"></span>, que são identificadores usados pelo código JavaScript vinculado na página (<script>). O JavaScript adiciona comportamentos dinâmicos à página, como atualizar o horário e exibir o nome do usuário.

Os elementos <script> no final do documento contêm o código JavaScript que adiciona funcionalidades à página. O primeiro carrega a biblioteca JavaScript do Bootstrap, um conjunto de ferramentas para desenvolvimento de sites, e o segundo inicializa algumas funcionalidades do Bootstrap. O último script contém código personalizado para a página, definido pelo desenvolvedor da página.

Arquivo /software/data/adm/minha-conta.html

<!DOCTYPE html>
<html lang="pt-br">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Sistema Web ESP32</title>
    <link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon.png">
    <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
</head>

<body>
    <nav class="navbar navbar-light navbar-expand-md sticky-top py-3" id="navbarPage" style="background: #ffffff;">
        <div class="container"><a class="navbar-brand d-flex align-items-center" href="#"><span
                    class="bs-icon-sm bs-icon-rounded d-flex justify-content-center align-items-center me-2 bs-icon"><img
                        src="/assets/img/favicon.png"></span><span>Sistema Web ESP32</span></a><button
                data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-2"><span
                    class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
            <div class="collapse navbar-collapse" id="navcol-2">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item"><a class="nav-link" href="/adm/home.html">Home</a></li>
                    <li class="nav-item"><a class="nav-link" href="/adm/ferramentas.html">Ferramentas</a></li>
                    <li class="nav-item"><a class="nav-link active" href="#">Meus Dados</a></li>
                </ul><a class="btn btn-primary ms-md-2" role="button" id="logout" href="#">Sair</a>
            </div>
            <div style="margin-left: 10px;cursor: help;margin-top: 10px;"><span data-bs-toggle="tooltip"
                    data-bss-tooltip="" id="horarioAtual" title="Horário Atual">Horário Atual</span></div>
        </div>
    </nav>
    <section class="py-4 py-xl-5">
        <div class="container">
            <div class="row d-flex justify-content-center">
                <div class="col-md-8 col-lg-6 col-xl-5 col-xxl-4">
                    <div class="card mb-5">
                        <div class="card-body p-sm-5">
                            <h2 class="text-center mb-4">Meus Dados</h2>
                            <form method="post">
                                <div style="margin: 10px;"><span style="font-weight: bold;">Usuário</span>
                                    <p id="nomeUser">.</p>
                                </div>
                                <div style="margin: 10px;"><span style="font-weight: bold;">Senha</span>
                                    <p id="senhaUser">.</p>
                                </div>
                                <div style="margin: 10px;"><span style="font-weight: bold;">Categoria de Usuário</span>
                                    <p id="nomeTipoUser">.</p>
                                </div>
                                <div></div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>
    <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
    <script src="/assets/js/bs-init.js"></script>
    <script src="/assets/js/javascript.js"></script>
</body>

</html>

O código começa com a declaração do tipo de documento usando <!DOCTYPE html>.

Em seguida, temos a tag <html> que contém todo o conteúdo da página. A tag <head> contém informações importantes sobre a página, como o título (<title>), a codificação de caracteres (<meta charset="utf-8">), as informações de visualização (<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">) e links para arquivos externos (<link>) de estilização e de imagem.

A tag <body> contém o conteúdo da página que será exibido no navegador. Neste exemplo, há um menu de navegação (<nav>) com links para diferentes páginas, um cabeçalho (<h2>) e um formulário (<form>) que será usado para preencher com informações do usuário, como nome de usuário, senha e categoria do usuário.

Por fim, temos as tags <script> que contêm código JavaScript para fazer a página funcionar corretamente, como links para arquivos externos (<script src="/assets/bootstrap/js/bootstrap.min.js"></script>), inicialização de scripts (<script src="/assets/js/bs-init.js"></script>) e scripts personalizados (<script src="/assets/js/javascript.js"></script>).

Arquivo /software/data/avd/ferramentas.html

<!DOCTYPE html>
<html lang="pt-br">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Sistema Web ESP32</title>
    <link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon.png">
    <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
</head>

<body>
    <nav class="navbar navbar-light navbar-expand-md sticky-top py-3" id="navbarPage" style="background: #ffffff;">
        <div class="container"><a class="navbar-brand d-flex align-items-center" href="#"><span class="bs-icon-sm bs-icon-rounded d-flex justify-content-center align-items-center me-2 bs-icon"><img src="/assets/img/favicon.png"></span><span>Sistema Web ESP32</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-2"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
            <div class="collapse navbar-collapse" id="navcol-2">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item"><a class="nav-link" href="/avd/home.html">Home</a></li>
                    <li class="nav-item"><a class="nav-link active" href="#">Ferramentas</a></li>
                    <li class="nav-item"><a class="nav-link" href="/avd/minha-conta.html">Meus Dados</a></li>
                </ul><button class="btn btn-primary ms-md-2" id="logout" type="button">Sair</button>
            </div>
            <div style="margin-left: 10px;cursor: help;margin-top: 10px;"><span data-bs-toggle="tooltip" data-bss-tooltip="" id="horarioAtual" title="Horário Atual">Horário Atual</span></div>
        </div>
    </nav>
    <div class="container py-4 py-xl-5">
        <div class="row mb-5">
            <div class="col-md-8 col-xl-6 text-center mx-auto">
                <h2>Acessar Funções do Sistema</h2>
                <p>Nesta seção, você encontrará informações sobre as funções do sistema que podem ser acessadas por sua categoria de usuário.</p>
            </div>
        </div>
        <div class="row gy-4 row-cols-1 row-cols-md-2 row-cols-xl-3 justify-content-center">
            <div class="col"><a href="#visualizar-dados" style="color: inherit;text-decoration: inherit;">
                    <div class="card">
                        <div class="card-body p-4">
                            <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center" style="width: 48px;height: 48px;background: rgb(13,110,253);border-radius: 8px;font-size: 24px;color: rgb(255,255,255);margin-bottom: 16px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-clipboard-data">
                                    <path d="M4 11a1 1 0 1 1 2 0v1a1 1 0 1 1-2 0v-1zm6-4a1 1 0 1 1 2 0v5a1 1 0 1 1-2 0V7zM7 9a1 1 0 0 1 2 0v3a1 1 0 1 1-2 0V9z"></path>
                                    <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"></path>
                                    <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"></path>
                                </svg></div>
                            <h4 class="card-title">Visualizar dados sensores</h4>
                            <p class="card-text">(Funcionalidade demonstrativa) Permite ao usuário visualizar dados de sensores específicos.</p>
                        </div>
                    </div>
                </a></div>
            <div class="col"><a href="#dados-sistema" style="color: inherit;text-decoration: inherit;">
                    <div class="card">
                        <div class="card-body p-4">
                            <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center" style="width: 48px;height: 48px;background: rgb(13,110,253);border-radius: 8px;font-size: 24px;color: rgb(255,255,255);margin-bottom: 16px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-hdd-network">
                                    <path d="M4.5 5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zM3 4.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"></path>
                                    <path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2H8.5v3a1.5 1.5 0 0 1 1.5 1.5h5.5a.5.5 0 0 1 0 1H10A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5H.5a.5.5 0 0 1 0-1H6A1.5 1.5 0 0 1 7.5 10V7H2a2 2 0 0 1-2-2V4zm1 0v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1zm6 7.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5z"></path>
                                </svg></div>
                            <h4 class="card-title">Configurar a Rede</h4>
                            <p class="card-text">(Funcionalidade demonstrativa) Os usuários podem configurar as configurações da rede, incluindo endereço IP, máscara de sub-rede e gateway padrão. Isso permite que o sistema se conecte à rede corretamente e, assim, possa se comunicar com outros dispositivos conectados à mesma rede.</p>
                        </div>
                    </div>
                </a></div>
            <div class="col"><a href="#enviar-comandos" style="color: inherit;text-decoration: inherit;">
                    <div class="card">
                        <div class="card-body p-4">
                            <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center" style="width: 48px;height: 48px;background: rgb(13,110,253);border-radius: 8px;font-size: 24px;color: rgb(255,255,255);margin-bottom: 16px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-send">
                                    <path fill-rule="evenodd" d="M15.854.146a.5.5 0 0 1 .11.54l-5.819 14.547a.75.75 0 0 1-1.329.124l-3.178-4.995L.643 7.184a.75.75 0 0 1 .124-1.33L15.314.037a.5.5 0 0 1 .54.11ZM6.636 10.07l2.761 4.338L14.13 2.576 6.636 10.07Zm6.787-8.201L1.591 6.602l4.339 2.76 7.494-7.493Z"></path>
                                </svg></div>
                            <h4 class="card-title">Enviar comandos ao ESP32</h4>
                            <p class="card-text">(Funcionalidade demonstrativa) Permite ao usuário enviar comandos para o ESP32, o que pode ser útil em situações em que é necessário acionar ou desativar dispositivos conectados ao sistema, por exemplo. Isso permite que o usuário controle o sistema de forma eficaz e conveniente.</p>
                        </div>
                    </div>
                </a></div>
            <div class="col"><a href="#visualizar-estatistica" style="color: inherit;text-decoration: inherit;">
                    <div class="card">
                        <div class="card-body p-4">
                            <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center" style="width: 48px;height: 48px;background: rgb(13,110,253);border-radius: 8px;font-size: 24px;color: rgb(255,255,255);margin-bottom: 16px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-bar-chart-line">
                                    <path d="M11 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12h.5a.5.5 0 0 1 0 1H.5a.5.5 0 0 1 0-1H1v-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3h1V7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7h1V2zm1 12h2V2h-2v12zm-3 0V7H7v7h2zm-5 0v-3H2v3h2z"></path>
                                </svg></div>
                            <h4 class="card-title">Monitorar estatísticas</h4>
                            <p class="card-text">(Funcionalidade demonstrativa) O usuário pode monitorar as estatísticas do sistema.</p>
                        </div>
                    </div>
                </a></div>
        </div>
    </div>
    <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
    <script src="/assets/js/bs-init.js"></script>
    <script src="/assets/js/javascript.js"></script>
</body>

</html>

O código começa com a declaração <!DOCTYPE html>, que indica que o código está escrito em HTML5. Em seguida, temos a tag <html> que contém todo o conteúdo da página. A linguagem principal usada na página é o português do Brasil, que é indicado pelo atributo lang="pt-br" da tag html.

A tag <head> contém informações sobre a página, como o título da página, a descrição, palavras-chave, estilos e scripts. É onde encontramos o título da página, que aparece na aba do navegador, definido pela tag <title>. A tag <meta> é usada para definir informações sobre a página, como o tipo de codificação usada e o tamanho da viewport. O <link> é usado para vincular arquivos externos, como folhas de estilo CSS.

A tag <body> contém todo o conteúdo da página visível para o usuário. Nesse exemplo, a página começa com um navbar usando a classe .navbar e outras classes do Bootstrap para criar um menu responsivo. Este menu tem um logo, um botão de menu (que é usado em dispositivos móveis) e três links para diferentes seções do site. O menu possui três itens: “Home”, “Ferramentas” e “Meus Dados”. Abaixo do menu, há um div com a classe .container, que contém o restante do conteúdo da página.

O conteúdo da página é organizado em duas seções principais. A primeira seção é um div com a classe .row que contém um título e uma descrição. A segunda seção é um div com a classe .row que contém vários cartões, cada um com um link para uma seção específica do site. Estes links destes catões não funcionam realmente, pois são apenas de demonstração.

Arquivo /software/data/avd/home.html

O código é o mesmo do arquivo /software/data/adm/home.html. As únicas diferenças são:

  • linha 22: O endereço do link para Ferramentas muda de “/adm/ferramentas.html” para “/avd/ferramentas.html”;
  • linha 23: O endereço do link para Meus Dados muda de “/adm/minha-conta.html” para “/avd/minha-conta.html”;
<li class="nav-item"><a class="nav-link" href="/avd/ferramentas.html">Ferramentas</a></li>
<li class="nav-item"><a class="nav-link" href="/avd/minha-conta.html">Meus Dados</a></li>

Arquivo /software/data/avd/minha-conta.html

O código é o mesmo do arquivo /software/data/adm/minha-conta.html. As únicas diferenças são:

  • linha 21: O endereço do link para Home muda de “/adm/home.html” para “/avd/home.html”;
  • linha 22: O endereço do link para Meus Dados muda de “/adm/ferramentas.html” para “/avd/ferramentas.html”;
<li class="nav-item"><a class="nav-link" href="/avd/home.html">Home</a></li>
<li class="nav-item"><a class="nav-link" href="/avd/ferramentas.html">Ferramentas</a></li>

Arquivo /software/data/com/ferramentas.html

<!DOCTYPE html>
<html lang="pt-br">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Sistema Web ESP32</title>
    <link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon.png">
    <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
</head>

<body>
    <nav class="navbar navbar-light navbar-expand-md sticky-top py-3" id="navbarPage" style="background: #ffffff;">
        <div class="container"><a class="navbar-brand d-flex align-items-center" href="#"><span class="bs-icon-sm bs-icon-rounded d-flex justify-content-center align-items-center me-2 bs-icon"><img src="/assets/img/favicon.png"></span><span>Sistema Web ESP32</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-2"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
            <div class="collapse navbar-collapse" id="navcol-2">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item"><a class="nav-link" href="/com/home.html">Home</a></li>
                    <li class="nav-item"><a class="nav-link active" href="#">Ferramentas</a></li>
                    <li class="nav-item"><a class="nav-link" href="/com/minha-conta.html">Meus Dados</a></li>
                </ul><button class="btn btn-primary ms-md-2" id="logout" type="button">Sair</button>
            </div>
            <div style="margin-left: 10px;cursor: help;margin-top: 10px;"><span data-bs-toggle="tooltip" data-bss-tooltip="" id="horarioAtual" title="Horário Atual">Horário Atual</span></div>
        </div>
    </nav>
    <div class="container py-4 py-xl-5">
        <div class="row mb-5">
            <div class="col-md-8 col-xl-6 text-center mx-auto">
                <h2>Acessar Funções do Sistema</h2>
                <p>Nesta seção, você encontrará informações sobre as funções do sistema que podem ser acessadas por sua categoria de usuário.</p>
            </div>
        </div>
        <div class="row gy-4 row-cols-1 row-cols-md-2 row-cols-xl-3 justify-content-center">
            <div class="col"><a href="#visualizar-dados" style="color: inherit;text-decoration: inherit;">
                    <div class="card">
                        <div class="card-body p-4">
                            <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center" style="width: 48px;height: 48px;background: rgb(13,110,253);border-radius: 8px;font-size: 24px;color: rgb(255,255,255);margin-bottom: 16px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-clipboard-data">
                                    <path d="M4 11a1 1 0 1 1 2 0v1a1 1 0 1 1-2 0v-1zm6-4a1 1 0 1 1 2 0v5a1 1 0 1 1-2 0V7zM7 9a1 1 0 0 1 2 0v3a1 1 0 1 1-2 0V9z"></path>
                                    <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"></path>
                                    <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"></path>
                                </svg></div>
                            <h4 class="card-title">Visualizar dados sensores</h4>
                            <p class="card-text">(Funcionalidade demonstrativa) Permite ao usuário visualizar dados de sensores específicos.</p>
                        </div>
                    </div>
                </a></div>
        </div>
    </div>
    <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
    <script src="/assets/js/bs-init.js"></script>
    <script src="/assets/js/javascript.js"></script>
</body>

</html>

A primeira linha <!DOCTYPE html> é um código que especifica qual é a versão do HTML que está sendo usada na página, neste caso a versão HTML5.

A tag <html> é o elemento raiz da página e envolve todo o conteúdo do documento HTML. O atributo lang="pt-br" especifica o idioma da página.

Dentro da tag <head> são inseridos metadados que fornecem informações sobre a página para os navegadores, como o título da página <title>, a codificação de caracteres <meta charset="utf-8">, a largura de exibição <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> e o ícone do site <link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon.png">.

Dentro do <body> é onde é colocado o conteúdo visível da página. A primeira parte é o <nav>, que é a barra de navegação da página. Dentro dele, há alguns links para outras páginas.

Depois disso, há um <div> com a classe container py-4 py-xl-5, que é um contêiner que é usado para centralizar e espaçar o conteúdo que vem abaixo. Dentro desse <div> há um título <h2> e um parágrafo <p> que apresentam o conteúdo principal da página.

Dentro dele, há outro <div> com a classe row gy-4 row-cols-1 row-cols-md-2 row-cols-xl-3 justify-content-center que contém um conjunto de cartões que representam as funções do sistema. Nesta categoria de usuário, há somente uma função demonstrativa: Visualizar dados sensores.

Arquivo /software/data/com/home.html

O código é o mesmo do arquivo /software/data/adm/home.html. As únicas diferenças são:

  • linha 22: O endereço do link para Ferramentas muda de “/adm/ferramentas.html” para “/com/ferramentas.html”;
  • linha 23: O endereço do link para Meus Dados muda de “/adm/minha-conta.html” para “/com/minha-conta.html”;
<li class="nav-item"><a class="nav-link" href="/com/ferramentas.html">Ferramentas</a></li>
<li class="nav-item"><a class="nav-link" href="/com/minha-conta.html">Meus Dados</a></li>

Arquivo /software/data/com/minha-conta.html

O código é o mesmo do arquivo /software/data/adm/minha-conta.html. As únicas diferenças são:

  • linha 21: O endereço do link para Home muda de “/adm/home.html” para “/com/home.html”;
  • linha 22: O endereço do link para Meus Dados muda de “/adm/ferramentas.html” para “/com/ferramentas.html”;
<li class="nav-item"><a class="nav-link" href="/com/home.html">Home</a></li>
<li class="nav-item"><a class="nav-link" href="/com/ferramentas.html">Ferramentas</a></li>

Arquivo /software/data/403.html

<!DOCTYPE html>
<html lang="pt-br">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Sistema Web ESP32</title>
    <link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon.png">
    <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
</head>

<body>
    <section class="py-4 py-xl-5">
        <div class="container">
            <div class="text-center p-4 p-lg-5"><span class="badge bg-primary">Erro 403</span>
                <p class="fw-bold text-primary mb-2">Opa! Acesso negado!</p>
                <h1 class="fw-bold mb-4">Você não tem permissão para acessar esta página, amigo.</h1>
                <div style="margin: 15px;"><span><strong>Página acessada:&nbsp;</strong><span
                            id="urlFalho">-</span></span></div><button class="btn btn-primary fs-5 me-2 py-2 px-4"
                    type="button" onclick="window.location.replace(&quot;/login.html&quot;);">Voltar à página
                    inicial</button>
            </div>
        </div>
    </section>
    <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
    <script src="/assets/js/bs-init.js"></script>
    <script src="/assets/js/javascript.js"></script>
</body>

</html>

A primeira linha <!DOCTYPE html> define o tipo de documento HTML que está sendo usado, neste caso, a versão mais recente do HTML.

Em seguida, temos o elemento HTML <html>, que é o contêiner principal para todo o conteúdo da página web. O atributo lang="pt-br" define que o idioma usado é o português do Brasil.

A seção <head> é onde são incluídas informações importantes sobre a página, como o título, a codificação de caracteres, a folha de estilos CSS e a imagem do ícone da página (favicon).

O elemento <body> contém o conteúdo da página que será exibido aos usuários. Neste caso, o conteúdo é uma seção de erro que informa ao usuário que ele não tem permissão para acessar a página solicitada.

No <body>, há também o elemento <button>, que é um botão, com o texto “Voltar à página inicial”, para que se clique nele, o usuário seja redirecionado para a página inicial.

Por fim, os arquivos JavaScript necessários para tornar a página interativa e para fornecer script personalizado, estão inclusos através dos elementos <script> no <body> da página.

Arquivo /software/data/404.html

<!DOCTYPE html>
<html lang="pt-br">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Sistema Web ESP32</title>
    <link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon.png">
    <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
</head>

<body>
    <section class="py-4 py-xl-5">
        <div class="container">
            <div class="text-center p-4 p-lg-5"><span class="badge bg-primary">Erro 404</span>
                <p class="fw-bold text-primary mb-2">Ops! Parece que algo deu errado...</p>
                <h1 class="fw-bold mb-4">Parece que a página que você está procurando não existe, ou foi movida para um
                    lugar secreto que nós não conhecemos.</h1><button class="btn btn-primary fs-5 me-2 py-2 px-4"
                    type="button" onclick="window.location.replace(&quot;/login.html&quot;);">Voltar à página
                    inicial</button>
            </div>
        </div>
    </section>
    <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
    <script src="/assets/js/bs-init.js"></script>
    <script src="/assets/js/javascript.js"></script>
</body>

</html>

Este é um código HTML básico que cria uma página web com uma mensagem de erro 404 personalizada.

A primeira linha, <!DOCTYPE html>, declara o tipo de documento como HTML5. Em seguida, o código define a linguagem da página como “pt-br” através do atributo “lang” na tag <html>.

A tag <head> contém informações sobre a página, incluindo o título, as folhas de estilo e as meta tags. Neste caso, a tag <meta> define a codificação de caracteres como UTF-8 e especifica como a página deve ser exibida em diferentes dispositivos com o atributo “viewport”. Além disso, há uma tag <title> que define o título da página.

A seguir, o código define o conteúdo principal da página dentro da tag <body>. A classe “py-4 py-xl-5” na tag <section> adiciona um espaçamento de preenchimento vertical na página. O elemento <div> é usado para agrupar os elementos HTML e a classe “text-center” centraliza o conteúdo na página.

O código HTML contém uma mensagem de erro 404 personalizada dentro da tag <div>. A classe “badge bg-primary” cria uma etiqueta com o texto “Erro 404” em um fundo azul. As outras tags HTML são usadas para exibir uma mensagem de erro personalizada com uma sugestão de como voltar à página inicial.

Por fim, o código inclui três scripts que fornecem funcionalidades adicionais à página:

  • O primeiro script inclui a biblioteca JavaScript do Bootstrap, que fornece funcionalidades de estilo e interação para a página.
  • O segundo script é usado para inicializar componentes do Bootstrap, garantindo que todos os elementos estejam configurados corretamente.
  • O terceiro script é o código JavaScript personalizado que adiciona interatividade à página.

Arquivo /software/data/login.html

<!DOCTYPE html>
<html lang="pt-br">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Sistema Web ESP32</title>
    <link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon.png">
    <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
</head>

<body>
    <section class="py-4 py-xl-5">
        <div class="container">
            <div class="row mb-5">
                <div class="col-md-8 col-xl-6 text-center mx-auto">
                    <h2>Login</h2>
                    <p>Faça login no sistema</p>
                </div>
            </div>
            <div class="row d-flex justify-content-center">
                <div class="col-md-6 col-xl-4">
                    <div class="card mb-5">
                        <div class="card-body d-flex flex-column align-items-center">
                            <div class="d-flex d-lg-flex justify-content-center align-items-center justify-content-lg-center align-items-lg-center"
                                style="background: rgb(13,110,253);width: 80px;height: 80px;border-radius: 80px;margin-top: 24px;margin-bottom: 24px;">
                                <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor"
                                    viewBox="0 0 16 16" class="bi bi-person"
                                    style="color: rgb(255,255,255);font-size: 40px;">
                                    <path
                                        d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664h10z">
                                    </path>
                                </svg>
                            </div>
                            <form class="text-center" id="formCred" method="post" action="/login.html"
                                onsubmit="event.preventDefault();">
                                <div class="text-start mb-3"><label class="form-label"
                                        style="margin-bottom: 0px;">Usuário</label><input class="form-control"
                                        type="text" placeholder="Seu nome de usuário" name="user" autocomplete="off"
                                        autofocus="" required=""></div>
                                <div class="text-start mb-3"><label class="form-label"
                                        style="margin-bottom: 0px;">Senha</label><input class="form-control"
                                        type="password" name="pass" placeholder="Sua senha" autocomplete="off"
                                        required=""></div>
                                <div class="mb-3"><button class="btn btn-primary d-block w-100" id="submitForm"
                                        type="submit">Entrar</button></div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>
    <div class="modal fade" role="dialog" tabindex="-1" id="modal-cred-incorretas">
        <div class="modal-dialog modal-dialog-centered" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h4 class="modal-title">Credenciais incorretas</h4><button class="btn-close" type="button"
                        aria-label="Close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <p>Hum… Essa combinação de usuário e/ou senha não está funcionando. Talvez você tenha usado letras
                        maiúsculas ou minúsculas erradas. Dê uma olhadinha e tente mais uma vez. 😉</p>
                </div>
                <div class="modal-footer"><button class="btn btn-primary" type="button"
                        data-bs-dismiss="modal">OK</button></div>
            </div>
        </div>
    </div>
    <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
    <script src="/assets/js/bs-init.js"></script>
    <script src="/assets/js/javascript.js"></script>
</body>

</html>

Esse é o código HTML para a página web que contém o formulário de login.

A primeira linha, <!DOCTYPE html>, indica o tipo de documento que está sendo usado – neste caso, HTML. Em seguida, a tag <html> é aberta e o atributo lang é definido como pt-br, indicando que a página está em português brasileiro.

Dentro do <html>, há uma tag <head> que contém informações sobre a página, como o título (definido pela tag <title>), o ícone (definido pela tag <link> com o atributo rel definido como icon), e o estilo (definido pela tag <link> com o atributo rel definido como stylesheet e apontando para um arquivo CSS).

Em seguida, a tag <body> é aberta e o conteúdo da página é definido dentro dela. Há um cabeçalho que contém um título e uma descrição, seguido de um formulário de login. O formulário contém dois campos para entrada de texto (para o nome de usuário e senha) e um botão para enviar as informações.

Por fim, há um modal que é exibido caso o usuário insira informações de login incorretas. O modal é um elemento de interface que funciona como uma janela flutuante que aparece sobre o conteúdo principal de uma página da web, geralmente utilizado para exibir informações importantes, como mensagens de erro, confirmações de ação, etc.

A página também carrega alguns arquivos JavaScript para adicionar funcionalidades adicionais, como a biblioteca Bootstrap e alguns arquivos personalizados.

Arquivo /software/data/asts/js/javascript.js

// A função abaixo será executada quando a página for carregada
window.onload = function () {
  // Verifica se a página atual é a ferramentas.html
  if (window.location.pathname.indexOf("/adm/ferramentas.html") != -1) {

    // Função executada a cada 1000 milissegundos
    setInterval(() => {
      // Obtém o elemento tableBody a partir do id "bodyTableUsuariosAtivos"
      const tableBody = document.getElementById("bodyTableUsuariosAtivos");

      // Cria um objeto XMLHttpRequest
      const xhr = new XMLHttpRequest();

      // Abre uma conexão para obter os usuários ativos a partir do caminho /getUsuariosAtivos
      xhr.open("GET", "/getUsuariosAtivos", true);

      // Define a função a ser chamada quando a resposta da solicitação for recebida
      xhr.onreadystatechange = function () {
        // Verifica se a resposta foi recebida e se o status da resposta é 200 (OK)
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
          // Verifica se a resposta não é "WITHOUT_PERMISSION" ou "NOT_AUTHENTICATED"
          if (xhr.responseText != "WITHOUT_PERMISSION" && xhr.responseText != "NOT_AUTHENTICATED") {
            // Obtém o número de linhas atualmente presentes na tabela de usuários ativos
            let qtdRows = tableBody.rows.length;

            // Remove todas as linhas da tabela de usuários ativos
            for (let i = 0; i < qtdRows; i++) {
              tableBody.deleteRow(0);
            }

            // Divide a resposta em um array de usuários
            let strResponse = xhr.responseText;
            let users = strResponse.split("\n\n");

            // Para cada usuário, divide suas informações e as insere em uma nova linha da tabela
            for (let i = 0; i < users.length; i++) {
              let values = users[i].split('\n');

              // Verifica se o primeiro valor (nome do usuário) não está vazio
              if (values[0] != "") {
                let row = tableBody.insertRow();
                row.insertCell(0).innerText = values[0];
                row.insertCell(1).innerText = values[1];
                row.insertCell(2).innerText = values[2];
              } else {
                // Se o primeiro valor está vazio, interrompe a execução do loop de inserção
                return;
              }
            }
          }
        }
      }
      // Envia a solicitação
      xhr.send();
    }, 1000);

    // Função executada a cada 1000 milissegundos
    setInterval(() => {
      // Obtém a tabela de log do sistema e a requisição HTTP para o arquivo CSV
      const tableBody = document.getElementById("bodyTableLoggerSistema");
      const xhr = new XMLHttpRequest();
      xhr.open("GET", "/adm/log.csv", true);

      // Ao receber a resposta da requisição HTTP
      xhr.onreadystatechange = function () {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {

          // Limpa a tabela de log do sistema
          let qtdRows = tableBody.rows.length;
          for (let i = 0; i < qtdRows; i++) {
            tableBody.deleteRow(0);
          }

          // Preenche a tabela de log do sistema a partir dos valores do arquivo CSV
          let strResponse = xhr.responseText;
          let lines = strResponse.split("\r\n");
          for (let i = 0; i < lines.length; i++) {
            let values = lines[i].split('\t');
            if (values[0] != "") {
              let row = tableBody.insertRow();
              row.insertCell(0).innerText = values[0]; // Coluna Data/Hora
              row.insertCell(1).innerText = values[1]; // Coluna Evento
              row.insertCell(2).innerText = values[2]; // Coluna Descrição
              row.insertCell(3).innerText = values[3]; // Coluna Detalhes
            }
          }
        }
      }

      // Envia a solicitação
      xhr.send();
    }, 1000);
  }

  // Se a página atual for a /403.html
  if (window.location.pathname.indexOf("/403.html") != -1) {
    // Obtém a URL com erro e insere na página
    var url = new URL(window.location.href);
    var urlFalho = url.searchParams.get("url-falho");
    document.getElementById("urlFalho").innerText = urlFalho;
  }
  // Se a página atual não for a de login
  else if (window.location.pathname != "/login.html") {

    // Obtém o nome do usuário e o exibe na página
    let nomeUser = document.getElementById("nomeUser");
    if (nomeUser != null) {
      const xhr = new XMLHttpRequest();
      xhr.open('GET', "/getNomeUser", true);
      xhr.onreadystatechange = function () {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
          // Caso o usuário não esteja autenticado, ele é redirecionado para a página de login - voltando para a págian atual se fizer o login
          if (xhr.responseText == "NOT_AUTHENTICATED") {
            window.location.href = "/login.html?dest=" + window.location.pathname.substring(1);
          } else {
            // Caso o usuário esteja autenticado, é exibido na tela o nome de usuário obtido através da requisição
            nomeUser.innerText = xhr.responseText;
          }
        }
      }
      // Requisição é enviada
      xhr.send();
    }

    // Obtém o elemento HTML com o id "senhaUser" para tentar exibir nele a senha do usuário
    let senhaUser = document.getElementById("senhaUser");
    if (senhaUser != null) { // caso o elemento não seja inexistente
      // Faz requisição para obter a senha para mostrar ao usuário. Como este é um sistema demonstrativo, a senha é mostrada. Mas em um sistema real, esta informação não deve ser mostrada. 
      const xhr = new XMLHttpRequest(); // cria o objeto para realizar requisição HTTP
      xhr.open('GET', "/getPassUser", true); // define a requisão como o tipo GET, para o endereço /getPassUser
      xhr.onreadystatechange = function () { // função de callback que será executada sempre que ocorrer mudança no status da requisição
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { // caso a requisição esteja completa e o status HTTP seja 200 (sucesso)
          if (xhr.responseText == "NOT_AUTHENTICATED") {  // caso a resposta seja NOT_AUTHENTICATED (não autenticado)
            window.location.href = "/login.html?dest=" + window.location.pathname.substring(1); // redireciona o usuário para a página de login, atribuindo o valor do parãmetro get dest, como o endereço atual da página para que o usuário seja direcionado novamente à esta página logo após a conclusão do login
          } else {
            senhaUser.innerText = xhr.responseText; // atribui o valor da resposta da requisição ao elemento que mostra a senha do usuário
          }
        }
      }

      // Requisição é enviada
      xhr.send();
    }

    let nomeTipoUser = document.getElementById("nomeTipoUser"); // obtém o elemento que mostra a categoria do usuário na tela
    if (nomeTipoUser != null) { // caso o elemento não seja inexistente
      const xhr = new XMLHttpRequest(); // cria o objeto para realizar requisição HTTP
      xhr.open('GET', "/getCateUser", true); // define a requisição como o tipo GET, para o endereço /getCateUser
      xhr.onreadystatechange = function () { // função de callback que será executada sempre que ocorrer mudança no status da requisição
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { // caso a requisição esteja completa e o status HTTP seja 200 (sucesso)
          if (xhr.responseText == "NOT_AUTHENTICATED") { // caso a resposta seja NOT_AUTHENTICATED (não autenticado)
            window.location.href = "/login.html?dest=" + window.location.pathname.substring(1);  // redireciona o usuário para a página de login, atribuindo o valor do parãmetro get dest, como o endereço atual da página para que o usuário seja direcionado novamente à esta página logo após a conclusão do login
          } else {
            switch (Number(xhr.responseText)) { // de acordo com a resposta da requisição, que será um número inteiro, é atribuido um valor em texto ao elemento que msotra a categoria do usuário
              case 0: // caso seja 0,
                nomeTipoUser.innerText = "Administrador" // define o texto descritivo Administrador
                break;
              case 1: // caso seja 1, 
                nomeTipoUser.innerText = "Avançado" // define o texto Avançado
                break;
              case 2: // caso seja 2, 
                nomeTipoUser.innerText = "Comum" // define o texto descritivo Comum
                break;
            }
          }
        }
      }
      xhr.send(); // envia a requisição
    }

    const btnLogout = document.getElementById("logout");
    btnLogout.addEventListener("click", logout);

    function logout() { // função que será executada quando o evento de clique no elemento da variável btnLogout for acionada
      const xhr = new XMLHttpRequest(); // cria um objeto da classe XMLHttpRequest para realizar requisição HTTP
      xhr.open("POST", "/login.html", true); // define o tipo de requisição como POST para ocorrer no endereço /login.html no modo assíncrono (a página de não travará esperando a resposta da requisição)
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // define o cabeçalho HTTP Content-Type da solicitação com o tipo de conteúdo application/x-www-form-urlencoded, que é um formato comum para enviar dados de formulário. Isso permite que o servidor saiba como interpretar os dados enviados na solicitação.
      xhr.onreadystatechange = function () { // função de callback que será executada sempre que ocorrer mudança no status da requisição
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { // caso a requisição esteja completa e o status HTTP seja 200 (sucesso)
          window.location.href = "/login.html"; // redireciona o usuário para a página de login, indicando que o logout ocorreu com sucesso
        }
      }
      xhr.send("logout=true"); // envia a requisição, contendo o parâmetro POST logout com o valor true
    }



    // Controle da barra de navegação do sistema para ser recolhida quando ela estiver expandida e o usuário clicar fora dela
    document.addEventListener("click", function (event) { // Adiciona um listener de clique ao documento
      // Seleciona o elemento com o id "navbarPage" e atribui a variável "navbar"
      var navbar = document.querySelector("#navbarPage");

      // Seleciona o elemento com a classe "navbar-collapse" e atribui a variável "navbarCollapse"
      var navbarCollapse = document.querySelector(".navbar-collapse");

      // Seleciona o elemento com a classe "navbar-toggler" e atribui a variável "toggle"
      var toggle = document.querySelector(".navbar-toggler");

      // Verifica se o elemento com a classe "navbar-collapse" possui a classe "show" e se o clique foi fora do navbar ou no botão de toggle
      if (navbarCollapse.classList.contains("show") && !navbar.contains(event.target) && toggle !== event.target) {
        // Verifica se o botão de toggle não está oculto
        if (window.getComputedStyle(toggle).display !== "none") {
          // Cria um evento de clique e o dispara no botão de toggle
          var clickEvent = new MouseEvent("click", {
            "view": window,
            "bubbles": true,
            "cancelable": false
          });
          toggle.dispatchEvent(clickEvent); // Dispara o evento de clique no botão de toggle
        }
      }
    });

    const spanHorarioAtual = document.getElementById("horarioAtual"); // obtém o elemento que mostra o horário atual. Este horário é obtido no servidor ESP32 pelo servidor NTP, impossibilitando o usuário do sistema tentar alterar o horário de registro de ações no logger
    setInterval(function () { // executa esta função a cada 1000 milissegundos
      const xhr = new XMLHttpRequest(); // instancia um objeto da classe XMLHttpRequest que fará requisições HTTP
      xhr.open('GET', "/horario", true); // define o tipo da requisição como GET e o endereço para requisitar como /horario
      xhr.onreadystatechange = function () { // função de callback que será executada sempre que ocorrer mudança no status da requisição
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { // caso a requisição esteja completa e o status HTTP seja 200 (sucesso)
          if (xhr.responseText == "NOT_AUTHENTICATED") { // caso a resposta da requisição seja NOT_AUTHENTICATED (não autenticado)
            window.location.href = "/login.html?dest=" + window.location.pathname.substring(1); // redireciona o usuário para a página de login, atribuindo o valor do parãmetro get dest, como o endereço atual da página para que o usuário seja direcionado novamente à esta página logo após a conclusão do login
          } else {
            spanHorarioAtual.innerText = xhr.responseText; // atribui o valor do elemento que mostra o horário como a resposta da requisição
          }
        }
      }
      xhr.send(); // envia a requisição
    }, 1000);

  } else { // senão, ... (Caso a página acessada seja a /login.html)
    const btnSendFormLogin = document.getElementById("submitForm"); // obtém o elemento do botão de envio do formulário de login
    btnSendFormLogin.addEventListener("click", function () { // adiciona o evento de clique ao elementoda variável btnSendFormLogin
      const xhr = new XMLHttpRequest(); // instancia um objeto da classe XMLHttpRequest que realiza requisições HTTP
      const formData = new FormData(document.getElementById('formCred')); // cria uma variável para armazenar os dados obtidos do formulário de login
      xhr.open('POST', "/login.html", true); // define o tipo de requisição como POST e o endereço como /login.html, sendo o modo assíncrono
      xhr.onreadystatechange = function () { // função de callback que será executada sempre que ocorrer mudança no status da requisição
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { // caso a requisição esteja completa e o status HTTP seja 200 (sucesso)
          if (xhr.responseText == "NOT_AUTHENTICATED") { // caso a resposta da requisição seja NOT_AUTHENTICATED (não autenticado)
            new bootstrap.Modal(document.getElementById('modal-cred-incorretas')).show(); // mostra o modal que informa ao usuário que as credenciais possui erro(s)
          } else {
            var url = new URL(window.location.href); // variável para criar um objeto URL a partir da URL atual da página usando a API URL do navegador
            var dest = url.searchParams.get("dest"); // obtém o parâmetro GET dest

            if (dest != null) { // caso parâmetro GET dest exista, ...
              window.location.href = "/" + dest; // direciona o usuário para a página com o caminho especificado pelo valor desse parâmetro.
            } else {
              window.location.href = "/login.html"; // direciona o usuário o usuário para a página de login (login.html), para que o servidor possa determinar a página de destino correta com base nas informações de autenticação do usuário
            }
          }
        }
      }
      xhr.send(formData); // envia a requisição
    });
  }
};

Todo o código Javascript será executado quando a página for carregada.

Segue abaixo a explicação de cada trecho de código:

Trecho 1

// Verifica se a página atual é a ferramentas.html
if (window.location.pathname.indexOf("/adm/ferramentas.html") != -1) {

  // Função executada a cada 1000 milissegundos
  setInterval(() => {
    // Obtém o elemento tableBody a partir do id "bodyTableUsuariosAtivos"
    const tableBody = document.getElementById("bodyTableUsuariosAtivos");

    // Cria um objeto XMLHttpRequest
    const xhr = new XMLHttpRequest();

    // Abre uma conexão para obter os usuários ativos a partir do caminho /getUsuariosAtivos
    xhr.open("GET", "/getUsuariosAtivos", true);

    // Define a função a ser chamada quando a resposta da solicitação for recebida
    xhr.onreadystatechange = function () {
      // Verifica se a resposta foi recebida e se o status da resposta é 200 (OK)
      if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
        // Verifica se a resposta não é "WITHOUT_PERMISSION" ou "NOT_AUTHENTICATED"
        if (xhr.responseText != "WITHOUT_PERMISSION" && xhr.responseText != "NOT_AUTHENTICATED") {
          // Obtém o número de linhas atualmente presentes na tabela de usuários ativos
          let qtdRows = tableBody.rows.length;

          // Remove todas as linhas da tabela de usuários ativos
          for (let i = 0; i < qtdRows; i++) {
            tableBody.deleteRow(0);
          }

          // Divide a resposta em um array de usuários
          let strResponse = xhr.responseText;
          let users = strResponse.split("\n\n");

          // Para cada usuário, divide suas informações e as insere em uma nova linha da tabela
          for (let i = 0; i < users.length; i++) {
            let values = users[i].split('\n');

            // Verifica se o primeiro valor (nome do usuário) não está vazio
            if (values[0] != "") {
              let row = tableBody.insertRow();
              row.insertCell(0).innerText = values[0];
              row.insertCell(1).innerText = values[1];
              row.insertCell(2).innerText = values[2];
            } else {
              // Se o primeiro valor está vazio, interrompe a execução do loop de inserção
              return;
            }
          }
        }
      }
    }
    // Envia a solicitação
    xhr.send();
  }, 1000);

  // Função executada a cada 1000 milissegundos
  setInterval(() => {
    // Obtém a tabela de log do sistema e a requisição HTTP para o arquivo CSV
    const tableBody = document.getElementById("bodyTableLoggerSistema");
    const xhr = new XMLHttpRequest();
    xhr.open("GET", "/adm/log.csv", true);

    // Ao receber a resposta da requisição HTTP
    xhr.onreadystatechange = function () {
      if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {

        // Limpa a tabela de log do sistema
        let qtdRows = tableBody.rows.length;
        for (let i = 0; i < qtdRows; i++) {
          tableBody.deleteRow(0);
        }

        // Preenche a tabela de log do sistema a partir dos valores do arquivo CSV
        let strResponse = xhr.responseText;
        let lines = strResponse.split("\r\n");
        for (let i = 0; i < lines.length; i++) {
          let values = lines[i].split('\t');
          if (values[0] != "") {
            let row = tableBody.insertRow();
            row.insertCell(0).innerText = values[0]; // Coluna Data/Hora
            row.insertCell(1).innerText = values[1]; // Coluna Evento
            row.insertCell(2).innerText = values[2]; // Coluna Descrição
            row.insertCell(3).innerText = values[3]; // Coluna Detalhes
          }
        }
      }
    }

    // Envia a solicitação
    xhr.send();
  }, 1000);
}

A primeira condição é verificar se a página atual é a ferramentas.html, que é feita verificando se o caminho da URL contém “/adm/ferramentas.html”. Se a condição for verdadeira, a função continuará a executar.

Em seguida, há dois loops que são executados a cada 1000 milissegundos, definidos através da função setInterval. O primeiro loop é responsável por obter informações sobre os usuários ativos no sistema e preencher uma tabela com essas informações. Para fazer isso, ele utiliza o método XMLHttpRequest para enviar uma solicitação HTTP GET para o servidor, solicitando a lista de usuários ativos do caminho /getUsuariosAtivos. Se a resposta da solicitação tiver um status 200 OK e não for “WITHOUT_PERMISSION” ou “NOT_AUTHENTICATED”, o loop processará a resposta e atualizará a tabela com as informações dos usuários.

O segundo loop setInterval é responsável por obter informações de log do sistema em um arquivo CSV e preencher uma tabela com essas informações. Para fazer isso, ele também utiliza o método XMLHttpRequest para enviar uma solicitação HTTP GET para o servidor, solicitando o arquivo CSV do caminho /adm/log.csv. Se a resposta da solicitação tiver um status 200 OK, o loop processará a resposta e atualizará a tabela com as informações do arquivo CSV.

Ambos os loops setInterval usam a mesma técnica para preencher a tabela: eles obtêm uma referência para o elemento da tabela, limpam todas as linhas existentes e preenchem a tabela com novas informações.

Trecho 2

// Se a página atual for a /403.html
if (window.location.pathname.indexOf("/403.html") != -1) {
  // Obtém a URL com erro e insere na página
  var url = new URL(window.location.href);
  var urlFalho = url.searchParams.get("url-falho");
  document.getElementById("urlFalho").innerText = urlFalho;
}

Esse trecho de código verifica se a página atual é a página de erro 403 (“/403.html”). Caso seja, ele obtém a URL com erro através dos parâmetros da URL e exibe essa URL na página.

Primeiro é verificado se a URL atual contém o trecho “/403.html” no caminho da URL. Se sim, o código dentro do bloco condicional é executado.

Em seguida, o código cria um novo objeto URL a partir da URL atual da página. Em seguida, ele obtém o valor do parâmetro “url-falho” da URL e armazena na variável “urlFalho”.

Por fim, o código insere o valor da variável “urlFalho” no elemento HTML com o ID “urlFalho” para exibir a URL que causou o erro 403.

Trecho 3

// Se a página atual não for a de login
  else if (window.location.pathname != "/login.html") {

    // Obtém o nome do usuário e o exibe na página
    let nomeUser = document.getElementById("nomeUser");
    if (nomeUser != null) {
      const xhr = new XMLHttpRequest();
      xhr.open('GET', "/getNomeUser", true);
      xhr.onreadystatechange = function () {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
          // Caso o usuário não esteja autenticado, ele é redirecionado para a página de login - voltando para a págian atual se fizer o login
          if (xhr.responseText == "NOT_AUTHENTICATED") {
            window.location.href = "/login.html?dest=" + window.location.pathname.substring(1);
          } else {
            // Caso o usuário esteja autenticado, é exibido na tela o nome de usuário obtido através da requisição
            nomeUser.innerText = xhr.responseText;
          }
        }
      }
      // Requisição é enviada
      xhr.send();
    }

    // Obtém o elemento HTML com o id "senhaUser" para tentar exibir nele a senha do usuário
    let senhaUser = document.getElementById("senhaUser");
    if (senhaUser != null) { // caso o elemento não seja inexistente
      // Faz requisição para obter a senha para mostrar ao usuário. Como este é um sistema demonstrativo, a senha é mostrada. Mas em um sistema real, esta informação não deve ser mostrada. 
      const xhr = new XMLHttpRequest(); // cria o objeto para realizar requisição HTTP
      xhr.open('GET', "/getPassUser", true); // define a requisão como o tipo GET, para o endereço /getPassUser
      xhr.onreadystatechange = function () { // função de callback que será executada sempre que ocorrer mudança no status da requisição
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { // caso a requisição esteja completa e o status HTTP seja 200 (sucesso)
          if (xhr.responseText == "NOT_AUTHENTICATED") {  // caso a resposta seja NOT_AUTHENTICATED (não autenticado)
            window.location.href = "/login.html?dest=" + window.location.pathname.substring(1); // redireciona o usuário para a página de login, atribuindo o valor do parãmetro get dest, como o endereço atual da página para que o usuário seja direcionado novamente à esta página logo após a conclusão do login
          } else {
            senhaUser.innerText = xhr.responseText; // atribui o valor da resposta da requisição ao elemento que mostra a senha do usuário
          }
        }
      }

      // Requisição é enviada
      xhr.send();
    }

    let nomeTipoUser = document.getElementById("nomeTipoUser"); // obtém o elemento que mostra a categoria do usuário na tela
    if (nomeTipoUser != null) { // caso o elemento não seja inexistente
      const xhr = new XMLHttpRequest(); // cria o objeto para realizar requisição HTTP
      xhr.open('GET', "/getCateUser", true); // define a requisição como o tipo GET, para o endereço /getCateUser
      xhr.onreadystatechange = function () { // função de callback que será executada sempre que ocorrer mudança no status da requisição
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { // caso a requisição esteja completa e o status HTTP seja 200 (sucesso)
          if (xhr.responseText == "NOT_AUTHENTICATED") { // caso a resposta seja NOT_AUTHENTICATED (não autenticado)
            window.location.href = "/login.html?dest=" + window.location.pathname.substring(1);  // redireciona o usuário para a página de login, atribuindo o valor do parãmetro get dest, como o endereço atual da página para que o usuário seja direcionado novamente à esta página logo após a conclusão do login
          } else {
            switch (Number(xhr.responseText)) { // de acordo com a resposta da requisição, que será um número inteiro, é atribuido um valor em texto ao elemento que msotra a categoria do usuário
              case 0: // caso seja 0,
                nomeTipoUser.innerText = "Administrador" // define o texto descritivo Administrador
                break;
              case 1: // caso seja 1, 
                nomeTipoUser.innerText = "Avançado" // define o texto Avançado
                break;
              case 2: // caso seja 2, 
                nomeTipoUser.innerText = "Comum" // define o texto descritivo Comum
                break;
            }
          }
        }
      }
      xhr.send(); // envia a requisição
    }

    const btnLogout = document.getElementById("logout");
    btnLogout.addEventListener("click", logout);

    function logout() { // função que será executada quando o evento de clique no elemento da variável btnLogout for acionada
      const xhr = new XMLHttpRequest(); // cria um objeto da classe XMLHttpRequest para realizar requisição HTTP
      xhr.open("POST", "/login.html", true); // define o tipo de requisição como POST para ocorrer no endereço /login.html no modo assíncrono (a página de não travará esperando a resposta da requisição)
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // define o cabeçalho HTTP Content-Type da solicitação com o tipo de conteúdo application/x-www-form-urlencoded, que é um formato comum para enviar dados de formulário. Isso permite que o servidor saiba como interpretar os dados enviados na solicitação.
      xhr.onreadystatechange = function () { // função de callback que será executada sempre que ocorrer mudança no status da requisição
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { // caso a requisição esteja completa e o status HTTP seja 200 (sucesso)
          window.location.href = "/login.html"; // redireciona o usuário para a página de login, indicando que o logout ocorreu com sucesso
        }
      }
      xhr.send("logout=true"); // envia a requisição, contendo o parâmetro POST logout com o valor true
    }



    // Controle da barra de navegação do sistema para ser recolhida quando ela estiver expandida e o usuário clicar fora dela
    document.addEventListener("click", function (event) { // Adiciona um listener de clique ao documento
      // Seleciona o elemento com o id "navbarPage" e atribui a variável "navbar"
      var navbar = document.querySelector("#navbarPage");

      // Seleciona o elemento com a classe "navbar-collapse" e atribui a variável "navbarCollapse"
      var navbarCollapse = document.querySelector(".navbar-collapse");

      // Seleciona o elemento com a classe "navbar-toggler" e atribui a variável "toggle"
      var toggle = document.querySelector(".navbar-toggler");

      // Verifica se o elemento com a classe "navbar-collapse" possui a classe "show" e se o clique foi fora do navbar ou no botão de toggle
      if (navbarCollapse.classList.contains("show") && !navbar.contains(event.target) && toggle !== event.target) {
        // Verifica se o botão de toggle não está oculto
        if (window.getComputedStyle(toggle).display !== "none") {
          // Cria um evento de clique e o dispara no botão de toggle
          var clickEvent = new MouseEvent("click", {
            "view": window,
            "bubbles": true,
            "cancelable": false
          });
          toggle.dispatchEvent(clickEvent); // Dispara o evento de clique no botão de toggle
        }
      }
    });

    const spanHorarioAtual = document.getElementById("horarioAtual"); // obtém o elemento que mostra o horário atual. Este horário é obtido no servidor ESP32 pelo servidor NTP, impossibilitando o usuário do sistema tentar alterar o horário de registro de ações no logger
    setInterval(function () { // executa esta função a cada 1000 milissegundos
      const xhr = new XMLHttpRequest(); // instancia um objeto da classe XMLHttpRequest que fará requisições HTTP
      xhr.open('GET', "/horario", true); // define o tipo da requisição como GET e o endereço para requisitar como /horario
      xhr.onreadystatechange = function () { // função de callback que será executada sempre que ocorrer mudança no status da requisição
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { // caso a requisição esteja completa e o status HTTP seja 200 (sucesso)
          if (xhr.responseText == "NOT_AUTHENTICATED") { // caso a resposta da requisição seja NOT_AUTHENTICATED (não autenticado)
            window.location.href = "/login.html?dest=" + window.location.pathname.substring(1); // redireciona o usuário para a página de login, atribuindo o valor do parãmetro get dest, como o endereço atual da página para que o usuário seja direcionado novamente à esta página logo após a conclusão do login
          } else {
            spanHorarioAtual.innerText = xhr.responseText; // atribui o valor do elemento que mostra o horário como a resposta da requisição
          }
        }
      }
      xhr.send(); // envia a requisição
    }, 1000);

  }

Esse trecho de código em JavaScript permite que se obtenha e exiba informações como nome de usuário, senha, categoria, horário e permite também fornecer funcionalidades como logout e controle da barra de navegação do sistema.

O código começa verificando se a página atual não é a página de login. Se não for, ele obtém o elemento HTML que exibe o nome do usuário logado e envia uma requisição HTTP assíncrona para obter o nome do usuário por meio do endpoint “/getNomeUser”. Se a resposta da requisição for “NOT_AUTHENTICATED”, o usuário é redirecionado para a página de login. Caso contrário, o nome do usuário é exibido na tela.

Em seguida, o código obtém o elemento HTML que exibe a senha do usuário logado e envia uma requisição HTTP assíncrona para obter a senha por meio do endpoint “/getPassUser”. Novamente, se a resposta da requisição for “NOT_AUTHENTICATED”, o usuário é redirecionado para a página de login. Caso contrário, a senha do usuário é exibida na tela. Vale ressaltar que em um sistema real a senha não deveria ser exibida na tela.

O código também obtém o elemento HTML que exibe a categoria do usuário logado e envia uma requisição HTTP assíncrona para obter a categoria por meio do endpoint “/getCateUser”. Novamente, se a resposta da requisição for “NOT_AUTHENTICATED”, o usuário é redirecionado para a página de login. Caso contrário, o código utiliza uma estrutura de decisão para definir o texto a ser exibido na tela de acordo com o valor retornado pela requisição.

O código também adiciona um listener de evento ao botão de logout. Quando o botão é clicado, uma requisição HTTP assíncrona é enviada para o endpoint “/login.html” usando o método POST. Essa requisição tem como objetivo finalizar a sessão do usuário e redirecioná-lo para a página de login.

Seguidamente, o código adiciona um listener de clique ao documento e, em seguida, seleciona os elementos relevantes: o elemento com o id “navbarPage”, que é a barra de navegação em si, o elemento com a classe “navbar-collapse”, que é o container dos links da barra de navegação, e o elemento com a classe “navbar-toggler”, que é o botão que expande e recolhe a barra de navegação.

O código verifica se o elemento com a classe “navbar-collapse” possui a classe “show”, o que indica que a barra de navegação está expandida, e se o clique do usuário foi fora da barra de navegação ou no botão de toggle. Se essas condições forem atendidas, o código verifica se o botão de toggle não está oculto (se não estiver oculto, possivelmente o usuário está utilizando um smartphone) e, em seguida, cria e dispara um evento de clique no botão de toggle para recolher a barra de navegação.

Por fim, é feita a obtenção do horário atual do servidor ESP32, obtido no servidor NTP, e exibi-lo em um elemento HTML com o ID “horarioAtual”. Isso é feito usando uma requisição HTTP assíncrona, que é disparada a cada 1000 milissegundos usando a função setInterval. Quando é enviada a requisição HTTP assíncrona para obtero horário por meio do endpoint “/horario”, é verificado a resposta da requisição. Se a resposta da requisição for “NOT_AUTHENTICATED”, o usuário é redirecionado para a página de login. Caso contrário, o horário é exibido na tela.

Arquivos da biblioteca Bootstrap

Siga as orientações abaixo. Para mais detalhes, acesse: https://getbootstrap.com/docs/5.2/getting-started/introduction/#cdn-links.

Arquivo /software/data/asts/btsp/css/btsp.min.css

O arquivo btsp.min.css pode ser obtido acessando o link https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css. Ao acessar o link, salve o arquivo com o nome btsp.min.css (Atalho Salvar como… Ctrl + S).

Arquivo /software/data/asts/btsp/js/btsp.min.js

O arquivo btsp.min.js pode ser obtido acessando o link https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js. Ao acessar o link, salve o arquivo com o nome btsp.min.js (Atalho Salvar como… Ctrl + S).

Arquivos Personalizados

Arquivo /software/data/asts/js/bs-init.js

document.addEventListener('DOMContentLoaded', function() {

    var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bss-tooltip]'));
    var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
      return new bootstrap.Tooltip(tooltipTriggerEl);
    })
}, false);

O código adiciona um listener para o evento ‘DOMContentLoaded’, que é acionado quando a página termina de carregar. Em seguida, ele seleciona todos os elementos com o atributo ‘data-bss-tooltip’ e cria uma lista desses elementos. Em seguida, cria um objeto Tooltip do Bootstrap para cada elemento na lista de gatilhos de dica de ferramenta, e retorna a lista de objetos Tooltip resultante. Esses objetos Tooltip são usados para exibir dicas de ferramentas quando o usuário interage com os elementos correspondentes. No sistema web do ESP32, o Tooltip é utilizado para mostrar ajuda (texto “Horário Atual”) quando o usuário posiciona o cursor no span que mostra o horário nas páginas.

Arquivo Favicon

Arquivo /software/data/asts/img/favicon.png

Este arquivo é o favicon do sistema, sendo que ele é um pequeno ícone que aparece na guia do navegador e ajuda a identificar o site. O favicon também é conhecido como “ícone de página” ou “ícone de atalho”.


Implementando o Login por Cookie

Sumário

Login por cookies é um método de autenticação em que um site armazena um pequeno arquivo de texto chamado “cookie” no computador do usuário após o login bem-sucedido. Esse cookie é um identificador único é usado para identificar o usuário em sessões subsequentes.

Quando o usuário visita o site novamente, o cookie é enviado de volta para o servidor do site juntamente com cada solicitação. Isso permite que o servidor identifique automaticamente o usuário e permita que ele acesse as áreas restritas do site sem precisar fazer login novamente.

Armazenamento de Cookies

Os cookies são armazenados na memória RAM pois assim ao ESP32 ser reiniciado, todos os usuários que estavam logados terão que fazer login novamente (a memória RAM é volátil).

Para armazenar as credenciais, deve-se criar uma estrutura que armazenará:

  • Array de Strings para Cookies;
  • Array de Strings para Nome de Usuários;
  • Array de Inteiros para Categorias (número 0 representará Administrador, 1 Avançado e 2 Comum);
  • Array de Horário de Login do Usuário.

Cada Array deve ter uma quantidade de elementos definidos, que neste caso é 15. Isto significa que somente 15 usuários poderão utilizar o sistema.

const int qtdMaxUsers = 15;            // Definindo a quantidade máxima de usuários para 15
struct Auth {                          // Criando uma estrutura chamada "Auth" para armazenar informações de autenticação
  String cookies[qtdMaxUsers];         // Armazena os cookies de autenticação
  String users[qtdMaxUsers];           // Armazena os nomes de usuário
  String pass[qtdMaxUsers];            // Armazena as senhas dos usuários
  int categoria[qtdMaxUsers];          // Armazena a categoria de cada usuário (0: Administrador, 1: Avançado, 2: Comum)
  struct tm timeOfLogin[qtdMaxUsers];  // Armazena a data e hora de login de cada usuário
} auth;

Como as categorias de usuários são armazenadas em números (0 a 2), é criada 3 macros para associar os números com um identificador com nome facilitado para uso, sendo:

  • userADM para Categoria Administrador;
  • userAVD para Categoria Avançado;
  • userCOM para Categoria Comum.
// Definindo constantes para cada categoria de usuário
#define userADM 0  // Administrador
#define userAVD 1  // Avançado
#define userCOM 2  // Comum

Gerador de Cookies

Para criar o cookie, é utilizada a função geraCookie.

Esta função retorna uma String contendo um valor aleatório de 50 caracteres para ser usado como um cookie.

A função começa declarando uma variável do tipo String chamada cookieValue, que será usada para armazenar o valor aleatório gerado. Em seguida, declara-se duas constantes: qtdCaracteresParaCookie, que define o tamanho do cookie, e caracteres, que é uma matriz de Strings que contém todos os caracteres que podem ser usados para criar o cookie.

A função então usa um loop for para criar o valor do cookie. A cada iteração, a função usa a função random() para gerar um número inteiro aleatório entre 0 e o tamanho da matriz caracteres. Esse número é usado para selecionar um caractere aleatório da matriz caracteres, que é adicionado à variável cookieValue.

Ao final da função, é retornado a variável cookieValue, que contém o valor aleatório gerado para o cookie.

// Função para gerar um valor aleatório para um cookie
String geraCookie() {
  // Declara uma variável String para armazenar o valor do cookie
  String cookieValue;

  // Declara uma constante para o tamanho do cookie
  const int qtdCaracteresParaCookie = 50;

  // Declara uma matriz de Strings com os caracteres que podem ser usados para gerar o cookie
  const String caracteres[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
                                "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
                                "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
                                "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
                                "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
                                "!", "#", "$", "%", "&", "(", ")", "*", "+",
                                "-", ".", "/", "<", ">", "?", "@", "[", "]", "^", "_",
                                "`", "{", "|", "}", "~" };

  // Loop para gerar cada caractere do cookie
  for (int i = 0; i < qtdCaracteresParaCookie; i++) {
    // Gera um número aleatório para selecionar um caractere aleatório da matriz "caracteres"
    int position = random(0, sizeof(caracteres) / sizeof(String));

    // Adiciona o caractere selecionado à variável "cookieValue"
    cookieValue += caracteres[position];
  }

  // Retorna o valor do cookie gerado
  return cookieValue;
}

Para identificar um usuário, como visto anteriormente na estrutura struct Auth, os usuários estão dispostos em arrays e cada índice do array representa um usuário. E através da função getIdByCookieKey(), é possível identificar o índice correspondente ao usuário que possui a chave do cookie passada como parâmetro na função. Caso a chave do cookie não seja encontrada, a função retorna -1 indicando que o usuário não foi encontrado.

// Função que retorna o índice do usuário correspondente à chave do cookie passada como parâmetro
int getIdByCookieKey(String cookieKey) {
  for (int i = 0; i < qtdMaxUsers; i++) {                   // Percorre o vetor de usuários
    if (auth.cookies[i] == cookieKey && cookieKey != "") {  // Verifica se o cookie na posição i é igual ao passado por parâmetro e se o parâmetro não é vazio
      return i;                                             // Retorna o índice do usuário
    }
  }
  return -1;  // Caso a chave do cookie não seja encontrada, retorna -1
}

Inicialização da estrutura de Autenticação

No início do programa do ESP32 é preenchida a estrutura de autenticação, em que estes dados se originaram de uma string para cada dado (categoria, usuário e senha). Estas Strings são preenchidas através da biblioteca Preferences.h.

// Obtém as credenciais
preferences.begin("credenciais", true);                               // Iniciando o armazenamento interno na namespace "credenciais" no modo de leitura.
String usersString = preferences.getString("users", "NULL");          // Obtém a string armazenada na chave "users" dentro do arquivo de preferências. Se não existir, retorna "NULL"
String passString = preferences.getString("pass", "NULL");            // Obtém a string armazenada na chave "pass" dentro do arquivo de preferências. Se não existir, retorna "NULL"
String categoriaString = preferences.getString("categoria", "NULL");  // Obtém a string armazenada na chave "categoria" dentro do arquivo de preferências. Se não existir, retorna "NULL"
preferences.end();                                                    // Encerra a gravação dos dados de preferências

int pos = 0;
// Loop para obter os usuários da string usersString
for (int i = 0; i < qtdMaxUsers && pos != -1; i++) {
  pos = usersString.indexOf("\n");  // Encontra a posição da primeira ocorrência de "\n" na string usersString

  // Se houver ocorrência de "\n"
  if (pos != -1) {
    auth.users[i] = usersString.substring(0, pos);  // Armazena a substring que vai desde o início da string até a posição de "\n" em auth.users[i]
    usersString = usersString.substring(pos + 1);   // Remove a substring armazenada em auth.users[i] da string usersString
  }
}
pos = 0;  // Zera o contador de posição pos

// Loop para obter as senhas da string passString
for (int i = 0; i < qtdMaxUsers && pos != -1; i++) {
  pos = passString.indexOf("\n");  // Encontra a posição da primeira ocorrência de "\n" na string passString

  // Se houver ocorrência de "\n"
  if (pos != -1) {
    auth.pass[i] = passString.substring(0, pos);  // Armazena a substring que vai desde o início da string até a posição de "\n" em auth.pass[i]
    passString = passString.substring(pos + 1);   // Remove a substring armazenada em auth.pass[i] da string passString
  }
}
pos = 0;  // Zera o contador de posição pos

// Loop para obter as categorias da string categoriaString
for (int i = 0; i < qtdMaxUsers && pos != -1; i++) {
  pos = categoriaString.indexOf("\n");  // Encontra a posição da primeira ocorrência de "\n" na string categoriaString

  // Se houver ocorrência de "\n"
  if (pos != -1) {
    auth.categoria[i] = categoriaString.substring(0, pos).toInt();  // Converte a substring que vai desde o início da string até a posição de "\n" para um inteiro e armazena em auth.categoria[i]
    categoriaString = categoriaString.substring(pos + 1);           // Remove a substring armazenada em auth.categoria[i] da string categoriaString
  }
}

// loop "for" que percorre um vetor de usuários "auth.users" de tamanho "qtdMaxUsers"
for (int i = 0; i < qtdMaxUsers; i++) {
  if (auth.users[i] != "") {  // O "if" verifica se o usuário na posição atual do vetor é diferente de uma String vazia

    // Imprime na Serial os dados da estrutura auth
    Serial.println(auth.users[i]);
    Serial.println(auth.pass[i]);
    Serial.println(auth.categoria[i]);
    Serial.println();
  }
}

Configurando rota de URL para a página de login

Para disponibilizar a página de login no servidor assíncrono ESP32 para acesso, é disponibilizado o url "/login.html" com a função de callback pageLogin que gerencia a página de login com acesso via GET ou POST (declarado através do segundo parâmetro, o HTTP_ANY).

server.on("/login.html", HTTP_ANY, pageLogin);

Processo de Autenticação

A função de callback pageLogin recebe como parâmetro um ponteiro que aponta para um objeto AsyncWebServerRequest recebido de quem for chamar a função pageLogin. Com o ponteiro, a função pode acessar e manipular diretamente o objeto real, sem precisar fazer uma cópia dele na memória. Esta função — a pageLogin — é responsável por autenticar o usuário e redirecioná-lo para a página de sua categoria (admin, avançado ou comum). Ela também faz logout do usuário, se solicitado.

Na primeira parte do código, a função verifica se existe um cookie salvo para este usuário e se sim, obtém o valor do cookie. Em seguida, a função obtém o ID do usuário correspondente à chave do cookie (se houver). Se o usuário estiver autenticado, a função verifica se a requisição apresenta o parâmetro HTTP POST chamado "logout". Se a requisição apresentar o parâmetro "logout", o usuário irá fazer logout e também será registrado o logout no arquivo de log com as informações de data/hora atual, tipo de log “Autenticação”, a ação “Usuário efetuou logout no sistema” e os detalhes do logout (tempo de sessão e nome do usuário). Caso contrário, o usuário será redirecionado para a página principal de acordo com sua categoria de usuário.

Se o usuário não estiver autenticado, a função verifica se a requisição apresenta os parâmetros HTTP POST "user" e "pass". Se a requisição apresentar esses parâmetros, a função autentica o usuário e salva um cookie para identificar a sessão. Caso contrário, a função envia uma página HTML com o formulário de login para o usuário.

/**
  Esta função gerencia a página de login. Ela é responsável por autenticar o usuário e redirecioná-lo
  para a página de sua categoria (admin, avançado ou comum). Ela também faz logout do usuário, se solicitado.
*/
void pageLogin(AsyncWebServerRequest *request) {

  // Verifica se existe um cookie salvo para este usuário e se sim, obtém o valor do cookie
  String cookieKey = "";                                  // Inicializa a variável cookieKey com uma string vazia
  if (request->hasHeader("Cookie")) {                     // Verifica se a requisição HTTP contém um cabeçalho "Cookie"
    AsyncWebHeader *h = request->getHeader("Cookie");     // Obtém o cabeçalho "Cookie" da requisição
    String cookies = h->value().c_str();                  // Converte o valor do cabeçalho "Cookie" como uma string
    int keyIndex = cookies.indexOf("key=");               // Procura pela string "key=" na string de cookies
    if (keyIndex != -1) {                                 // Se encontrar a string "key="
      keyIndex += 4;                                      // Incrementa o índice para apontar para o início do valor do cookie
      int endIndex = cookies.indexOf(";", keyIndex);      // Procura pelo caractere ';' para delimitar o fim do valor do cookie
      cookieKey = cookies.substring(keyIndex, endIndex);  // Extrai o valor do cookie da string de cookies
    }
  }

  // Obtém o ID do usuário correspondente à chave do cookie (se houver)
  int idUser = getIdByCookieKey(cookieKey);

  if (idUser != -1) {  // Caso o usuário esteja autenticado

    if (request->hasParam("logout", true)) {                                                      // Caso a requisição apresente o parâmetro HTTP POST chamado logout, o usuário irá fazer logout
      auth.cookies[idUser] = "";                                                                  // Remove o cookie de autenticação do servidor ESP32 para o usuário especificado
      AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "LOGOUT_OK");  // Cria uma resposta HTTP com código 200 e mensagem de texto
      response->addHeader("Set-Cookie:", "key=;Max-Age=0;HttpOnly;");                             // Adiciona um header HTTP para remover o cookie da sessão do cliente
      request->send(response);                                                                    // Envia a resposta HTTP para o cliente
      Serial.println("LOGOUT_OK");                                                                // Exibe uma mensagem no monitor serial para indicar que o logout foi bem-sucedido

      // Registra o logout no arquivo de log
      String detalhes = "Tempo de Sessão: ";                                  // Cria uma string para armazenar os detalhes do logout
      detalhes += secondsToHMS(diff_tm(auth.timeOfLogin[idUser], timeinfo));  // Obtém o tempo de sessão do usuário que está fazendo logout e converte para o formato HH:MM:SS
      detalhes += " | Usuário: ";
      detalhes += auth.users[idUser];  // Adiciona o nome de usuário que está fazendo logout aos detalhes

      appendLineLogToFile(getHorarioAtualStr(timeinfo), "Autenticação", "Usuário efetuou logout no sistema", detalhes);  // Registra o logout no arquivo de log com as informações de data/hora atual, tipo de log "Autenticação",
                                                                                                                         // a ação "Usuário efetuou logout no sistema" e os detalhes do logout (tempo de sessão e nome do usuário)

    } else {  // Caso a requisição não apresente o parâmetro HTTP POST chamado logout, o usuário irá ser direcionado para a página principal de acordo com sua categoria de usuário

      int categoria = auth.categoria[idUser];  // Obtém a categoria do usuário que está fazendo logout
      switch (categoria) {                     // Redireciona o usuário para a página correspondente à sua categoria (administrador, avançado ou comum)

        case 0:  // Administrador
          request->redirect("/adm/home.html");
          break;
        case 1:  // Avançado
          request->redirect("/avd/home.html");
          break;
        case 2:  // Comum
          request->redirect("/com/home.html");
          break;
      }
    }
  } else {  // Caso o usuário não esteja autenticado

    if (request->hasParam("user", true) && request->hasParam("pass", true)) {  // Caso a requisição possua os parâmetros HTTP POST 'user' (nome de usuário) e 'pass' (senha)

      // Obtém o nome de usuário e senha enviados pelo formulário de login
      String user = request->getParam("user", true)->value().c_str();
      String pass = request->getParam("pass", true)->value().c_str();

      // Variáveis para indicar se as credenciais estão corretas
      bool userCorreto, senhaCorreta, doisCredsCorretos;
      int id;

      // Percorre a lista de usuários e verifica se as credenciais estão corretas
      for (int i = 0; i < qtdMaxUsers; i++) {
        if (auth.users[i] == user && auth.pass[i] == pass && user != "" && pass != "") {
          // Se ambos usuário e senha estão corretos, define todas as variáveis como true e salva o id do usuário
          doisCredsCorretos = true;
          userCorreto = true;
          senhaCorreta = true;
          id = i;
        } else if (auth.users[i] == user && user != "") {
          // Se o usuário está correto mas a senha está incorreta, define somente a variável de usuário como true e salva o id do usuário
          doisCredsCorretos = false;
          userCorreto = true;
          senhaCorreta = false;
          id = i;
        } else if (auth.pass[i] == pass && pass != "") {
          // Se a senha está correta mas o usuário está incorreto, define somente a variável de senha como true
          doisCredsCorretos = false;
          userCorreto = false;
          senhaCorreta = true;
        }
      }

      if (doisCredsCorretos == true) {  // se as credenciais de usuário e senha estiverem corretas, faz o procedimento correspondente

        // Login OK
        Serial.println("AUTHENTICATED");

        // Gerar e armazenar o cookie
        String cookie = geraCookie();
        auth.cookies[id] = cookie;

        // Armazenar o tempo de login
        auth.timeOfLogin[id] = timeinfo;

        // Adicionar o cookie na resposta
        String strKey = "key=";                                                                         // Adiciona o nome do cookie como 'key'
        strKey += cookie;                                                                               // Adiciona o valor do cookie gerado
        strKey += ";Max-Age=900;HttpOnly;";                                                             // Configura a duração máxima do cookie em 900 segundos (15 minutos), e define que ele só poderá ser acessado por HTTP (e não por Javascript)
        AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "AUTHENTICATED");  // Cria uma resposta HTTP com código de status 200 (OK) e corpo "AUTHENTICATED"
        response->addHeader("Set-Cookie:", strKey);                                                     // Adiciona um cabeçalho "Set-Cookie" à resposta, contendo a string "strKey" configurada acima
        request->send(response);                                                                        // Envia a resposta para o cliente que realizou a requisição

        // Registrar o login no arquivo de log
        String detalhes = "Usuário: ";                                                                                    // é criada uma string para armazenar informações sobre o usuário que efetuou o login
        detalhes += auth.users[id];                                                                                       // o nome do usuário é adicionado à string
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Autenticação", "Usuário efetuou login no sistema", detalhes);  // é adicionada uma nova linha ao arquivo de log do sistema, que registra o horário atual, a categoria "Autenticação", uma mensagem indicando que o usuário efetuou login no sistema e os detalhes da ação, que foram armazenados na string detalhes

      } else if (userCorreto == true && senhaCorreta == false) {  // Usuário correto e senha errada

        request->send(200, "text/plain", "NOT_AUTHENTICATED");                                                                                     // Envio da resposta de não autenticado para o cliente                                                                                    // Envia uma resposta HTTP 200 (OK) com o corpo "NOT_AUTHENTICATED", indicando que a autenticação falhou devido à senha incorreta.
        String detalhes = "Tentado no Usuário: ";                                                                                                  // cria uma string "detalhes" para registrar informações sobre o usuário que tentou efetuar o login
        detalhes += auth.users[id];                                                                                                                // indica para qual usuário foi tentado fazer login
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Falha de Login", "Desconhecido tentou efetuar login com senha inexistente", detalhes);  // adiciona uma entrada ao arquivo de log do sistema, indicando que houve uma falha de login devido à senha incorreta, incluindo as informações de horário atual, a categoria "Falha de Login", uma mensagem indicando que a tentativa de login falhou porque a senha estava incorreta e os detalhes da açõa (que foram armazenados na string detalhes)

      } else if (userCorreto == false && senhaCorreta == true) {  // Usuário errado e senha correta

        request->send(200, "text/plain", "NOT_AUTHENTICATED");                                                                                  // Envio da resposta de não autenticado para o cliente
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Falha de Login", "Desconhecido tentou efetuar login com usuário inexistente", "-");  // Registro da tentativa de login com usuário inexistente no arquivo de log

      } else {  // Usuário e senha errada

        Serial.println("NOT_AUTHENTICATED");                                                                                                       // imprime a mensagem no monitor serial
        request->send(200, "text/plain", "NOT_AUTHENTICATED");                                                                                     // envia a mensagem "NOT_AUTHENTICATED" na resposta da solicitação
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Falha de Login", "Desconhecido tentou efetuar login com credenciais incorretas", "-");  // adiciona uma entrada ao arquivo de log com a data/hora atuais, tipo de evento e detalhes
      }

    } else {  // Caso a requisição não possua os parâmetros HTTP POST 'user' (nome de usuário) e 'pass' (senha)

      Serial.print("Os parâmetro user e pass  não existem nesta requisição\n");  // Imprime mensagem de erro na serial
      request->send(SPIFFS, "/login.html");                                      // Envia a página de login para o cliente, pois nenhum dos casos acima corresponderam ao caso atual, o que significa que o usuário está acessando a página de login

      // Cria uma descrição do acesso à página de login, para o logger
      String descricao = "A Página de Login (http://";
      descricao += request->host();
      descricao += request->url();
      descricao += ") foi acessada";

      appendLineLogToFile(getHorarioAtualStr(timeinfo), "Acesso à página", descricao, "-");  // Adiciona uma entrada de log com a descrição do acesso à página de login
    }
  }
}

Tornar acesso de página controlado por Cookies

Para controlar o acesso de uma página, onde previamente o usuário realizou login, através de cookies e também controlar o acesso por categorias, deve-se seguir um passo a passo:

  • 1. obter o valor do cookie de login (chamado de ‘key’) do usuário;
  • 2. verificar se tem algum valor correspondente ao valor do cookie de login na memória RAM do ESP32;
    • 2.1 se tiver um valor correspondente: obtém o id do usuário e executa os passos abaixo;
      • 2.1.1 verifica se o usuário possui a categoria desejada (administrador, avançado ou comum. Depende de que página o usuário está fazendo login);
        • 2.1.1.1 se o usuário for da categoria desejada, envia a página HTML ao usuário;
        • 2.1.1.2 se o usuário não for da categoria desejada, envia a página de erro 403.html ao usuário indicando acesso não autorizado;
    • 2.2 se não tiver nenhum valor correspondente: redireciona o usuário para a página de login.

Como exemplo, utilizamos a página /avd/home.html — que é controlada pela função void pageAvdHome(AsyncWebServerRequest *request) — para demonstrar o uso de cookies para controle de acesso à uma página web:

/**
  Função responsável por verificar se o usuário tem permissão para acessar a página home de categoria Avançado e gerar logs de acesso e erro.
*/
void pageAvdHome(AsyncWebServerRequest *request) {

  // Verifica se há um cookie de autenticação na requisição
  String cookieKey = "";
  if (request->hasHeader("Cookie")) {

    // Obtém o valor do header Cookie da requisição
    AsyncWebHeader *h = request->getHeader("Cookie");
    String cookies = h->value().c_str();

    int keyIndex = cookies.indexOf("key=");  // Encontra a posição da chave "key" no header Cookie
    if (keyIndex != -1) {                    // Se "key=" for encontrado, extrai o valor da chave do cookie.

      // Obtém a chave de autenticação do cookie
      keyIndex += 4;
      int endIndex = cookies.indexOf(";", keyIndex);
      cookieKey = cookies.substring(keyIndex, endIndex);
    }
  }

  int idUser = getIdByCookieKey(cookieKey);  // Obtém o ID do usuário autenticado pela chave de autenticação

  if (idUser != -1) {  // Se o ID do usuário foi encontrado

    if (auth.categoria[idUser] == userAVD) {  // Verifica se o usuário tem permissão para acessar a página home do AVD

      request->send(SPIFFS, "/avd/home.html");  // Envia a página home para o cliente do tipo avançado

      // Registra o acesso do usuário no arquivo de log
      String descricao = "Usuário acessou a página http://";
      descricao += request->host();
      descricao += request->url();
      String detalhes = "Usuário: ";
      detalhes += auth.users[idUser];
      appendLineLogToFile(getHorarioAtualStr(timeinfo), "Acesso à página", descricao, detalhes);  // Adiciona uma entrada de log ao arquivo

    } else {  // Se o usuário NÃO for um de categoria Avançado

      // Define o link para a página de erro, com o parâmetro HTTP GET 'url-falho' para o link da página atual
      String link = "/403.html?url-falho=http://";
      link += request->host();
      link += request->url();
      request->redirect(link);  // Redireciona o usuário para a página de erro

      // Cria uma descrição para a entrada de log
      String descricao = "Usuário tentou acessar a página http://";
      descricao += request->host();
      descricao += request->url();
      descricao += " sem autorização";

      // Cria um detalhamento para a entrada de log
      String detalhes = "Usuário: ";
      detalhes += auth.users[idUser];
      appendLineLogToFile(getHorarioAtualStr(timeinfo), "Erro", descricao, detalhes);  // Adiciona uma entrada de log ao arquivo
    }
  } else {  // Se não foi possível obter o ID do usuário correspondente ao cookie

    request->redirect("/login.html?dest=avd/home.html");  // Redireciona o usuário para a página de login, com o parâmetro HTTP GET 'dest' para o link da página atual
  }
}

Implementando Sistema de Logs

Sumário

Um sistema de logs é uma ferramenta importante para qualquer aplicação ou site, permitindo que os desenvolvedores e administradores monitorem a atividade do sistema e identifiquem problemas ou erros. Um bom sistema de logs deve ser fácil de usar, eficiente e preciso.

Uma das principais funcionalidades de um sistema de logs é registrar todas as atividades do usuário, desde solicitações de acesso até erros de sistema. Isso permite que os desenvolvedores identifiquem problemas e implementem soluções rapidamente.

No caso específico deste projeto, entre muitas outras opções, a decisão foi tomada de armazenar as informações em um arquivo CSV. Essa escolha se deve principalmente à sua facilidade de leitura e escrita, tornando o arquivo CSV uma opção adequada para lidar com grandes quantidades de dados de forma simples e eficiente.

É importante lembrar que a segurança das informações armazenadas em um arquivo CSV deve ser levada em consideração. Neste caso específico deste projeto, é garantido que apenas usuários da categoria Administrador tenham acesso a essas informações, além disso estes usuários devem estar logados.

Para a implementação do sistema de logs deste artigo, foi escolhido os seguintes eventos para serem registrados, com cada um contendo Nome do Evento, Descrição e Detalhes, além de horário e data em todos eventos.

  • Autenticação: quando um usuário efetua login ou logout no sistema;
    • Usuário efetuou login no sistema;
      • Detalhes: Usuário: [USER].
    • Usuário efetuou logout no sistema.
      • Detalhes: Tempo de Sessão: [TEMPO_SESSAO] | Usuário: [USER].
  • Falha de Login: quando um usuário desconhecido tenta efetuar login com um usuário, senha ou credenciais incorretas;
    • Desconhecido tentou efetuar login com usuário inexistente;
      • Detalhes: -.
    • Desconhecido tentou efetuar login com senha inexistente;
      • Detalhes: Tentado no usuário: [USER].
    • Desconhecido tentou efetuar login com credenciais incorretas.
      • Detalhes: -.
  • Acesso à página: quando um usuário acessa qualquer página do site;
    • A Página de Login (http://[HOST+URL]) foi acessada;
      • Detalhes: -.
    • Usuário acessou a página http://[HOST+URL].
      • Detalhes: Usuário: [USER].
  • Erro: quando um usuário tenta acessar uma página inexistente ou sem autorização.
    • Usuário tentou acessar a página http://[HOST+URL] sem autorização;
      • Detalhes: Usuário: [USER].
    • Usuário tentou acessar a página inexistente http://[HOST+URL];
      • Detalhes: Usuário: [USER].
    • Desconhecido tentou acessar a página inexistente http://[HOST+URL].
      • Detalhes: -.

Para registrar o horário no sistema de logs, é feita a obtenção do horário através do protocolo NTP no servidor time.google.com. Para prevenir impossibilidade de obtenção de horário por conta de queda de internet da rede WiFi, é aconselhável utilizar integradamente um módulo RTC (como por exemplo o Real Time Clock RTC DS3231) para manter o horário mesmo sem internet, onde neste caso o horário do servidor NTP serviria para manter atualizado o horário do módulo RTC. Mais detalhes do servidor NTP Google, acesse developers.google.com/time.

Dessa forma, é possível manter um histórico detalhado das atividades dos usuários no sistema. Isso pode ser útil para detectar possíveis falhas de segurança, identificar padrões de uso e monitorar o desempenho do sistema.

Criando as configurações do cliente NTP

Para se conectar no servidor NTP Google para obtenção do horário atual, precisamos criar algumas configurações de conexão com o servidor de tempo:

  • const char* ntpServer = "time.google.com";: Isso cria uma constante chamada ntpServer que armazena o endereço do servidor NTP a ser usado. Neste caso, o servidor é o “time.google.com”.
  • const long gmtOffset_sec = -3 * 3600;: Isso cria uma constante chamada gmtOffset_sec, que especifica o deslocamento em segundos (por isso é multiplicado por 3600) entre o fuso horário local e o Tempo Universal Coordenado (UTC/GMT). Neste caso, a configuração é para um deslocamento de -3 horas, ou seja, UTC-3.
  • const int daylightOffset_sec = 0;: Isso cria uma constante chamada daylightOffset_sec, que especifica o deslocamento em segundos para o horário de verão. Neste caso, a configuração é para não ter deslocamento para o horário de verão.
  • struct tm timeinfo;: Isso cria uma estrutura chamada timeinfo que armazenará dados de hora, minuto, segundo, dia, mês, ano, etc. obtidos do servidor NTP.
const char* ntpServer = "time.google.com";  // endereço servidor
const long gmtOffset_sec = -3 * 3600;       // define o deslocamento em segundos entre o fuso horário local e o GMT
const int daylightOffset_sec = 0;           // define o deslocamento em 0 segundos para o horário de verão
struct tm timeinfo;                         // estrutura armazenará dados de hora, minuto, segundo, etc. atuais obtidos do servidor NTP

Função de Anexação de Linha de Log ao Registro

Para anexar uma linha de registro ao arquivo de log CSV, é utilizada a função chamada appendLineLogToFile.

A função recebe quatro argumentos do tipo String: dataHora, nomeEvento, descricao e detalhes.

Dentro da função, uma nova linha é construída concatenando as informações fornecidas. Em seguida, o arquivo de log é aberto no modo de anexação (FILE_APPEND) e é armazenado na variável file. Em caso de falha na abertura do arquivo, a função exibe uma mensagem de erro na porta serial.

Se a abertura do arquivo for bem-sucedida, a nova linha é escrita no arquivo usando o método print() da classe File. Se a escrita for bem-sucedida, a função exibe uma mensagem de sucesso na porta serial. Se houver falha na escrita, a função exibe uma mensagem de erro na porta serial.

Finalmente, o arquivo de log é fechado usando o método close() da classe File.

// Função para anexar uma linha de registro a um arquivo de log CSV
void appendLineLogToFile(String dataHora, String nomeEvento, String descricao, String detalhes) {
  // Imprime uma mensagem na serial para indicar que a função está sendo executada
  Serial.println("Anexando conteúdo ao arquivo '/adm/log.csv': ");

  // Cria uma string com a nova linha a ser adicionada ao arquivo
  String line = "\r\n";
  line += dataHora;
  line += '\t';
  line += nomeEvento;
  line += '\t';
  line += descricao;
  line += '\t';
  line += detalhes;

  // Abre o arquivo de log no modo anexar e armazena o retorno em uma variável do tipo File
  File file = SPIFFS.open("/adm/log.csv", FILE_APPEND);

  // Verifica se houve erro ao abrir o arquivo
  if (!file) {
    // informa ao usuário que houve falhas
    Serial.println(" - falha ao abrir o arquivo de log para anexar");
  }

  // Escreve a nova linha no arquivo de log
  if (file.print(line)) {
    /// Se a escrita for bem sucedida, informa o usuário na serial
    Serial.println(" - mensagem anexada");
  } else {
    // Se houver erro ao escrever a linha no arquivo, informa o usuário na serial
    Serial.println(" - Falha ao anexar");
  }
  file.close();  // Fecha o arquivo de log
}

Função de conversão de segundos para formato HH:MM:SS

Para anexar uma linha de registro ao arquivo de log CSV, para disponibilizar a API de horário atual para o cliente (navegador) ou até mesmo para disponibilizar a lista de usuários ativos incluindo o horário da ocorrência do login é necessário converter uma estrutura de tempo (estrutura tm) para uma String. Para isso é utilizado a função getHorarioAtualStr.

A função, que recebe uma estrutura tm contendo informações de data e hora como parâmetro (_timeinfo), começa criando uma string vazia chamada horarioFormatado. Em seguida, a função adiciona cada parte do horário à string formatada usando operadores ternários para adicionar um zero à esquerda quando necessário.

A primeira parte adiciona o dia no formato dd, verificando se o dia é menor que 10 e adicionando um zero à esquerda, se necessário. Em seguida, é adicionada uma barra (/) para separar o dia do mês.

A segunda parte adiciona o mês no formato mm, novamente verificando se é menor que 10 e adicionando um zero à esquerda, se necessário. É adicionada outra barra para separar o mês do ano.

A terceira parte adiciona o ano no formato aaaa. Como a variável _timeinfo.tm_year armazena a quantidade de anos desde 1900, é adicionado o valor inteiro 1900 à variável. Por exemplo:  se o valor de _timeinfo.tm_year for 0, o ano atual é 1900; se for 123, o ano atual é 2023, e assim por diante.

Entre a terceira e quarta parte, é adicionado um espaço em branco para separar a data da hora.

A quarta parte adiciona a hora no formato hh, verificando se a hora é menor que 10 e adicionando um zero à esquerda, se necessário. É adicionado um sinal de dois pontos (:) para separar a hora dos minutos.

A quinta parte adiciona os minutos no formato mm, novamente verificando se é menor que 10 e adicionando um zero à esquerda, se necessário. É adicionado outro sinal de dois pontos para separar os minutos dos segundos.

Por fim, a última parte adiciona os segundos no formato ss, verificando se é menor que 10 e adicionando um zero à esquerda, se necessário.

A função retorna a string formatada contendo a data e hora obtidas da estrutura tm.

// A função abaixo recebe uma struct tm com informações de data e hora e retorna uma string formatada para exibição.
String getHorarioAtualStr(tm _timeinfo) {
  String horarioFormatado = "";
  horarioFormatado += _timeinfo.tm_mday < 10 ? "0" + String(_timeinfo.tm_mday) : _timeinfo.tm_mday;  // Adiciona o dia no formato dd.
  horarioFormatado += "/";
  horarioFormatado += _timeinfo.tm_mon + 1 < 10 ? "0" + String(_timeinfo.tm_mon + 1) : _timeinfo.tm_mon + 1;  // Adiciona o mês no formato mm.
  horarioFormatado += "/";
  horarioFormatado += _timeinfo.tm_year + 1900;  // Adiciona o ano no formato aaaa.
  horarioFormatado += " ";
  horarioFormatado += _timeinfo.tm_hour < 10 ? "0" + String(_timeinfo.tm_hour) : _timeinfo.tm_hour;  // Adiciona a hora no formato hh.
  horarioFormatado += ":";
  horarioFormatado += _timeinfo.tm_min < 10 ? "0" + String(_timeinfo.tm_min) : _timeinfo.tm_min;  // Adiciona o minuto no formato mm.
  horarioFormatado += ":";
  horarioFormatado += _timeinfo.tm_sec < 10 ? "0" + String(_timeinfo.tm_sec) : _timeinfo.tm_sec;  // Adiciona o segundo no formato ss.

  // Retorna a string formatada.
  return horarioFormatado;
}

Função de cálculo de diferença de tempo

Para anexar uma linha de registro ao arquivo de log CSV quando o evento “Usuário efetuou logout no sistema” acontecer ou para disponibilizar a lista de usuários ativos incluindo o Tempo de Login (HH:MM:SS) é necessário calcular a diferença de tempo em segundos entre duas estruturas de tempo (estruturas tm) . Para isso é utilizado a função diff_tm.

A função diff_tm recebe dois parâmetros, o tm1 e o tm2, onde irá ser feito o cálculo da diferença entre tm2 e tm1. A função retorna o resultado da diferença de tempo em uma variável do tipo double.

No início da função é feita a conversão das duas estruturas tm (tm1 e tm2) para o tipo time_t utilizando a função mktime, que converte uma estrutura tm para o tempo correspondente em segundos desde 1º de janeiro de 1970.

Em seguida, a função utiliza a função difftime para calcular a diferença de tempo em segundos entre os dois tempos time1 e time2. A função difftime é nativa da linguagem C e recebe dois argumentos do tipo time_t e retorna a diferença entre eles em segundos.

Por fim, a função diff_tm retorna o resultado da diferença de tempo em segundos como um valor do tipo double.

// Função que calcula a diferença de tempo em segundos entre duas estruturas tm
double diff_tm(struct tm tm1, struct tm tm2) {
  // Converte cada estrutura tm para o tipo time_t
  time_t time1 = mktime(&tm1);
  time_t time2 = mktime(&tm2);

  // Calcula a diferença de tempo em segundos usando a função difftime
  double diff = difftime(time2, time1);

  // Retorna o resultado da diferença de tempo
  return diff;
}

Definindo as configurações do cliente NTP

A seguinte instrução passa as configurações do cliente NTP para o ESP32, que irá possibilitar a sincronização da hora e da data do dispositivo com um servidor NTP para garantir que estejam sempre corretas e atualizadas.

// Configura a hora e a data do dispositivo usando um servidor NTP (Network Time Protocol)
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

Configurando rota de URL para o arquivo de log

Para poder acessar o arquivo de log através do navegador, é necessário configurar uma rota no servidor web para o arquivo de log.

Esta rota no servidor web é manipulada pelo objeto server , que é associada com a função fileLog que lida com as solicitações HTTP.

A rota definida é "/adm/log.csv" e o tipo de solicitação HTTP definido é HTTP_GET, indicando que a solicitação é para obter o conteúdo do arquivo log.csv. O parâmetro final fileLog é a função que será chamada quando a rota for acessada.

server.on("/adm/log.csv", HTTP_GET, fileLog);

Obtenção de horário no servidor NTP

Para manter o horário da estrutura timeinfo sempre atualizado, é necessário criar uma rotina dentro do void loop para obtenção do horário no servidor NTP que irá ser executada a cada 1 segundo (1000 milissegundos). Mas como na primeira execução do sketch do ESP32 não temos o horário ainda obtido (pois o void loop ainda não foi executado), e se o servidor HTTP fosse acessado antes de ter o horário obtido, o horário exibido ao usuário poderia ser mostrado incorretamente. Para corrigir isso, é feito no void setup, antes de iniciar o servidor HTTP (com server.begin), a execução de uma rotina para obter o horário no servidor NTP.

A função getLocalTime() é responsável por atualizar a estrutura timeinfo com as informações de data e hora obtidas do servidor NTP. Caso não seja possível obter o horário, o programa fica tentando obter horário até conseguir sem prosseguir adiante.

// Loop "while" que aguarda a obtenção do horário de um servidor NTP
while (!getLocalTime(&timeinfo)) {

  // Imprime uma mensagem de erro na porta serial caso a obtenção do horário falhe
  Serial.println("Deu erro na obtenção de horário NTP, função obrigatória no sistema. O sistema ficará esperando a operação ser bem sucedida...");

  // Espera 1 segundo antes de tentar novamente
  delay(1000);
}

Dentro do void loop, é feita a rotina para obtenção do horário no servidor NTP que irá ser executada a cada 1 segundo (1000 milissegundos). A função getLocalTime() é responsável por atualizar a estrutura timeinfo com as informações de data e hora obtidas do servidor NTP. Se a função não obtém com sucesso essas informações, a mensagem de erro “Falha para obter o horário do servidor NTP” é exibida na porta serial, indicando que o dispositivo não conseguiu sincronizar o horário com o servidor NTP.

Nota: É importante garantir que a conexão com o servidor NTP esteja funcionando corretamente para garantir que o horário do dispositivo esteja correto e preciso, isto inclui ter internet na conexão WiFi ao qual o ESP32 foi configurado para se conectar.

if (!getLocalTime(&timeinfo)) {  // Verifica se foi possível obter o horário do servidor NTP local

  // Se não foi possível obter o horário, imprime uma mensagem de erro
  Serial.println("Falha para obter o horário do servidor NTP");
}

Processo de acesso ao arquivo de logs

Para disponibilizar o acesso do arquivo de logs pelo navegador, é utilizada a função fileLog. Esta função é chamada sempre quando a rota "/adm/log.csv" é acessada. Ela verifica se o usuário está autorizado a acessar o arquivo de log e, se sim, envia o arquivo de log em formato CSV para o cliente. Caso contrário, o usuário é redirecionado para a página de erro 403 (Acesso Negado) e o acesso negado é registrado no arquivo de log.

A função começa verificando se o usuário está logado e tem permissão para acessar o arquivo de log. Isso é feito verificando se a solicitação contém um cookie com a chave "key", que é usada para autenticar o usuário. Se contém, o valor do cookie é armazenado na variável cookieKey. Então é chamada a função getIdByCookieKey para obter o ID do usuário a partir do valor do cookieKey (sendo válido ou não).

Se o usuário estiver logado, a função verifica se o usuário é um administrador. Se o usuário for um administrador, o arquivo de log é enviado ao cliente em formato CSV. Caso contrário, o usuário é redirecionado para a página de erro 403 e o acesso negado é registrado no arquivo de log.

Se o usuário não estiver logado, a função redireciona o usuário para a página de login com a URL de destino definida como "/adm/log.csv". Isso significa que, após o login bem-sucedido, o usuário será redirecionado para o arquivo de log. A função também registra a tentativa de acesso sem autorização no arquivo de log.

/**
  Função para exibir o arquivo de log de eventos.
  Esta função recebe uma requisição de página e verifica se o usuário está autorizado a acessar o arquivo de log.
  Se o usuário estiver autorizado, o arquivo de log é enviado ao cliente em formato CSV.
  Caso contrário, o usuário é redirecionado para a página de erro 403 (Acesso Negado) e é registrado o acesso negado no arquivo de log.
  Se o usuário não estiver logado, ele é redirecionado para a página de login.
*/
void fileLog(AsyncWebServerRequest *request) {

  // Verifica se o usuário está logado e tem permissão para acessar o arquivo de log.
  String cookieKey = "";                                  // Define uma string vazia para armazenar o valor do cookieKey
  if (request->hasHeader("Cookie")) {                     // Verifica se a requisição possui o header "Cookie"
    AsyncWebHeader *h = request->getHeader("Cookie");     // Obtém o header "Cookie" da requisição
    String cookies = h->value().c_str();                  // Obtém o valor do header "Cookie" como uma string
    int keyIndex = cookies.indexOf("key=");               // Obtém a posição inicial da string "key=" no valor do header "Cookie"
    if (keyIndex != -1) {                                 // Verifica se a string "key=" foi encontrada no valor do header "Cookie"
      keyIndex += 4;                                      // Adiciona 4 à posição da string "key=" para obter a posição inicial do valor do cookieKey
      int endIndex = cookies.indexOf(";", keyIndex);      // Obtém a posição final do valor do cookieKey na string de cookies
      cookieKey = cookies.substring(keyIndex, endIndex);  // Obtém o valor do cookieKey
    }
  }

  int idUser = getIdByCookieKey(cookieKey);   // Obtém o id do usuário a partir do valor do cookieKey
  if (idUser != -1) {                         // Verifica se o usuário está logado
    if (auth.categoria[idUser] == userADM) {  // Se o usuário for um administrador, envia o arquivo de log ao cliente.
      request->send(SPIFFS, "/adm/log.csv", "text/csv");
    } else {  // Se não, direciona o cliente para a página de erro 403 e registra no logger.

      String link = "/403.html?url-falho=http://";  // Cria um link de redirecionamento para a página de erro 403.
      link += request->host();                      // Adiciona o host ao endereço.
      link += request->url();                       // Adiciona a URL da página.
      request->redirect(link);                      // Redireciona o cliente para a página de erro 403.

      String descricao = "Usuário tentou acessar a página http://";  // Cria uma string que será utilizada como descrição no arquivo de log. A string indica que o usuário tentou acessar uma página sem autorização.
      descricao += request->host();                                  // Adiciona o host ao endereço da página.
      descricao += request->url();                                   // Adiciona a URL da página.
      descricao += " sem autorização";                               // Adiciona uma mensagem indicando que o acesso foi sem autorização.

      String detalhes = "Usuário: ";   // Cria uma string que será utilizada como detalhes no arquivo de log. A string contém o nome do usuário que tentou acessar a página.
      detalhes += auth.users[idUser];  // Adiciona o nome do usuário.

      appendLineLogToFile(getHorarioAtualStr(timeinfo), "Erro", descricao, detalhes);  // Adiciona uma nova entrada no arquivo de log com as informações obtidas acima.
    }
  } else {                                              // Se o usuário não está logado, redireciona para a página de login com destino para o arquivo de log.
    request->redirect("/login.html?dest=adm/log.csv");  // Redireciona o usuário para a página de login, com a URL de destino sendo o arquivo de log
  }
}

Registrar log ao ser feita alguma ação no servidor

Neste subtópico será mostrado todos os 10 diferentes tipos de logs que há no programa, registrados através da função appendLineLogToFile().

  • Autenticação: quando um usuário efetua login ou logout no sistema;
    • 1.Usuário efetuou login no sistema;
      • Na função void pageLogin(AsyncWebServerRequest *request), que é executada sempre quando a rota "/login.html" é acessada, é anexado ao arquivo de logs quando o usuário for autenticado com sucesso através de usuário e senha corretos.
        // Registrar o login no arquivo de log
        String detalhes = "Usuário: ";                                                                                    // é criada uma string para armazenar informações sobre o usuário que efetuou o login
        detalhes += auth.users[id];                                                                                       // o nome do usuário é adicionado à string
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Autenticação", "Usuário efetuou login no sistema", detalhes);  // é adicionada uma nova linha ao arquivo de log do sistema, que registra o horário atual, a categoria "Autenticação", uma mensagem indicando que o usuário efetuou login no sistema e os detalhes da ação, que foram armazenados na string detalhes
    • 2.Usuário efetuou logout no sistema.
      • Na função void pageLogin(AsyncWebServerRequest *request), que é executada sempre quando a rota "/login.html" é acessada, é anexado ao arquivo de logs quando o usuário faz logout.
        // Registra o logout no arquivo de log
        String detalhes = "Tempo de Sessão: ";                                  // Cria uma string para armazenar os detalhes do logout
        detalhes += secondsToHMS(diff_tm(auth.timeOfLogin[idUser], timeinfo));  // Obtém o tempo de sessão do usuário que está fazendo logout e converte para o formato HH:MM:SS
        detalhes += " | Usuário: ";
        detalhes += auth.users[idUser];  // Adiciona o nome de usuário que está fazendo logout aos detalhes
        
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Autenticação", "Usuário efetuou logout no sistema", detalhes);  // Registra o logout no arquivo de log com as informações de data/hora atual, tipo de log "Autenticação",
                                                                                                                           // a ação "Usuário efetuou logout no sistema" e os detalhes do logout (tempo de sessão e nome do usuário)

         

  • Falha de Login: quando um usuário desconhecido tenta efetuar login com um usuário, senha ou credenciais incorretas;
    • 3.Desconhecido tentou efetuar login com usuário inexistente;
      • Na função void pageLogin(AsyncWebServerRequest *request), que é executada sempre quando a rota "/login.html" é acessada, é anexado ao arquivo de logs quando um desconhecido tenta fazer login com um nome de usuário inválido e com uma senha correta.
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Falha de Login", "Desconhecido tentou efetuar login com usuário inexistente", "-");  // Registro da tentativa de login com usuário inexistente no arquivo de log
    • 4.Desconhecido tentou efetuar login com senha inexistente;
      • Na função void pageLogin(AsyncWebServerRequest *request), que é executada sempre quando a rota "/login.html" é acessada, é anexado ao arquivo de logs quando um desconhecido tenta fazer login com um nome de usuário correto e com uma senha inválida.
        String detalhes = "Tentado no Usuário: ";                                                                                                  // cria uma string "detalhes" para registrar informações sobre o usuário que tentou efetuar o login
        detalhes += auth.users[id];                                                                                                                // indica para qual usuário foi tentado fazer login
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Falha de Login", "Desconhecido tentou efetuar login com senha inexistente", detalhes);  // adiciona uma entrada ao arquivo de log do sistema, indicando que houve uma falha de login devido à senha incorreta, incluindo as informações de horário atual, a categoria "Falha de Login", uma mensagem indicando que a tentativa de login falhou porque a senha estava incorreta e os detalhes da açõa (que foram armazenados na string detalhes)
    • 5.Desconhecido tentou efetuar login com credenciais incorretas.
      • Na função void pageLogin(AsyncWebServerRequest *request), que é executada sempre quando a rota "/login.html" é acessada, é anexado ao arquivo de logs quando um desconhecido tenta fazer login com um nome de usuário inválido e com uma senha inválida.
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Falha de Login", "Desconhecido tentou efetuar login com credenciais incorretas", "-");  // adiciona uma entrada ao arquivo de log com a data/hora atuais, tipo de evento e detalhes

 

  • Acesso à página: quando um usuário acessa qualquer página do site;
    • 6.A Página de Login (http://[HOST+URL]) foi acessada;
      • Na função void pageLogin(AsyncWebServerRequest *request), que é executada sempre quando a rota "/login.html" é acessada, é anexado ao arquivo de logs quando o servidor envia a página “/login.html” da SPIFFS para o cliente (sem conter os parâmetros HTTP POST ‘user’ e ‘pass’).
        // Cria uma descrição do acesso à página de login, para o logger
        String descricao = "A Página de Login (http://";
        descricao += request->host();
        descricao += request->url();
        descricao += ") foi acessada";
        
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Acesso à página", descricao, "-");  // Adiciona uma entrada de log com a descrição do acesso à página de login
    • 7.Usuário acessou a página http://[HOST+URL].
      • Várias funções tem este evento. Como exemplo, escolhemos a função void pageAdmFerramentas(AsyncWebServerRequest *request). Nesta função, que é executada sempre quando a rota "/adm/ferramentas.html" é acessada, é anexado ao arquivo de logs quando o servidor envia a página "/adm/ferramentas.html" da SPIFFS para o cliente.
        // Cria uma descrição para a entrada de log
        String descricao = "Usuário acessou a página http://";
        descricao += request->host();
        descricao += request->url();
        
        // Cria um detalhamento para a entrada de log
        String detalhes = "Usuário: ";
        detalhes += auth.users[idUser];
        
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Acesso à página", descricao, detalhes);  // Adiciona uma entrada de log ao arquivo

 

  • Erro: quando um usuário tenta acessar uma página inexistente ou sem autorização.
    • 8.Usuário tentou acessar a página http://[HOST+URL] sem autorização;
      • Várias funções tem este evento. Como exemplo, escolhemos a função void pageAdmFerramentas(AsyncWebServerRequest *request). Nesta função, que é executada sempre quando a rota "/adm/ferramentas.html" é acessada, é anexado ao arquivo de logs quando um usuário de categoria diferente da categoria vinculada ao cookie (por exemplo, quando um usuário de categoria comum tenta acessar uma página que só permite o acesso de usuários de categoria administrador) tenta acessar a página.
        // Cria uma descrição para a entrada de log
        String descricao = "Usuário tentou acessar a página http://";
        descricao += request->host();
        descricao += request->url();
        descricao += " sem autorização";
        
        // Cria um detalhamento para a entrada de log
        String detalhes = "Usuário: ";
        detalhes += auth.users[idUser];
        
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Erro", descricao, detalhes);  // Adiciona uma entrada de log ao arquivo
    • 9.Usuário tentou acessar a página inexistente http://[HOST+URL]; e 10.Desconhecido tentou acessar a página inexistente http://[HOST+URL]
      • Na função void page404(AsyncWebServerRequest *request), que é executada sempre quando uma rota não encontrada é acessada, é anexado ao arquivo de logs quando o usuário logado ou um desconhecido tenta acessar alguma página inexistente.
        // Verifica se o valor do cookie é válido e, se for, registra informações sobre a solicitação no arquivo de log.
        int idUser = getIdByCookieKey(cookieKey);
        if (idUser != -1) {
          descricao = "Usuário tentou acessar a página inexistente http://";
          descricao += request->host();
          descricao += request->url();
        
          detalhes = "Usuário: ";
          detalhes += auth.users[idUser];
        } else {
          // Se o valor do cookie for inválido ou não existir, registra informações sobre a solicitação no arquivo de log sem informações sobre o usuário.
          descricao = "Desconhecido tentou acessar a página inexistente http://";
          descricao += request->host();
          descricao += request->url();
        
          detalhes = "-";
        }
        
        // Registra informações sobre a solicitação no arquivo de log, incluindo a data/hora atual, o tipo de erro, uma descrição e detalhes adicionais.
        appendLineLogToFile(getHorarioAtualStr(timeinfo), "Erro", descricao, detalhes);

Exibir os logs ao Administrador

As explicações da exibição dos logs para o usuário do tipo Administrador podem ser vistas no tópico Implementando as Páginas Web, mas especificamente nos subtópicos:


Outros Detalhes do Software

Sumário

Bibliotecas utilizadas

No arquivo software.ino, é feita a importação das bibliotecas necessárias para o projeto:

  • WiFi.h: Biblioteca que permite a configuração e o gerenciamento da conexão Wi-Fi do dispositivo.
  • AsyncTCP.h: Biblioteca que fornece funcionalidades de comunicação TCP assíncrona, permitindo a criação de conexões de rede assíncronas.
  • ESPAsyncWebServer.h: Biblioteca que permite a criação de um servidor web assíncrono no ESP32, oferecendo recursos para tratar solicitações e fornecer respostas HTTP.
  • FS.h: Biblioteca que gerencia o sistema de arquivos do ESP32, permitindo operações de leitura, gravação, exclusão e manipulação de arquivos.
  • SPIFFS.h: Biblioteca que fornece acesso ao sistema de arquivos SPIFFS (SPI Flash File System), uma opção de armazenamento em flash disponível no ESP32.
  • Preferences.h: Biblioteca que permite armazenar e recuperar preferências do usuário no ESP32, como configurações personalizadas.
  • time.h: Biblioteca que fornece funções para manipulação e obtenção de informações de data e hora, incluindo sincronização com servidores NTP (Network Time Protocol).
  • credenciais.h: Arquivo de cabeçalho personalizado que contém as credenciais de acesso ao Wi-Fi, como SSID e senha. Essas credenciais estão definidas nesse arquivo externo para manter as informações sensíveis separadas do código principal.
// Importando bibliotecas necessárias para o projeto
#include <WiFi.h>               // Biblioteca para configuração da conexão WiFi
#include <AsyncTCP.h>           // Biblioteca para comunicação TCP assíncrona
#include <ESPAsyncWebServer.h>  // Biblioteca para criação do servidor web assíncrono
#include <FS.h>                 // Biblioteca para gerenciamento do sistema de arquivos do ESP32
#include <SPIFFS.h>             // Biblioteca para uso do sistema de arquivos SPIFFS
#include <Preferences.h>        // Biblioteca para armazenamento de preferências do usuário
#include <time.h>               // Biblioteca para acesso ao servidor NTP
#include "credenciais.h"        // Importando credenciais de acesso ao WiFi

Objetos globais necessários das bibliotecas

No arquivo software.ino, é feita a instanciação dos objetos globais das classes necessárias para o projeto:

O trecho de código AsyncWebServer server(80) cria um objeto da classe AsyncWebServer que será utilizado para criar e gerenciar um servidor web assíncrono na porta 80. A porta 80 é a porta padrão para servidores HTTP.

Já o trecho de código Preferences preferences cria um objeto da classe Preferences que será utilizado para armazenar para armazenar as informações de cada usuário. A classe Preferences permite que essas informações sejam armazenadas de forma persistente, mesmo após o reinício do dispositivo.

AsyncWebServer server(80);  // Criando um objeto de servidor web assíncrono na porta 80
Preferences preferences;    // Criando um objeto para armazenar informações de cada usuário

Função secondsToHMS

No arquivo software.ino, está a função secondsToHMS:

A função chamada secondsToHMS converte um valor inteiro de segundos em uma string no formato “HH:MM:SS”, onde HH representa as horas, MM representa os minutos e SS representa os segundos.

Primeiro, a função divide o valor de segundos por 3600 para obter as horas, armazenando o resultado em uma variável inteira chamada hours. Em seguida, calcula os minutos dividindo o resto dos segundos por 3600 e dividindo o resultado por 60, armazenando o resultado em uma variável inteira chamada minutes. O valor restante dos segundos é armazenado em uma variável inteira chamada remainingSeconds.

A seguir, a função formata as horas, minutos e segundos com 2 dígitos, adicionando um zero à esquerda, se necessário. Isso é feito utilizando a função sprintf(), que permite a formatação de uma string com base em um modelo, no caso "%02d" (que indica um número inteiro com 2 dígitos, com um zero à esquerda, se necessário). As horas, minutos e segundos formatados são armazenados em variáveis do tipo char.

Finalmente, a função concatena as strings formatadas usando o operador + e retorna a string resultante.

// Converte um número de segundos para uma string no formato "HH:MM:SS"
String secondsToHMS(int seconds) {
  int hours = seconds / 3600;           // Calcula as horas dividindo os segundos por 3600
  int minutes = (seconds % 3600) / 60;  // Calcula os minutos dividindo o resto dos segundos dividido por 3600 e divindo o resultado por 60
  int remainingSeconds = seconds % 60;  // Calcula os segundos restantes

  // Formata as horas, minutos e segundos com 2 dígitos, adicionando um 0 à esquerda se necessário
  char formattedHours[3];
  sprintf(formattedHours, "%02d", hours);
  char formattedMinutes[3];
  sprintf(formattedMinutes, "%02d", minutes);
  char formattedSeconds[3];
  sprintf(formattedSeconds, "%02d", remainingSeconds);

  // Concatena as strings formatadas para obter a hora no formato "HH:MM:SS" e retorna a string result
  String result = String(formattedHours) + ":" + String(formattedMinutes) + ":" + String(formattedSeconds);
  return result;
}

Função setup

Aqui será explicado os principais pontos que há na função setup:

Definição das credenciais dos usuários (caso seja a primeira execução do programa)

Caso o sketch do projeto nunca tenha sido executada pelo menos uma vez no ESP32, é definido as credenciais dos usuários. Para isso, é verificado se o ESP32 já foi iniciado alguma vez com este firmware. Se não tiver sido iniciado antes, ele armazena a informação sobre a inicialização do ESP32 em uma variável booleana e registra algumas credenciais de demonstração no armazenamento interno e reinicia o ESP32.

Para verificar se o ESP32 já foi iniciado, é acessado o armazenamento interno para verificar se a informação sobre a inicialização do ESP32 já foi armazenada. Se não foi iniciado antes, a variável booleana jaIniciado é definida como false e o código entra na condição if (!jaIniciado).

Dentro desta condição, o código armazena a informação de que o ESP32 já foi iniciado em jaIniciado e registra algumas credenciais de demonstração no armazenamento interno. Ele começa iniciando o armazenamento interno na namespace "credenciais" no modo de leitura/gravação e, em seguida, atribui valores de nome de usuário, senha e categoria aos arrays auth.users, auth.pass e auth.categoria, respectivamente.

Em seguida, ele usa um loop while para adicionar os usuários, senhas e categorias às strings formatadas usersFormatado, passFormatado e categoriaFormatado. As informações de autenticação são armazenadas no armazenamento interno usando preferences.putString().

Finalmente, o código encerra a conexão com o armazenamento interno e reinicia o ESP32 usando ESP.restart().

// Se o ESP32 não foi iniciado antes
if (!jaIniciado) {
  Serial.println("Não foi iniciado.");
  Serial.println("Registrando dados de autenticação");

  // Registrando que o ESP32 já foi iniciado alguma vez
  preferences.begin("verificacao", false);  // Iniciando o armazenamento interno na namespace "verificacao" no modo de leitura/gravação.
  preferences.putBool("jaIniciado", true);  // Armazenando que o ESP32 já foi iniciado alguma vez
  preferences.end();                        // Encerrando a conexão com o armazenamento interno


  // Registra algumas credenciais de demonstração
  preferences.begin("credenciais", false);  // Iniciando o armazenamento interno na namespace "credenciais" no modo de leitura/gravação.
  auth.users[0] = "admin";
  auth.pass[0] = "123";
  auth.categoria[0] = 0;

  auth.users[1] = "user2";
  auth.pass[1] = "asd";
  auth.categoria[1] = 1;

  auth.users[2] = "usuario";
  auth.pass[2] = "wsx";
  auth.categoria[2] = 2;

  int i = 0;  // Contador de usuários

  // Strings formatadas para armazenar informações
  String usersFormatado = "";
  String passFormatado = "";
  String categoriaFormatado = "";

  // Enquanto houver usuários
  while (auth.users[i] != "") {
    // Adicionando o usuário na string formatada
    usersFormatado += auth.users[i];
    usersFormatado += "\n";

    // Adicionando a senha na string formatada
    passFormatado += auth.pass[i];
    passFormatado += "\n";

    // Adicionando a categoria na string formatada
    categoriaFormatado += auth.categoria[i];
    categoriaFormatado += "\n";

    // Avançando para o próximo usuário
    i++;
  }

  // Armazenando as informações de autenticação no armazenamento interno
  preferences.putString("users", usersFormatado);
  preferences.putString("pass", passFormatado);
  preferences.putString("categoria", categoriaFormatado);

  // Encerrando a conexão com o armazenamento interno
  preferences.end();

  Serial.println("Fazendo reinício obrigatório");
  delay(100);     // Esperando um curto intervalo de tempo
  ESP.restart();  // Reiniciando o ESP32
}

Inicialização do WiFi

Deve-se conectar o ESP32 na rede WiFi usando as credenciais fornecidas (SSID e senha) no arquivo credenciais.h. Para isso, a função WiFi.disconnect() é chamada para desconectar de qualquer rede WiFi existente. Em seguida, a função WiFi.mode(WIFI_STA) é usada para configurar o modo WiFi para o Station Mode (modo de cliente).

Depois disso, a função WiFi.begin(ssid, password) é usada para tentar se conectar à rede WiFi especificada, onde ssid e password são as variáveis que contêm o nome e a senha da rede WiFi, respectivamente.

A função WiFi.waitForConnectResult() é usada para aguardar o resultado da conexão com a rede WiFi. Se a conexão foi bem-sucedida (ou seja, o ESP32 está conectado à rede WiFi), o resultado da função será WL_CONNECTED.

Se a conexão falhar, o programa imprime uma mensagem de erro na porta serial e reinicia o ESP32 após um atraso de 5 segundos, usando a função ESP.restart().

WiFi.disconnect();           // Desconecta de qualquer rede WiFi existente
WiFi.mode(WIFI_STA);         // Configura o modo WiFi para Station Mode (modo de cliente)
WiFi.begin(ssid, password);  // Tenta conectar-se à rede WiFi especificada usando as credenciais fornecidas (SSID e senha)

// Verifica se a conexão foi bem-sucedida
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
  // Se não foi possível se conectar à rede WiFi, reinicia o ESP32

  Serial.println("WiFi Falhado. Reiniciando em breve...");
  delay(5000);
  ESP.restart();
  return;
}

Inicialização da SPIFFS

Para iniciar o sistema de arquivos SPIFFS é feita a chamada da função begin() da biblioteca SPIFFS para inicializar o sistema de arquivos. Se a inicialização falhar, uma mensagem de erro será impressa no monitor serial e o ESP32 será reiniciado após um atraso de 5 segundos. Se a inicialização for bem-sucedida, o programa continuará sua execução normalmente. O parâmetro true (da função begin()) indica que o sistema de arquivos deve ser formatado se ele ainda não tiver sido formatado anteriormente.

// Inicia o sistema de arquivos SPIFFS
if (!SPIFFS.begin(true)) {
  // Se não foi possível iniciar o sistema de arquivos SPIFFS, reinicia o ESP32

  Serial.println("Ocorreu um erro ao montar SPIFFS. Reiniciando em breve...");
  delay(5000);
  ESP.restart();
  return;
}

Configurando as rotas de URL

O seguinte trecho de código define as rotas de URL para manipular as solicitações HTTP recebidas pelo servidor web embutido. Cada rota é definida usando a função server.on(), que recebe três parâmetros:

  1. A URL que o servidor deve associar à rota;
  2. O método HTTP que a rota deve lidar (HTTP_GET, HTTP_POST ou HTTP_ANY — pode ser GET ou POST);
  3. A função que deve ser chamada quando a URL é acessada.

Por exemplo, server.on("/assets/bootstrap/css/bootstrap.min.css", HTTP_GET, pageBootstrapCss); define a URL "/assets/bootstrap/css/bootstrap.min.css" que o servidor associará à rota, e a função pageBootstrapCss será chamada quando essa rota for acessada através de uma solicitação HTTP GET. Nesta função terá o retorno adequado, seja ele uma página da SPIFFS, um redirecionamento ou um erro HTTP.

Além disso, há um caso específico, que é o da rota /. Nesta rota, o retorno será um redirecionamento para a página "/login.html" quando essa rota for acessada através de uma solicitação HTTP GET.

Há outro caso específico, que é a rota definida usando a função server.onNotFound(), que chama a função page404 sempre que é feita uma tentativa de acesso à uma rota não encontrada.

// Configura rotas de URL para manipular as solicitações HTTP recebidas pelo servidor web embutido
server.on("/assets/bootstrap/css/bootstrap.min.css", HTTP_GET, pageBootstrapCss);
server.on("/assets/bootstrap/js/bootstrap.min.js", HTTP_GET, pageBootstrapJs);
server.on("/assets/js/javascript.js", HTTP_GET, pageJs);
server.on("/assets/js/bs-init.js", HTTP_GET, pageJsBsInit);
server.on("/assets/img/favicon.png", HTTP_GET, favicon);
server.on("/adm/log.csv", HTTP_GET, fileLog);
server.on("/getNomeUser", HTTP_GET, getNomeUser);
server.on("/getPassUser", HTTP_GET, getPassUser);
server.on("/getCateUser", HTTP_GET, getCateUser);
server.on("/getUsuariosAtivos", HTTP_GET, getUsuariosAtivos);
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
  request->redirect("/login.html");
});
server.on("/horario", HTTP_GET, getHorario);
server.on("/login.html", HTTP_ANY, pageLogin);
server.onNotFound(page404);
server.on("/403.html", HTTP_GET, page403);
server.on("/adm/ferramentas.html", HTTP_GET, pageAdmFerramentas);
server.on("/adm/home.html", HTTP_GET, pageAdmHome);
server.on("/adm/minha-conta.html", HTTP_GET, pageAdmMinhaConta);
server.on("/avd/ferramentas.html", HTTP_GET, pageAvdFerramentas);
server.on("/avd/home.html", HTTP_GET, pageAvdHome);
server.on("/avd/minha-conta.html", HTTP_GET, pageAvdMinhaConta);
server.on("/com/ferramentas.html", HTTP_GET, pageComFerramentas);
server.on("/com/home.html", HTTP_GET, pageComHome);
server.on("/com/minha-conta.html", HTTP_GET, pageComMinhaConta);

Iniciando o servidor HTTP

Para iniciar o servidor HTTP, é utilizado o método server.begin(), que é da biblioteca ESPAsyncWebServer. Após chamar esse método, o servidor começa a escutar por solicitações HTTP na porta especificada no momento da criação do objeto AsyncWebServer (neste projeto foi a porta 80).

server.begin();  // Inicia o servidor HTTP

Função getHorario

A função getHorario() é chamada quando a rota "/horario" é acessada via HTTP_GET. Esta rota destina-se à enviar a hora atual para o cliente, desde que o cliente esteja autenticado.

A função começa verificando se a solicitação HTTP contém um cabeçalho "Cookie". Se houver um cabeçalho "Cookie", o valor do cabeçalho é obtido e a função procura a chave "key" no cabeçalho "Cookie". Se a chave "key" for encontrada, a função obtém o valor da chave e armazena-o em uma variável cookieKey.

A função então chama a função getIdByCookieKey para obter o ID do usuário correspondente à chave do cookie armazenado na variável cookieKey. Se o ID do usuário for válido, a função envia a hora atual para o cliente usando a função getHorarioAtualStr, passando timeinfo como argumento. Essa função retorna uma string formatada da hora atual, no formato “HH:MM:SS”. Se o ID do usuário não for válido, a função envia a mensagem "NOT_AUTHENTICATED" como resposta.

/*
  Esta função "getHorario" recebe uma solicitação "AsyncWebServerRequest" do cliente e envia a hora atual,
  mas somente se o cliente estiver autenticado. Se o cliente não estiver autenticado, a resposta será "NOT_AUTHENTICATED".
*/
void getHorario(AsyncWebServerRequest *request) {
  String cookieKey = "";  // Inicializa a variável "cookieKey" como uma String vazia.

  // Verifica se a solicitação HTTP contém um cabeçalho "Cookie".
  if (request->hasHeader("Cookie")) {
    // Se houver um cabeçalho "Cookie", obtém o valor do cabeçalho.
    AsyncWebHeader *h = request->getHeader("Cookie");
    String cookies = h->value().c_str();

    // Procura o valor da chave "key" no cabeçalho "Cookie".
    int keyIndex = cookies.indexOf("key=");
    if (keyIndex != -1) {
      // Se a chave "key" for encontrada, obtém o valor da chave e armazena-o em "cookieKey".
      keyIndex += 4;
      int endIndex = cookies.indexOf(";", keyIndex);
      cookieKey = cookies.substring(keyIndex, endIndex);
    }
  }

  // Obtém o ID do usuário correspondente à chave do cookie "cookieKey".
  int idUser = getIdByCookieKey(cookieKey);

  // Verifica se o ID do usuário é válido.
  if (idUser != -1) {
    // Se o ID do usuário for válido, envia a hora atual para o cliente.
    request->send(200, "text/plain", getHorarioAtualStr(timeinfo));
  } else {
    // Se o ID do usuário não for válido, envia uma resposta de "NÃO AUTENTICADO" para o cliente.
    request->send(200, "text/plain", "NOT_AUTHENTICATED");
  }
}

Função getNomeUser

A função getNomeUser() é chamada quando a rota "/getNomeUser" é acessada via HTTP_GET. Esta rota destina-se à enviar o nome de usuário associado ao cookie recebido na requisição, desde que o cliente esteja autenticado.

A função começa verificando se o cookie "key" está presente na solicitação do cliente e, em seguida, obtém o ID do usuário correspondente a esse cookie usando a função getIdByCookieKey(). Se o ID do usuário for encontrado, o nome do usuário correspondente será enviado em uma resposta HTTP com código 200. Caso contrário, a resposta "NOT_AUTHENTICATED" será enviada.

/**
  Esta função recebe uma requisição de um cliente e verifica se o cookie 'key' está presente na requisição.
  Se o cookie está presente, a função chama a função getIdByCookieKey para obter o id do usuário correspondente.
  Se o id do usuário é encontrado, o nome do usuário correspondente é enviado em um texto simples como resposta da requisição.
  Caso contrário, a resposta "NOT_AUTHENTICATED" é enviada.
*/
void getNomeUser(AsyncWebServerRequest *request) {
  String cookieKey = "";  // Inicializa uma variável para armazenar a chave do cookie 'key'

  // Verifica se o cabeçalho 'Cookie' está presente na requisição.
  if (request->hasHeader("Cookie")) {
    AsyncWebHeader *h = request->getHeader("Cookie");
    String cookies = h->value().c_str();     // Obtém o valor do cabeçalho 'Cookie' da requisição HTTP e armazena em uma string.
    int keyIndex = cookies.indexOf("key=");  // Procura pela string 'key=' dentro do cabeçalhos cookies.

    // Se a string "key=" for encontrada
    if (keyIndex != -1) {
      // Obtém a posição inicial e final do valor do cookie 'key' dentro da string de cookies.
      keyIndex += 4;
      int endIndex = cookies.indexOf(";", keyIndex);
      cookieKey = cookies.substring(keyIndex, endIndex);  // Obtém o valor do cookie 'key' da string de cookies e armazena na variável cookieKey.
    }
  }

  int idUser = getIdByCookieKey(cookieKey);  // Chama a função getIdByCookieKey para obter o id do usuário correspondente ao valor do cookie 'key'.

  // Verifica se o id do usuário foi encontrado e envia a resposta correspondente.
  if (idUser != -1) {
    request->send(200, "text/plain", auth.users[idUser]);  // Se encontrado, envia uma resposta HTTP com código 200 (OK), com o nome de usuário correspondente ao ID 'idUser'
  } else {
    request->send(200, "text/plain", "NOT_AUTHENTICATED");  //  Se não, envia uma resposta HTTP com código 200 (OK), com um corpo de texto
                                                            // indicando que o usuário não está autenticado ("NOT_AUTHENTICATED").
  }
}

Função getPassUser

A função getPassUser() é chamada quando a rota "/getPassUser" é acessada via HTTP_GET. Esta rota destina-se à enviar a senha de usuário associada ao cookie recebido na requisição, desde que o cliente esteja autenticado.

A função começa verificando se a solicitação contém um cabeçalho "Cookie" e, em caso afirmativo, obtém o valor desse cabeçalho e procura pela string "key=" dentro dele. Se essa string for encontrada, a função obtém o valor do cookie "key" e passa esse valor para uma função chamada getIdByCookieKey que retorna o ID do usuário associado ao cookie.

Se o ID do usuário for encontrado, a função envia uma resposta HTTP com código 200 (OK) e a senha de usuário correspondente ao ID. Caso contrário, a função envia uma resposta com um corpo de texto indicando que o usuário não está autenticado ("NOT_AUTHENTICATED").

Nota: como este é um sistema demonstrativo, não há problemas de segurança em enviar a senha do usuário para ele mesmo. Mas em uma aplicação real, evite de fazer este procedimento.

/**
  Esta função recebe uma solicitação do servidor e envia a senha do usuário associado à chave do cookie key presente na solicitação
*/
void getPassUser(AsyncWebServerRequest *request) {
  String cookieKey = "";  // Inicializa uma variável para armazenar a chave do cookie 'key'

  // Verifica se a solicitação contém um header "Cookie"
  if (request->hasHeader("Cookie")) {

    // Recupera o valor do header "Cookie"
    AsyncWebHeader *h = request->getHeader("Cookie");
    String cookies = h->value().c_str();

    // Procura pela string 'key=' dentro do cabeçalhos cookies.
    int keyIndex = cookies.indexOf("key=");

    // Verifica se a string "key=" foi encontrada
    if (keyIndex != -1) {
      // Obtém a posição inicial e final do valor do cookie 'key' dentro da string de cookies.
      keyIndex += 4;
      int endIndex = cookies.indexOf(";", keyIndex);
      cookieKey = cookies.substring(keyIndex, endIndex);  // Obtém o valor do cookie 'key' da string de cookies e armazena na variável cookieKey.
    }
  }

  int idUser = getIdByCookieKey(cookieKey);  // Obtém o ID do usuário associado ao valor do cookie 'key'.

  // Verifica se o id do usuário foi encontrado e envia a resposta correspondente.
  if (idUser != -1) {
    request->send(200, "text/plain", auth.pass[idUser]);  // Se encontrado, envia uma resposta HTTP com código 200 (OK), com a senha de usuário correspondente ao ID 'idUser'
  } else {
    request->send(200, "text/plain", "NOT_AUTHENTICATED");  //  Se não, envia uma resposta HTTP com código 200 (OK), com um corpo de texto
                                                            // indicando que o usuário não está autenticado ("NOT_AUTHENTICATED").
  }
}

Função getCateUser

A função getCateUser() é chamada quando a rota "/getCateUser" é acessada via HTTP_GET. Esta rota destina-se à enviar a categoria de usuário associada ao cookie recebido na requisição, desde que o cliente esteja autenticado.

A função começa verificando se a solicitação contém um cabeçalho "Cookie" usando o método hasHeader(). Se o cabeçalho estiver presente, o valor do cabeçalho é recuperado usando o método getHeader() e armazenado em uma variável do tipo String.

Em seguida, a função procura pelo valor "key=" no cabeçalho "Cookie" usando o método indexOf(). Se o valor for encontrado, a função extrai o valor do cookie "key" usando o método substring() e armazena-o em uma variável cookieKey.

A função então chama a função getIdByCookieKey() para obter o ID do usuário associado ao valor do cookie "key". Se o ID do usuário for válido, a função envia a categoria correspondente como uma resposta HTTP com o código 200 (OK) usando o método request->send(). Caso contrário, a função envia uma mensagem indicando que o usuário não está autenticado ("NOT_AUTHENTICATED").

/**
  A função getCateUser() é responsável por enviar a categoria do usuário autenticado a partir do valor do cookie 'key' recebido no cabeçalho da solicitação.
*/
void getCateUser(AsyncWebServerRequest *request) {
  String cookieKey = "";  // Inicializa uma variável para armazenar a chave do cookie

  // Verifica se a solicitação contém um cabeçalho "Cookie".
  if (request->hasHeader("Cookie")) {

    // Obtém o cabeçalho "Cookie" da solicitação.
    AsyncWebHeader *h = request->getHeader("Cookie");

    // Converte o valor do cabeçalho "Cookie" para uma String.
    String cookies = h->value().c_str();

    // Procura pelo valor "key=" no cabeçalho "Cookie".
    int keyIndex = cookies.indexOf("key=");

    // Se o valor "key=" for encontrado, extrai o valor do cookie.
    if (keyIndex != -1) {
      // Define a posição inicial do valor do cookie.
      keyIndex += 4;

      // Procura pelo próximo ponto e vírgula (;) no cabeçalho "Cookie", indicando o fim do valor do cookie 'key'.
      int endIndex = cookies.indexOf(";", keyIndex);

      // Extrai o valor do cookie 'key' a partir das posições inicial e final encontradas.
      cookieKey = cookies.substring(keyIndex, endIndex);
    }
  }

  // Obtém o ID do usuário a partir do valor do cookie 'key'.
  int idUser = getIdByCookieKey(cookieKey);

  if (idUser != -1) {  // Se o ID do usuário for válido, envia a categoria correspondente.
    request->send(200, "text/plain", String(auth.categoria[idUser]));
  } else {  // Caso contrário, envia uma mensagem indicando que o usuário não está autenticado.
    request->send(200, "text/plain", "NOT_AUTHENTICATED");
  }
}

Função getUsuariosAtivos

A função getUsuariosAtivos() é chamada quando a rota "/getUsuariosAtivos" é acessada via HTTP_GET. Esta rota destina-se à enviar para os usuários logados de categoria Administrador uma lista de usuários que estão logados no momento.

O processo começa verificando se a requisição contém um cookie de autenticação "key", que é usado para identificar o usuário. Caso o cookie seja encontrado, a função chama a função getIdByCookieKey() para obter o ID do usuário correspondente ao valor do cookie "key".

Depois disso, a função verifica se o ID do usuário é válido e se o usuário é um Administrador. Se o usuário for um Administrador, a função cria uma string (veja abaixo a explicação da geração da lista de usuários) com os dados dos usuários ativos (nome, horário de login e tempo de login) e a envia como resposta para a requisição HTTP. Caso contrário, a função envia uma mensagem de erro de permissão para a requisição HTTP. Se o cookie de autenticação "key" não for encontrado, a função envia uma mensagem de erro de autenticação para a requisição HTTP.

Gerando a lista de usuários

A lista de usuários ativos é gerada através de um loop for, que itera sobre todos os índices da matriz auth.cookies, que armazena os cookies de autenticação de cada usuário. Para cada índice não vazio na matriz, a função concatena os dados de nome de usuário, horário de login e tempo de login em uma única string strUsersActives, que representa a lista completa de usuários ativos.

A concatenação desses dados é realizada da seguinte forma:

  1. O nome de usuário é adicionado à string strUsersActives através do comando strUsersActives += auth.users[i];.
  2. Uma quebra de linha é adicionada após o nome de usuário com o comando strUsersActives += '\n';.
  3. O horário de login é adicionado à string strUsersActives através do comando strUsersActives += getHorarioAtualStr(auth.timeOfLogin[i]);.
  4. Uma quebra de linha é adicionada após o horário de login com o comando strUsersActives += '\n';.
  5. O tempo desde o login é adicionado à string strUsersActives através do comando strUsersActives += secondsToHMS(diff_tm(auth.timeOfLogin[i], timeinfo));.
  6. Duas quebras de linha são adicionadas após o tempo desde o login com o comando strUsersActives += "\n\n"; para separar os usuários na lista.

Ao final do loop for, a string strUsersActives contém todos os usuários ativos no sistema, com seus respectivos dados.

/**
  Função responsável por enviar uma lista de usuários ativos.
*/
void getUsuariosAtivos(AsyncWebServerRequest *request) {
  String cookieKey = "";

  // Verifica se a requisição contém o cabeçalho "Cookie" e, em caso afirmativo, extrai o valor do cookie 'key' para identificar o usuário.
  if (request->hasHeader("Cookie")) {

    // Verifica se a solicitação contém um cabeçalho "Cookie".
    AsyncWebHeader *h = request->getHeader("Cookie");

    // Converte o valor do cabeçalho "Cookie" para uma String.
    String cookies = h->value().c_str();

    // Procura pelo índice inicial do cookie "key"
    int keyIndex = cookies.indexOf("key=");

    // Se o valor "key=" for encontrado, extrai o valor do cookie.
    if (keyIndex != -1) {
      keyIndex += 4;                                      // Define a posição inicial do valor do cookie.
      int endIndex = cookies.indexOf(";", keyIndex);      // Procura pelo próximo ponto e vírgula (;) no cabeçalho "Cookie", indicando o fim do valor do cookie 'key'.
      cookieKey = cookies.substring(keyIndex, endIndex);  // Extrai o valor do cookie 'key' a partir das posições inicial e final encontradas.
    }
  }

  // Obtém o ID do usuário correspondente ao valor do cookie 'key'
  int idUser = getIdByCookieKey(cookieKey);

  if (idUser != -1) {                          // Verifica se o ID do usuário é válido
    if (auth.categoria[idUser] == userADM) {   // Verifica se a categoria do usuário é do tipo Administrador
      String strUsersActives = "";             // Cria uma string contendo a lista de usuários ativos
      for (int i = 0; i < qtdMaxUsers; i++) {  // Loop 'for' que terá o mesmo número de iterações que a quantidade máxima de usuários (qtdMaxUsers)
        if (auth.cookies[i] != "") {           // verifica se o cookie armazenado com índice da variável 'i' é diferente de vazio
          // Cria a lista de usuários com dados de nome de usuário, horário de login, e tempo de login
          strUsersActives += auth.users[i];                                         // adiciona o nome do usuário à string
          strUsersActives += '\n';                                                  // adiciona uma quebra de linha
          strUsersActives += getHorarioAtualStr(auth.timeOfLogin[i]);               // Adiciona o horário da ocorrência do login
          strUsersActives += '\n';                                                  // adiciona uma quebra de linha
          strUsersActives += secondsToHMS(diff_tm(auth.timeOfLogin[i], timeinfo));  // Adiciona o tempo desde a ocorrência do login
          strUsersActives += "\n\n";                                                // // adiciona duas quebras de linha para separar os usuários na lista
        }
      }
      request->send(200, "text/plain", strUsersActives);  // Envia a lista de usuários ativos como resposta para a requisição HTTP
    } else {
      request->send(200, "text/plain", "WITHOUT_PERMISSION");  // Envia uma mensagem de erro de permissão para a requisição HTTP, pois o usuário não é Administrador
    }
  } else {
    request->send(200, "text/plain", "NOT_AUTHENTICATED");  // Envia uma mensagem de erro de autenticação para a requisição HTTP
  }
}

Definindo credenciais do WiFi

Para se conectar o ESP32 na rede WiFi, deve-se definir as credenciais de SSID e senha no arquivo credenciais.h.

As duas seguintes linhas de código definem o nome e a senha da rede WiFi à qual o dispositivo se conectará. O valor <SSID_DA_SUA_REDE_WIFI> deve ser substituído pelo nome da sua rede WiFi e <SENHA_DA_SUA_REDE_WIFI> deve ser substituído pela senha da sua rede WiFi. Isso é importante para que o dispositivo possa se conectar com sucesso à rede WiFi e possa acessar a Internet.

const char* ssid = "<SSID_DA_SUA_REDE_WIFI>";       // Define o nome da rede WiFi a se conectar
const char* password = "<SENHA_DA_SUA_REDE_WIFI>";  // Define a senha da rede WiFi a se conectar

Função page404

Quando o servidor recebe uma solicitação para uma página inexistente, a função page404() é chamada. Ela começa enviando uma resposta de erro 404 ao cliente informando o arquivo HTML "/404.html" como resposta e o tipo "text/html".

Em seguida, a função verifica se a solicitação HTTP contém um cabeçalho "Cookie". Se sim, ela recupera o valor do cabeçalho "Cookie" e procura na string o valor da chave "key". Se a chave "key" for encontrada na string "cookies", o valor do cookie é extraído da string e armazenado na variável "cookieKey". Em seguida, a função declara as variáveis "descricao" e "detalhes" para armazenar informações no sistema de logs.

A função usa a função getIdByCookieKey(cookieKey) para obter o ID do usuário correspondente ao valor do cookie. Se o valor do cookie for válido, a função registra informações sobre a solicitação no arquivo de log, incluindo a data/hora atual, o tipo de erro, uma descrição e detalhes adicionais. A descrição indica que o usuário tentou acessar a página inexistente http://[host]/[url], onde [host] é o host do servidor e [url] é a URL solicitada. Os detalhes incluem o nome do usuário que fez a solicitação. Se o valor do cookie for inválido ou não existir, a função registra informações sobre a solicitação no arquivo de log sem informações sobre o usuário.

/**
  A função page404 é chamada quando o servidor recebe uma solicitação para uma página inexistente.
  Essa função envia uma resposta de erro 404 e registra informações sobre a solicitação no arquivo de log.
*/
void page404(AsyncWebServerRequest *request) {

  // Envia a resposta de erro 404 para o cliente, informando o arquivo HTML "/404.html" como resposta e o tipo "text/html".
  request->send(SPIFFS, "/404.html", "text/html");

  // Inicia a variável "cookieKey" com uma string vazia.
  String cookieKey = "";

  // Verifica se a solicitação HTTP contém um cabeçalho "Cookie".
  if (request->hasHeader("Cookie")) {

    // Recupera o valor do cabeçalho "Cookie" e armazena-o em um objeto AsyncWebHeader.
    AsyncWebHeader *h = request->getHeader("Cookie");

    // Converte o valor do cabeçalho "Cookie" em uma string.
    String cookies = h->value().c_str();

    // Procura na string "cookies" o valor da chave "key".
    int keyIndex = cookies.indexOf("key=");

    // Se a chave "key" foi encontrada na string "cookies":
    if (keyIndex != -1) {

      // Ajusta o índice da chave "key" e procura o índice do próximo caractere ";", que delimita o final do valor do cookie.
      keyIndex += 4;
      int endIndex = cookies.indexOf(";", keyIndex);

      // Extrai o valor do cookie da string "cookies" e armazena-o na variável "cookieKey".
      cookieKey = cookies.substring(keyIndex, endIndex);
    }
  }

  // Declara as variáveis "descricao" e "detalhes" para armazenar informações sobre a solicitação recebida.
  String descricao;
  String detalhes;

  // Verifica se o valor do cookie é válido e, se for, registra informações sobre a solicitação no arquivo de log.
  int idUser = getIdByCookieKey(cookieKey);
  if (idUser != -1) {
    descricao = "Usuário tentou acessar a página inexistente http://";
    descricao += request->host();
    descricao += request->url();

    detalhes = "Usuário: ";
    detalhes += auth.users[idUser];
  } else {
    // Se o valor do cookie for inválido ou não existir, registra informações sobre a solicitação no arquivo de log sem informações sobre o usuário.
    descricao = "Desconhecido tentou acessar a página inexistente http://";
    descricao += request->host();
    descricao += request->url();

    detalhes = "-";
  }

  // Registra informações sobre a solicitação no arquivo de log, incluindo a data/hora atual, o tipo de erro, uma descrição e detalhes adicionais.
  appendLineLogToFile(getHorarioAtualStr(timeinfo), "Erro", descricao, detalhes);
}

Função page403

Esta função é responsável por retornar a página de erro 403 (Acesso Negado) para o cliente que solicitou uma página que não tem permissão de acesso.

A função recebe um objeto do tipo AsyncWebServerRequest como parâmetro, que representa a requisição HTTP que chegou ao servidor. Ela envia a página de erro 403, que está armazenada no sistema de arquivos SPIFFS, ao cliente que fez a requisição, indicando que o acesso à página solicitada foi negado.

A página de erro 403 é enviada com o tipo de conteúdo "text/html".

/**
  Função responsável por retornar a página de erro 403 (Acesso Negado)
  A função recebe um objeto do tipo AsyncWebServerRequest que representa a requisição HTTP
  que chegou ao servidor. Ela envia a página de erro 403 ao cliente que fez a requisição,
  indicando que o acesso à página solicitada foi negado.
*/
void page403(AsyncWebServerRequest *request) {

  // Envia a página de erro 403 ao cliente.
  request->send(SPIFFS, "/403.html", "text/html");
}

Função pageAdmFerramentas

Esta função é responsável por exibir uma página de ferramentas para um usuário de categoria Administrador. A função verifica se o usuário tem permissão para acessar a página, verificando se existe um cookie no cabeçalho da requisição e, se sim, obtém o valor do cookie. Em seguida, a função utiliza o valor do cookie para obter o ID do usuário correspondente, por meio da função getIdByCookieKey(). Se o ID do usuário for encontrado, a função verifica se o usuário é um administrador, comparando sua categoria com a constante userADM. Caso as verificações tenham sido validadas, a página de ferramentas é exibida e uma entrada de log é criada para registrar o acesso. Caso contrário, o usuário é redirecionado para uma página de erro e uma entrada de log é criada para registrar a tentativa de acesso não autorizado.

Se não foi possível obter o ID do usuário correspondente ao cookie, o usuário é redirecionado para a página de login, com o parâmetro HTTP GET 'dest' para o link da página atual, para que o usuário possa ser redirecionado de volta para a página de ferramentas após o login. Em seguida, a função finaliza.

/**
  Função responsável por exibir a página de ferramentas para o usuário administrador
  A função extrai o cookie do cabeçalho da solicitação e verifica se o usuário possui autorização para acessar
  a página de ferramentas. Em caso afirmativo, a página é exibida e uma entrada de log é criada. Caso contrário,
  o usuário é redirecionado para uma página de erro e uma entrada de log é criada indicando a tentativa de acesso
  não autorizado.
*/
void pageAdmFerramentas(AsyncWebServerRequest *request) {

  // Verifica se existe um cookie salvo para este usuário e se sim, obtém o valor do cookie
  String cookieKey = "";                                  // Inicializa a variável cookieKey com uma string vazia
  if (request->hasHeader("Cookie")) {                     // Verifica se há um cabeçalho de cookie na solicitação
    AsyncWebHeader *h = request->getHeader("Cookie");     // Extrai o cabeçalho de cookie da solicitação
    String cookies = h->value().c_str();                  // Converte o valor do cabeçalho para uma string
    int keyIndex = cookies.indexOf("key=");               // Procura pelo cookie "key" na string
    if (keyIndex != -1) {                                 // Se o cookie "key" foi encontrado
      keyIndex += 4;                                      // Define o índice do início do valor do cookie
      int endIndex = cookies.indexOf(";", keyIndex);      // Procura pelo final do valor do cookie
      cookieKey = cookies.substring(keyIndex, endIndex);  // Extrai o valor do cookie
    }
  }

  int idUser = getIdByCookieKey(cookieKey);  // Obtém o ID do usuário correspondente à chave do cookie (se houver)
  if (idUser != -1) {                        // Se o ID do usuário foi encontrado

    // Imprime a categoria do usuário no monitor serial
    Serial.print("categoria: ");
    Serial.println(auth.categoria[idUser]);

    if (auth.categoria[idUser] == userADM) {  // Se o usuário for um administrador

      request->send(SPIFFS, "/adm/ferramentas.html");  // Envia a página de ferramentas para o usuário

      // Cria uma descrição para a entrada de log
      String descricao = "Usuário acessou a página http://";
      descricao += request->host();
      descricao += request->url();

      // Cria um detalhamento para a entrada de log
      String detalhes = "Usuário: ";
      detalhes += auth.users[idUser];

      appendLineLogToFile(getHorarioAtualStr(timeinfo), "Acesso à página", descricao, detalhes);  // Adiciona uma entrada de log ao arquivo

    } else {  // Se o usuário NÃO for um administrador

      // Define o link para a página de erro, com o parâmetro HTTP GET 'url-falho' para o link da página atual
      String link = "/403.html?url-falho=http://";
      link += request->host();
      link += request->url();

      request->redirect(link);  // Redireciona o usuário para a página de erro

      // Cria uma descrição para a entrada de log
      String descricao = "Usuário tentou acessar a página http://";
      descricao += request->host();
      descricao += request->url();
      descricao += " sem autorização";

      // Cria um detalhamento para a entrada de log
      String detalhes = "Usuário: ";
      detalhes += auth.users[idUser];

      appendLineLogToFile(getHorarioAtualStr(timeinfo), "Erro", descricao, detalhes);  // Adiciona uma entrada de log ao arquivo
    }

  } else {  // Se não foi possível obter o ID do usuário correspondente ao cookie

    request->redirect("/login.html?dest=adm/ferramentas.html");  // Redireciona o usuário para a página de login, com o parâmetro HTTP GET 'dest' para o link da página atual
  }
}

Função pageAdmHome

Esta função é responsável por exibir a página inicial para um usuário de categoria Administrador. A função verifica se o usuário está logado e possui permissão de administrador (categoria userADM). Caso positivo, envia a página "/adm/home.html" como resposta para o cliente e registra o acesso no log de acesso. Caso contrário, redireciona o cliente para a página "/403.html" e registra a tentativa de acesso não autorizado no log de erro.

Primeiro, a função verifica se existe um cookie salvo para este usuário e, se sim, obtém o valor do cookie. Em seguida, obtém o ID do usuário correspondente à chave do cookie (se houver). Se o ID do usuário for encontrado, a função verifica se o usuário é um administrador. Se o usuário for um administrador, a função envia a página home para o usuário e cria uma entrada de log no arquivo de log de acesso. Se o usuário não for um administrador, a função redireciona o usuário para a página de erro 403 e cria uma entrada de log no arquivo de log de erro.

Se não for possível obter o ID do usuário correspondente ao cookie, a função redireciona o usuário para a página de login com o parâmetro HTTP GET 'dest' para o link da página atual.

/**
  Função responsável por gerenciar a página inicial do painel de administração.
  A função verifica se o usuário está logado e possui permissão de administrador (categoria userADM).
  Caso positivo, envia a página "/adm/home.html" como resposta para o cliente e registra o acesso no log de acesso.
  Caso contrário, redireciona o cliente para a página "/403.html" e registra a tentativa de acesso não autorizado no log de erro.
*/
void pageAdmHome(AsyncWebServerRequest *request) {

  // Verifica se existe um cookie salvo para este usuário e se sim, obtém o valor do cookie
  String cookieKey = "";                                  // Inicializa a variável cookieKey com uma string vazia
  if (request->hasHeader("Cookie")) {                     // Verifica se há um cabeçalho de cookie na solicitação
    AsyncWebHeader *h = request->getHeader("Cookie");     // Extrai o cabeçalho de cookie da solicitação
    String cookies = h->value().c_str();                  // Converte o valor do cabeçalho para uma string
    int keyIndex = cookies.indexOf("key=");               // Procura pelo cookie "key" na string
    if (keyIndex != -1) {                                 // Se o cookie "key" foi encontrado
      keyIndex += 4;                                      // Define o índice do início do valor do cookie
      int endIndex = cookies.indexOf(";", keyIndex);      // Procura pelo final do valor do cookie
      cookieKey = cookies.substring(keyIndex, endIndex);  // Extrai o valor do cookie
    }
  }

  int idUser = getIdByCookieKey(cookieKey);     // Obtém o ID do usuário correspondente à chave do cookie (se houver)
  if (idUser != -1) {                           // Se o ID do usuário foi encontrado
    if (auth.categoria[idUser] == userADM) {    // Se o usuário for um administrador
      request->send(SPIFFS, "/adm/home.html");  // Envia a página home para o usuário

      // Cria uma descrição para a entrada de log
      String descricao = "Usuário acessou a página http://";
      descricao += request->host();
      descricao += request->url();

      // Cria um detalhamento para a entrada de log
      String detalhes = "Usuário: ";
      detalhes += auth.users[idUser];

      appendLineLogToFile(getHorarioAtualStr(timeinfo), "Acesso à página", descricao, detalhes);  // Adiciona uma entrada de log ao arquivo

    } else {  // Se o usuário NÃO for um administrador

      // Define o link para a página de erro, com o parâmetro HTTP GET 'url-falho' para o link da página atual
      String link = "/403.html?url-falho=http://";
      link += request->host();
      link += request->url();

      request->redirect(link);  // Redireciona o usuário para a página de erro

      // Cria uma descrição para a entrada de log
      String descricao = "Usuário tentou acessar a página http://";
      descricao += request->host();
      descricao += request->url();
      descricao += " sem autorização";

      // Cria um detalhamento para a entrada de log
      String detalhes = "Usuário: ";
      detalhes += auth.users[idUser];

      appendLineLogToFile(getHorarioAtualStr(timeinfo), "Erro", descricao, detalhes);  // Adiciona uma entrada de log ao arquivo
    }

  } else {  // Se não foi possível obter o ID do usuário correspondente ao cookie

    request->redirect("/login.html?dest=adm/home.html");  // Redireciona o usuário para a página de login, com o parâmetro HTTP GET 'dest' para o link da página atual
  }
}

Função pageAdmMinhaConta

Esta função é responsável por exibir a página de Minha Conta para um usuário de categoria Administrador. Ela recebe um objeto que contém informações da requisição HTTP e é responsável por verificar se o usuário que fez a requisição tem autorização para acessar a página “Minha Conta”.

Primeiro, o código verifica se há um cookie salvo para o usuário que fez a requisição, procurando pelo cookie "key". Se o cookie é encontrado, a chave é obtida e usada para obter o ID do usuário correspondente.

Se o ID do usuário é encontrado, o código verifica se o usuário é um administrador. Se o usuário for um administrador, a página “Minha Conta” é enviada para o usuário e uma entrada de log é adicionada ao arquivo. Se o usuário não for um administrador, o usuário é redirecionado para a página de erro e uma entrada de log é adicionada ao arquivo.

Se o ID do usuário não pôde ser encontrado, o usuário é redirecionado para a página de login, com o parâmetro HTTP GET 'dest' para o link da página atual.

/**
  Função responsável por controlar o acesso à página "Minha Conta" no painel de administração.
  Esta função recebe um objeto que contém informações da requisição HTTP e é responsável por verificar se o usuário que fez a requisição tem autorização para acessar a página "Minha Conta".
*/
void pageAdmMinhaConta(AsyncWebServerRequest *request) {

  // Verifica se existe um cookie salvo para este usuário e se sim, obtém o valor do cookie
  String cookieKey = "";                                  // Inicializa a variável cookieKey com uma string vazia
  if (request->hasHeader("Cookie")) {                     // Verifica se há um cabeçalho de cookie na solicitação
    AsyncWebHeader *h = request->getHeader("Cookie");     // Extrai o cabeçalho de cookie da solicitação
    String cookies = h->value().c_str();                  // Converte o valor do cabeçalho para uma string
    int keyIndex = cookies.indexOf("key=");               // Procura pelo cookie "key" na string
    if (keyIndex != -1) {                                 // Se o cookie "key" foi encontrado
      keyIndex += 4;                                      // Define o índice do início do valor do cookie
      int endIndex = cookies.indexOf(";", keyIndex);      // Procura pelo final do valor do cookie
      cookieKey = cookies.substring(keyIndex, endIndex);  // Extrai o valor do cookie
    }
  }

  int idUser = getIdByCookieKey(cookieKey);  // Obtém o ID do usuário correspondente à chave do cookie (se houver)
  if (idUser != -1) {                        // Se o ID do usuário foi encontrado

    if (auth.categoria[idUser] == userADM) {  // Se o usuário for um administrador

      request->send(SPIFFS, "/adm/minha-conta.html");  // Envia a página de Minha Conta para o usuário

      // Cria uma descrição para a entrada de log
      String descricao = "Usuário acessou a página http://";
      descricao += request->host();
      descricao += request->url();

      // Cria um detalhamento para a entrada de log
      String detalhes = "Usuário: ";
      detalhes += auth.users[idUser];

      appendLineLogToFile(getHorarioAtualStr(timeinfo), "Acesso à página", descricao, detalhes);  // Adiciona uma entrada de log ao arquivo

    } else {  // Se o usuário NÃO for um administrador

      // Define o link para a página de erro, com o parâmetro HTTP GET 'url-falho' para o link da página atual
      String link = "/403.html?url-falho=http://";
      link += request->host();
      link += request->url();

      request->redirect(link);  // Redireciona o usuário para a página de erro

      // Cria uma descrição para a entrada de log
      String descricao = "Usuário tentou acessar a página http://";
      descricao += request->host();
      descricao += request->url();
      descricao += " sem autorização";

      // Cria um detalhamento para a entrada de log
      String detalhes = "Usuário: ";
      detalhes += auth.users[idUser];

      appendLineLogToFile(getHorarioAtualStr(timeinfo), "Erro", descricao, detalhes);  // Adiciona uma entrada de log ao arquivo
    }
  } else {  // Se não foi possível obter o ID do usuário correspondente ao cookie

    request->redirect("/login.html?dest=adm/minha-conta.html");  // Redireciona o usuário para a página de login, com o parâmetro HTTP GET 'dest' para o link da página atual
  }
}

Outras Funções do arquivo pages.ino

Além das funções já mencionadas, existem outras que seguem uma estrutura semelhante, porém mudam a categoria de acesso. São elas:

  • pageAvdFerramentas;
  • pageAvdHome;
  • pageAvdMinhaConta;
  • pageComFerramentas;
  • pageComHome;
  • pageComMinhaConta.

Essas funções são responsáveis por controlar o acesso às páginas correspondentes de acordo com a categoria do usuário que está fazendo a requisição. Dessa forma, garantem que apenas usuários autorizados possam acessar determinadas funcionalidades do sistema.

Função pageBootstrapCss

Esta função é usada para enviar o arquivo CSS minificado do Bootstrap para o cliente em resposta a uma requisição HTTP GET. A função usa o objeto AsyncWebServerRequest para enviar o arquivo através do método send() que possui três parâmetros para esse envio:

  • O primeiro parâmetro é o sistema de arquivos SPIFFS que será usado como a fonte do arquivo CSS;
  • O segundo parâmetro é o caminho do arquivo CSS no sistema de arquivos;
  • O terceiro parâmetro é o tipo MIME do arquivo CSS que é definido como "text/css", que informa ao navegador que o arquivo contém código CSS.
/**
 * Envia o arquivo CSS minificado do Bootstrap para o cliente.
 */
void pageBootstrapCss(AsyncWebServerRequest *request) {
  request->send(SPIFFS,                         // Usa o sistema de arquivos SPIFFS como fonte do arquivo.
                "/asts/btsp/css/btsp.min.css",  // Especifica o caminho para o arquivo CSS.
                "text/css"                      // Define o tipo MIME do arquivo CSS para o navegador processar corretamente.
  );                                            // Envia o arquivo CSS minificado do Bootstrap para o cliente que fez a requisição HTTP.
}

Função pageBootstrapJs

Esta função é responsável por enviar o arquivo JavaScript minificado do Bootstrap para o cliente que fez a requisição HTTP.

A função usa o objeto request do tipo AsyncWebServerRequest para manipular a resquisição do cliente. A função send é chamada no objeto request para enviar o arquivo especificado. Os parâmetros para a função send são:

  • SPIFFS: o sistema de arquivos SPIFFS é usado como fonte do arquivo;
  • "/asts/btsp/js/btsp.min.js": especifica o caminho no sistema de arquivos para o arquivo JavaScript;
  • "application/javascript": define o tipo MIME do arquivo JavaScript para o navegador processar corretamente.

Desta forma, o arquivo JavaScript minificado do Bootstrap é enviado para o cliente que fez a requisição HTTP.

/**
 * Envia o arquivo JavaScript minificado do Bootstrap para o cliente.
 */
void pageBootstrapJs(AsyncWebServerRequest *request) {
  request->send(SPIFFS, "/asts/btsp/js/btsp.min.js", "application/javascript");  // Envia o arquivo JavaScript minificado do Bootstrap para o cliente que fez a requisição HTTP.
}

Função pageJs

Esta função é responsável por enviar um arquivo JavaScript personalizado para o cliente que fez uma requisição HTTP.

A função utiliza o objeto AsyncWebServerRequest *request para enviar a resposta para o cliente. O método request->send() é usado para enviar o arquivo e seus parâmetros são:

  • SPIFFS: define que o sistema de arquivos utilizado para buscar o arquivo é o SPIFFS.
  • "/asts/js/javascript.js": especifica o caminho para o arquivo JavaScript personalizado no sistema de arquivos.
  • "application/javascript": define o tipo MIME do arquivo para o navegador processar corretamente.

Desta forma, o arquivo JavaScript personalizado é enviado para o cliente que fez a requisição HTTP.

/**
 * Envia o arquivo JavaScript personalizado para o cliente.
 */
void pageJs(AsyncWebServerRequest *request) {
  request->send(SPIFFS, "/asts/js/javascript.js", "application/javascript");
}

Função pageJsBsInit

Esta função envia um arquivo JavaScript, que é usado para inicializar os componentes do Bootstrap de forma personalizada, para o cliente que fez a requisição HTTP.

A função pageJsBsInit tem como parâmetro o objeto request da biblioteca AsyncWebServer. Este parâmetro é um ponteiro para o objeto que representa a requisição HTTP feita pelo cliente. Esse objeto é usado para enviar a resposta com o conteúdo do arquivo JavaScript solicitado.

A função request->send() é usada para enviar o arquivo JavaScript para o cliente. O primeiro parâmetro é o sistema de arquivos onde o arquivo está localizado, que neste caso é o SPIFFS. O segundo parâmetro é o caminho para o arquivo JavaScript. E o terceiro parâmetro é o tipo MIME do arquivo, que é "application/javascript" neste caso (indica que ele contém código JavaScript).

/**
 * Envia o arquivo JavaScript de inicialização do Bootstrap para o cliente.
 */
void pageJsBsInit(AsyncWebServerRequest *request) {
  request->send(SPIFFS, "/asts/js/bs-init.js", "application/javascript");
}

Função favicon

Esta função é usada para enviar o arquivo de ícone do site (favicon) para o cliente.

A função request->send() é usada para enviar o arquivo de ícone para o cliente. O primeiro argumento é o sistema de arquivos SPIFFS que contém o arquivo de ícone, o segundo argumento é o caminho para o arquivo de ícone e o terceiro argumento é o tipo MIME do arquivo. Neste caso, o tipo MIME é "image/png" porque o arquivo de ícone é uma imagem PNG.

/**
 * Envia o arquivo de ícone do site (favicon) para o cliente.
 */
void favicon(AsyncWebServerRequest *request) {
  request->send(SPIFFS, "/asts/img/favicon.png", "image/png");
}

Hardware

Para executar o projeto deste post, é utilizado somente o seguinte hardware:


Download do Software Completo

Para baixar todos os arquivos do projeto, clique em Fazer Download:


Demonstração do Sistema Funcionando

Com o upload dos arquivos para o ESP32 realizado, tanto os arquivos do sketch quanto os arquivos da SPIFFS (veja instruções de upload de arquivos na SPIFFS no post SPIFFS: Armazenamento de Arquivos do ESP32), foi feito uma demonstração de funcionamento do sistema. Veja o vídeo:


Conclusão

Concluindo, o uso de cookies no ESP32 é uma maneira eficiente e segura de manter os usuários autenticados durante uma sessão de login. Neste artigo, vimos como implementar um sistema de login web com cookies utilizando o ESPAsyncWebServer e a biblioteca Preferences. Criamos diferentes categorias hierárquicas de usuários e implementamos um sistema de log para registrar as ações realizadas por cada usuário. Esperamos que este artigo tenha sido útil para você. Se tiver alguma dúvida ou sugestão, deixe um comentário abaixo. E não se esqueça de nos seguir no Instagram em @eletrogate e nos marcar quando fizer algum projeto nosso. Curtiu o post? Avalie e deixe um comentário! Até a próxima!


Sobre o Autor


Michel Galvão

Graduando em Engenharia de Software pela UniCV de Maringá/PR. Tem experiência em Automação Residencial e Agrícola. É um Eletrogater Expert. Tem como Hobby Sistemas Embarcados e IoT. É desenvolvedor de software de Sistemas MicSG. Site: https://micsg.com.br/


Eletrogate

29 de junho de 2023

A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.

Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!

Eletrogate Robô

Cadastre-se e fique por
dentro de novidades!