2023-09-08 22:05:21 +02:00
# 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 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 ;
}
2023-09-09 10:52:28 +02:00
my $ token = frontend_session:: newSessionToken ( ) ;
2023-09-08 22:05:21 +02:00
$ 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 ;
}
2023-09-09 10:58:26 +02:00
when ( "/logout_action" ) {
if ( defined ( $ aRequest - > { "cookies" } { "session" } ) ) {
frontend_session:: deleteSession ( $ aRequest - > { "cookies" } { "session" } ) ;
}
frontend:: redirect ( $ aClient , "/" ) ;
return 1 ;
}
2023-09-10 14:30:40 +02:00
when ( "/panel" ) {
if ( ! defined ( $ aRequest - > { "cookies" } { "session" } ) || ! frontend_session:: isValidSession ( $ aRequest - > { "cookies" } { "session" } ) ) {
frontend:: redirect ( $ aClient , "/" ) ;
return 1 ;
}
my $ session = $ frontend_session:: sessions { $ aRequest - > { "cookies" } { "session" } } ;
my $ query = $ aConnection - > prepare ( qq( select privileges from users where name=?; ) ) ;
$ query - > execute ( $ session - > { "username" } ) ;
my @ row = $ query - > fetchrow_array ( ) ;
my $ privileges = $ row [ 0 ] ;
my $ manageChannelAccess = "" ;
if ( $ privileges >= 1 ) { # moderator
$ manageChannelAccess . = "<h3>Manage channel access</h3>" ;
$ manageChannelAccess . = "<form action=\"manage_access_action\" method=\"POST\">" ;
$ manageChannelAccess . = "<select name=\"user\">" ;
$ query = $ aConnection - > prepare ( qq( select id, name from users; ) ) ;
$ query - > execute ( ) ;
while ( @ row = $ query - > fetchrow_array ( ) ) {
my $ id = $ row [ 0 ] ;
my $ name = $ row [ 1 ] ;
if ( $ name eq $ session - > { "username" } ) {
next ;
}
$ manageChannelAccess . = "<option value=\"$id\">$name</option>" ;
}
$ manageChannelAccess . = "</select>" ;
$ manageChannelAccess . = "<select name=\"channel\">" ;
$ query = $ aConnection - > prepare ( qq( select channels.id, channels.name, servers.name from channels inner join servers on channels.server_id=servers.id; ) ) ;
$ query - > execute ( ) ;
while ( @ row = $ query - > fetchrow_array ( ) ) {
my $ channelID = $ row [ 0 ] ;
my $ channel = $ row [ 1 ] ;
my $ server = $ row [ 2 ] ;
$ manageChannelAccess . = "<option value=\"$channelID\">$channel at $server</option>" ;
}
$ manageChannelAccess . = "</select><br />" ;
$ manageChannelAccess . = "<input name=\"grant\" type=\"submit\" value=\"Grant access\" /> " ;
$ manageChannelAccess . = "<input name=\"revoke\" type=\"submit\" value=\"Revoke access\" />" ;
$ manageChannelAccess . = "</form>" ;
}
my $ manageServers = "" ;
if ( $ privileges == 2 ) {
$ manageServers . = "<h3>Manage servers</h3>" ;
$ manageServers . = "<form action=\"add_server_action\" method=\"POST\">" ;
$ manageServers . = "<input name=\"name\" type=\"text\" placeholder=\"Server name\" /><br />" ;
$ manageServers . = "<input name=\"address\" type=\"text\" placeholder=\"Server address\" /> " ;
$ manageServers . = "<input name=\"port\" type=\"number\" placeholder=\"Server port\" /><br />" ;
$ manageServers . = "<input type=\"submit\" value=\"Add\" />" ;
$ manageServers . = "</form>" ;
}
my $ manageChannels = "" ;
if ( $ privileges == 2 ) {
$ manageChannels . = "<h3>Manage channels</h3>" ;
$ manageChannels . = "<form action=\"add_channel_action\" method=\"POST\">" ;
$ manageChannels . = "<input name=\"channel\" type=\"text\" placeholder=\"Channel\" /> at " ;
$ manageChannels . = "<select name=\"server\">" ;
$ query = $ aConnection - > prepare ( qq( select id, name from servers; ) ) ;
$ query - > execute ( ) ;
while ( @ row = $ query - > fetchrow_array ( ) ) {
my $ id = $ row [ 0 ] ;
my $ name = $ row [ 1 ] ;
$ manageChannels . = "<option value=\"$id\">$name</option>" ;
}
$ manageChannels . = "</select><br />" ;
$ manageChannels . = "<input type=\"submit\" value=\"Add\" />" ;
$ manageChannels . = "</form>" ;
}
my $ addUser = "" ;
if ( $ privileges == 2 ) {
$ addUser . = "<h3>Add user</h3>" ;
$ addUser . = "<form action=\"add_user_action\" method=\"POST\">" ;
$ addUser . = "<input name=\"name\" type=\"text\" placeholder=\"Username\" /><br />" ;
$ addUser . = "<input name=\"password\" type=\"password\" placeholder=\"Password\" /><br />" ;
$ addUser . = "<input name=\"confirmPassword\" type=\"password\" placeholder=\"Confirm password\" /><br />" ;
$ addUser . = "<input type=\"submit\" value=\"Add\" />" ;
$ addUser . = "</form>" ;
}
frontend:: sendTemplate ( "templates/panel.html" , $ aClient , { "username" = > $ session - > { "username" } , "manageChannelAccess" = > $ manageChannelAccess , "manageServers" = > $ manageServers , "manageChannels" = > $ manageChannels , "addUser" = > $ addUser } ) ;
return 1 ;
}
2023-09-10 18:57:44 +02:00
when ( "/change_password_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 $ session = $ frontend_session:: sessions { $ aRequest - > { "cookies" } { "session" } } ;
my % parameters = frontend:: parsePathParameters ( $ aRequest - > { "content" } ) ;
if ( ! defined ( $ parameters { "currentPassword" } ) ) {
frontend:: sendBadRequest ( $ aClient , "Current password parameter required" ) ;
return 1 ;
}
if ( ! defined ( $ parameters { "newPassword" } ) ) {
frontend:: sendBadRequest ( $ aClient , "New password parameter required" ) ;
return 1 ;
}
my $ query = $ aConnection - > prepare ( qq( select password from users where name=?; ) ) ;
$ query - > execute ( $ session - > { "username" } ) ;
my @ row = $ query - > fetchrow_array ( ) ;
my $ password = $ row [ 0 ] ;
if ( $ parameters { "currentPassword" } ne $ password ) {
frontend:: sendBadRequest ( $ aClient , "Wrong password" ) ;
return 1 ;
}
if ( $ parameters { "newPassword" } eq $ password ) {
frontend:: sendBadRequest ( $ aClient , "New password and current password match" ) ;
return 1 ;
}
$ query = $ aConnection - > prepare ( qq( update users set password=? where name=?; ) ) ;
$ query - > execute ( $ parameters { "newPassword" } , $ session - > { "username" } ) ;
frontend:: redirect ( $ aClient , "/password_changed.html" ) ;
return 1 ;
}
2023-09-08 22:05:21 +02:00
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 ;