summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDaniel Friesel <derf@finalrewind.org>2021-09-13 20:55:11 +0200
committerDaniel Friesel <derf@finalrewind.org>2021-09-13 20:55:11 +0200
commita34a67b2f9127440860eb7228b295c17e592d6c8 (patch)
tree2570042bc6a4dfde92244dffdb11839f8b822e2b /lib
parent85fcf63dd843e928a9a3149568682aa89795a1c9 (diff)
Add account add / delete CLI for sites with web registration disabled
Diffstat (limited to 'lib')
-rw-r--r--lib/Travelynx/Command/account.pm125
-rw-r--r--lib/Travelynx/Model/Users.pm10
2 files changed, 133 insertions, 2 deletions
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;