Frontend: Implement user logging in
This commit is contained in:
parent
0a6e4d1d15
commit
606b13470e
233
frontend.pm
233
frontend.pm
@ -23,6 +23,7 @@ use DBI;
|
||||
|
||||
use lib ".";
|
||||
use configuration;
|
||||
use frontend_routes;
|
||||
|
||||
use feature qw(switch);
|
||||
use strict;
|
||||
@ -58,29 +59,17 @@ use constant {
|
||||
PPATH_GET_KEY => 1,
|
||||
PPATH_GET_VALUE => 2
|
||||
};
|
||||
sub parsePath {
|
||||
sub parsePathParameters {
|
||||
my $aPath = $_[0];
|
||||
|
||||
my $pathLength = length($aPath);
|
||||
my $state = PPATH_URL;
|
||||
my $state = PPATH_GET_KEY;
|
||||
my $currentString = "";
|
||||
my $currentString2 = "";
|
||||
my %output;
|
||||
foreach my $i (0..$pathLength-1) {
|
||||
my $char = substr($aPath, $i, 1);
|
||||
given($state) {
|
||||
when(PPATH_URL) {
|
||||
if($char eq "?") {
|
||||
$output{"url"} = $currentString;
|
||||
$currentString = "";
|
||||
$state = PPATH_GET_KEY;
|
||||
next;
|
||||
}
|
||||
$currentString.=$char;
|
||||
if($i==$pathLength-1) {
|
||||
$output{"url"} = $currentString;
|
||||
}
|
||||
}
|
||||
when(PPATH_GET_KEY) {
|
||||
if($char eq "=") {
|
||||
$state = PPATH_GET_VALUE;
|
||||
@ -91,27 +80,96 @@ sub parsePath {
|
||||
when(PPATH_GET_VALUE) {
|
||||
if($char eq "&") {
|
||||
$state = PPATH_GET_KEY;
|
||||
$output{"parameters"}{$currentString} = $currentString2;
|
||||
$output{$currentString} = $currentString2;
|
||||
$currentString = "";
|
||||
$currentString2 = "";
|
||||
next;
|
||||
}
|
||||
$currentString2.=$char;
|
||||
if($i==$pathLength-1) {
|
||||
$output{"parameters"}{$currentString} = $currentString2;
|
||||
$output{$currentString} = $currentString2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return %output;
|
||||
}
|
||||
sub parsePath {
|
||||
my $aPath = $_[0];
|
||||
|
||||
my $pathLength = length($aPath);
|
||||
my $state = PPATH_URL;
|
||||
my $currentString = "";
|
||||
my %output;
|
||||
my $index = 0;
|
||||
while($index<$pathLength) {
|
||||
my $char = substr($aPath, $index++, 1);
|
||||
if($char eq "?") {
|
||||
$output{"url"} = $currentString;
|
||||
$state = PPATH_GET_KEY;
|
||||
last;
|
||||
}
|
||||
$currentString.=$char;
|
||||
}
|
||||
$output{"url"} = $currentString;
|
||||
if($state==PPATH_GET_KEY) {
|
||||
$output{"parameters"} = { parsePathParameters(substr($aPath, $index, $pathLength-$index)) };
|
||||
}
|
||||
return %output;
|
||||
}
|
||||
|
||||
use constant {
|
||||
PCOOKIE_NAME => 0,
|
||||
PCOOKIE_VALUE => 1
|
||||
};
|
||||
sub parseCookies {
|
||||
my $aCookies = $_[0];
|
||||
|
||||
my $cookiesLength = length($aCookies);
|
||||
my $state = PCOOKIE_NAME;
|
||||
my $currentString = "";
|
||||
my $currentString2 = "";
|
||||
my %output;
|
||||
foreach my $i (0..$cookiesLength-1) {
|
||||
my $char = substr($aCookies, $i, 1);
|
||||
given($state) {
|
||||
when(PCOOKIE_NAME) {
|
||||
if($char eq " ") {
|
||||
next;
|
||||
}
|
||||
if($char eq "=") {
|
||||
$state = PCOOKIE_VALUE;
|
||||
next;
|
||||
}
|
||||
$currentString.=$char;
|
||||
}
|
||||
when(PCOOKIE_VALUE) {
|
||||
if($char eq ";" || $i==$cookiesLength-1) {
|
||||
if(length($currentString)>0 && length($currentString2)>0) {
|
||||
if($i==$cookiesLength-1) {
|
||||
$currentString2.=$char;
|
||||
}
|
||||
$output{$currentString} = $currentString2;
|
||||
}
|
||||
$currentString = "";
|
||||
$currentString2 = "";
|
||||
$state = PCOOKIE_NAME;
|
||||
next;
|
||||
}
|
||||
$currentString2.=$char;
|
||||
}
|
||||
}
|
||||
}
|
||||
return %output;
|
||||
}
|
||||
|
||||
use constant {
|
||||
PHTTP_METHOD => 0,
|
||||
PHTTP_PATH => 1,
|
||||
PHTTP_VERSION => 2,
|
||||
PHTTP_HEADER => 3,
|
||||
PHTTP_VALUE => 4
|
||||
PHTTP_VALUE => 4,
|
||||
PHTTP_CONTENT => 5
|
||||
};
|
||||
sub parseHTTPRequest {
|
||||
my $aRequest = $_[0];
|
||||
@ -164,14 +222,31 @@ sub parseHTTPRequest {
|
||||
when(PHTTP_VALUE) {
|
||||
if($char eq "\r") {
|
||||
$index++;
|
||||
$output{"headers"}{$currentString} = $currentString2;
|
||||
$currentString = "";
|
||||
$currentString2 = "";
|
||||
$state = PHTTP_HEADER;
|
||||
if($currentString eq "Cookie") {
|
||||
$output{"cookies"} = { parseCookies($currentString2) };
|
||||
}
|
||||
else {
|
||||
$output{"headers"}{$currentString} = $currentString2;
|
||||
}
|
||||
if($index+1<$requestLength && substr($aRequest, $index, 1) eq "\r") {
|
||||
if(defined($output{"headers"}{"Content-Length"})) {
|
||||
$index+=2;
|
||||
$state = PHTTP_CONTENT;
|
||||
$output{"content"} = "";
|
||||
}
|
||||
}
|
||||
else {
|
||||
$currentString = "";
|
||||
$currentString2 = "";
|
||||
$state = PHTTP_HEADER;
|
||||
}
|
||||
next;
|
||||
}
|
||||
$currentString2.=$char;
|
||||
}
|
||||
when(PHTTP_CONTENT) {
|
||||
$output{"content"}.=$char;
|
||||
}
|
||||
}
|
||||
}
|
||||
return %output;
|
||||
@ -221,6 +296,16 @@ sub sendBadRequest {
|
||||
$aClient->send($response);
|
||||
}
|
||||
|
||||
sub redirect {
|
||||
my $aClient = $_[0];
|
||||
my $aLocation = $_[1];
|
||||
|
||||
my $response = getBaseResponse(301, "Moved Permanently");
|
||||
$response.="Content-Length: 0\r\n";
|
||||
$response.="Location: $aLocation\r\n";
|
||||
$aClient->send($response);
|
||||
}
|
||||
|
||||
use constant {
|
||||
PREPROCESSOR_STATE_TEXT => 0,
|
||||
PREPROCESSOR_STATE_VAR => 1,
|
||||
@ -313,101 +398,6 @@ sub sendTemplate {
|
||||
$aClient->send($response);
|
||||
}
|
||||
|
||||
sub handlePath {
|
||||
my $aClient = $_[0];
|
||||
my $aPath = $_[1];
|
||||
my $aRequest = $_[2];
|
||||
my $aConnection = $_[3];
|
||||
|
||||
given($aPath) {
|
||||
when("/") {
|
||||
my $query = $aConnection->prepare(qq(select channels.id, channels.name, servers.name from channels inner join servers on channels.server_id=servers.id where channels.public=1;));
|
||||
$query->execute();
|
||||
my $table = "";
|
||||
while(my @row = $query->fetchrow_array()) {
|
||||
my $channelID = $row[0];
|
||||
my $channelName = $row[1];
|
||||
my $serverName = $row[2];
|
||||
|
||||
$table.="<tr><td><a href=\"view_logs?channel=$channelID\">$channelName</a></td><td>$serverName</td></tr>";
|
||||
}
|
||||
sendTemplate("templates/index.html", $aClient, {"publicChannels"=>$table});
|
||||
return 1;
|
||||
}
|
||||
when("/view_logs") {
|
||||
my $channelID = $aRequest->{"path"}{"parameters"}{"channel"};
|
||||
if(!defined($channelID)) {
|
||||
sendBadRequest($aClient, "view_logs requires channel URL parameter");
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $query = $aConnection->prepare(qq(select channels.name, servers.name from channels inner join servers on channels.server_id=servers.id where channels.id=?;));
|
||||
$query->execute($channelID);
|
||||
my @row = $query->fetchrow_array();
|
||||
if(scalar(@row)==0) {
|
||||
sendBadRequest($aClient, "Unknown channel with ID $channelID");
|
||||
return 1;
|
||||
}
|
||||
my $channelName = $row[0];
|
||||
my $serverName = $row[1];
|
||||
my $logsPath = "logs/".$serverName."/".$channelName;
|
||||
|
||||
my $result = opendir(my $folder, $logsPath);
|
||||
if(!$result) {
|
||||
sendBadRequest($aClient, "Channel $channelName on $serverName doesn't have any logs");
|
||||
return 1;
|
||||
}
|
||||
my @entries = grep(!/^\.\.?$/, readdir($folder));
|
||||
|
||||
my $table = "";
|
||||
foreach my $entry (@entries) {
|
||||
$table.="<tr><td><a href=\"view_log?channel=$channelID&file=$entry\">$entry</a></td></tr>";
|
||||
}
|
||||
|
||||
sendTemplate("templates/view_logs.html", $aClient, {"channel"=>$channelName, "server"=>$serverName, "logs"=>$table});
|
||||
return 1;
|
||||
}
|
||||
when("/view_log") {
|
||||
my $channelID = $aRequest->{"path"}{"parameters"}{"channel"};
|
||||
if(!defined($channelID)) {
|
||||
sendBadRequest($aClient, "view_log requires channel URL parameter");
|
||||
return 1;
|
||||
}
|
||||
my $logFile = $aRequest->{"path"}{"parameters"}{"file"};
|
||||
if(!defined($channelID)) {
|
||||
sendBadRequest($aClient, "view_log requires file URL parameter");
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $query = $aConnection->prepare(qq(select channels.name, servers.name from channels inner join servers on channels.server_id=servers.id where channels.id=?;));
|
||||
$query->execute($channelID);
|
||||
my @row = $query->fetchrow_array();
|
||||
if(scalar(@row)==0) {
|
||||
sendBadRequest($aClient, "Unknown channel with ID $channelID");
|
||||
return 1;
|
||||
}
|
||||
my $channelName = $row[0];
|
||||
my $serverName = $row[1];
|
||||
my $logFilePath = "logs/".$serverName."/".$channelName."/".$logFile;
|
||||
|
||||
my $result = open(my $file, "<", $logFilePath);
|
||||
if(!$result) {
|
||||
sendBadRequest($aClient, "No log file $logFile for channel $channelName at $serverName");
|
||||
return 1;
|
||||
}
|
||||
my $content = readFullFile($file);
|
||||
close($file);
|
||||
|
||||
my $response = getBaseResponse(200, "OK");
|
||||
$response.="Content-Type: text/plain;charset=utf-8\r\n";
|
||||
$response.="Content-Length: ".length($content)."\r\n\r\n";
|
||||
$response.=$content;
|
||||
$aClient->send($response);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub sendResponse {
|
||||
my $aClient = $_[0];
|
||||
my $aRequest = $_[1];
|
||||
@ -424,7 +414,7 @@ sub sendResponse {
|
||||
if($path eq "/index.html" || $path eq "/index.htm") {
|
||||
$path = "/";
|
||||
}
|
||||
if(handlePath($aClient, $path, $aRequest, $aConnection)) {
|
||||
if(frontend_routes::handlePath($aClient, $path, $aRequest, $aConnection)) {
|
||||
return;
|
||||
}
|
||||
my $filePath = "static".$path;
|
||||
@ -443,6 +433,15 @@ sub sendResponse {
|
||||
$response.=$content;
|
||||
$aClient->send($response);
|
||||
}
|
||||
when(HTTP_METHOD_POST) {
|
||||
my $path = File::Spec->canonpath($aRequest->{"path"}{"url"});
|
||||
if($path eq "/index.html" || $path eq "/index.htm") {
|
||||
$path = "/";
|
||||
}
|
||||
if(!frontend_routes::handlePath($aClient, $path, $aRequest, $aConnection)) {
|
||||
sendNotFound($aClient);
|
||||
}
|
||||
}
|
||||
default {
|
||||
sendNotImplemented($aClient);
|
||||
}
|
||||
|
194
frontend_routes.pm
Normal file
194
frontend_routes.pm
Normal file
@ -0,0 +1,194 @@
|
||||
# irclogger_web
|
||||
# Copyright (C) 2023 mrkubax10 <mrkubax10@onet.pl>
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package frontend_routes;
|
||||
|
||||
use lib ".";
|
||||
use frontend_session;
|
||||
|
||||
use Digest::SHA;
|
||||
use Data::Dumper;
|
||||
|
||||
use feature qw(switch);
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub handlePath {
|
||||
my $aClient = $_[0];
|
||||
my $aPath = $_[1];
|
||||
my $aRequest = $_[2];
|
||||
my $aConnection = $_[3];
|
||||
|
||||
given($aPath) {
|
||||
when("/") {
|
||||
my $userbar;
|
||||
my $logged = 0;
|
||||
if(defined($aRequest->{"cookies"}) && defined($aRequest->{"cookies"}{"session"})) {
|
||||
my $session = $aRequest->{"cookies"}{"session"};
|
||||
if(frontend_session::isValidSession($session) && defined($frontend_session::sessions{$session}{"username"}) && $frontend_session::sessions{$session}{"logged"}) {
|
||||
my $username = $frontend_session::sessions{$session}{"username"};
|
||||
$userbar = "<a href=\"panel\">$username</a>";
|
||||
$userbar.="<a href=\"logout_action\">Log out</a>";
|
||||
$logged = 1;
|
||||
}
|
||||
}
|
||||
if(!$logged) {
|
||||
$userbar = "<form action=\"login_action\" method=\"POST\">";
|
||||
$userbar.="<input name=\"username\" type=\"text\" placeholder=\"Username\" /> ";
|
||||
$userbar.="<input name=\"password\" type=\"password\" placeholder=\"Password\" /> ";
|
||||
$userbar.="<input name=\"login\" type=\"submit\" value=\"Login\" />";
|
||||
$userbar.="</form>";
|
||||
}
|
||||
|
||||
my $query = $aConnection->prepare(qq(select channels.id, channels.name, servers.name from channels inner join servers on channels.server_id=servers.id where channels.public=1;));
|
||||
$query->execute();
|
||||
my $table = "";
|
||||
while(my @row = $query->fetchrow_array()) {
|
||||
my $channelID = $row[0];
|
||||
my $channelName = $row[1];
|
||||
my $serverName = $row[2];
|
||||
|
||||
$table.="<tr><td><a href=\"view_logs?channel=$channelID\">$channelName</a></td><td>$serverName</td></tr>";
|
||||
}
|
||||
|
||||
frontend::sendTemplate("templates/index.html", $aClient, {"userbar"=>$userbar, "publicChannels"=>$table});
|
||||
return 1;
|
||||
}
|
||||
when("/login_action") {
|
||||
if(defined($aRequest->{"cookies"}{"session"}) && frontend_session::isValidSession($aRequest->{"cookies"}{"session"})) {
|
||||
frontend::redirect($aClient, "/");
|
||||
return 1;
|
||||
}
|
||||
if(defined($aRequest->{"headers"}{"Content-Type"}) && $aRequest->{"headers"}{"Content-Type"} ne "application/x-www-form-urlencoded") {
|
||||
frontend::sendBadRequest($aClient, "Unsupported form Content-Type (application/x-www-form-urlencoded required)");
|
||||
return 1;
|
||||
}
|
||||
if(!defined($aRequest->{"content"})) {
|
||||
frontend::sendBadRequest($aClient, "Request content required");
|
||||
return 1;
|
||||
}
|
||||
|
||||
my %parameters = frontend::parsePathParameters($aRequest->{"content"});
|
||||
if(!defined($parameters{"username"})) {
|
||||
frontend::sendBadRequest($aClient, "Username parameter required");
|
||||
return 1;
|
||||
}
|
||||
if(!defined($parameters{"password"})) {
|
||||
frontend::sendBadRequest($aClient, "Password parameter required");
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $username = $parameters{'username'};
|
||||
#my $hashedPassword = Digest::SHA::sha256_hex($parameters{"password"});
|
||||
my $hashedPassword = $parameters{"password"};
|
||||
my $query = $aConnection->prepare(qq(select name, password from users where name=?;));
|
||||
$query->execute($username);
|
||||
my @row = $query->fetchrow_array();
|
||||
if(scalar(@row)==0) {
|
||||
frontend::sendBadRequest($aClient, "Unknown user $username");
|
||||
return 1;
|
||||
}
|
||||
if($row[1] ne $hashedPassword) {
|
||||
frontend::sendBadRequest($aClient, "Wrong password");
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $token = Digest::SHA::sha256_hex(sprintf("%x", rand(0xFFFFFFFF)%0xFF));
|
||||
$frontend_session::sessions{$token}{"username"} = $username;
|
||||
$frontend_session::sessions{$token}{"logged"} = 1;
|
||||
|
||||
my $response = frontend::getBaseResponse(301, "OK");
|
||||
$response.="Location: /\r\n";
|
||||
$response.="Content-Length: 0\r\n";
|
||||
$response.="Set-Cookie: session=$token\r\n\r\n";
|
||||
$aClient->send($response);
|
||||
return 1;
|
||||
}
|
||||
when("/view_logs") {
|
||||
my $channelID = $aRequest->{"path"}{"parameters"}{"channel"};
|
||||
if(!defined($channelID)) {
|
||||
frontend::sendBadRequest($aClient, "view_logs requires channel URL parameter");
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $query = $aConnection->prepare(qq(select channels.name, servers.name from channels inner join servers on channels.server_id=servers.id where channels.id=?;));
|
||||
$query->execute($channelID);
|
||||
my @row = $query->fetchrow_array();
|
||||
if(scalar(@row)==0) {
|
||||
frontend::sendBadRequest($aClient, "Unknown channel with ID $channelID");
|
||||
return 1;
|
||||
}
|
||||
my $channelName = $row[0];
|
||||
my $serverName = $row[1];
|
||||
my $logsPath = "logs/".$serverName."/".$channelName;
|
||||
|
||||
my $result = opendir(my $folder, $logsPath);
|
||||
if(!$result) {
|
||||
frontend::sendBadRequest($aClient, "Channel $channelName on $serverName doesn't have any logs");
|
||||
return 1;
|
||||
}
|
||||
my @entries = grep(!/^\.\.?$/, readdir($folder));
|
||||
|
||||
my $table = "";
|
||||
foreach my $entry (@entries) {
|
||||
$table.="<tr><td><a href=\"view_log?channel=$channelID&file=$entry\">$entry</a></td></tr>";
|
||||
}
|
||||
|
||||
frontend::sendTemplate("templates/view_logs.html", $aClient, {"channel"=>$channelName, "server"=>$serverName, "logs"=>$table});
|
||||
return 1;
|
||||
}
|
||||
when("/view_log") {
|
||||
my $channelID = $aRequest->{"path"}{"parameters"}{"channel"};
|
||||
if(!defined($channelID)) {
|
||||
frontend::sendBadRequest($aClient, "view_log requires channel URL parameter");
|
||||
return 1;
|
||||
}
|
||||
my $logFile = $aRequest->{"path"}{"parameters"}{"file"};
|
||||
if(!defined($channelID)) {
|
||||
frontend::sendBadRequest($aClient, "view_log requires file URL parameter");
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $query = $aConnection->prepare(qq(select channels.name, servers.name from channels inner join servers on channels.server_id=servers.id where channels.id=?;));
|
||||
$query->execute($channelID);
|
||||
my @row = $query->fetchrow_array();
|
||||
if(scalar(@row)==0) {
|
||||
frontend::sendBadRequest($aClient, "Unknown channel with ID $channelID");
|
||||
return 1;
|
||||
}
|
||||
my $channelName = $row[0];
|
||||
my $serverName = $row[1];
|
||||
my $logFilePath = "logs/".$serverName."/".$channelName."/".$logFile;
|
||||
|
||||
my $result = open(my $file, "<", $logFilePath);
|
||||
if(!$result) {
|
||||
frontend::sendBadRequest($aClient, "No log file $logFile for channel $channelName at $serverName");
|
||||
return 1;
|
||||
}
|
||||
my $content = frontend::readFullFile($file);
|
||||
close($file);
|
||||
|
||||
my $response = frontend::getBaseResponse(200, "OK");
|
||||
$response.="Content-Type: text/plain;charset=utf-8\r\n";
|
||||
$response.="Content-Length: ".length($content)."\r\n\r\n";
|
||||
$response.=$content;
|
||||
$aClient->send($response);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
30
frontend_session.pm
Normal file
30
frontend_session.pm
Normal file
@ -0,0 +1,30 @@
|
||||
# irclogger_web
|
||||
# Copyright (C) 2023 mrkubax10 <mrkubax10@onet.pl>
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package frontend_session;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our %sessions;
|
||||
|
||||
sub isValidSession {
|
||||
my $aSession = $_[0];
|
||||
|
||||
return defined($sessions{$aSession});
|
||||
}
|
||||
|
||||
1;
|
@ -4,6 +4,7 @@
|
||||
<title>irclogger_web</title>
|
||||
</head>
|
||||
<body>
|
||||
{{userbar}}
|
||||
<h2>Channel list</h2>
|
||||
<table border>
|
||||
<tr><th>Channel</th><th>Network</th></tr>
|
||||
|
Loading…
Reference in New Issue
Block a user