1
0
mirror of https://gitlab.com/depesz/explain.depesz.com.git synced 2025-10-31 00:07:57 +02:00

WIP - users support

- users can now:
    - register
    - login
    - logout
    - change password
- new explains know about logged user (it's stored in database)
This commit is contained in:
Hubert depesz Lubaczewski
2013-10-28 14:58:17 +01:00
parent 02331652e5
commit 22b973d093
9 changed files with 307 additions and 17 deletions

View File

@@ -5,6 +5,9 @@ use Mojo::Base 'Mojolicious';
sub startup {
my $self = shift;
$self->sessions->cookie_name('explain');
$self->sessions->default_expiration( 60 * 60 * 24 * 365 );
# register Explain plugins namespace
$self->plugins->namespaces( [ "Explain::Plugin", @{ $self->plugins->namespaces } ] );
@@ -29,6 +32,15 @@ sub startup {
# route: 'index'
$routes->route( '/' )->to( 'controller#index' )->name( 'new-explain' );
# route: 'login'
$routes->route( '/login' )->to( 'controller#login' )->name( 'login' );
# route: 'logout'
$routes->route( '/logout' )->to( 'controller#logout' )->name( 'logout' );
# route: 'user'
$routes->route( '/user' )->to( 'controller#user' )->name( 'user' );
# route: 'show'
$routes->route( '/s/:id' )->to( 'controller#show', id => '' )->name( 'show' );

View File

@@ -8,6 +8,86 @@ use Pg::Explain;
use Encode;
use Email::Valid;
sub logout {
my $self = shift;
delete $self->session->{'user'};
$self->redirect_to( 'new-explain' );
}
sub user {
my $self = shift;
my $old = $self->req->param('old-pw');
my $new = $self->req->param('new-pw');
my $new2 = $self->req->param('new-pw2');
return $self->render unless defined $old;
if (
( !defined $new ) ||
( !defined $new2) ||
( $new ne $new2 )
) {
$self->stash->{'message'} = 'You have to provide two identical copies of new password!';
return;
}
my $status = $self->database->user_change_password( $self->session->{'user'}, $old, $new );
if ( $status ) {
$self->flash('message' => 'Password changed.');
$self->redirect_to( 'new-explain' );
}
$self->stash->{'message'} = 'Changing the password failed.';
}
sub login {
my $self = shift;
# If there is no username - there is nothing to do
my $username = $self->req->param('username');
return $self->render unless defined $username;
if ( 30 < length( $username ) ) {
$self->stash->{'message'} = 'Username cannot be longer than 30 characters. Really?!';
return;
}
my $password = $self->req->param('password');
my $password2 = $self->req->param('password2');
if ( ( ! defined $password ) || ( '' eq $password ) ) {
$self->stash->{'message'} = 'There has to be some password!';
return;
}
# Registration
if ( $self->req->param('is_registration') ) {
if (
( ! defined $password2 ) ||
( $password2 ne $password )
) {
$self->stash->{'message'} = 'You have to repeat password correctly!';
return;
}
my $status = $self->database->user_register( $username, $password );
if ( $status ) {
$self->flash('message' => 'User registered.');
$self->session( 'user' => $username );
$self->redirect_to( 'new-explain' );
}
$self->stash->{'message'} = 'Registration failed.';
return;
}
if ( $self->database->user_login( $username, $password ) ) {
$self->flash('message' => 'User logged in.');
$self->session( 'user' => $username );
$self->redirect_to( 'new-explain' );
}
$self->stash->{'message'} = 'Bad username or password.';
return;
}
sub index {
my $self = shift;
@@ -61,7 +141,7 @@ sub index {
}
# save to database
my ( $id, $delete_key ) = $self->database->save_with_random_name( $title, $plan, $is_public, $is_anon, );
my ( $id, $delete_key ) = $self->database->save_with_random_name( $title, $plan, $is_public, $is_anon, $self->session->{'user'} );
# redirect to /show/:id
$self->flash( delete_key => $delete_key );

View File

@@ -7,9 +7,11 @@ use Mojo::Util 'trim';
use Carp;
use DBI;
use Date::Simple;
use English qw( -no_match_vars );
has dbh => undef;
has connection_args => sub { [] };
has log => undef;
sub register {
my ( $self, $app, $config ) = @_;
@@ -45,6 +47,7 @@ sub register {
# log debug message
$app->log->debug( 'Database connection args: ' . $app->dumper( $self->connection_args ) );
$self->log( $app->log );
# register helper
$app->helper(
@@ -67,14 +70,74 @@ sub register {
return;
}
sub save_with_random_name {
sub user_login {
my $self = shift;
my ( $title, $content, $is_public, $is_anon ) = @_;
my ( $username, $password ) = @_;
my @row = $self->dbh->selectrow_array(
'SELECT id, delete_key FROM register_plan(?, ?, ?, ?)',
'SELECT password FROM users where username = ?',
undef,
$title, $content, $is_public, $is_anon,
$username,
);
return if 0 == scalar @row;
my $crypted = crypt( $password, $row[0] );
return if $crypted ne $row[0];
return 1;
}
sub user_change_password {
my $self = shift;
my ($username, $old, $new) = @_;
my @row = $self->dbh->selectrow_array(
'SELECT password FROM users where username = ?',
undef,
$username,
);
return if 0 == scalar @row;
my $crypted_old = crypt( $old, $row[0] );
my $crypted_new = crypt( $new, $self->get_pw_salt() );
@row = $self->dbh->selectrow_array(
'UPDATE users SET password = ? WHERE ( username, password ) = ( ?, ? ) returning username',
undef,
$crypted_new, $username, $crypted_old,
);
return 1 if 1 == scalar @row;
return;
}
sub get_pw_salt {
my $self = shift;
my @salt_chars = ( 'a'..'z', 'A'..'Z', 0..9, '.', '/' );
my $salt = sprintf '$6$%s$', join( '', map { $salt_chars[ rand @salt_chars ] } 1..16 );
return $salt;
}
sub user_register {
my $self = shift;
my ( $username, $password ) = @_;
my $crypted = crypt( $password, $self->get_pw_salt() );
eval {
$self->dbh->do( 'INSERT INTO users (username, password, registered) values (?, ?, now())', undef, $username, $crypted, );
};
return 1 unless $EVAL_ERROR;
$self->log->error( "user_register( $username ) => " . $EVAL_ERROR );
return;
}
sub save_with_random_name {
my $self = shift;
my ( $title, $content, $is_public, $is_anon, $username ) = @_;
my @row = $self->dbh->selectrow_array(
'SELECT id, delete_key FROM register_plan(?, ?, ?, ?, ?)',
undef,
$title, $content, $is_public, $is_anon, $username,
);
# return id and delete_key

View File

@@ -123,8 +123,7 @@ nav a, nav span {height:35px;padding:0 20px;font-size:1.
nav a:hover {text-decoration:underline}
nav span {margin-left:1px;margin-right:1px;background-position:0px -120px;color:#eee}
nav .donate {left:auto;right:20px}
nav .donate span input {position:relative;top:2px;border:none;width:74px}
nav .right {left:auto;right:20px}
section {
margin-bottom:20px;
@@ -185,10 +184,17 @@ form .fe-buttons button span {display:block;padding:5px 25px;color:#123}
form .fe-buttons button:hover {background:#234;border-color:123}
form .fe-buttons button:hover span {color:#fff;cursor:pointer}
form .fe-buttons-single {padding:5px 0 0 0}
form .fe-buttons-single button {display:block;float:left;margin-right:5px;border:1px solid #d0d8d8;background:#e5ecf9}
form .fe-buttons-single button span {display:block;padding:5px 25px;color:#123}
form .fe-buttons-single button:hover {background:#234;border-color:123}
form .fe-buttons-single button:hover span {color:#fff;cursor:pointer}
form p.nfo-required {font-size:0.8em;color:#888}
form sup.fe-required {font-size:1.2em;color:#800}
.password2 {display:none;}
div.explain-form {margin-bottom:5px;text-align:center}
div.explain-form a {padding:0 1em 0.5em 1em;border:1px solid #ccc;background:#f8f8f8;color:#666;text-decoration:none;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}
div.explain-form a span {font-size:0.9em;position:relative;top:0.2em}

View File

@@ -354,6 +354,22 @@
}
});
}
// login form support
var pass2 = $('.password2');
var lr_button_text = $('#login-button span');
var is_reg_check = $('#is_registration');
is_reg_check.change(function(e) {
if (this.checked) {
pass2.show();
lr_button_text.text( 'Register');
} else {
pass2.hide();
lr_button_text.text( 'Login');
}
});
is_reg_check.trigger( 'change' );
// login form support
});
} )( jQuery );

View File

@@ -5,3 +5,28 @@ CREATE TABLE users (
);
ALTER TABLE plans add column added_by TEXT references users ( username );
DROP FUNCTION register_plan(in_title text, in_plan text, in_is_public boolean, in_is_anonymized boolean);
CREATE FUNCTION register_plan(in_title text, in_plan text, in_is_public boolean, in_is_anonymized boolean, in_username text) RETURNS register_plan_return
LANGUAGE plpgsql
AS $$
DECLARE
use_hash_length int4 := 2;
reply register_plan_return;
BEGIN
reply.delete_key := get_random_string( 50 );
LOOP
reply.id := get_random_string(use_hash_length);
BEGIN
INSERT INTO plans (id, title, plan, is_public, entered_on, is_anonymized, delete_key, added_by) values (reply.id, in_title, in_plan, in_is_public, now(), in_is_anonymized, reply.delete_key, in_username );
RETURN reply;
EXCEPTION WHEN unique_violation THEN
-- do nothing
END;
use_hash_length := use_hash_length + 1;
IF use_hash_length >= 30 THEN
raise exception 'Random string of length == 30 requested. something''s wrong.';
END IF;
END LOOP;
END;
$$;

View File

@@ -0,0 +1,42 @@
% layout 'default';
% my $title = 'Login / Register';
% title $title;
<h1><%= $title =%></h1>
% if ( stash( 'message' ) ) {
<div class="message">
<p class="message"><%= stash( 'message' ) =%></p>
</div>
% }
% if ( flash( 'message' ) ) {
<div class="message">
<p class="message"><%= flash( 'message' ) =%></p>
</div>
% }
<form id="login" method="post" action="<%= url_for 'current' %>">
<div class="fe fe-first fe_login">
<label for="username">username:</label>
<input id="username" name="username" class="auto-hint" title="Enter your username"
<% if ( param('username') ) { %>
value="<%= param('username') %>"
<% } %>
/>
<label for="password">password:</label>
<input id="password" type="password" name="password" title="Enter your password"/>
<label class="password2" for="password2">repeat password:</label>
<input class="password2" id="password2" type="password" name="password2" title="Enter your password"/>
</div>
<div class="fe fe_register">
<label for="is_registration">I want to register new account</label>
<input type="checkbox" class="checkbox" id="is_registration" name="is_registration" value="1" <% if ( param('is_registration') ) { %>checked="1"<% } %>/>
</div>
<div class="fe fe-last fe-buttons">
<button type="submit" name="login" id="login-button"><span>Login</span></button>
</div>
</form>

View File

@@ -0,0 +1,40 @@
% layout 'default';
% my $title = 'User: ' . session( 'user' );
% title $title;
<h1><%= $title %></h1>
<h2>Change password:</h2>
% if ( stash( 'message' ) ) {
<div class="message">
<p class="message"><%= stash( 'message' ) =%></p>
</div>
% }
% if ( flash( 'message' ) ) {
<div class="message">
<p class="message"><%= flash( 'message' ) =%></p>
</div>
% }
<form id="logout" method="post" action="<%= url_for 'user' %>">
<div class="fe">
<label for="old-pw">Current password:</label>
<input id="old-pw" type="password" name="old-pw" title="Enter your old password"/>
</div>
<div class="fe">
<label for="new-pw">New password:</label>
<input id="new-pw" type="password" name="new-pw" title="Enter your new password"/>
<label for="new-pw2">Repeat new password:</label>
<input id="new-pw2" type="password" name="new-pw2" title="Repeat your new password"/>
</div>
<div class="fe fe-last fe-buttons">
<button type="submit" name="changepw" id="changepw-button"><span>Change password</span></button>
</div>
</form>
<div class="fe">
<h2><a href="<%= url_for 'logout' %>">Logout user <%= " ".session( 'user' ) =%></a></h2>
</div>

View File

@@ -77,17 +77,23 @@
</li>
% }
</ul>
<ul class="donate">
<ul class="right">
<li>
<span>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----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-----END PKCS7-----">
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
</form>
</span>
</li>
% if ( session('user') ) {
% if ( $self->match->endpoint->name eq "user" ) {
<span>user: <%= session('user') %></span>
% } else {
<a href="<%= url_for 'user' %>" title="link to: user page" rel="permalink">user: <%= session('user') %></a>
% }
% } else {
% if ( $self->match->endpoint->name eq "login" ) {
<span>login</span>
% } else {
<a href="<%= url_for 'login' %>" title="link to: login/register" rel="permalink">login</a>
% }
% }
<li>
</ul>
</nav>