#!/usr/bin/perl
use Mojolicious::Lite; # Aplicación simple
use Mojolicious::Plugin::Authentication; # Complemento de autenticación
use Mojolicious::Plugin::Bcrypt; # Complemento de codificación
use Mojolicious::Plugin::Database; # Complemento de acceso a BD
use DBI; # Funciones de acceso a BD
#
# La base de datos de autenticación contiene las cuentas de usuario
#
# Ejemplo de esquema:
# CREATE TABLE user (user_id integer primary key,
# user_name varchar,
# user_passwd varchar);
#
# La contraseña contiene un hash codificado con bcrypt
#
# Carga complemento de acceso a la base de datos
plugin 'database' => {
dsn => 'dbi:SQLite:dbname=auth',
username => q{},
password => q{},
options => { RaiseError => 1 },
helper => 'db',
};
# Usar codificación fuerte
plugin 'bcrypt';
# Ejemplo de autenticación basado en base de datos
plugin 'authentication' => {
# Subrutina para acceder a los datos del usuario
load_user => sub {
my ( $self, $uid ) = @_;
# Creación de la consulta
my $sth = $self->db->prepare(' select * from user where user_id=? ');
$sth->execute($uid);
# Hacer la consulta
if ( my $res = $sth->fetchrow_hashref ) {
# Si la respuesta es afirmativa, devolvemos los datos
return $res;
}
else {
# Si ese usuario no existe o hay un error, salimos con error
return;
}
},
# Subrutina para validar a un usuario
validate_user => sub {
my ( $self, $username, $password ) = @_;
# Creación de la consulta
my $sth
= $self->db->prepare(' select * from user where user_name = ? ');
$sth->execute($username);
# Regresar con error si la consulta no funcionó
return unless $sth;
if ( my $res = $sth->fetchrow_hashref ) {
# Esta línea no se usa (?)
my $salt = substr $password, 0, 2;
# Comparar el $password que nos pasa el usuario con el que obtenemos de la BD
if ( $self->bcrypt_validate( $password, $res->{user_passwd} ) ) {
# Si la contraseña es correcta, creamos una sesión
$self->session(user => $username);
#
# Para los datos que deberían ser visibles solo para la siguiente petición,
# como un mensaje de confirmación después de un redirect 302, puedes usar
# flash.
#
$self->flash(message => 'Thanks for logging in.');
return $res->{user_id};
}
else {
# Devolvemos un error si no coincide la contraseña
return;
}
}
else {
# Devolvemos un error si falló la consulta
return;
}
},
};
#
# Esta página solo es visible para usuarios autenticados
#
any '/welcome' => sub {
my $self = shift;
if ( not $self->user_exists ) {
# Si el usuario no existe, ponemos un mensaje de aviso, y lo enviamos a la página principal
$self->flash( message => 'You must log in to view this page' );
$self->redirect_to('/');
return;
}
else {
# Si el usuario existe, pintamos la pantalla de bienvenida
$self->render( template => 'welcome' );
}
};
#
# Intentar registrar la entrada del usuario
#
any '/login' => sub {
my $self = shift;
# Credenciales que nos manda el usuario por el formulario
my $user = $self->param('name') || q{};
my $pass = $self->param('pass') || q{};
# Si el usuario se autentica de forma correcta
if ( $self->authenticate( $user, $pass ) ) {
# le llevamos a la página de bienvenida
$self->redirect_to('/welcome');
}
else {
# si no, mensaje de error y regresamos a pantalla principal
$self->flash( message => 'Invalid credentials!' );
$self->redirect_to('/');
}
};
#
# Cerrar la sesión
#
any '/logout' => sub {
my $self = shift;
$self->session( expires => 1 ); # Expirar la sesión
$self->redirect_to('/'); # Ir a página principal
};
#
# Si está registrado, mostramos la página de bienvenida
# Si no, mostramos el formulario de registro
#
any '/' => sub {
my $self = shift;
# Si hay sesión
if ( $self->session('name') ) {
# Ir a página de bienvenida
return $self->redirect_to('/welcome');
}
else {
# Si no, mostrar formulario de registro
$self->render( template => 'login' );
}
};
#
# Clave secreta que se usa para firmar las galletas (cookies)
# Esta frase de paso lo usa el algoritmo HMAC-MD5 para crear galletas firmadas
# seguras y se puede cambiar en cualquier momento para invalidar todas las sesiones actuales.
#
app->secret('9dd1571a116fccce362d54996c3d8c70c101cad5');
#
# ¡Ejecución!
#
app->start;
__DATA__
@@ layouts/default.html.ep
<!doctype html><html>
<head><title><%= title %></title></head>
<body><%= content %></body>
</html>
@@ login.html.ep
% layout 'default';
% title 'Login';
<h1>Log In</h1>
<% if (my $message = flash 'message' ) { %>
<b><%= $message %></b><br>
<% } %>
<%= form_for login => (method => 'post') => begin %>
Name: <%= text_field 'name' %>
<br>
Password: <%= password_field 'pass' %>
<br>
<%= submit_button 'Login' %>
<% end %>
@@ index.html.ep
% layout 'default';
% title 'Welcome';
<% if (my $message = flash 'message' ) { %>
<b><%= $message %></b><br>
<% } %>
@@ welcome.html.ep
% title 'Welcome page';
<% if (my $message = flash 'message' ) { %>
<b><%= $message %></b><br>
<% } %>
Welcome <%= session 'user' %>.<br>
<%= link_to Logout => 'logout' %>