From 9a47d097bfbd09af45dce6b61dc2b0f8aefde18e Mon Sep 17 00:00:00 2001 From: Kayden Tebau Date: Wed, 22 Feb 2023 22:23:38 -0800 Subject: [PATCH] Improve readme and allow password changing. --- README.md | 18 ++++++++----- lib/IpfsUpload.pm | 1 + lib/IpfsUpload/Controller/Login.pm | 43 +++++++++++++++++++++++++++--- lib/IpfsUpload/Model/Users.pm | 11 +++++++- lib/IpfsUpload/Util.pm | 2 +- templates/interface/tokens.html.ep | 12 +++++++++ 6 files changed, 75 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8b5f674..318ee48 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,6 @@ Simple IPFS remote pinning service and HTTP upload provider. -Currently, it only supports Postgres and LDAP -as backends. LDAP is used to login while Postgres -is used to store information about pins and access tokens -used by tools. - In addition to providing a remote pinning endpoint at /api, you can also POST uploads directly to the root `/` with an access token and recieve a URL back: @@ -17,8 +12,8 @@ https://unix.dog/ipfs/Qm... ## Setup To setup this service locally, you will need: -- PostgreSQL -- LDAP service with password auth +- PostgreSQL (optional) +- LDAP service with password auth (optional) - IPFS node with RPC API (Kubo) Copy the config ipfs_upload.default.yml to @@ -26,3 +21,12 @@ ipfs_upload.yml, and edit the config appropriately. Then use hypnotoad or morbo to run `script/IpfsUpload`. Log in, generate tokens, and point your IPFS remote pinning to /api. Done! + +## Databases +IPFS Upload supports PostgreSQL or SQLite. LDAP and in-database +password hashing with Argon2ID are also supported. Check the default +config to learn how to configure it properly. + +When using in-database authentication, you can change your password +on the Access Token page. In the config, you can whitelist usernames. +The password will be set on first login. diff --git a/lib/IpfsUpload.pm b/lib/IpfsUpload.pm index 308c672..efa3622 100644 --- a/lib/IpfsUpload.pm +++ b/lib/IpfsUpload.pm @@ -70,6 +70,7 @@ sub startup($self) { $r->get('/login')->to('Login#login'); $r->get('/my/logout')->to('Login#logout'); $r->post('/auth')->to('Login#auth'); + $r->post('/my/password')->to('Login#change_password'); $r->get('/my')->to('Interface#landing'); $r->get('/my/tokens')->to('Interface#token_list'); diff --git a/lib/IpfsUpload/Controller/Login.pm b/lib/IpfsUpload/Controller/Login.pm index b668051..8ea77ca 100644 --- a/lib/IpfsUpload/Controller/Login.pm +++ b/lib/IpfsUpload/Controller/Login.pm @@ -31,7 +31,7 @@ sub auth($c) { my $mesg = $ldap->bind($bindDN, password=>$password); $mesg->code and die $mesg->error; })->then(sub ($res) { - return $c->users->getOrMake($username); + return $c->users->get_or_make($username); })->then(sub ($res) { $c->session->{uid} = $res; $c->flash(msg => "Logged in."); @@ -41,11 +41,26 @@ sub auth($c) { $c->redirect_to('/login'); }); } elsif ($config->{auth} eq 'db') { + # Check if whitelisted + if (not grep $_ eq $username, @{$config->{whitelist_names}}) { + $c->flash(msg => "Login failed."); + return $c->redirect_to('/login'); + } + + # Attempt to get user from username return $c->users->get($username)->then(sub ($uid) { + # Create if not exists if (!defined $uid) { - $c->flash(msg => "Login failed."); - return $c->redirect_to('/login'); + # Hash pass + insert and create user + my $hash = IpfsUpload::Util::add_pass($password); + return $c->users->make_with_pass($username, $hash)->then(sub ($uid_new) { + $c->session->{uid} = $uid_new; + $c->flash(msg => "Logged in."); + return $c->redirect_to('/my'); + }); } + + # Verify password return $c->users->get_pass_hash($uid)->then(sub ($hash) { if (IpfsUpload::Util::check_pass($password, $hash)) { $c->session->{uid} = $uid; @@ -55,6 +70,7 @@ sub auth($c) { die "Login failed."; }); })->catch(sub ($err) { + say $err; $c->flash(msg => "Login failed."); $c->redirect_to('/login'); }); @@ -73,4 +89,25 @@ sub logout($c) { return $c->redirect_to('/login'); } +sub change_password($c) { + if (!IpfsUpload::Util::check_auth($c)) { + return $c->redirect_to("/login"); + } + + if ($c->config->{auth} ne 'db') { + return $c->redirect_to('/my'); + } + + my $uid = $c->stash('uid'); + my $v = $c->validation; + $v->required('password'); + return $c->redirect_to('/my') if $v->has_error; + + my $password = $c->param('password'); + return $c->users->set_pass_hash($uid, IpfsUpload::Util::add_pass($password))->then(sub { + $c->flash(msg => "Password set."); + return $c->redirect_to('/my'); + }); +} + 1; diff --git a/lib/IpfsUpload/Model/Users.pm b/lib/IpfsUpload/Model/Users.pm index d6ec102..2328e05 100644 --- a/lib/IpfsUpload/Model/Users.pm +++ b/lib/IpfsUpload/Model/Users.pm @@ -57,7 +57,7 @@ sub list_tokens($self, $uid) { }); } -sub getOrMake($self, $username) { +sub get_or_make($self, $username) { return $self->sql->db->select_p('users', ['uid'], {username => $username})->then(sub ($res) { my $val = $res->hash; if (defined $val) { @@ -85,4 +85,13 @@ sub get_pass_hash($self, $uid) { }); } +sub make_with_pass($self, $username, $hash) { + return $self->sql->db->insert_p('users', {username => $username, pass => $hash}, {returning => 'uid'})->then(sub ($n) { + return $n->hash->{uid}; + }); +} + +sub set_pass_hash($self, $uid, $hash) { + return $self->sql->db->update_p('users', { pass => $hash }, { uid => $uid }); +} 1; diff --git a/lib/IpfsUpload/Util.pm b/lib/IpfsUpload/Util.pm index fcbea80..ebef106 100644 --- a/lib/IpfsUpload/Util.pm +++ b/lib/IpfsUpload/Util.pm @@ -11,7 +11,7 @@ use Bytes::Random::Secure qw/random_bytes_hex/; sub add_pass($pass) { my $salt = random_bytes_hex(16); - my $encoded = argon2id_pass($pass, $salt, '32M', 1, 16); + my $encoded = argon2id_pass($pass, $salt, 3, '32M', 1, 16); return $encoded; } diff --git a/templates/interface/tokens.html.ep b/templates/interface/tokens.html.ep index 2a35c01..dad412c 100644 --- a/templates/interface/tokens.html.ep +++ b/templates/interface/tokens.html.ep @@ -45,3 +45,15 @@

More info on pinning

+ +% if ($c->config->{auth} eq 'db') { +

Change your password here:

+%= form_for '/my/password' => (method => 'POST') => begin +

+ %= label_for password => 'New Password' + %= password_field 'password', id=>'password' +

+ %= submit_button +% end + +% }