From 46690f9241fab95b852fb7c25810dd00d3a865f0 Mon Sep 17 00:00:00 2001 From: sergiotarxz Date: Mon, 19 Dec 2022 00:49:53 +0100 Subject: [PATCH] Adding User Controller. --- lib/MyRedland/Controller/User.pm | 487 +++++++++++++++++++++++++++++++ 1 file changed, 487 insertions(+) create mode 100644 lib/MyRedland/Controller/User.pm diff --git a/lib/MyRedland/Controller/User.pm b/lib/MyRedland/Controller/User.pm new file mode 100644 index 0000000..41205d0 --- /dev/null +++ b/lib/MyRedland/Controller/User.pm @@ -0,0 +1,487 @@ +package MyRedland::Controller::User; + +use v5.34.1; + +use strict; +use warnings; + +use Digest::SHA qw/sha512_hex/; + +use DateTime; + +use Mojo::Base 'Mojolicious::Controller'; +use Crypt::URandom qw/urandom/; +use Crypt::Bcrypt qw/bcrypt/; +use Capture::Tiny qw/capture/; + +use MyRedland::Categories; +use MyRedland::Lusers; +use Mojo::Template; +use Mojo::URL; +use Mojo::Util qw/url_escape/; + +use MyRedland::Mail; +use Path::Tiny; + +my $PROJECT_ROOT = path(__FILE__)->parent->parent->parent->parent; +my $UPLOADS = $PROJECT_ROOT->child('uploads'); +$UPLOADS->mkpath; +my $mt = Mojo::Template->new( auto_escape => 1 ); + +sub login_get { + my $self = shift; + if ( my $user = $self->current_user ) { + $self->_already_logged_in; + return; + } + my $categories = MyRedland::Categories->new->Retrieve; + $self->stash( categories => $categories ); + $self->stash( current_slug => $categories->{index}{slug} ); + $self->render; +} + +sub setup_avatar { + my $self = shift; + my $upload = $self->req->upload('file'); + my $user = $self->current_user; + if (!defined $user) { + $self->render( + status => 401, + json => { + status => 401, + code => 'NOTLOGGED', + description => 'No estás loggeado', + } + ); + return; + } + if (!defined $user) { + $self->render( + status => 401, + json => { + status => 401, + code => 'NOTVALIDYET', + description => 'Tu usuario no está validado, comprueba tu correo electrónico.', + } + ); + return; + } + if ( !defined $upload ) { + $self->render( + status => 400, + json => { + status => 400, + code => 'NOFILE', + description => +'No se recibió ningún fichero. (¿Usaste un multipart/form-data?)' + } + ); + return; + } + my $tempdir = Path::Tiny->tempdir; + my $file = $tempdir->child('tempfile'); + $upload->move_to($file); + system 'file', $file; + my ( $stdout, $stderr, $error ) = capture { + system qw/identify -format "%wx%h"/, $file; + }; + if ( $error != 0 ) { + $self->render( + status => 400, + json => { + status => 400, + code => 'INVFILE', + description => + 'Archivo inválido. (El comando identify no lo reconoció)' + } + ); + return; + } + my ( $width, $height ) = $stdout =~ /^"(\d+)x(\d+)"$/; + if ( $width < 50 ) { + $self->render( + status => 400, + json => { + status => 400, + code => 'SMALLFILE', + description => + 'El archivo debería tener 50x50 píxeles como mínimo.' + } + ); + return; + } + + if ( $width != $height ) { + $self->render( + status => 400, + json => { + status => 400, + code => 'NOTASQUARE', + description => +'La imagen debe ser cuadrada, debe tener el mismo numero de pixeles de ancho que de alto.' + } + ); + return; + } + my $converted_file = $tempdir->child('converted.png'); + ( $stdout, $stderr, $error ) = capture { + system 'convert', $file, $converted_file; + }; + if ($error != 0) { + say STDERR $stdout; + say STDERR $stderr; + $self->render( + status => 500, + json => { + status => 500, + code => 'SERVERCONVERSIONFAILED', + description => 'El servidor no fué capaz de convertir el fichero a png, prueba a enviar otro formato.', + } + ); + return; + } + my $sha512 = sha512_hex($converted_file->slurp); + $user->avatar($sha512); + my $users_dao = MyRedland::Lusers->new( app => $self->app ); + $user = $users_dao->update( user => $user, fields => [qw/avatar/] ); + system 'cp', $converted_file, $UPLOADS->child($sha512); + + $self->render( + status => 200, + json => { + status => 200, + code => 'SUCESS', + description => 'Your avatar was correctly setup.', + } + ); +} + +sub get_avatar { + my $self = shift; + my $user = $self->current_user; + if (!defined $user) { + $self->render(status => 401, text => 'Still not logged in.'); + return; + } + if (!$user->avatar) { + $self->render(status => 400, text => 'Avatar still not setup.'); + return; + } + $self->render( + format => 'png', + data => $UPLOADS->child($user->avatar)->slurp + ); + +} + +sub setup_avatar_get { + my $self = shift; + my $user = $self->current_user; + if ( !defined $user ) { + $self->_must_be_logged; + return; + } + if ( !$user->verified ) { + $self->_must_be_verified; + return; + } + $self->render; + return; +} + +sub _must_be_verified { + my $self = shift; + $self->render( + status => 400, + text => 'Debes verificar tu correo antes de hacer eso' + ); +} + +sub _already_logged_in { + my $self = shift; + $self->render( text => 'Ya estás loggeado.', status => 403 ); +} + +sub login { + my $self = shift; + if ( my $user = $self->current_user ) { + $self->_already_logged_in; + return; + } + my $username = $self->param('username'); + my $password = $self->param('password'); + my $users_dao = MyRedland::Lusers->new( app => $self->app ); + my $user = $users_dao->find_by_username( username => $username ); + my $ip = $self->tx->remote_address; + if ( !$user ) { + say "No such user for login $username from $ip."; + $self->_invalid_login; + return; + } + if ( !$user->check_password($password) ) { + say "Invalid login attempt for @{[$user->username]} from $ip."; + $self->_invalid_login; + return; + } + $self->_create_session_for_user($user); + $self->res->headers->location('/perfil'); + $self->render( + status => 302, + text => 'Exito logueandose, redirigiendo...' + ); +} + +sub _invalid_login { + my $self = shift; + $self->render( text => 'Login invalido.', status => 401 ); +} + +sub sign_up_get { + my $self = shift; + if ( my $user = $self->current_user ) { + $self->_already_logged_in; + return; + } + my $categories = MyRedland::Categories->new->Retrieve; + $self->stash( categories => $categories ); + $self->stash( current_slug => $categories->{index}{slug} ); + $self->render; +} + +sub sign_up { + my $self = shift; + if ( my $user = $self->current_user ) { + $self->_already_logged_in; + return; + } + my $username = $self->param('username'); + my $password = $self->param('password'); + my $repeat_password = $self->param('repeat_password'); + my $email = $self->param('email'); + if ( !defined $username || length($username) < 5 ) { + $self->render( + text => 'El nombre de usuario debe ser mayor a 4 caracteres.', + status => 400 + ); + return; + } + if ( $username =~ /\// ) { + $self->render( + text => '"/" es un carácter prohibido en el nombre de usuario', + status => 400, + ); + return; + } + if ( !defined $password + || !defined $repeat_password + || length $password < 10 ) + { + $self->render( + text => 'La contraseña debe contener al menos 10 caracteres.', + status => 400 + ); + return; + } + if ( $password =~ /^\d*$/ ) { + $self->render( + text => +'La contraseña no puede estar compuesta solo por números debido a que es extremadamente inseguro.', + status => 400 + ); + return; + } + if ( !defined $email || $email !~ /@.*\.\w+$/ ) { + $self->render( + text => +'El email que has enviado no parece valido, contacta con contact@owlcode.tech desde dicho email si estás seguro que lo es.', + status => 400 + ); + return; + } + if ( $password ne $repeat_password ) { + $self->render( + text => 'La contraseña no coincide con la repetición.', + status => 400 + ); + return; + } + my $password_hash = bcrypt( $password, '2b', 12, urandom(16) ); + + my $users_dao = MyRedland::Lusers->new( app => $self->app ); + my $user; + eval { + $user = $users_dao->create( + username => $username, + password => $password_hash, + email => $email + ); + }; + if ($@) { + say STDERR $@; + if ( $@ =~ /duplicate.*username/ ) { + $self->render( + text => +'El nombre de usuario ya existe, prueba a recuperar contraseña.', + status => 400 + ); + return; + } + if ( $@ =~ /duplicate.*email/ ) { + $self->render( + text => 'El email ya existe, prueba a recuperar contraseña.', + status => 400 + ); + return; + } + $self->render( + text => +'Error desconocido de base de datos contacta con contact@owlcode.tech.', + status => 500 + ); + return; + } + + $self->_create_session_for_user($user); + say "Created user @{[$user->username]} with email @{[$user->email]}."; + if ( $user->verified ) { + $self->res->headers->location('/perfil/configura-tu-avatar'); + } + else { + my $mailer = MyRedland::Mail->new( app => $self->app ); + my $current_url = $self->req->url->to_abs; + $current_url->path(''); + $current_url->query(''); + my $url = Mojo::URL->new( +"${current_url}usuario/@{[url_escape($user->username)]}/verificacion" + ); + $url->query( a => $user->mail_verification_payload ); + my $html_email = $mt->render( <<'EOF', $url ); +% my ($url) = @_; + + +

Hemos recibido una solicitud de registro en Redland Official con tu dirección de correo, si no has sido tú por favor ignora este email.

+ +

El enlace de verificación es <%= $url %>.

+ + +EOF + my $text_email = <<"EOF"; +Hemos recibido una solicitud de registro en Redland Official con tu dirección de correo, si no has sido tú por favor ignora este email. + +El enlace de verificación es $url. +EOF + $mailer->sendmail( + to => $user->email, + subject => 'Verifica tu email.', + html => $html_email, + text => $text_email, + ); + $self->res->headers->location('/perfil/verifica-el-correo'); + } + $self->render( text => 'Login success.', status => 302 ); +} + +sub user_verification { + my $self = shift; + my $username_to_verify = $self->param('username'); + my $verification_payload = $self->param('a'); + my $user = $self->current_user; + if ( !defined $user ) { + my $url = Mojo::URL->new('/login'); + my $current_url = $self->req->url; + $url->query( redirect => $current_url ); + $self->res->headers->location($url); + $self->render( text => 'Debes loggearte antes.', status => 302 ); + return; + } + if ( $user->username ne $username_to_verify ) { + $self->render( + text => +'Has tratado de verificar una cuenta estando loggeado con otra, este incidente será reportado para futuras acciones de moderación.', + status => 403 + ); + say STDERR +"@{[$user->username]} attempted to verify the account $username_to_verify, possible hacking or multiaccount."; + return; + } + if ( $verification_payload ne $user->mail_verification_payload ) { + $self->render( + text => 'La url de verificación no es correcta, prueba de nuevo.', + status => 400 + ); + return; + } + my $current_date = DateTime->now; + if ( $current_date > $user->mail_verification_expiration ) { + $self->render( + text => 'Este enlace ya expiró, genera otro desde tu perfil', + status => 400 + ); + return; + } + $user->verified(1); + my $users_dao = MyRedland::Lusers->new( app => $self->app ); + $user = $users_dao->update( user => $user, fields => [qw/verified/] ); + say "@{[$user->username]} was verified."; + $self->res->headers->location('/perfil/configura-tu-avatar'); + $self->render( text => 'Usuario verificado con exito.', status => 302 ); +} + +sub _create_session_for_user { + my $self = shift; + my $user = shift; + my $session = $self->session; + if ( !defined $user ) { + die "User is not defined."; + } + $session->{user_uuid} = $user->uuid; +} + +sub _must_be_logged { + my $self = shift; + $self->render( + text => 'Debes estar loggeado para acceder esta parte.', + status => 401 + ); +} + +sub logout_get { + my $self = shift; + my $user = $self->current_user; + if ( !defined $user ) { + $self->_must_be_logged; + return; + } + my $categories = MyRedland::Categories->new->Retrieve; + $self->stash( categories => $categories ); + $self->stash( current_slug => $categories->{index}{slug} ); + $self->render; +} + +sub profile { + my $self = shift; + my $user = $self->current_user; + if ( !defined $user ) { + $self->_must_be_logged; + return; + } + my $categories = MyRedland::Categories->new->Retrieve; + $self->stash( categories => $categories ); + $self->stash( current_slug => $categories->{index}{slug} ); + $self->render; +} + +sub logout { + my $self = shift; + my $user = $self->current_user; + if ( !defined $user ) { + $self->_must_be_logged; + return; + } + if ( defined $self->param('yes') ) { + delete $self->session->{user_uuid}; + } + $self->res->headers->location('/'); + $self->render( text => 'Action succeded', status => 302 ); +} +1;