package BeastBB::DAO::UserManager; use 5.32.1; use strict; use warnings; use Carp; use Params::ValidationCompiler 'validation_for'; use Types::Standard qw/Bool Str Int/; use Crypt::Bcrypt::Easy; use Const::Fast; use DateTime; use DateTime::Format::Pg; use BeastBB::Types ( '$MATRIX_ADDRESS_REGEX', 'IsClassTypeGenerator', '$MATRIX_ADDRESS_TYPE' ); use BeastBB::DAO::GroupManager; use BeastBB::Model::User; use BeastBB::Response; const my $MINIMUM_PASSWORD_LENGHT => 8; { my $validator = validation_for( params => { app => { type => IsClassTypeGenerator('Mojolicious::Controller') }, } ); sub new { my $class = shift; my %params = $validator->(@_); return bless \%params, $class; } } { my $validator = validation_for( params => { id_user => { type => Int, optional => 1 }, username => { type => Str, optional => 1 }, matrix_address => { type => $MATRIX_ADDRESS_TYPE, optional => 1 }, recover_group => { type => Bool, default => 0 }, } ); sub Get { my $self = shift; my %params = $validator->(@_); my $id_user = $params{id_user}; my $username = $params{username}; my $matrix_address = $params{matrix_address}; my $recover_group = $params{recover_group}; confess 'You should pass id_user xor matrix_address xor username to Get.' unless ( defined $id_user xor defined $username xor defined $matrix_address ); my $maybe_user_hash = $self->_RecoverUserFromDatabase( ( ( defined $id_user ) ? ( id_user => $id_user ) : () ), ( ( defined $username ) ? ( username => $username ) : () ), ( ( defined $matrix_address ) ? ( matrix_address => $matrix_address ) : () ) ); return $maybe_user_hash if $maybe_user_hash->IsError; my $user_hash = $maybe_user_hash->Content; my $id_group = delete $user_hash->{id_group}; my $user = BeastBB::Model::User->new(%$user_hash); if ($recover_group) { my $maybe_recovered_group = $self->_RecoverUserGroup( id_group => $id_group, user => $user ); return $maybe_recovered_group if $maybe_recovered_group->IsError; } return BeastBB::Response->new( content => $user ); } } { my $validator = validation_for( params => { id_group => { type => Int }, user => { type => IsClassTypeGenerator('BeastBB::Model::User') }, } ); sub _RecoverUserGroup { my $self = shift; my %params = $validator->(@_); my $id_group = $params{id_group}; my $user = $params{user}; my $app = $self->_App; my $group_manager = BeastBB::DAO::GroupManager->new( app => $app ); my $maybe_group = $group_manager->Get( id_group => $id_group ); if ( $maybe_group->IsError ) { return $maybe_group; } my $group = $maybe_group->Content; $user->Group($group); return BeastBB::Response->new; } } { my $validator = validation_for( params => { id_user => { type => Int, optional => 1 }, username => { type => Str, optional => 1 }, matrix_address => { type => $MATRIX_ADDRESS_TYPE, optional => 1 }, } ); sub _RecoverUserFromDatabase { my $self = shift; my %params = $validator->(@_); my $app = $self->_App; my $database = $app->db; my $pg = $database->Pg->db; my ( $id_user, $username, $matrix_address ) = @params{ 'id_user', 'username', 'matrix_address' }; my $results = $pg->select( 'user', [ 'id_user', 'username', 'matrix_address', 'password_bcrypt', 'is_confirmed', 'creation_date', 'id_group', 'last_connection', ], { ( ( defined $id_user ) ? ( id_user => $id_user ) : () ), ( ( defined $username ) ? ( username => $username ) : () ), ( ( defined $matrix_address ) ? ( matrix_address => $matrix_address ) : () ) } ); if ( !$results->rows ) { return BeastBB::Response->new( is_error => 1, error_message => 'No such user found.', ); } my $user_hash = $results->hash; $user_hash->{creation_date} = DateTime::Format::Pg->parse_datetime( $user_hash->{creation_date} ); $user_hash->{last_connection} = DateTime::Format::Pg->parse_datetime( $user_hash->{last_connection} ); return BeastBB::Response->new( content => $user_hash ); } } { my $validator = validation_for( params => { username => { type => Str }, matrix_address => { type => Str }, password => { type => Str }, repeat_password => { type => Str }, is_confirmed => { type => Bool }, groupname => { type => Str }, } ); sub Create { my $self = shift; my %params = $validator->(@_); my ( $username, $matrix_address, $password, $repeat_password, $is_confirmed, $groupname ) = @params{ 'username', 'matrix_address', 'password', 'repeat_password', 'is_confirmed', 'groupname' }; my $app = $self->_App; my $database = $app->db; my $pg = $database->Pg->db; my $response_check_user_creation_requeriments = $self->CheckUserCreationRequeriments( matrix_address => $matrix_address, password => $password, repeat_password => $repeat_password ); return $response_check_user_creation_requeriments if $response_check_user_creation_requeriments->IsError; my $result = $pg->select( 'group', 'id_group', { groupname => $groupname } ); if ( !$result->rows ) { return BeastBB::Response->new( is_error => 1, error_message => "Unable to find the group $groupname." ); } my $id_group = $result->hash->{id_group}; my $result_create_user = $pg->insert( 'user', { username => $username, password_bcrypt => bcrypt->crypt($password), matrix_address => $matrix_address, is_confirmed => $is_confirmed ? 1 : 0, creation_date => "" . DateTime->now, last_connection => "" . DateTime->now, id_group => $id_group, }, { returning => ['id_user'], } ); if ( !$result_create_user->rows ) { return BeastBB::Response->new( is_error => 1, error_message => "Unable to create user $username.", ); } return BeastBB::Response->new( content => $result_create_user->hash->{id_user} ); } } { my $validator = validation_for( params => { matrix_address => { type => Str }, password => { type => Str }, repeat_password => { type => Str }, } ); sub CheckUserCreationRequeriments { my $self = shift; my %params = $validator->(@_); my ( $matrix_address, $password, $repeat_password ) = @params{ 'matrix_address', 'password', 'repeat_password' }; my $response = $self->CheckMatrixAddress( matrix_address => $matrix_address ); return $response if $response->IsError; $response = $self->CheckPasswordLength( password => $password ); return $response if $response->IsError; $response = $self->CheckPasswordNotOnlyNumeric( password => $password ); return $response if $response->IsError; $response = $self->CheckTwoPasswordsEqual( password => $password, repeat_password => $repeat_password ); return $response; } } { my $validator = validation_for( params => { password => { type => Str }, repeat_password => { type => Str }, } ); sub CheckTwoPasswordsEqual { my $self = shift; my %params = $validator->(@_); my $password = $params{password}; my $repeat_password = $params{repeat_password}; my $error_message; if ( $password ne $repeat_password ) { $error_message = 'Password and repeat password not matching.'; } return BeastBB::Response->new( ( ( defined $error_message ) ? ( is_error => 1, error_message => $error_message ) : () ) ); } } { my $validator = validation_for( params => { password => { type => Str }, } ); sub CheckPasswordNotOnlyNumeric { my $self = shift; my %params = $validator->(@_); my $password = $params{'password'}; my $error_message; if ( $password =~ /^\d+$/ ) { $error_message = "Password is numeric, it is not allowed."; } return BeastBB::Response->new( ( ( defined $error_message ) ? ( is_error => 1, error_message => $error_message ) : () ) ); } } { my $validator = validation_for( params => { password => { type => Str }, } ); sub CheckPasswordLength { my $self = shift; my %params = $validator->(@_); my $password = $params{'password'}; my $error_message; if ( !length($password) > $MINIMUM_PASSWORD_LENGHT ) { $error_message = "Password has less than $MINIMUM_PASSWORD_LENGHT characters."; } return BeastBB::Response->new( ( ( defined $error_message ) ? ( is_error => 1, error_message => $error_message ) : () ) ); } } { my $validator = validation_for( params => { matrix_address => { type => Str } }, ); sub CheckMatrixAddress { my $self = shift; my %params = $validator->(@_); my $matrix_address = $params{matrix_address}; my $error_message; if ( $matrix_address !~ /$MATRIX_ADDRESS_REGEX/ ) { $error_message = "This does not look like a Matrix address."; } return BeastBB::Response->new( ( ( defined $error_message ) ? ( is_error => 1, error_message => $error_message ) : () ) ); } } sub _App { my $self = shift; return $self->{app}; } 1;