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, 'DBIx::Class' => 0,
'UUID::URandom' => 0, 'UUID::URandom' => 0,
'Crypt::Bcrypt' => 0, 'Crypt::Bcrypt' => 0,
'DBIx::Class::TimeStamp' => 0,
}, },
); );
$build->create_build_script; $build->create_build_script;

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ use Mojo::Base 'Mojolicious::Controller', '-signatures';
use UUID::URandom qw/create_uuid_string/; use UUID::URandom qw/create_uuid_string/;
use Crypt::Bcrypt qw/bcrypt bcrypt_check/; use Crypt::Bcrypt qw/bcrypt bcrypt_check/;
use Crypt::URandom qw/urandom/; use Crypt::URandom qw/urandom/;
use JSON;
use BurguillosInfo::Schema; use BurguillosInfo::Schema;
@ -21,18 +22,56 @@ my $password_minimum_chars = 8;
my $password_maximum_chars = 4096; my $password_maximum_chars = 4096;
sub create ($self) { sub create ($self) {
my $input; my $input = $self->_expectJson;
eval { $input = $self->req->json; }; if ( !defined $input ) {
if ($@) { return;
say STDERR $@;
return $self->render( text => 'Expecting json', status => 400 );
} }
my $username = $input->{username}; my $username = $input->{username};
my $password = $input->{password}; my $password = $input->{password};
my $repeat_password = $input->{repeat_password}; my $repeat_password = $input->{repeat_password};
return return
unless $self->_createCheckInput( $username, $password, $repeat_password ); 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 ) { sub _createUser ( $self, $username, $password ) {
@ -52,19 +91,19 @@ sub _createUser ( $self, $username, $password ) {
}; };
if ($@) { if ($@) {
if ( $@ =~ /Key \((.*?)\)=\((.*?)\) already exists\./ ) { if ( $@ =~ /Key \((.*?)\)=\((.*?)\) already exists\./ ) {
return $self->renderError( 400, return $self->_renderError( 400,
"The key $1 ($2) already exists in the database.", "La clave $1 ($2) ya existe en la base de datos.",
); );
} }
say STDERR $@; say STDERR $@;
return $self->renderError( 400, return $self->_renderError( 400,
'No se pudo crear el usuario por razones desconocidas.' ); '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; return 1;
} }
sub renderError ( $self, $status, $message ) { sub _renderError ( $self, $status, $message ) {
$self->render( status => $status, json => { error => $message } ); $self->render( status => $status, json => { error => $message } );
return 0; return 0;
} }
@ -75,7 +114,7 @@ sub _createCheckInput ( $self, $username, $password, $repeat_password ) {
/^(?:\w|\d|[ÑÁÉÍÓÚñáéíóú ]){$username_minimum_chars,$username_maximum_chars}$/ /^(?:\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" "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.' . ' 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 !~ /^.{$password_minimum_chars,$password_maximum_chars}$/
|| $password =~ /^\d+$/ ) || $password =~ /^\d+$/ )
{ {
return $self->renderError( return $self->_renderError(
400, 400,
'Contraseña invalida, las reglas son la contraseña debe ser' 'Contraseña invalida, las reglas son la contraseña debe ser'
. ' distinta al nombre de usuario, la contraseña debe tener entre' . ' 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 ) { if ( !defined $repeat_password || $password ne $repeat_password ) {
$self->renderError( $self->_renderError(
400, 400,
'El campo de repetir contraseña debe coincidir de forma' 'El campo de repetir contraseña debe coincidir de forma'
. ' totalmente exacta con el campo de contraseña para asegurar' . ' totalmente exacta con el campo de contraseña para asegurar'

View File

@ -53,10 +53,10 @@ sub MIGRATIONS {
uuid UUID NOT NULL PRIMARY KEY, uuid UUID NOT NULL PRIMARY KEY,
username TEXT NOT NULL UNIQUE, username TEXT NOT NULL UNIQUE,
encrypted_password TEXT NOT NULL, encrypted_password TEXT NOT NULL,
last_activity TEXT NOT NULL DEFAULT NOW(), last_activity TIMESTAMP NOT NULL DEFAULT NOW(),
is_admin boolean NOT NULL DEFAULT false 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'; use feature 'signatures';
__PACKAGE__->table('conquer_user'); __PACKAGE__->table('conquer_user');
__PACKAGE__->load_components("TimeStamp");
__PACKAGE__->add_columns( __PACKAGE__->add_columns(
uuid => { uuid => {

File diff suppressed because one or more lines are too long