Compare commits
2 Commits
f0ab7713cc
...
cce6e230c9
Author | SHA1 | Date |
---|---|---|
ngoomie | cce6e230c9 | |
ngoomie | dc9a42f0e6 |
|
@ -1,9 +1,6 @@
|
||||||
# CharmBoard-specific
|
# CharmBoard-specific
|
||||||
charmboard.conf
|
charmboard.conf
|
||||||
|
|
||||||
# Mojolicious examples to be nuked later
|
|
||||||
t/basic.t
|
|
||||||
|
|
||||||
# SQLite
|
# SQLite
|
||||||
*.db
|
*.db
|
||||||
*.db-*
|
*.db-*
|
|
@ -10,7 +10,6 @@
|
||||||
"Facepunch",
|
"Facepunch",
|
||||||
"passchk",
|
"passchk",
|
||||||
"passgen",
|
"passgen",
|
||||||
"pgsql",
|
|
||||||
"resultset",
|
"resultset",
|
||||||
"signup",
|
"signup",
|
||||||
"subf",
|
"subf",
|
||||||
|
@ -38,15 +37,6 @@
|
||||||
"bold": false,
|
"bold": false,
|
||||||
"italic": false
|
"italic": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"tag": "//",
|
|
||||||
"color": "#474747",
|
|
||||||
"strikethrough": true,
|
|
||||||
"underline": false,
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"bold": false,
|
|
||||||
"italic": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"tag": "todo",
|
"tag": "todo",
|
||||||
"color": "#FF8C00",
|
"color": "#FF8C00",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
board_name => '', # this doesn't do anything yet
|
board_name => '',
|
||||||
|
|
||||||
database => {
|
database => {
|
||||||
type => '', # 'sqlite' or 'mysql'
|
type => '', # 'sqlite' or 'mysql'
|
||||||
|
|
|
@ -1,67 +1,105 @@
|
||||||
package CharmBoard;
|
package CharmBoard;
|
||||||
|
use utf8;
|
||||||
use experimental 'try', 'smartmatch';
|
use experimental 'try', 'smartmatch';
|
||||||
use Mojo::Base 'Mojolicious', -signatures;
|
use Mojo::Base 'Mojolicious', -signatures;
|
||||||
use CharmBoard::Schema;
|
use CharmBoard::Schema;
|
||||||
|
|
||||||
|
=pod
|
||||||
|
=head1 NAME
|
||||||
|
CharmBoard - revive the fun posting experience!
|
||||||
|
=head1 NOTES
|
||||||
|
This documentation is intended for prospective code
|
||||||
|
contributors. If you're looking to set CharmBoard up,
|
||||||
|
look for the Markdown format (.md) documentation instead.
|
||||||
|
|
||||||
|
CharmBoard uses a max line length of 60 chars and a tab
|
||||||
|
size of two spaces.
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
CharmBoard is forum software written in Perl with
|
||||||
|
Mojolicious, intended to be a more fun alternative to the
|
||||||
|
bigger forum suites available today, inspired by older
|
||||||
|
forum software like AcmlmBoard, while also being more
|
||||||
|
modernized in terms of security practices than they are.
|
||||||
|
Customization ability is another important goal next to
|
||||||
|
making software that feels fun for the end user to use.
|
||||||
|
=cut
|
||||||
|
|
||||||
# this method will run once at server start
|
# this method will run once at server start
|
||||||
sub startup ($app) {
|
sub startup ($self) {
|
||||||
# load plugins that require no additional conf
|
# load plugins that require no additional conf
|
||||||
$app->plugin('TagHelpers');
|
$self->plugin('TagHelpers');
|
||||||
|
|
||||||
# load configuration from config file
|
# load configuration from config file
|
||||||
my $config = $app->plugin('Config' => {file => 'charmboard.conf'});
|
my $config = $self->plugin('Config' =>
|
||||||
|
{file => 'charmboard.conf'});
|
||||||
|
|
||||||
|
# set this specific forum's name
|
||||||
|
$self->helper(boardName => sub {$config->{board_name}});
|
||||||
|
|
||||||
# load dev env only stuff, if applicable
|
# load dev env only stuff, if applicable
|
||||||
if ( $config->{environment} eq 'dev' ) {
|
if ( $config->{environment} eq 'dev' ) {
|
||||||
$app->plugin('Renderer::WithoutCache');
|
$self->plugin('Renderer::WithoutCache');
|
||||||
$app->renderer->cache->max_keys(0)};
|
$self->renderer->cache->max_keys(0)};
|
||||||
|
|
||||||
# import Mojolicious secrets
|
# import Mojolicious secrets
|
||||||
$app->secrets($config->{secrets});
|
$self->secrets($config->{secrets});
|
||||||
|
|
||||||
# import password pepper value
|
# import password pepper value
|
||||||
$app->helper(pepper => sub {$config->{pass_crypt}->{pepper}});
|
$self->helper(pepper => sub {$config->{pass_crypt}->{pepper}});
|
||||||
|
|
||||||
## database setup
|
## database setup
|
||||||
|
# ? this could maybe be a given/when
|
||||||
my ($dsn, $dbUnicode);
|
my ($dsn, $dbUnicode);
|
||||||
if ($app->config->{database}->{type} ~~ 'sqlite') {
|
if ($self->config->{database}->{type} ~~ 'sqlite') {
|
||||||
$dsn = "dbi:SQLite:" . $config->{database}->{name};
|
$dsn = "dbi:SQLite:" . $config->{database}->{name};
|
||||||
$dbUnicode = "sqlite_unicode"}
|
$dbUnicode = "sqlite_unicode"}
|
||||||
elsif ($app->config->{database}->{type} ~~ 'mysql') {
|
|
||||||
|
elsif ($self->config->{database}->{type} ~~ 'mysql') {
|
||||||
$dsn = "dbi:mysql:" . $config->{database}->{name};
|
$dsn = "dbi:mysql:" . $config->{database}->{name};
|
||||||
$dbUnicode = "mysql_enable_utf"}
|
$dbUnicode = "mysql_enable_utf"}
|
||||||
else {die "\nUnknown, unsupported, or empty database type in charmboard.conf.
|
|
||||||
If you're sure you've set it to something supported, maybe double check your spelling?\n
|
else {die "\nUnknown, unsupported, or empty database type
|
||||||
|
in charmboard.conf. If you're sure you've set it to
|
||||||
|
something supported, maybe double check your spelling?
|
||||||
|
\n\n\t
|
||||||
Valid options: 'sqlite', 'mysql'"};
|
Valid options: 'sqlite', 'mysql'"};
|
||||||
|
|
||||||
my $schema = CharmBoard::Schema->connect(
|
my $schema = CharmBoard::Schema->connect(
|
||||||
$dsn,
|
$dsn,
|
||||||
$config->{database}->{user},
|
$config->{database}->{user},
|
||||||
$config->{database}->{pass},
|
$config->{database}->{pass},
|
||||||
{$dbUnicode => 1});
|
{$dbUnicode => 1});
|
||||||
$app->helper(schema => sub {$schema});
|
$self->helper(schema => sub {$schema});
|
||||||
|
|
||||||
# router
|
# router
|
||||||
my $r = $app->routes;
|
my $r = $self->routes;
|
||||||
|
|
||||||
# controller routes
|
# controller routes
|
||||||
## index page
|
## index page
|
||||||
$r->get('/')->to(
|
$r->get('/')->to(
|
||||||
controller => 'Controller::Main',
|
controller => 'Controller::Index',
|
||||||
action => 'index');
|
action => 'index');
|
||||||
|
|
||||||
## registration page
|
## registration page
|
||||||
$r->get('/register')->to(
|
$r->get('/register')->to(
|
||||||
controller => 'Controller::Auth',
|
controller => 'Controller::Register',
|
||||||
action => 'register');
|
action => 'register');
|
||||||
$r->post('/register')->to(
|
$r->post('/register')->to(
|
||||||
controller => 'Controller::Auth',
|
controller => 'Controller::Register',
|
||||||
action => 'register_do');
|
action => 'register_do');
|
||||||
|
|
||||||
## login page
|
## login page
|
||||||
$r->get('/login')->to(
|
$r->get('/login')->to(
|
||||||
controller => 'Controller::Auth',
|
controller => 'Controller::Login',
|
||||||
action => 'login');
|
action => 'login');
|
||||||
$r->post('/login')->to(
|
$r->post('/login')->to(
|
||||||
controller => 'Controller::Auth',
|
controller => 'Controller::Login',
|
||||||
action => 'login_do');
|
action => 'login_do');
|
||||||
|
|
||||||
|
## logout
|
||||||
|
$r->get('/logout')->to(
|
||||||
|
controller => 'Controller::Logout',
|
||||||
|
action => 'logout_do')
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -1,139 +0,0 @@
|
||||||
package CharmBoard::Controller::Auth;
|
|
||||||
use experimental 'try', 'smartmatch';
|
|
||||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
|
||||||
use CharmBoard::Crypt::Password;
|
|
||||||
use CharmBoard::Crypt::Seasoning;
|
|
||||||
|
|
||||||
# initial registration page
|
|
||||||
sub register ($app) {
|
|
||||||
$app->render(
|
|
||||||
template => 'register',
|
|
||||||
error => $app->flash('error'),
|
|
||||||
message => $app->flash('message'))};
|
|
||||||
|
|
||||||
# process submitted registration form
|
|
||||||
sub register_do ($app) {
|
|
||||||
my $username = $app->param('username');
|
|
||||||
my $email = $app->param('email');
|
|
||||||
my $password = $app->param('password');
|
|
||||||
my $confirmPassword = $app->param('confirm-password');
|
|
||||||
|
|
||||||
my $catchError;
|
|
||||||
|
|
||||||
# declare vars used through multiple try/catch blocks with
|
|
||||||
# 'our' so they work throughout the entire subroutine
|
|
||||||
our ($userCheck, $emailCheck, $salt, $hash);
|
|
||||||
|
|
||||||
try { # make sure registration info is valid
|
|
||||||
# TODO: implement email validation here at some point
|
|
||||||
|
|
||||||
# check to make sure all required fields are filled
|
|
||||||
($username, $email, $password, $confirmPassword) ne undef
|
|
||||||
or die "Please fill out all required fields.";
|
|
||||||
|
|
||||||
# check to make sure both passwords match
|
|
||||||
# TODO: add check on frontend for this for people with JS enabled
|
|
||||||
$password eq $confirmPassword
|
|
||||||
or die "Passwords do not match";
|
|
||||||
|
|
||||||
# check to make sure username and/or email isn't already in use;
|
|
||||||
# if not, continue with registration
|
|
||||||
## search for input username and email in database
|
|
||||||
$userCheck = $app->schema->resultset('Users')->search({username => $username})->single;
|
|
||||||
$emailCheck = $app->schema->resultset('Users')->search({email => $email})->single;
|
|
||||||
|
|
||||||
($userCheck && $emailCheck) eq undef
|
|
||||||
or die "Username already in use.\nemail already in use.";
|
|
||||||
($userCheck) eq undef
|
|
||||||
or die "Username already in use.";
|
|
||||||
($emailCheck) eq undef
|
|
||||||
or die "email already in use."}
|
|
||||||
catch ($catchError) {
|
|
||||||
$app->flash(error => $catchError);
|
|
||||||
$app->redirect_to('register');}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$password = $app->pepper . ':' . $password;
|
|
||||||
# return hashed result + salt
|
|
||||||
($salt, $hash) = passgen($password) or die;
|
|
||||||
|
|
||||||
# add user info and pw/salt to DB
|
|
||||||
$app->schema->resultset('Users')->create({
|
|
||||||
username => $username,
|
|
||||||
email => $email,
|
|
||||||
password => $hash,
|
|
||||||
salt => $salt,
|
|
||||||
signup_date => time }) or die;
|
|
||||||
|
|
||||||
$app->flash(message => 'User registered successfully!');
|
|
||||||
$app->redirect_to('register')}
|
|
||||||
catch ($catchError) {
|
|
||||||
print $catchError;
|
|
||||||
$app->flash(error => 'Your registration info was correct,
|
|
||||||
but a server error prevented you from registering.
|
|
||||||
This has been logged so your administrator can fix it.');
|
|
||||||
$app->redirect_to('register')}};
|
|
||||||
|
|
||||||
sub login ($app) {
|
|
||||||
$app->render(
|
|
||||||
template => 'login',
|
|
||||||
error => $app->flash('error'),
|
|
||||||
message => $app->flash('message'))};
|
|
||||||
|
|
||||||
sub login_do ($app) {
|
|
||||||
my $username = $app->param('username');
|
|
||||||
my $password = $app->pepper . ':' . $app->param('password');
|
|
||||||
|
|
||||||
my $catchError;
|
|
||||||
|
|
||||||
# declare vars used through multiple try/catch blocks with
|
|
||||||
# 'our' so they work throughout the entire subroutine
|
|
||||||
our ($userInfo, $passCheck, $userID, $sessionKey);
|
|
||||||
|
|
||||||
try { # check user credentials first
|
|
||||||
# check to see if user by entered username exists
|
|
||||||
$userInfo = $app->schema->resultset('Users')->search({username => $username});
|
|
||||||
$userInfo or die;
|
|
||||||
|
|
||||||
# now check password validity
|
|
||||||
$passCheck = passchk($userInfo->get_column('salt')->first,
|
|
||||||
$userInfo->get_column('password')->first, $password);
|
|
||||||
$passCheck or die;}
|
|
||||||
|
|
||||||
catch ($catchError) { # redirect to login page on fail
|
|
||||||
print $catchError;
|
|
||||||
$app->flash(error => 'Username or password incorrect.');
|
|
||||||
$app->redirect_to('login');}
|
|
||||||
|
|
||||||
try { # now attempt to create session
|
|
||||||
# get user ID for session creation
|
|
||||||
$userID = $userInfo->get_column('user_id')->first;
|
|
||||||
|
|
||||||
# gen session key
|
|
||||||
$sessionKey = seasoning(16);
|
|
||||||
|
|
||||||
# add session to database
|
|
||||||
$app->schema->resultset('Session')->create({
|
|
||||||
session_key => $sessionKey,
|
|
||||||
user_id => $userID,
|
|
||||||
session_expiry => time + 604800,
|
|
||||||
is_ip_bound => 0,
|
|
||||||
bound_ip => undef }) or die;
|
|
||||||
|
|
||||||
# now create session cookie for user
|
|
||||||
$app->session(is_auth => 1);
|
|
||||||
$app->session(user_id => $userID);
|
|
||||||
$app->session(session_key => $sessionKey);
|
|
||||||
$app->session(expiration => 604800);
|
|
||||||
|
|
||||||
# redirect to index upon success
|
|
||||||
$app->redirect_to('/')}
|
|
||||||
|
|
||||||
catch ($catchError) { # redirect to login page on fail
|
|
||||||
print $catchError;
|
|
||||||
$app->flash(error => 'Your username and password were correct,
|
|
||||||
but a server error prevented you from logging in.
|
|
||||||
This has been logged so your administrator can fix it.');
|
|
||||||
$app->redirect_to('login')}}
|
|
||||||
|
|
||||||
1;
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package CharmBoard::Controller::Index;
|
||||||
|
use utf8;
|
||||||
|
use experimental 'try', 'smartmatch';
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
|
|
||||||
|
sub index ($self) {
|
||||||
|
$self->render(template => 'index')
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
|
@ -0,0 +1,76 @@
|
||||||
|
package CharmBoard::Controller::Login;
|
||||||
|
use utf8;
|
||||||
|
use experimental 'try', 'smartmatch';
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
|
use CharmBoard::Crypt::Password;
|
||||||
|
use CharmBoard::Crypt::Seasoning;
|
||||||
|
|
||||||
|
=pod
|
||||||
|
=head1 NAME
|
||||||
|
CharmBoard::Controller::Login
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub login ($self) {
|
||||||
|
$self->render(
|
||||||
|
template => 'login',
|
||||||
|
error => $self->flash('error'),
|
||||||
|
message => $self->flash('message'))};
|
||||||
|
|
||||||
|
sub login_do ($self) {
|
||||||
|
my $username = $self->param('username');
|
||||||
|
my $password = $self->pepper . ':' . $self->param('password');
|
||||||
|
|
||||||
|
my $catchError;
|
||||||
|
|
||||||
|
# declare vars used through multiple try/catch blocks with
|
||||||
|
# 'our' so they work throughout the entire subroutine
|
||||||
|
our ($userInfo, $passCheck, $userID, $sessionKey);
|
||||||
|
|
||||||
|
try { # check user credentials first
|
||||||
|
# check to see if user by entered username exists
|
||||||
|
$userInfo = $self->schema->resultset('Users')->search(
|
||||||
|
{username => $username});
|
||||||
|
$userInfo or die;
|
||||||
|
|
||||||
|
# now check password validity
|
||||||
|
$passCheck = passchk($userInfo->get_column('salt')->first,
|
||||||
|
$userInfo->get_column('password')->first, $password);
|
||||||
|
$passCheck or die;}
|
||||||
|
|
||||||
|
catch ($catchError) { # redirect to login page on fail
|
||||||
|
print $catchError;
|
||||||
|
$self->flash(error => 'Username or password incorrect.');
|
||||||
|
$self->redirect_to('login');}
|
||||||
|
|
||||||
|
try { # now attempt to create session
|
||||||
|
# get user ID for session creation
|
||||||
|
$userID = $userInfo->get_column('user_id')->first;
|
||||||
|
|
||||||
|
# gen session key
|
||||||
|
$sessionKey = seasoning(16);
|
||||||
|
|
||||||
|
# add session to database
|
||||||
|
$self->schema->resultset('Session')->create({
|
||||||
|
session_key => $sessionKey,
|
||||||
|
user_id => $userID,
|
||||||
|
session_expiry => time + 604800,
|
||||||
|
is_ip_bound => 0,
|
||||||
|
bound_ip => undef }) or die;
|
||||||
|
|
||||||
|
# now create session cookie for user
|
||||||
|
$self->session(is_auth => 1);
|
||||||
|
$self->session(user_id => $userID);
|
||||||
|
$self->session(session_key => $sessionKey);
|
||||||
|
$self->session(expiration => 604800);
|
||||||
|
|
||||||
|
# redirect to index upon success
|
||||||
|
$self->redirect_to('/')}
|
||||||
|
|
||||||
|
catch ($catchError) { # redirect to login page on fail
|
||||||
|
print $catchError;
|
||||||
|
$self->flash(error => 'Your username and password were correct,
|
||||||
|
but a server error prevented you from logging in. This has been
|
||||||
|
logged so the administrator can fix it.');
|
||||||
|
$self->redirect_to('login')}}
|
||||||
|
|
||||||
|
1;
|
|
@ -0,0 +1,15 @@
|
||||||
|
package CharmBoard::Controller::Logout;
|
||||||
|
use utf8;
|
||||||
|
use experimental 'try', 'smartmatch';
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
|
|
||||||
|
sub logout_do ($self) {
|
||||||
|
# destroy entry for this session in the database
|
||||||
|
$self->schema->resultset('Session')->search({
|
||||||
|
session_key => $self->session('session_key')})->delete;
|
||||||
|
# now nuke the actual session cookie
|
||||||
|
$self->session(expires => 1);
|
||||||
|
# redirect to index
|
||||||
|
$self->redirect_to('/')}
|
||||||
|
|
||||||
|
1;
|
|
@ -1,7 +0,0 @@
|
||||||
package CharmBoard::Controller::Main;
|
|
||||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
|
||||||
|
|
||||||
sub index ($app) {
|
|
||||||
$app->render(template => 'index')}
|
|
||||||
|
|
||||||
1;
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package CharmBoard::Controller::Register;
|
||||||
|
use utf8;
|
||||||
|
use experimental 'try', 'smartmatch';
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
|
use CharmBoard::Crypt::Password;
|
||||||
|
|
||||||
|
# initial registration page
|
||||||
|
sub register ($self) {
|
||||||
|
$self->render(
|
||||||
|
template => 'register',
|
||||||
|
error => $self->flash('error'),
|
||||||
|
message => $self->flash('message'))};
|
||||||
|
|
||||||
|
# process submitted registration form
|
||||||
|
sub register_do ($self) {
|
||||||
|
my $username = $self->param('username');
|
||||||
|
my $email = $self->param('email');
|
||||||
|
my $password = $self->param('password');
|
||||||
|
my $confirmPassword = $self->param('confirm-password');
|
||||||
|
|
||||||
|
my $catchError;
|
||||||
|
|
||||||
|
# declare vars used through multiple try/catch blocks with
|
||||||
|
# 'our' so they work throughout the entire subroutine
|
||||||
|
our ($userCheck, $emailCheck, $salt, $hash);
|
||||||
|
|
||||||
|
try { # make sure registration info is valid
|
||||||
|
# TODO: implement email validation here at some point
|
||||||
|
|
||||||
|
# check to make sure all required fields are filled
|
||||||
|
($username, $email, $password, $confirmPassword)
|
||||||
|
or die "Please fill out all required fields.";
|
||||||
|
|
||||||
|
# check to make sure both passwords match
|
||||||
|
# TODO: add check on frontend for this for people with JS enabled
|
||||||
|
$password eq $confirmPassword
|
||||||
|
or die "Passwords do not match";
|
||||||
|
|
||||||
|
# check to make sure username and/or email isn't already in use;
|
||||||
|
# if not, continue with registration
|
||||||
|
## search for input username and email in database
|
||||||
|
$userCheck = $self->schema->resultset('Users')->search(
|
||||||
|
{username => $username})->single;
|
||||||
|
$emailCheck = $self->schema->resultset('Users')->search(
|
||||||
|
{email => $email})->single;
|
||||||
|
|
||||||
|
($userCheck && $emailCheck) eq undef
|
||||||
|
or die "Username already in use.\nemail already in use.";
|
||||||
|
($userCheck) eq undef
|
||||||
|
or die "Username already in use.";
|
||||||
|
($emailCheck) eq undef
|
||||||
|
or die "email already in use."}
|
||||||
|
catch ($catchError) {
|
||||||
|
$self->flash(error => $catchError);
|
||||||
|
$self->redirect_to('register');}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$password = $self->pepper . ':' . $password;
|
||||||
|
# return hashed result + salt
|
||||||
|
($salt, $hash) = passgen($password) or die;
|
||||||
|
|
||||||
|
# add user info and pw/salt to DB
|
||||||
|
$self->schema->resultset('Users')->create({
|
||||||
|
username => $username,
|
||||||
|
email => $email,
|
||||||
|
password => $hash,
|
||||||
|
salt => $salt,
|
||||||
|
signup_date => time }) or die;
|
||||||
|
|
||||||
|
$self->flash(message => 'User registered successfully!');
|
||||||
|
$self->redirect_to('register')}
|
||||||
|
catch ($catchError) {
|
||||||
|
print $catchError;
|
||||||
|
$self->flash(error => 'Your registration info was correct, but a
|
||||||
|
server error prevented you from registering. This has been
|
||||||
|
logged so the administrator can fix it.');
|
||||||
|
$self->redirect_to('register')}}
|
||||||
|
|
||||||
|
1;
|
|
@ -1,14 +1,37 @@
|
||||||
package CharmBoard::Crypt::Password;
|
package CharmBoard::Crypt::Password;
|
||||||
|
use utf8;
|
||||||
use Authen::Passphrase::Argon2;
|
use Authen::Passphrase::Argon2;
|
||||||
use CharmBoard::Crypt::Seasoning;
|
use CharmBoard::Crypt::Seasoning;
|
||||||
|
|
||||||
use Exporter qw(import);
|
use Exporter qw(import);
|
||||||
our @EXPORT = qw(passgen passchk);
|
our @EXPORT = qw(passgen passchk);
|
||||||
|
|
||||||
# subroutine to generate password salt + hashed pw on pass creation
|
=pod
|
||||||
# outputs the salt and then the hashed pw, so when assigning vars
|
=head1 NAME
|
||||||
# from this sub's output, do it like this:
|
CharmBoard::Crypt::Password - password processing module
|
||||||
# `my ($salt, $hash) = passgen($password);`
|
=head1 SYNOPSIS
|
||||||
|
=begin perl
|
||||||
|
use CharmBoard::Crypt::Password;
|
||||||
|
|
||||||
|
($salt, $hash) = passgen($plaintextPassword);
|
||||||
|
$passwordVerification = passchk($salt, $hash, $plaintextPassword)
|
||||||
|
=end perl
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
CharmBoard::Crypt::Password processes passwords, either processing
|
||||||
|
new passwords for database storage, or checking passwords entered
|
||||||
|
when logging in to make sure they're correct.
|
||||||
|
|
||||||
|
Currently the only available password hashing scheme is Argon2, but
|
||||||
|
this might be changed later on.
|
||||||
|
=cut
|
||||||
|
|
||||||
|
=pod
|
||||||
|
=head2 passgen
|
||||||
|
passgen is the function for generating password salts and hashes to
|
||||||
|
be inserted into the database. It takes the plaintext password you
|
||||||
|
wish to hash as the only argument, and outputs the salt and
|
||||||
|
Argon2 hash string in hexadecimal form.
|
||||||
|
=cut
|
||||||
sub passgen ($) {
|
sub passgen ($) {
|
||||||
my $argon2 = Authen::Passphrase::Argon2->new(
|
my $argon2 = Authen::Passphrase::Argon2->new(
|
||||||
salt => seasoning(32),
|
salt => seasoning(32),
|
||||||
|
@ -20,9 +43,17 @@ sub passgen ($) {
|
||||||
|
|
||||||
return ($argon2->salt_hex, $argon2->hash_hex)};
|
return ($argon2->salt_hex, $argon2->hash_hex)};
|
||||||
|
|
||||||
# subroutine to check inputted password against one in DB
|
=pod
|
||||||
# `$_[0]` is the salt, `$_[1]` is the hashed pass, and
|
=head2 passchk
|
||||||
# `$_[2]` is the inputted plaintext pepper:password to check
|
passchk is the function for checking plaintext passwords against the
|
||||||
|
hashed password + salt already stored in the database. It takes the
|
||||||
|
salt and Argon2 hash string in hex form plus the plaintext password
|
||||||
|
as inputs, and outputs a true/false value indicating whether or not
|
||||||
|
the input password matched. Intended for login authentication or
|
||||||
|
anywhere else where one may need to verify passwords (i.e. before
|
||||||
|
changing existing passwords, or for admins confirming they wish to
|
||||||
|
perform a risky or nonreversible operation.)
|
||||||
|
=cut
|
||||||
sub passchk ($$$) {
|
sub passchk ($$$) {
|
||||||
my $argon2 = Authen::Passphrase::Argon2->new(
|
my $argon2 = Authen::Passphrase::Argon2->new(
|
||||||
salt_hex => $_[0],
|
salt_hex => $_[0],
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
package CharmBoard::Crypt::Seasoning;
|
package CharmBoard::Crypt::Seasoning;
|
||||||
|
use utf8;
|
||||||
use Math::Random::Secure qw(irand);
|
use Math::Random::Secure qw(irand);
|
||||||
|
|
||||||
use Exporter qw(import);
|
use Exporter qw(import);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package CharmBoard::Schema;
|
package CharmBoard::Schema;
|
||||||
use base qw(DBIx::Class::Schema);
|
use base qw(DBIx::Class::Schema);
|
||||||
|
|
||||||
__PACKAGE__->load_namespaces();
|
__PACKAGE__->load_namespaces(
|
||||||
|
result_namespace => 'Source',
|
||||||
|
resultset_namespace => 'Set');
|
||||||
|
|
||||||
1;
|
1;
|
|
@ -1,4 +1,4 @@
|
||||||
package CharmBoard::Schema::Result::Categories;
|
package CharmBoard::Schema::Source::Categories;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
__PACKAGE__->table('categories');
|
__PACKAGE__->table('categories');
|
|
@ -1,22 +1,26 @@
|
||||||
package CharmBoard::Schema::Result::Posts;
|
package CharmBoard::Schema::Source::Posts;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
__PACKAGE__->table('posts');
|
__PACKAGE__->table('posts');
|
||||||
__PACKAGE__->add_columns(
|
__PACKAGE__->add_columns(
|
||||||
post_id => {
|
post_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
|
is_foreign_key => 0,
|
||||||
is_auto_increment => 1,
|
is_auto_increment => 1,
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
user_id => {
|
user_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
|
is_foreign_key => 1,
|
||||||
is_auto_increment => 0,
|
is_auto_increment => 0,
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
thread_id => {
|
thread_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
|
is_foreign_key => 1,
|
||||||
is_auto_increment => 0,
|
is_auto_increment => 0,
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
post_date => {
|
post_date => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
|
is_foreign_key => 0,
|
||||||
is_auto_increment => 0,
|
is_auto_increment => 0,
|
||||||
is_nullable => 0, });
|
is_nullable => 0, });
|
||||||
|
|
||||||
|
@ -24,11 +28,11 @@ __PACKAGE__->set_primary_key('post_id');
|
||||||
|
|
||||||
__PACKAGE__->belongs_to(
|
__PACKAGE__->belongs_to(
|
||||||
user_id =>
|
user_id =>
|
||||||
'CharmBoard::Schema::Result::Users',
|
'CharmBoard::Schema::Source::Users',
|
||||||
'user_id' );
|
'user_id' );
|
||||||
__PACKAGE__->belongs_to(
|
__PACKAGE__->belongs_to(
|
||||||
thread_id =>
|
thread_id =>
|
||||||
'CharmBoard::Schema::Result::Threads',
|
'CharmBoard::Schema::Source::Threads',
|
||||||
'thread_id' );
|
'thread_id' );
|
||||||
|
|
||||||
1
|
1
|
|
@ -1,4 +1,4 @@
|
||||||
package CharmBoard::Schema::Result::Session;
|
package CharmBoard::Schema::Source::Session;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
__PACKAGE__->table('sessions');
|
__PACKAGE__->table('sessions');
|
||||||
|
@ -28,7 +28,7 @@ __PACKAGE__->set_primary_key('session_key');
|
||||||
|
|
||||||
__PACKAGE__->belongs_to(
|
__PACKAGE__->belongs_to(
|
||||||
user_id =>
|
user_id =>
|
||||||
'CharmBoard::Schema::Result::Users',
|
'CharmBoard::Schema::Source::Users',
|
||||||
'user_id');
|
'user_id');
|
||||||
|
|
||||||
1
|
1
|
|
@ -1,4 +1,4 @@
|
||||||
package CharmBoard::Schema::Result::Subforums;
|
package CharmBoard::Schema::Source::Subforums;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
__PACKAGE__->table('subforums');
|
__PACKAGE__->table('subforums');
|
||||||
|
@ -9,6 +9,7 @@ __PACKAGE__->add_columns(
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
subf_cat => {
|
subf_cat => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
|
is_foreign_key => 1,
|
||||||
is_auto_increment => 0,
|
is_auto_increment => 0,
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
subf_name => {
|
subf_name => {
|
||||||
|
@ -24,7 +25,7 @@ __PACKAGE__->set_primary_key('subf_id');
|
||||||
|
|
||||||
__PACKAGE__->belongs_to(
|
__PACKAGE__->belongs_to(
|
||||||
subf_cat =>
|
subf_cat =>
|
||||||
'CharmBoard::Schema::Result::Categories',
|
'CharmBoard::Schema::Source::Categories',
|
||||||
{'foreign.cat_id' => 'self.subf_cat'});
|
{'foreign.cat_id' => 'self.subf_cat'});
|
||||||
|
|
||||||
1
|
1
|
|
@ -1,4 +1,4 @@
|
||||||
package CharmBoard::Schema::Result::Threads;
|
package CharmBoard::Schema::Source::Threads;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
__PACKAGE__->table('threads');
|
__PACKAGE__->table('threads');
|
||||||
|
@ -12,13 +12,16 @@ __PACKAGE__->add_columns(
|
||||||
is_nullable => 0, },
|
is_nullable => 0, },
|
||||||
thread_subf => {
|
thread_subf => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
|
is_foreign_key => 1,
|
||||||
is_nullable => 1, });
|
is_nullable => 1, });
|
||||||
|
# ! thread_subf should NOT be nullable once subforums
|
||||||
|
# ! are properly implemented
|
||||||
|
|
||||||
__PACKAGE__->set_primary_key('thread_id');
|
__PACKAGE__->set_primary_key('thread_id');
|
||||||
|
|
||||||
__PACKAGE__->belongs_to(
|
__PACKAGE__->belongs_to(
|
||||||
thread_subf =>
|
thread_subf =>
|
||||||
'CharmBoard::Schema::Result::Subforums',
|
'CharmBoard::Schema::Source::Subforums',
|
||||||
{'foreign.subf_id' => 'self.thread_subf'});
|
{'foreign.subf_id' => 'self.thread_subf'});
|
||||||
|
|
||||||
1
|
1
|
|
@ -1,4 +1,5 @@
|
||||||
package CharmBoard::Schema::Result::Users;
|
package CharmBoard::Schema::Source::Users;
|
||||||
|
use utf8;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
__PACKAGE__->table('users');
|
__PACKAGE__->table('users');
|
|
@ -1,4 +1,2 @@
|
||||||
% layout 'default', title => 'CharmBoard';
|
% layout 'default', title => $self->boardName;
|
||||||
% my $hpm = "you are not logged in";
|
this is the index page
|
||||||
% if ($self->session('is_auth')) {$hpm = "you're logged in!"};
|
|
||||||
<%= $hpm %>
|
|
|
@ -1,2 +0,0 @@
|
||||||
<a href="/"><h2>CharmBoard</h2></a>
|
|
||||||
<a href="/login">login</a> | <a href="/register">register</a><br /><br />
|
|
|
@ -5,8 +5,8 @@
|
||||||
<title><%= title %></title>
|
<title><%= title %></title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
%= include 'layouts/_header'
|
%= include 'layouts/default/_header'
|
||||||
<%= content %>
|
<%= content %>
|
||||||
%= include 'layouts/_footer'
|
%= include 'layouts/default/_footer'
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<%
|
||||||
|
my $userControls;
|
||||||
|
|
||||||
|
# TODO: once implemented, put username + profile link first
|
||||||
|
if ($self->session('is_auth') == 1) {
|
||||||
|
my $username =
|
||||||
|
$userControls = "<a href=\"/logout\">logout</a>"}
|
||||||
|
else {
|
||||||
|
$userControls =
|
||||||
|
"<a href=\"/login\">login</a> |
|
||||||
|
<a href=\"/register\">register</a>"};
|
||||||
|
%>
|
||||||
|
<a href="/"><h2><%== $self->boardName %></h2></a>
|
||||||
|
<%== $userControls %><br /><br />
|
|
@ -1,4 +1,4 @@
|
||||||
% layout 'default', title => 'CharmBoard - Login';
|
% layout 'default', title => $self->boardName . ' - Login';
|
||||||
% if ($error) {
|
% if ($error) {
|
||||||
<p style="color: red"><%= $error %></p>
|
<p style="color: red"><%= $error %></p>
|
||||||
%};
|
%};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
% layout 'default', title => 'CharmBoard - Registration';
|
% layout 'default', title => $self->boardName . ' - Registration';
|
||||||
% if ($error) {
|
% if ($error) {
|
||||||
<p style="color: red"><%= $error %></p>
|
<p style="color: red"><%= $error %></p>
|
||||||
%};
|
%};
|
||||||
|
|
Loading…
Reference in New Issue