misc improvements, basic authentication system (no session support yet)

This commit is contained in:
Al Beano 2017-07-15 19:24:05 +01:00
parent 6525ef9f80
commit 7038dc3afa
9 changed files with 314 additions and 25 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.swp
db.sqlite

View File

@ -27,26 +27,8 @@ engines:
auto_page: 1
# session engine
#
# Simple: in-memory session store - Dancer2::Session::Simple
# YAML: session stored in YAML files - Dancer2::Session::YAML
#
# Check out metacpan for other session storage options:
# https://metacpan.org/search?q=Dancer2%3A%3ASession&search_type=modules
#
# Default value for 'cookie_name' is 'dancer.session'. If you run multiple
# Dancer apps on the same host then you will need to make sure 'cookie_name'
# is different for each app.
#
#engines:
# session:
# Simple:
# cookie_name: testapp.session
#
#engines:
# session:
# YAML:
# cookie_name: eshop.session
# is_secure: 1
# is_http_only: 1
plugins:
Database:
driver: 'SQLite'
dbname: 'db.sqlite'
log_queries: 1

View File

@ -5,6 +5,11 @@ requires "URL::Encode::XS" => "0";
requires "CGI::Deurl::XS" => "0";
requires "HTTP::Parser::XS" => "0";
requires "Plack::Middleware::Deflater" => "0";
requires "Dancer2::Plugin::Database" => "0";
requires "DBD::SQLite" => "0";
requires "HTML::Entities" => "0";
requires "Digest::Bcrypt" => "0";
requires "Math::Random::Secure" => "0";
on "test" => sub {
requires "Test::More" => "0";

View File

@ -1,10 +1,161 @@
package cyberman;
use Dancer2;
use Dancer2::Plugin::Database;
use Digest::Bcrypt;
use Math::Random::Secure qw(rand irand);
our $VERSION = '0.1';
#####
# cyberman.pm
# index page and authentication
#####
# misc authentication subs
sub get_auth {
my $uid = shift;
my $token = shift;
my $result = database->quick_select("session", {"uid" => $uid, "token" => $token});
if ($result) {
return $uid;
} else {
return 0;
}
}
sub randstring {
my $len = shift;
my @chars = (0..9, "a".."z", "A".."Z");
my $ret;
for (1..$len) {
$ret .= $chars[irand(scalar(@chars))];
}
return $ret;
}
prefix undef;
hook 'before' => sub {
sub cookieval {
my $name = shift;
my $cookie = cookie($name);
if ($cookie) {
return $cookie->value;
} else {
return undef;
}
}
my $uid = cookieval("token");
my $token = cookieval("token");
my $auth = 0;
if ($uid && $token) {
$auth = get_auth($uid, $token);
}
var auth => $auth;
};
get '/' => sub {
template 'index' => { 'title' => 'cyberman' };
if (!vars->{auth}) {
template 'index';
}
};
post '/register' => sub {
my %errs;
for my $param ("password", "password2", "email") {
if (!param($param)) {
$errs{"e_no_$param"} = 1;
}
}
if (!exists $errs{"e_no_password"} || !exists $errs{"e_no_password2"}) {
if (param("password") ne param("password2")) {
$errs{"e_pass_match"} = 1;
}
if (length param("password") < 8) {
$errs{"e_pass_len"} = 1;
}
}
if (scalar(keys(%errs)) != 0) {
return template 'register' => {
error => 1,
params,
%errs,
};
}
# Hash password
my $salt = randstring(16);
my $b = new Digest::Bcrypt;
$b->cost(8);
$b->salt($salt);
$b->add(param "password");
# Create the account in the database
database->quick_insert(
"user",
{
"email" => param("email"),
"password" => $b->bcrypt_b64digest,
"salt" => $salt,
},
);
# TODO: send confirmation email
template 'login' => {
account_created => 1,
params,
};
};
post '/login' => sub {
my %errs;
my $user = database->quick_select(
"user",
{
"email" => param("email"),
},
);
if (!$user) {
$errs{"e_no_user"} = 1;
}
if (scalar(keys(%errs)) == 0) {
my $b = new Digest::Bcrypt;
$b->cost(8);
$b->salt($user->{"salt"});
$b->add(param "password");
$errs{"e_pass"} = 1 unless $b->bcrypt_b64digest eq $user->{"password"};
}
if (scalar(keys(%errs)) == 0) {
$errs{"e_not_confirmed"} = 1 unless $user->{"active"};
}
if (scalar(keys(%errs)) != 0) {
return template 'login' => {
error => 1,
%errs,
params,
};
}
# checks finished, we can create a session now
return;
};
true;

16
schema.sql Normal file
View File

@ -0,0 +1,16 @@
drop table if exists user;
create table user (
id integer primary key,
email text not null,
password text not null,
salt text not null,
active integer not null default 0
);
drop table if exists session;
create table session (
id integer primary key,
uid text not null,
since integer not null,
token text not null
);

View File

@ -10,6 +10,7 @@
<div class="body">
<p>.cyb is a new TLD (top level domain) for anything and everything cyberpunk-related, on the OpenNIC network. To get started, change your computer's DNS settings to use OpenNIC's servers - <a href="https://www.moderntld.com/get-started/">here's how</a>. Registering a .cyb domain is free for everyone.</p>
<p>You can <a href="charter">read our charter</a> for more information and details on how to report abuse of cybNIC services.</p>
<p>Create or log in to an account in order to register or update .cyb domains.</p>
<center>
<p>[&nbsp;<a class="bracketButton" href="login">log&nbsp;in</a>&nbsp;]&nbsp;&nbsp;[&nbsp;<a class="bracketButton" href="register">register</a>&nbsp;]</p>
</center>

View File

@ -3,6 +3,7 @@
<head>
<meta charset="utf8" />
<meta name="viewport" content="width=device-width" />
<title>cybNIC</title>
<style>
* {
margin: 0;
@ -22,12 +23,23 @@ p {
margin-left: auto;
margin-right: auto;
}
a.bracketButton {
text-decoration: none;
}
a.bracketButton:hover {
text-decoration: underline;
}
form.login>table>tbody>tr>td>label {
margin-right: 10px;
}
.msgBox {
border: 1px solid white;
display: inline-block;
padding: 10px;
}
</style>
</head>
<body>

53
views/login.tt Normal file
View File

@ -0,0 +1,53 @@
<center>
<br />
<h1>Log in</h1>
<br />
<% IF account_created %>
<div class="msgBox">
Your account has been created and a confirmation email sent to <% email | html_entity %>. Please confirm your email address, then log in here.
</div>
<br /><br />
<% END %>
<% IF error %>
<div class="msgBox">
<% IF e_no_user %>
The email address <% email | html_entity %> is not registered.
<% END %>
<% IF e_pass %>
Your password was incorrect, sorry.
<% END %>
<% IF e_not_confirmed %>
Please confirm your email address using the link sent to <% email | html_entity %>.
<% END %>
</div>
<br /><br />
<% END %>
</center>
<div class="body">
<form method=POST class="login" action="login">
<table>
<tr>
<td>
<label for="email">Email address:</label>
</td>
<td>
<input type="email" name="email" id="email" />
</td>
</tr>
<tr>
<td>
<label for="password">Password:</label>
</td>
<td>
<input type="password" name="password" id="password" />
</td>
</tr>
</table>
<br />
<button action="submit">Log in</button>
</form>
</div>

67
views/register.tt Normal file
View File

@ -0,0 +1,67 @@
<center>
<br />
<h1>Register</h1>
<br />
</center>
<% IF error %>
<div style="text-align:center">
<div class="msgBox" style="text-align:left">
There were some problems with your registration:
<br />
<ul>
<% IF e_no_email %>
<li>You need to enter a valid email address.</li>
<% END %>
<% IF e_no_password %>
<li>You need to enter a password.</li>
<% END %>
<% IF e_pass_len %>
<li>Your password must be at least 8 characters!</li>
<% END %>
<% IF e_no_password2 %>
<li>You need to enter a password confirmation.</li>
<% END %>
<% IF e_pass_match %>
<li>The two passwords you entered do not match!</li>
<% END %>
</ul>
</div>
</div>
<br />
<% END %>
<div class="body">
<form class="login" method="POST" action="register">
<table>
<tr>
<td>
<label for="email">Email address:</label>
</td>
<td>
<input type="email" name="email" id="email" value="<% email | html_entity %>" />
</td>
</tr>
<tr>
<td>
<label for="password">Password:</label>
</td>
<td>
<input type="password" name="password" id="password" value="<% password | html_entity %>" />
</td>
</tr>
<tr>
<td>
<label for="password2">Confirm password:</label>
</td>
<td>
<input type="password" name="password2" id="password2" value="<% password2 | html_entity %>" />
</td>
</tr>
</table>
<br />
<button action="submit">Register</button>
<br /><br />
<em>By registering, you agree to be bound by the policies set forward in our <a href="charter">charter</a>.</em>
</form>
</div>