From a34a67b2f9127440860eb7228b295c17e592d6c8 Mon Sep 17 00:00:00 2001 From: Daniel Friesel Date: Mon, 13 Sep 2021 20:55:11 +0200 Subject: [PATCH] Add account add / delete CLI for sites with web registration disabled --- lib/Travelynx/Command/account.pm | 125 +++++++++++++++++++++++++++++++ lib/Travelynx/Model/Users.pm | 10 ++- 2 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 lib/Travelynx/Command/account.pm diff --git a/lib/Travelynx/Command/account.pm b/lib/Travelynx/Command/account.pm new file mode 100644 index 0000000..6cd3498 --- /dev/null +++ b/lib/Travelynx/Command/account.pm @@ -0,0 +1,125 @@ +package Travelynx::Command::account; + +# Copyright (C) 2021 Daniel Friesel +# +# SPDX-License-Identifier: AGPL-3.0-or-later +use Mojo::Base 'Mojolicious::Command'; +use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64); +use UUID::Tiny qw(:std); + +has description => 'Add or remove user accounts'; + +has usage => sub { shift->extract_usage }; + +sub hash_password { + my ($password) = @_; + my @salt_bytes = map { int( rand(255) ) + 1 } ( 1 .. 16 ); + my $salt = en_base64( pack( 'C[16]', @salt_bytes ) ); + + return bcrypt( $password, '$2a$12$' . $salt ); +} + +sub add_user { + my ( $self, $name, $email ) = @_; + + my $db = $self->app->pg->db; + + if ( my $error = $self->app->users->is_name_invalid( name => $name ) ) { + say "Cannot add account '$name': $error"; + die; + } + + my $token = "tmp"; + my $password = substr( create_uuid_as_string(UUID_V4), 0, 18 ); + my $password_hash = hash_password($password); + + my $tx = $db->begin; + my $user_id = $self->app->users->add_user( + db => $db, + name => $name, + email => $email, + token => $token, + password_hash => $password_hash, + ); + my $success = $self->app->users->verify_registration_token( + db => $db, + uid => $user_id, + token => $token, + in_transaction => 1, + ); + + if ($success) { + $tx->commit; + say "Added user $name ($email) with UID $user_id"; + say "Temporary password for login: $password"; + } +} + +sub delete_user { + my ( $self, $uid ) = @_; + + my $user_data = $self->app->users->get_data( uid => $uid ); + + if ( not $user_data ) { + say "UID $uid does not exist."; + return; + } + + $self->app->users->flag_deletion( uid => $uid ); + + say "User $user_data->{name} (UID $uid) has been flagged for deletion."; +} + +sub really_delete_user { + my ( $self, $uid, $name ) = @_; + + my $user_data = $self->app->users->get_data( uid => $uid ); + + if ( $user_data->{name} ne $name ) { + say + "User name $name does not match UID $uid. Account deletion aborted."; + return; + } + + say "Immediate deletion is not implemented yet."; + return; +} + +sub run { + my ( $self, $command, @args ) = @_; + + if ( $command eq 'add' ) { + $self->add_user(@args); + } + elsif ( $command eq 'delete' ) { + $self->delete_user(@args); + } + elsif ( $command eq 'DELETE' ) { + $self->really_delete_user(@args); + } + else { + $self->help; + } +} + +1; + +__END__ + +=head1 SYNOPSIS + + Usage: index.pl account add [name] [email] + + Adds user [name] with a temporary password, which is shown on stdout. + Users can change the password once logged in. + + Usage: index.pl account delete [uid] + + Request deletion of user [uid]. This has the same effect as using the + account deletion button. The user account and all corresponding data will + be deleted by a maintenance run after three days. + + Usage: index.pl account DELETE [uid] [name] + + Immediately delete user [uid]/[name] and all associated data. Deletion is + irrevocable. Deletion is only performed if [name] matches the name of [uid]. diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index 535b938..1371b8a 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -34,7 +34,11 @@ sub verify_registration_token { my $token = $opt{token}; my $db = $opt{db} // $self->{pg}->db; - my $tx = $db->begin; + my $tx; + + if ( not $opt{in_transaction} ) { + $tx = $db->begin; + } my $res = $db->select( 'pending_registrations', @@ -48,7 +52,9 @@ sub verify_registration_token { if ( $res->hash->{count} ) { $db->update( 'users', { status => 1 }, { id => $uid } ); $db->delete( 'pending_registrations', { user_id => $uid } ); - $tx->commit; + if ( not $opt{in_transaction} ) { + $tx->commit; + } return 1; } return;