Some prep for user sign in implementation + formatting changes
global changes: - config now uses snake_case instead of camelCase for config entry names - closing brackets/braces never occur on their own line - there are almost never spaces inside brackets/braces before and after the actual content - I also removed a bunch of linebreaks I really shouldn't have because it just ended up worsening readability, so I'm going to put them back tomorrow lol `lib/CharmBoard.pm` : - removed PostgreSQL from the database driver detector, I just wanna stick with SQLite and MySQL initially since I'm only really familiar with those - detect dev environment (from conf file setting) and only shut caching off in that situation - removed the default layout config option `lib/CharmBoard/Crypt/Password.pm` - renamed subroutines from snake_case to whateverthisiscalled - changed what format `passgen` outputs salts and hashes in - changed size and factor for reasons I honestly don't remember at this point. I should probably recalibrate that properly tomorrow - added `passchk` subroutine for verifying of passwords on the login screen - nice and helpful comments `lib/CharmBoard/Crypt/Seasoning.pm` - this is what used to be `tools/pepper.pl`. it's not currently used for anything but it will be used for pepper generation during setup if CharmBoard ever gets to that point. also might use it for generating salts actually `lib/CharmBoard/Controller/Auth.pm` - realized I had the salt and hash variable assignments the wrong way 'round like an idiot, so I fixed that - added part of signup (password auth) - also lots of hopefully-helpful comments? `lib/CharmBoard/Schema/*.pm` - added more params for each column `lib/CharmBoard/Schema/Session.pm` - added `is_ip_bound` and `bound_ip` columns `database.sql` - as for stuff not mentioned in the schema module changes, I added `ON CONFLICT` behavior (it's all `ABORT`, which rolls back the entire transaction) i'm tired i'm spenfing too much time either reading about mojolicious/perl or actually programming in them (usually the former atm) i need to chillax and play some videogames
This commit is contained in:
parent
5880920e5f
commit
c5785301ca
|
@ -8,8 +8,13 @@
|
||||||
"Authen",
|
"Authen",
|
||||||
"CharmBoard",
|
"CharmBoard",
|
||||||
"Facepunch",
|
"Facepunch",
|
||||||
|
"passchk",
|
||||||
|
"passgen",
|
||||||
"pgsql",
|
"pgsql",
|
||||||
"resultset",
|
"resultset",
|
||||||
"signup"
|
"signup",
|
||||||
|
"subf",
|
||||||
|
"subforum",
|
||||||
|
"subforums"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -4,13 +4,13 @@ Please keep in mind that CharmBoard is alpha software, and as such should not be
|
||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
|
|
||||||
### Database types
|
### Selecting a database type
|
||||||
|
|
||||||
CharmBoard supports two different types of databases. Below is a table listing off each type, as well as an explanation of the differences for people who are unsure which to pick.
|
CharmBoard supports two different types of databases. Below is a table listing off each type, as well as an explanation of the differences for people who are unsure which to pick.
|
||||||
|
|
||||||
| Name | config value | DBD package | Information |
|
| Name | config value | DBD package | Information |
|
||||||
|-|-|-|-|
|
|-|-|-|-|
|
||||||
| SQLite | `sqlite` | `DBD:SQLite` | Good for small installs (private forum with one friend group, etc.)<br />Easy to set up as the database is contained in a standalone file. |
|
| SQLite | `sqlite` | `DBD:SQLite` | Good for small installs (i.e. a private forum with one friend group, etc.)<br />Easy to set up as the database is contained in a standalone file. |
|
||||||
| MySQL | `mysql` | `DBD:mysql` | Has better performance on larger databases than SQLite does.<br />Harder to set up than SQLite as it requires the separate database server software to be set up alongside CharmBoard. |
|
| MySQL | `mysql` | `DBD:mysql` | Has better performance on larger databases than SQLite does.<br />Harder to set up than SQLite as it requires the separate database server software to be set up alongside CharmBoard. |
|
||||||
|
|
||||||
### Installing dependencies
|
### Installing dependencies
|
||||||
|
|
16
README.md
16
README.md
|
@ -1,15 +1,17 @@
|
||||||
# CharmBoard
|
# CharmBoard
|
||||||
|
|
||||||
CharmBoard is forum software written in Perl, inspired by AcmlmBoard/its derivatives, the original Facepunch forums, and Knockout.chat. It's intended to be a more "fun" alternative to the bigger forum software suites available today.
|
CharmBoard is forum software written in Perl, inspired by AcmlmBoard/its derivatives, the original Facepunch forums, and Knockout.chat. It's intended to be a more "fun" alternative to the bigger forum software suites available today. Though largely, it's just intended as a sort of pet project of mine for now and a way to learn Perl + Mojolicious, and some other modules I've been wanting to learn.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Perl 5 (TODO: specific version reqs)
|
- Perl5 v5.20.0 or higher
|
||||||
- `Mojolicious` ([website](https://www.mojolicious.org/), [metacpan](https://metacpan.org/pod/Mojolicious))
|
- `Mojolicious` ([website](https://www.mojolicious.org/), [metacpan](https://metacpan.org/pod/Mojolicious))
|
||||||
- `DBI`
|
- `Mojolicious::Plugin::Renderer::WithoutCache` — only needed in dev environment
|
||||||
- `DBIx::Class`
|
- `DBI`
|
||||||
- one of two `DBD` database drivers — see `INSTALLING.md` for detailed information
|
- `DBIx::Class`
|
||||||
- `Authen::Passphrase::Argon2`
|
- one of two `DBD` database drivers — see `INSTALLING.md` for detailed information
|
||||||
|
- `Authen::Passphrase::Argon2`
|
||||||
|
- `Math::Random::Secure`
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
{
|
{
|
||||||
boardName => '', # this doesn't do anything yet
|
board_name => '', # this doesn't do anything yet
|
||||||
|
|
||||||
database => {
|
database => {
|
||||||
type => '', # 'sqlite', 'mysql', or 'pgsql'
|
type => '', # 'sqlite' or 'mysql'
|
||||||
name => '',
|
name => '',
|
||||||
user => '',
|
user => '',
|
||||||
pass => ''
|
pass => ''
|
||||||
},
|
},
|
||||||
|
|
||||||
passCrypt => {
|
pass_crypt => {
|
||||||
pepper => '' # generate this with `tools/pepper.pl` for now
|
pepper => '' # generate this with `tools/pepper.pl` for now
|
||||||
},
|
},
|
||||||
|
|
||||||
|
environment => '', # only use 'dev' for now
|
||||||
|
|
||||||
secrets => ['']
|
secrets => ['']
|
||||||
};
|
};
|
104
database.sql
104
database.sql
|
@ -1,5 +1,5 @@
|
||||||
--
|
--
|
||||||
-- File generated with SQLiteStudio v3.4.4 on Sat. May 6 00:01:26 2023
|
-- File generated with SQLiteStudio v3.4.4 on Sun. May 7 00:02:05 2023
|
||||||
--
|
--
|
||||||
-- Text encoding used: UTF-8
|
-- Text encoding used: UTF-8
|
||||||
--
|
--
|
||||||
|
@ -8,40 +8,104 @@ BEGIN TRANSACTION;
|
||||||
|
|
||||||
-- Table: categories
|
-- Table: categories
|
||||||
DROP TABLE IF EXISTS categories;
|
DROP TABLE IF EXISTS categories;
|
||||||
CREATE TABLE IF NOT EXISTS categories (cat_id INTEGER NOT NULL UNIQUE, cat_name TEXT, PRIMARY KEY (cat_id AUTOINCREMENT));
|
|
||||||
|
CREATE TABLE IF NOT EXISTS categories (
|
||||||
|
cat_id INTEGER NOT NULL ON CONFLICT ROLLBACK
|
||||||
|
UNIQUE ON CONFLICT ROLLBACK,
|
||||||
|
cat_name TEXT,
|
||||||
|
PRIMARY KEY (
|
||||||
|
cat_id AUTOINCREMENT
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
-- Table: posts
|
-- Table: posts
|
||||||
DROP TABLE IF EXISTS posts;
|
DROP TABLE IF EXISTS posts;
|
||||||
CREATE TABLE IF NOT EXISTS "posts" (
|
|
||||||
"post_id" INTEGER NOT NULL UNIQUE,
|
CREATE TABLE IF NOT EXISTS posts (
|
||||||
"user_id" INTEGER NOT NULL,
|
post_id INTEGER NOT NULL ON CONFLICT ROLLBACK
|
||||||
"thread_id" INTEGER NOT NULL,
|
UNIQUE ON CONFLICT ROLLBACK,
|
||||||
"post_date" INTEGER NOT NULL,
|
user_id INTEGER NOT NULL ON CONFLICT ROLLBACK,
|
||||||
PRIMARY KEY("post_id" AUTOINCREMENT),
|
thread_id INTEGER NOT NULL ON CONFLICT ROLLBACK,
|
||||||
FOREIGN KEY("user_id") REFERENCES "users"("user_id"),
|
post_date INTEGER NOT NULL ON CONFLICT ROLLBACK,
|
||||||
FOREIGN KEY("thread_id") REFERENCES "threads"("thread_id")
|
PRIMARY KEY (
|
||||||
|
post_id AUTOINCREMENT
|
||||||
|
),
|
||||||
|
FOREIGN KEY (
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
REFERENCES users (user_id),
|
||||||
|
FOREIGN KEY (
|
||||||
|
thread_id
|
||||||
|
)
|
||||||
|
REFERENCES threads (thread_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Table: session
|
|
||||||
DROP TABLE IF EXISTS session;
|
-- Table: sessions
|
||||||
CREATE TABLE IF NOT EXISTS "session" (
|
DROP TABLE IF EXISTS sessions;
|
||||||
"user_id" INTEGER NOT NULL UNIQUE,
|
|
||||||
"session_id" TEXT NOT NULL,
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
"session_expiry" INTEGER,
|
user_id INTEGER PRIMARY KEY
|
||||||
PRIMARY KEY("user_id")
|
REFERENCES users (user_id)
|
||||||
|
UNIQUE
|
||||||
|
NOT NULL,
|
||||||
|
session_key TEXT NOT NULL
|
||||||
|
UNIQUE,
|
||||||
|
session_expiry NUMERIC NOT NULL,
|
||||||
|
is_ip_bound INTEGER (1, 1) NOT NULL
|
||||||
|
DEFAULT (0),
|
||||||
|
bound_ip TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
-- Table: subforums
|
-- Table: subforums
|
||||||
DROP TABLE IF EXISTS subforums;
|
DROP TABLE IF EXISTS subforums;
|
||||||
CREATE TABLE IF NOT EXISTS subforums (subf_id INTEGER PRIMARY KEY UNIQUE NOT NULL, subf_cat INTEGER REFERENCES categories (cat_id) UNIQUE NOT NULL, subf_name TEXT NOT NULL, subf_desc);
|
|
||||||
|
CREATE TABLE IF NOT EXISTS subforums (
|
||||||
|
subf_id INTEGER PRIMARY KEY
|
||||||
|
UNIQUE ON CONFLICT ROLLBACK
|
||||||
|
NOT NULL ON CONFLICT ROLLBACK,
|
||||||
|
subf_cat INTEGER REFERENCES categories (cat_id)
|
||||||
|
UNIQUE ON CONFLICT ROLLBACK
|
||||||
|
NOT NULL ON CONFLICT ROLLBACK,
|
||||||
|
subf_name TEXT NOT NULL ON CONFLICT ROLLBACK,
|
||||||
|
subf_desc
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
-- Table: threads
|
-- Table: threads
|
||||||
DROP TABLE IF EXISTS threads;
|
DROP TABLE IF EXISTS threads;
|
||||||
CREATE TABLE IF NOT EXISTS threads (thread_id INTEGER NOT NULL, thread_title TEXT NOT NULL, thread_subf INTEGER REFERENCES categories (cat_id), PRIMARY KEY (thread_id AUTOINCREMENT));
|
|
||||||
|
CREATE TABLE IF NOT EXISTS threads (
|
||||||
|
thread_id INTEGER NOT NULL ON CONFLICT ROLLBACK,
|
||||||
|
thread_title TEXT NOT NULL ON CONFLICT ROLLBACK,
|
||||||
|
thread_subf INTEGER REFERENCES categories (cat_id),
|
||||||
|
PRIMARY KEY (
|
||||||
|
thread_id AUTOINCREMENT
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
-- Table: users
|
-- Table: users
|
||||||
DROP TABLE IF EXISTS users;
|
DROP TABLE IF EXISTS users;
|
||||||
CREATE TABLE IF NOT EXISTS users (user_id INTEGER NOT NULL UNIQUE, username TEXT NOT NULL UNIQUE ON CONFLICT ABORT, email TEXT UNIQUE NOT NULL, password INTEGER NOT NULL, salt TEXT NOT NULL, signup_date INTEGER NOT NULL, PRIMARY KEY (user_id AUTOINCREMENT) ON CONFLICT FAIL);
|
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
user_id INTEGER NOT NULL ON CONFLICT ROLLBACK
|
||||||
|
UNIQUE ON CONFLICT ROLLBACK,
|
||||||
|
username TEXT NOT NULL ON CONFLICT ROLLBACK
|
||||||
|
UNIQUE ON CONFLICT ROLLBACK,
|
||||||
|
email TEXT UNIQUE ON CONFLICT ROLLBACK
|
||||||
|
NOT NULL ON CONFLICT ROLLBACK,
|
||||||
|
password TEXT NOT NULL ON CONFLICT ROLLBACK,
|
||||||
|
salt TEXT NOT NULL ON CONFLICT ROLLBACK,
|
||||||
|
signup_date REAL NOT NULL,
|
||||||
|
PRIMARY KEY (
|
||||||
|
user_id AUTOINCREMENT
|
||||||
|
)
|
||||||
|
ON CONFLICT ABORT
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
COMMIT TRANSACTION;
|
COMMIT TRANSACTION;
|
||||||
PRAGMA foreign_keys = on;
|
PRAGMA foreign_keys = on;
|
||||||
|
|
|
@ -3,84 +3,65 @@ use experimental 'smartmatch';
|
||||||
use Mojo::Base 'Mojolicious', -signatures;
|
use Mojo::Base 'Mojolicious', -signatures;
|
||||||
use CharmBoard::Schema;
|
use CharmBoard::Schema;
|
||||||
|
|
||||||
# This method will run once at server start
|
# this method will run once at server start
|
||||||
sub startup ($app) {
|
sub startup ($app) {
|
||||||
|
# load plugins that require no additional conf
|
||||||
$app = shift;
|
|
||||||
|
|
||||||
$app->plugin('TagHelpers');
|
$app->plugin('TagHelpers');
|
||||||
$app->plugin('Renderer::WithoutCache'); # for dev env only
|
|
||||||
|
|
||||||
$app->renderer->cache->max_keys(0); # for dev env only
|
# load configuration from config file
|
||||||
|
my $config = $app->plugin('Config' => {file => 'charmboard.conf'});
|
||||||
|
|
||||||
$app->defaults(layout => 'default');
|
# load dev env only stuff, if applicable
|
||||||
|
if ( $config->{environment} eq 'dev' ) {
|
||||||
# Load configuration from config file
|
$app->plugin('Renderer::WithoutCache');
|
||||||
my $config = $app->plugin('Config' => {
|
$app->renderer->cache->max_keys(0)};
|
||||||
file => 'charmboard.conf'
|
|
||||||
});
|
|
||||||
|
|
||||||
# Configure the application
|
# import Mojolicious secrets
|
||||||
## Import Mojolicious secrets (cookie encryption)
|
|
||||||
$app->secrets($config->{secrets});
|
$app->secrets($config->{secrets});
|
||||||
## Import password pepper value
|
|
||||||
my $pepper = $config->{passCrypt}->{pepper};
|
|
||||||
$app->helper( pepper => sub { $pepper } );
|
|
||||||
## Database setup
|
|
||||||
my ($dsn, $dbUnicode);
|
|
||||||
|
|
||||||
|
# import password pepper value
|
||||||
|
$app->helper(pepper => sub {$config->{pass_crypt}->{pepper}});
|
||||||
|
|
||||||
|
## database setup
|
||||||
|
my ($dsn, $dbUnicode);
|
||||||
if ($app->config->{database}->{type} ~~ 'sqlite') {
|
if ($app->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 ($app->config->{database}->{type} ~~ 'mysql') {
|
||||||
$dsn = "dbi:mysql:" . $config->{database}->{name};
|
$dsn = "dbi:mysql:" . $config->{database}->{name};
|
||||||
$dbUnicode = "mysql_enable_utf";
|
$dbUnicode = "mysql_enable_utf"}
|
||||||
} elsif ($app->config->{database}->{type} ~~ 'pgsql') {
|
else {die "\nUnknown, unsupported, or empty database type in charmboard.conf.
|
||||||
$dsn = "dbi:Pg:" . $config->{database}->{name};
|
|
||||||
$dbUnicode = "pg_enable_utf8";
|
|
||||||
} 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
|
If you're sure you've set it to something supported, maybe double check your spelling?\n
|
||||||
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});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
$app->helper( schema => sub { $schema } );
|
# router
|
||||||
|
|
||||||
# Router
|
|
||||||
my $r = $app->routes;
|
my $r = $app->routes;
|
||||||
|
|
||||||
# Controller routes
|
# controller routes
|
||||||
## Index page
|
## index page
|
||||||
$r->get('/')->to(
|
$r->get('/')->to(
|
||||||
controller => 'Controller::Main',
|
controller => 'Controller::Main',
|
||||||
action => 'index'
|
action => 'index');
|
||||||
);
|
## registration page
|
||||||
## Registration page
|
|
||||||
$r->get('/register')->to(
|
$r->get('/register')->to(
|
||||||
controller => 'Controller::Auth',
|
controller => 'Controller::Auth',
|
||||||
action => 'register'
|
action => 'register');
|
||||||
);
|
|
||||||
$r->post('/register')->to(
|
$r->post('/register')->to(
|
||||||
controller => 'Controller::Auth',
|
controller => 'Controller::Auth',
|
||||||
action => 'registration_do'
|
action => 'register_do');
|
||||||
);
|
## login page
|
||||||
## Login page
|
|
||||||
$r->get('/login')->to(
|
$r->get('/login')->to(
|
||||||
controller => 'Controller::Auth',
|
controller => 'Controller::Auth',
|
||||||
action => 'login'
|
action => 'login');
|
||||||
);
|
|
||||||
$r->post('/login')->to(
|
$r->post('/login')->to(
|
||||||
controller => 'Controller::Auth',
|
controller => 'Controller::Auth',
|
||||||
action => 'login_do'
|
action => 'login_do');
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package CharmBoard::Controller::Auth;
|
package CharmBoard::Controller::Auth;
|
||||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
use CharmBoard::Crypt::Password;
|
use CharmBoard::Crypt::Password;
|
||||||
|
use CharmBoard::Crypt::Seasoning;
|
||||||
|
use Time::HiRes qw(time);
|
||||||
|
|
||||||
# initial registration page
|
# initial registration page
|
||||||
sub register ($app) {
|
sub register ($app) {
|
||||||
|
@ -11,63 +13,80 @@ sub register ($app) {
|
||||||
)};
|
)};
|
||||||
|
|
||||||
# process submitted registration form
|
# process submitted registration form
|
||||||
sub registration_do ($app) {
|
# TODO: implement email validation here at some point
|
||||||
|
sub register_do ($app) {
|
||||||
my $username = $app->param('username');
|
my $username = $app->param('username');
|
||||||
my $email = $app->param('email');
|
my $email = $app->param('email');
|
||||||
my $password = $app->param('password');
|
my $password = $app->param('password');
|
||||||
my $confirmPassword = $app->param('confirm-password');
|
my $confirmPassword = $app->param('confirm-password');
|
||||||
|
|
||||||
# check to make sure all required fields are filled
|
# check to make sure all required fields are filled
|
||||||
if ( ! $username || ! $password || ! $confirmPassword ) {
|
if (! $username || ! $password || ! $confirmPassword) {
|
||||||
$app->flash( error => 'All fields required.' );
|
$app->flash(error => 'All fields required.');
|
||||||
$app->redirect_to('register');
|
$app->redirect_to('register')};
|
||||||
};
|
|
||||||
|
|
||||||
# check to make sure both passwords match
|
# check to make sure both passwords match
|
||||||
# TODO: add check on frontend for this for people with JS enabled
|
# TODO: add check on frontend for this for people with JS enabled
|
||||||
if ( $confirmPassword ne $password ) {
|
if ($confirmPassword ne $password) {
|
||||||
$app->flash( error => 'Passwords do not match.' );
|
$app->flash(error => 'Passwords do not match.');
|
||||||
$app->redirect_to('register');
|
$app->redirect_to('register')};
|
||||||
};
|
|
||||||
|
|
||||||
# check to make sure username and/or email isn't already in use;
|
# check to make sure username and/or email isn't already in use;
|
||||||
# if not, continue with registration
|
# if not, continue with registration
|
||||||
|
## search for input username and email in database
|
||||||
my $userCheck = $app->schema->resultset('Users')->search({username => $username})->single;
|
my $userCheck = $app->schema->resultset('Users')->search({username => $username})->single;
|
||||||
my $emailCheck = $app->schema->resultset('Users')->search({email => $email})->single;
|
my $emailCheck = $app->schema->resultset('Users')->search({email => $email})->single;
|
||||||
if ( $userCheck || $emailCheck ) {
|
|
||||||
if ( $userCheck && $emailCheck ) {
|
if ($userCheck || $emailCheck) {
|
||||||
# notify user that username and email are both already being used
|
if ($userCheck && $emailCheck) { # notify user that username and email are both already being used
|
||||||
$app->flash( error => 'Username and email already in use.' );
|
$app->flash(error => 'Username and email already in use.');
|
||||||
$app->redirect_to('register');
|
$app->redirect_to('register')}
|
||||||
} elsif ( $userCheck ) {
|
elsif ($userCheck) { # notify user that only username is already in use
|
||||||
# notify user that only username is already in use
|
$app->flash(error => 'Username is already in use.');
|
||||||
$app->flash( error => 'Username is already in use.' );
|
$app->redirect_to('register')}
|
||||||
$app->redirect_to('register');
|
elsif ($emailCheck) { # notify user that only email is already in use
|
||||||
} elsif ( $emailCheck ) {
|
$app->flash(error => 'email is already in use.');
|
||||||
# notify user that only email is already in use
|
$app->redirect_to('register')}}
|
||||||
$app->flash( error => 'email is already in use.' );
|
else { # TODO: add more error handling here, in case SQL transact fails
|
||||||
$app->redirect_to('register');
|
# append pepper to pass before hashing
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$password = $app->pepper . ':' . $password;
|
$password = $app->pepper . ':' . $password;
|
||||||
my ($hash, $salt) = pass_gen($password);
|
# return hashed result + salt
|
||||||
|
my ($salt, $hash) = passgen($password);
|
||||||
|
# add user info and pw/salt to DB
|
||||||
$app->schema->resultset('Users')->create({
|
$app->schema->resultset('Users')->create({
|
||||||
username => $username,
|
username => $username,
|
||||||
email => $email,
|
email => $email,
|
||||||
password => $hash,
|
password => $hash,
|
||||||
salt => $salt,
|
salt => $salt,
|
||||||
signup_date => time
|
signup_date => time });
|
||||||
});
|
$app->flash(message => 'User registered successfully!');
|
||||||
$app->flash( message => 'User registered successfully!' );
|
$app->redirect_to('register')}};
|
||||||
$app->redirect_to('register');
|
|
||||||
}};
|
|
||||||
|
|
||||||
sub login ($app) {
|
sub login ($app) {
|
||||||
$app->render(
|
$app->render(
|
||||||
template => 'login',
|
template => 'login',
|
||||||
error => $app->flash('error'),
|
error => $app->flash('error'),
|
||||||
message => $app->flash('message')
|
message => $app->flash('message'))};
|
||||||
);
|
|
||||||
|
sub login_do ($app) {
|
||||||
|
my $username = $app->param('username');
|
||||||
|
my $password = $app->param('password');
|
||||||
|
$password = $app->pepper . ':' . $password;
|
||||||
|
my $userInfoCheck = $app->schema->resultset('Users')->search({username => $username});
|
||||||
|
if ($userInfoCheck) {
|
||||||
|
my $savedSalt = $userInfoCheck->get_column('salt')->first;
|
||||||
|
my $savedHash = $userInfoCheck->get_column('password')->first;
|
||||||
|
my $passCheckStatus = passchk($savedSalt, $savedHash, $password);
|
||||||
|
if ($passCheckStatus) {
|
||||||
|
$app->flash(message => 'Password correct, but auth isn\'t implemented yet');
|
||||||
|
$app->redirect_to('login')
|
||||||
|
} else {
|
||||||
|
$app->flash(error => 'Password incorrect');
|
||||||
|
$app->redirect_to('login')}}
|
||||||
|
else {
|
||||||
|
$app->flash(error => 'User ' . $username . ' does not exist.');
|
||||||
|
$app->redirect_to('login')};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
|
@ -2,7 +2,6 @@ package CharmBoard::Controller::Main;
|
||||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
|
|
||||||
sub index ($app) {
|
sub index ($app) {
|
||||||
$app->render(template => 'index');
|
$app->render(template => 'index')}
|
||||||
}
|
|
||||||
|
|
||||||
1;
|
1;
|
|
@ -2,20 +2,35 @@ package CharmBoard::Crypt::Password;
|
||||||
use Authen::Passphrase::Argon2;
|
use Authen::Passphrase::Argon2;
|
||||||
|
|
||||||
use Exporter qw(import);
|
use Exporter qw(import);
|
||||||
|
our @EXPORT = qw(passgen passchk);
|
||||||
|
|
||||||
our @EXPORT = qw(pass_gen);
|
# subroutine to generate password salt + hashed pw on pass creation
|
||||||
|
# outputs the salt and then the hashed pw, so when assigning vars
|
||||||
sub pass_gen ($) {
|
# from this sub's output, do it like this:
|
||||||
|
# `my ($salt, $hash) = passgen($password);`
|
||||||
|
sub passgen ($) {
|
||||||
my $argon2 = Authen::Passphrase::Argon2->new(
|
my $argon2 = Authen::Passphrase::Argon2->new(
|
||||||
salt_random => 1,
|
salt_random => 1,
|
||||||
passphrase => $_[0],
|
passphrase => $_[0],
|
||||||
cost => 3,
|
cost => 3,
|
||||||
factor => '16M',
|
factor => '32M',
|
||||||
parallelism => 1,
|
parallelism => 1,
|
||||||
size => 32
|
size => 16 );
|
||||||
);
|
|
||||||
|
|
||||||
return ($argon2->salt_hex, $argon2->as_hex);
|
return ($argon2->salt, $argon2->as_crypt)};
|
||||||
}
|
|
||||||
|
# subroutine to check inputted password against one in DB
|
||||||
|
# `$_[0]` is the salt, `$_[1]` is the hashed pass, and
|
||||||
|
# `$_[2]` is the inputted plaintext pepper:password to check
|
||||||
|
sub passchk ($$$) {
|
||||||
|
my $argon2 = Authen::Passphrase::Argon2->new(
|
||||||
|
salt => $_[0],
|
||||||
|
hash => $_[1],
|
||||||
|
cost => 3,
|
||||||
|
factor => '32M',
|
||||||
|
parallelism => 1,
|
||||||
|
size => 16 );
|
||||||
|
|
||||||
|
return ($argon2->match($_[2]))}
|
||||||
|
|
||||||
1;
|
1;
|
|
@ -0,0 +1,20 @@
|
||||||
|
package CharmBoard::Crypt::Seasoning;
|
||||||
|
use Math::Random::Secure qw(irand);
|
||||||
|
|
||||||
|
use Exporter qw(import);
|
||||||
|
our @EXPORT = qw(seasoning);
|
||||||
|
|
||||||
|
sub seasoning ($) {
|
||||||
|
my @spices = qw(0 1 2 3 4 5 6 7 8 9 a b c d e f g
|
||||||
|
h i j k l m n o p q r s t u v w x y z A B C D E F
|
||||||
|
G H I J K L M N O P Q R S T U V W X Y Z ! @ $ % ^
|
||||||
|
& * / ? . ; : \ [ ] - _ < > ` ~ + = £ ¥ ¢);
|
||||||
|
|
||||||
|
my $blend;
|
||||||
|
while (length($blend) < $_[0]) {
|
||||||
|
# gen num to choose char for $blend
|
||||||
|
$blend = $blend . $spices[irand(@spices)]};
|
||||||
|
|
||||||
|
return ($blend); }
|
||||||
|
|
||||||
|
1;
|
|
@ -6,12 +6,10 @@ __PACKAGE__->add_columns(
|
||||||
cat_id => {
|
cat_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_auto_increment => 1,
|
is_auto_increment => 1,
|
||||||
is_nullable => 0,
|
is_nullable => 0, },
|
||||||
},
|
|
||||||
cat_name => {
|
cat_name => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_nullable => 0,
|
is_nullable => 0, });
|
||||||
});
|
|
||||||
__PACKAGE__->set_primary_key('cat_id');
|
__PACKAGE__->set_primary_key('cat_id');
|
||||||
|
|
||||||
1
|
1
|
|
@ -6,33 +6,27 @@ __PACKAGE__->add_columns(
|
||||||
post_id => {
|
post_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
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_auto_increment => 1,
|
is_auto_increment => 0,
|
||||||
is_nullable => 0,
|
is_nullable => 0, },
|
||||||
},
|
|
||||||
thread_id => {
|
thread_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_auto_increment => 1,
|
is_auto_increment => 1,
|
||||||
is_nullable => 0,
|
is_nullable => 0, },
|
||||||
},
|
|
||||||
post_date => {
|
post_date => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_auto_increment => 1,
|
is_auto_increment => 0,
|
||||||
is_nullable => 0,
|
is_nullable => 0, });
|
||||||
});
|
|
||||||
__PACKAGE__->set_primary_key('post_id');
|
__PACKAGE__->set_primary_key('post_id');
|
||||||
__PACKAGE__->belongs_to(
|
__PACKAGE__->belongs_to(
|
||||||
user_id =>
|
user_id =>
|
||||||
'CharmBoard::Schema::Result::Users',
|
'CharmBoard::Schema::Result::Users',
|
||||||
'user_id'
|
'user_id' );
|
||||||
);
|
|
||||||
__PACKAGE__->belongs_to(
|
__PACKAGE__->belongs_to(
|
||||||
thread_id =>
|
thread_id =>
|
||||||
'CharmBoard::Schema::Result::Threads',
|
'CharmBoard::Schema::Result::Threads',
|
||||||
'thread_id'
|
'thread_id' );
|
||||||
);
|
|
||||||
|
|
||||||
1
|
1
|
|
@ -1,25 +1,32 @@
|
||||||
package CharmBoard::Schema::Result::Session;
|
package CharmBoard::Schema::Result::Session;
|
||||||
use base qw(DBIx::Class::Core);
|
use base qw(DBIx::Class::Core);
|
||||||
|
|
||||||
__PACKAGE__->table('session');
|
__PACKAGE__->table('sessions');
|
||||||
__PACKAGE__->add_columns(
|
__PACKAGE__->add_columns(
|
||||||
user_id => {
|
user_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_nullable => 0,
|
is_auto_increment => 0,
|
||||||
},
|
is_nullable => 0, },
|
||||||
session_id => {
|
session_key => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_nullable => 0,
|
is_auto_increment => 0,
|
||||||
},
|
is_nullable => 0, },
|
||||||
session_expiry => {
|
session_expiry => {
|
||||||
data_type => 'integer',
|
data_type => 'numeric',
|
||||||
is_nullable => 0,
|
is_auto_increment => 0,
|
||||||
});
|
is_nullable => 0, },
|
||||||
|
is_ip_bound => {
|
||||||
|
data_type => 'integer',
|
||||||
|
is_auto_increment => 0,
|
||||||
|
is_nullable => 0, },
|
||||||
|
bound_ip => {
|
||||||
|
data_type => 'text',
|
||||||
|
is_auto_increment => 0,
|
||||||
|
is_nullable => 1, });
|
||||||
__PACKAGE__->set_primary_key('user_id');
|
__PACKAGE__->set_primary_key('user_id');
|
||||||
__PACKAGE__->belongs_to(
|
__PACKAGE__->belongs_to(
|
||||||
user_id =>
|
user_id =>
|
||||||
'CharmBoard::Schema::Result::Users',
|
'CharmBoard::Schema::Result::Users',
|
||||||
'user_id'
|
'user_id');
|
||||||
);
|
|
||||||
|
|
||||||
1
|
1
|
|
@ -6,25 +6,23 @@ __PACKAGE__->add_columns(
|
||||||
subf_id => {
|
subf_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_auto_increment => 1,
|
is_auto_increment => 1,
|
||||||
is_nullable => 0,
|
is_nullable => 0, },
|
||||||
},
|
|
||||||
subf_cat => {
|
subf_cat => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_nullable => 0,
|
is_auto_increment => 0,
|
||||||
},
|
is_nullable => 0, },
|
||||||
subf_name => {
|
subf_name => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_nullable => 0,
|
is_auto_increment => 0,
|
||||||
},
|
is_nullable => 0, },
|
||||||
subf_desc => {
|
subf_desc => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_nullable => 1,
|
is_auto_increment => 0,
|
||||||
});
|
is_nullable => 1, });
|
||||||
__PACKAGE__->set_primary_key('subf_id');
|
__PACKAGE__->set_primary_key('subf_id');
|
||||||
__PACKAGE__->belongs_to(
|
__PACKAGE__->belongs_to(
|
||||||
subf_cat =>
|
subf_cat =>
|
||||||
'CharmBoard::Schema::Result::Categories',
|
'CharmBoard::Schema::Result::Categories',
|
||||||
{'foreign.cat_id' => 'self.subf_cat'}
|
{'foreign.cat_id' => 'self.subf_cat'});
|
||||||
);
|
|
||||||
|
|
||||||
1
|
1
|
|
@ -6,21 +6,17 @@ __PACKAGE__->add_columns(
|
||||||
thread_id => {
|
thread_id => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_auto_increment => 1,
|
is_auto_increment => 1,
|
||||||
is_nullable => 0,
|
is_nullable => 0, },
|
||||||
},
|
|
||||||
thread_title => {
|
thread_title => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_nullable => 0,
|
is_nullable => 0, },
|
||||||
},
|
|
||||||
thread_subf => {
|
thread_subf => {
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_nullable => 1,
|
is_nullable => 1, });
|
||||||
});
|
|
||||||
__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::Result::Subforums',
|
||||||
{'foreign.subf_id' => 'self.thread_subf'}
|
{'foreign.subf_id' => 'self.thread_subf'});
|
||||||
);
|
|
||||||
|
|
||||||
1
|
1
|
|
@ -7,28 +7,23 @@ __PACKAGE__->add_columns(
|
||||||
data_type => 'integer',
|
data_type => 'integer',
|
||||||
is_numeric => 1,
|
is_numeric => 1,
|
||||||
is_nullable => 0,
|
is_nullable => 0,
|
||||||
is_auto_increment => 1
|
is_auto_increment => 1, },
|
||||||
},
|
|
||||||
username => {
|
username => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_nullable => 0
|
is_nullable => 0, },
|
||||||
},
|
email => {
|
||||||
email => {
|
data_type => 'text',
|
||||||
data_type => 'text'
|
is_nullable => 0, },
|
||||||
},
|
|
||||||
password => {
|
password => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_nullable => 0
|
is_nullable => 0, },
|
||||||
},
|
|
||||||
salt => {
|
salt => {
|
||||||
data_type => 'text',
|
data_type => 'text',
|
||||||
is_nullable => 0
|
is_nullable => 0, },
|
||||||
},
|
|
||||||
signup_date => {
|
signup_date => {
|
||||||
data_type => 'integer',
|
data_type => 'real',
|
||||||
is_numeric => 1,
|
is_numeric => 1,
|
||||||
is_nullable => 0
|
is_nullable => 0, });
|
||||||
});
|
|
||||||
__PACKAGE__->set_primary_key('user_id');
|
__PACKAGE__->set_primary_key('user_id');
|
||||||
|
|
||||||
1
|
1
|
|
@ -1,25 +0,0 @@
|
||||||
#!/usr/bin/env perl
|
|
||||||
use warnings;
|
|
||||||
use strict;
|
|
||||||
use Math::Random::Secure qw( irand );
|
|
||||||
|
|
||||||
my @chars = qw( 0 1 2 3 4 5 6 7 8 9
|
|
||||||
a b c d e f g h i j
|
|
||||||
k l m n o p q r s t
|
|
||||||
u v w x y z A B C D
|
|
||||||
E F G H I J K L M N
|
|
||||||
O P Q R S T U V W X
|
|
||||||
Y Z ! @ $ % ^ & * /
|
|
||||||
? . ; : \ [ ] - _ );
|
|
||||||
|
|
||||||
my $pepper = '';
|
|
||||||
while ( length($pepper) < 25 ) {
|
|
||||||
# gen and discard numbers to flush out dupe chance
|
|
||||||
irand(255); irand(255); irand(255); irand(255);
|
|
||||||
irand(255); irand(255); irand(255); irand(255);
|
|
||||||
# gen num for pepper
|
|
||||||
$pepper = $pepper . $chars[irand(@chars)];
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Your pepper value is:\n");
|
|
||||||
print($pepper);
|
|
Loading…
Reference in New Issue