#!/usr/bin/perl -w use lib qw(public); use Mojolicious::Lite -signatures; use Path::Iterator::Rule; use WWWDBI; use Tools; use Mojo::Util qw(trim); use HTML::Entities qw(encode_entities); my $db = WWWDBI->new(); my $secret = $db->getAppSecret(); app->secrets([$secret]); app->sessions->cookie_name('session'); app->config( hypnotoad => { listen => [ 'http://127.0.0.1:8082/' ], proxy => 1, }, ); helper listFiles => sub { my @locations = ('public', 'templates'); my @all_files; my $rule = Path::Iterator::Rule->new->not_dir->name(qr/(pm|pl|js|css|ep)$/); for my $location (@locations) { push @all_files, $rule->all($location); } (my $file = __FILE__) =~ s{.*/}{}; push @all_files, $file; my @sorted = sort { fc($a) cmp fc($b) } @all_files; return @sorted; }; helper is_logged_in => sub ($c) { return $c->session('user') ? 1 : 0; }; helper is_allowed_ip => sub ($c, $ip) { my $allowed_range = '^10\.0\.0\.\d+$'; return $ip =~ /$allowed_range/; }; helper render_error => sub ($c, $message, $status = 400) { $c->app->log->error($message); return $c->render(json => { error => $message }, status => $status); }; get '/cwd' => sub { my $c = shift; $c->render(text => "CWD: " . Cwd::getcwd()); }; get '/' => sub ($c) { my $client_ip = $c->tx->remote_address; my $last_visit = $c->session('last_visit') || time(); $c->session(last_visit => time()); $c->stash( client_ip => $client_ip, last_visit => $last_visit, ); $c->stash(is_logged_in => $c->is_logged_in); $c->render(template => 'index'); }; get '/protected' => sub ($c) { return $c->redirect_to('/login') unless $c->is_logged_in; $c->render(text => 'This is a protected page'); }; get '/source' => sub ($c) { if ($c->param('f') =~ m{^(public|templates)/} || $c->param('f') eq (__FILE__ =~ s{.*/}{}r)) { my $text = source($c->param('f')); $c->render(text => $text, format => 'txt'); } else { $c->render(text => "Access denied.", format => 'txt', status => 403); } }; get '/age' => sub ($c) { my $dob = WWWDBI->new()->dob(); my @andrea = howOld($dob->{andrea}->{dob}); my @nicky = howOld($dob->{nicky}->{dob}); $c->render( json => { andrea => $andrea[0], andreas => $andrea[1], nicky => $nicky[0], nickys => $nicky[1], }); }; get '/t' => sub ($c) { $c->render(template => 't') }; my $rNum = sub ($c) { $c->render(template => 'p') }; get '/m' => $rNum; get '/p' => $rNum; get '/phone' => $rNum; get '/mobile' => $rNum; get '/copy' => sub ($c) { #unless ($c->session('user')) { # return $c->redirect_to('/login'); #} my @msgs = WWWDBI->new()->getPasted(); my $client_ip = $c->tx->remote_address; my $is_allowed = $c->is_allowed_ip($client_ip); $c->stash(messages => \@msgs, client_ip => $client_ip, is_allowed => $is_allowed); $c->render(template => 'copy'); }; post '/copy' => sub ($c) { my $text = trim($c->param('paste') // ''); return $c->render_error('Input too long') if length($text) > 250; $text = encode_entities($text); if ($text =~ m{^https?://}i) { unless ($text =~ m{^https?://[\w\-]+(?:\.[\w\-]+)+(?:/[^\s]*)?$}i) { return $c->render_error('Invalid URL'); } } $db->paste($text); $db->pushOver($text); my @msgs = $db->getPasted(); my $client_ip = $c->tx->remote_address; my $is_allowed = $c->is_allowed_ip($client_ip); $c->stash(messages => \@msgs, client_ip => $client_ip, is_allowed => $is_allowed); $c->render(template => 'copy'); }; post '/delete' => sub ($c) { unless ($c->is_logged_in && $c->is_allowed_ip($c->tx->remote_address)) { return $c->render_error('Unauthorized', 403); } my $id = $c->param('id'); unless (defined $id && $id =~ /^\d+$/) { return $c->render_error('Invalid ID'); } $db->deleteMessage($id); $c->redirect_to('/copy'); }; get 'this.is.totally.not.sus' => sub ($c) { $c->render(template => 'sus'); }; sub contact_page ($c) { my @qr_images = qw(discord.png email.png line.jpg messenger.png); $c->render(template => 'contact', qr_images => \@qr_images); } get '/contacts' => \&contact_page; get '/contact' => \&contact_page; get '/c' => \&contact_page; get '/login' => sub ($c) { $c->render(template => 'login'); }; post '/login' => sub ($c) { my $username = trim($c->param('username') // ''); my $password = $c->param('password'); if ($db->authenticateUser($username, $password)) { $c->session(user => $username); $c->app->log->info("User $username logged in from IP " . $c->tx->remote_address); return $c->redirect_to('/'); } else { $c->app->log->warn("Failed login attempt for user $username from IP " . $c->tx->remote_address); return $c->render_error('Login failed'); } }; get '/logout' => sub ($c) { $c->session(expires => 1); return $c->redirect_to('/'); }; get '/register' => sub ($c) { $c->render(template => 'register'); }; post '/register' => sub ($c) { my $username = trim($c->param('username') // ''); my $password = $c->param('password'); my $email = trim($c->param('email') // ''); return $c->render_error('Invalid username') unless $username =~ /^[a-zA-Z0-9_]{3,20}$/; return $c->render_error('Password too short') if length($password) < 8; return $c->render_error('Invalid email') unless $email =~ /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; if ($db->userExists($username)) { return $c->render_error('Username already exists'); } eval { $db->createUser($username, $password, $email); }; if (my $error = $@) { $c->app->log->error("Failed to create user: $error"); return $c->render_error("Error creating user: $error", 500); } $c->session(user => $username); $c->app->log->info("New user registered: $username from IP " . $c->tx->remote_address); return $c->redirect_to('/'); }; get '/users' => sub ($c) { # Check if user is logged in unless ($c->session('user')) { return $c->redirect_to('/login'); } # Check if user is admin my $username = $c->session('user'); unless ($db->is_admin($username)) { return $c->render(text => 'Access denied', status => 403); } # Fetch all users my $users = $db->get_all_users(); # Render the users admin page $c->stash(users => $users); $c->render(template => 'users_admin'); }; helper is_admin => sub ($c) { return 0 unless $c->session('user'); return $db->is_admin($c->session('user')); }; app->start;