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.
Para este post, serão utilizados os seguintes materiais:
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.
Os arquivos que conterão a programação, os arquivos web e o arquivo de log estarão na seguinte estrutura de arquivos:
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”).
<!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.
<!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>
).
<!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.
O código é o mesmo do arquivo /software/data/adm/home.html. As únicas diferenças são:
<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>
O código é o mesmo do arquivo /software/data/adm/minha-conta.html. As únicas diferenças são:
<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>
<!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.
O código é o mesmo do arquivo /software/data/adm/home.html. As únicas diferenças são:
<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>
O código é o mesmo do arquivo /software/data/adm/minha-conta.html. As únicas diferenças são:
<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>
<!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: </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("/login.html");">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.
<!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("/login.html");">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:
<!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.
// 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:
// 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.
// 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.
// 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.
Siga as orientações abaixo. Para mais detalhes, acesse: https://getbootstrap.com/docs/5.2/getting-started/introduction/#cdn-links.
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).
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).
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.
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”.
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.
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á:
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
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 }
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(); } }
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);
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 } } }
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:
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 } }
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.
Usuário:
[USER]
.Tempo de Sessão:
[TEMPO_SESSAO]
| Usuário:
[USER]
.-
.Tentado no usuário:
[USER]
.-
.-
.Usuário:
[USER]
.Usuário:
[USER]
.Usuário:
[USER]
.-
.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.
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
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 }
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; }
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; }
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);
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);
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"); }
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 } }
Neste subtópico será mostrado todos os 10 diferentes tipos de logs que há no programa, registrados através da função appendLineLogToFile()
.
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
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)
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
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)
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
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
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
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
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);
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:
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
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
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; }
Aqui será explicado os principais pontos que há na função setup:
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 }
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; }
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; }
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:
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);
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
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"); } }
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"). } }
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"). } }
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"); } }
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.
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:
strUsersActives
através do comando strUsersActives += auth.users[i];
.strUsersActives += '\n';
.strUsersActives
através do comando strUsersActives += getHorarioAtualStr(auth.timeOfLogin[i]);
.strUsersActives += '\n';
.strUsersActives
através do comando strUsersActives += secondsToHMS(diff_tm(auth.timeOfLogin[i], timeinfo));
.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 } }
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
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); }
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"); }
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 } }
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 } }
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 } }
Além das funções já mencionadas, existem outras que seguem uma estrutura semelhante, porém mudam a categoria de acesso. São elas:
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.
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:
"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. }
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. }
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"); }
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"); }
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"); }
Para baixar todos os arquivos do projeto, clique em Fazer Download:
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:
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!
|
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!