Adding initial login support.

This commit is contained in:
Sergiotarxz 2023-11-19 23:14:02 +01:00
parent 61b0066f0a
commit 1447b2fa6e
8 changed files with 128 additions and 29 deletions

View File

@ -31,6 +31,7 @@ my $build = Module::Build->new(
'DBIx::Class' => 0,
'UUID::URandom' => 0,
'Crypt::Bcrypt' => 0,
'DBIx::Class::TimeStamp' => 0,
},
);
$build->create_build_script;

View File

@ -16,6 +16,9 @@ export default class Conquer {
private conquerLoginGoToRegister: HTMLAnchorElement
private conquerLoginError: HTMLParagraphElement
private conquerLoginSuccess: HTMLParagraphElement
private conquerLoginUsername: HTMLInputElement
private conquerLoginPassword: HTMLInputElement
private conquerLoginSubmit: HTMLButtonElement
private conquerRegisterGoToLogin: HTMLAnchorElement
private conquerRegister: HTMLDivElement
private conquerRegisterUsername: HTMLInputElement
@ -73,7 +76,54 @@ export default class Conquer {
Conquer.fail('Unable to find conquer login success.')
}
this.conquerLoginSuccess = conquerLoginSuccess
const conquerLoginUsername = document.querySelector('.conquer-login-username')
if (conquerLoginUsername === null || !(conquerLoginUsername instanceof HTMLInputElement)) {
Conquer.fail('Unable to find conquer login username field.')
}
this.conquerLoginUsername = conquerLoginUsername
const conquerLoginPassword = document.querySelector('.conquer-login-password')
if (conquerLoginPassword === null || !(conquerLoginPassword instanceof HTMLInputElement)) {
Conquer.fail('Unable to find conquer login password field.')
}
this.conquerLoginPassword = conquerLoginPassword
const conquerLoginSubmit = document.querySelector('.conquer-login-submit')
if (conquerLoginSubmit === null || !(conquerLoginSubmit instanceof HTMLButtonElement)) {
Conquer.fail('Unable to find the submit button for the login.')
}
this.conquerLoginSubmit = conquerLoginSubmit
this.conquerLoginSubmit.addEventListener('click', () => {
this.onLoginRequested()
});
}
async onLoginRequested(): Promise<void> {
const username = this.conquerLoginUsername.value;
const password = this.conquerLoginPassword.value;
const urlUser = new URL('/conquer/user/login', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
let responseJson
let status
try {
const response = await fetch(urlUser, {
method: 'POST',
body: JSON.stringify({
username: username,
password: password,
})
})
responseJson = await response.json()
status = response.status
} catch(e) {
console.error(e)
this.addNewLoginError('El servidor ha enviado datos inesperados.')
return;
}
if (status !== 200) {
this.addNewLoginError(responseJson.error)
return
}
this.unsetLoginAndRegisterErrors()
}
async goToRegister(): Promise<void> {
const isLogged = await this.isLogged();
await this.removeLoginRegisterCombo()
@ -150,22 +200,25 @@ export default class Conquer {
const password = this.conquerRegisterPassword.value
const repeatPassword = this.conquerRegisterRepeatPassword.value
const urlUser = new URL('/conquer/user', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
const response = await fetch(urlUser, {
method: 'PUT',
body: JSON.stringify({
username: username,
password: password,
repeat_password: repeatPassword
})
})
let responseJson
let status
try {
const response = await fetch(urlUser, {
method: 'PUT',
body: JSON.stringify({
username: username,
password: password,
repeat_password: repeatPassword
})
})
responseJson = await response.json()
status = response.status
} catch(e) {
console.error(e)
this.addNewRegisterError('El servidor ha enviado datos inesperados.')
return;
}
if (response.status !== 200) {
if (status !== 200) {
this.addNewRegisterError(responseJson.error)
return
}
@ -178,6 +231,12 @@ export default class Conquer {
this.conquerLoginSuccess.classList.remove('conquer-display-none')
}
addNewLoginError(error: string): void {
this.unsetLoginAndRegisterErrors()
this.conquerLoginSuccess.classList.add('conquer-display-none')
this.conquerLoginError.innerText = error
this.conquerLoginError.classList.remove('conquer-display-none')
}
addNewRegisterError(error: string): void {
this.unsetLoginAndRegisterErrors()
this.conquerLoginSuccess.classList.add('conquer-display-none')

View File

@ -52,6 +52,7 @@ sub startup ($self) {
$r->get('/stats')->to('Metrics#stats');
$r->get('/conquer')->to('Conquer#index');
$r->put('/conquer/user')->to('UserConquer#create');
$r->post('/conquer/user/login')->to('UserConquer#login');
$r->get('/search.json')->to('Search#search');
$r->get('/farmacia-guardia.json')->to('FarmaciaGuardia#current');
$r->get('/<:category>.rss')->to('Page#category_rss');

View File

@ -89,8 +89,6 @@ sub submit_login {
$self->render( text => 'Server error.', status => 500 );
return;
}
say $password;
say $bcrypted_pass;
if ( !bcrypt_check( $password, $bcrypted_pass ) ) {
$self->render( text => 'Wrong password', status => 401 );
return;

View File

@ -12,6 +12,7 @@ use Mojo::Base 'Mojolicious::Controller', '-signatures';
use UUID::URandom qw/create_uuid_string/;
use Crypt::Bcrypt qw/bcrypt bcrypt_check/;
use Crypt::URandom qw/urandom/;
use JSON;
use BurguillosInfo::Schema;
@ -21,18 +22,56 @@ my $password_minimum_chars = 8;
my $password_maximum_chars = 4096;
sub create ($self) {
my $input;
eval { $input = $self->req->json; };
if ($@) {
say STDERR $@;
return $self->render( text => 'Expecting json', status => 400 );
my $input = $self->_expectJson;
if ( !defined $input ) {
return;
}
my $username = $input->{username};
my $password = $input->{password};
my $repeat_password = $input->{repeat_password};
return
unless $self->_createCheckInput( $username, $password, $repeat_password );
return $self->_createUser($username, $password);
return $self->_createUser( $username, $password );
}
sub _expectJson ($self) {
my $input;
eval { $input = $self->req->json; };
if ($@) {
say STDERR $@;
$self->_renderError(400, 'Se esperaba JSON.');
return;
}
return $input;
}
sub login ($self) {
my $input = $self->_expectJson;
if ( !defined $input ) {
return;
}
my $username = $input->{username};
my $password = $input->{password};
my $resultset_conquer_user =
BurguillosInfo::Schema->Schema->resultset('ConquerUser');
my @tentative_users =
$resultset_conquer_user->search( { username => $username } );
my $tentative_user = $tentative_users[0];
if (!defined $tentative_user) {
$self->_renderError(401, 'El usuario especificado no existe.');
return;
}
if ( !bcrypt_check( $password, $tentative_user->encrypted_password ) ) {
$self->_renderError( 401, 'Contraseña incorrecta.' );
return;
}
$self->render(
json => {
success => $JSON::true
},
status => 200
);
}
sub _createUser ( $self, $username, $password ) {
@ -52,19 +91,19 @@ sub _createUser ( $self, $username, $password ) {
};
if ($@) {
if ( $@ =~ /Key \((.*?)\)=\((.*?)\) already exists\./ ) {
return $self->renderError( 400,
"The key $1 ($2) already exists in the database.",
return $self->_renderError( 400,
"La clave $1 ($2) ya existe en la base de datos.",
);
}
say STDERR $@;
return $self->renderError( 400,
return $self->_renderError( 400,
'No se pudo crear el usuario por razones desconocidas.' );
}
$self->render(status => 200, json => $user->serialize_to_owner);
$self->render( status => 200, json => $user->serialize_to_owner );
return 1;
}
sub renderError ( $self, $status, $message ) {
sub _renderError ( $self, $status, $message ) {
$self->render( status => $status, json => { error => $message } );
return 0;
}
@ -75,7 +114,7 @@ sub _createCheckInput ( $self, $username, $password, $repeat_password ) {
/^(?:\w|\d|[ÑÁÉÍÓÚñáéíóú ]){$username_minimum_chars,$username_maximum_chars}$/
)
{
return $self->renderError( 400,
return $self->_renderError( 400,
"Username invalido, las reglas son tamaño entre $username_minimum_chars y $username_maximum_chars"
. ' carácteres y solo se podrán usar letras, números y espacios.'
);
@ -85,7 +124,7 @@ sub _createCheckInput ( $self, $username, $password, $repeat_password ) {
|| $password !~ /^.{$password_minimum_chars,$password_maximum_chars}$/
|| $password =~ /^\d+$/ )
{
return $self->renderError(
return $self->_renderError(
400,
'Contraseña invalida, las reglas son la contraseña debe ser'
. ' distinta al nombre de usuario, la contraseña debe tener entre'
@ -96,7 +135,7 @@ sub _createCheckInput ( $self, $username, $password, $repeat_password ) {
);
}
if ( !defined $repeat_password || $password ne $repeat_password ) {
$self->renderError(
$self->_renderError(
400,
'El campo de repetir contraseña debe coincidir de forma'
. ' totalmente exacta con el campo de contraseña para asegurar'

View File

@ -53,10 +53,10 @@ sub MIGRATIONS {
uuid UUID NOT NULL PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
encrypted_password TEXT NOT NULL,
last_activity TEXT NOT NULL DEFAULT NOW(),
is_admin boolean NOT NULL DEFAULT false
last_activity TIMESTAMP NOT NULL DEFAULT NOW(),
is_admin BOOLEAN NOT NULL DEFAULT false,
registration_date TIMESTAMP NOT NULL DEFAULT NOW()
);',
'ALTER TABLE conquer_user ADD COLUMN registration_date TIMESTAMP NOT NULL DEFAULT NOW();',
);
}

View File

@ -10,6 +10,7 @@ use parent 'DBIx::Class::Core';
use feature 'signatures';
__PACKAGE__->table('conquer_user');
__PACKAGE__->load_components("TimeStamp");
__PACKAGE__->add_columns(
uuid => {

File diff suppressed because one or more lines are too long