From 867a5d4afbb3a5c0ad0e5dfbc10d2516e3e9123f Mon Sep 17 00:00:00 2001 From: Daniel Friesel Date: Tue, 30 Apr 2019 12:08:51 +0200 Subject: allow users to change their mail address Closes #6 --- lib/Travelynx/Command/database.pm | 21 +++++++- lib/Travelynx/Command/maintenance.pm | 13 +++-- lib/Travelynx/Controller/Account.pm | 100 ++++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 5 deletions(-) (limited to 'lib/Travelynx') diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index 62a470c..be5db72 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -378,7 +378,7 @@ my @migrations = ( }, # v6 -> v7 - # Add password_reset table to store data about pending password resets + # Add pending_passwords table to store data about pending password resets sub { my ($db) = @_; $db->query( @@ -393,6 +393,25 @@ my @migrations = ( } ); }, + + # v7 -> v8 + # Add pending_mails table to store data about pending mail changes + sub { + my ($db) = @_; + $db->query( + qq{ + alter table pending_mails rename to mail_blacklist; + create table pending_mails ( + user_id integer not null references users (id) primary key, + email varchar(256) not null, + token varchar(80) not null, + requested_at timestamptz not null + ); + comment on table pending_mails is 'Verification tokens for mail address changes'; + update schema_version set version = 8; + } + ); + }, ); sub setup_db { diff --git a/lib/Travelynx/Command/maintenance.pm b/lib/Travelynx/Command/maintenance.pm index 45969b3..2030705 100644 --- a/lib/Travelynx/Command/maintenance.pm +++ b/lib/Travelynx/Command/maintenance.pm @@ -34,13 +34,13 @@ sub run { ); my $pending - = $db->select( 'pending_mails', ['num_tries'], { email => $mail } ); + = $db->select( 'mail_blacklist', ['num_tries'], { email => $mail } ); my $pending_h = $pending->hash; if ($pending_h) { my $num_tries = $pending_h->{num_tries} + 1; $db->update( - 'pending_mails', + 'mail_blacklist', { num_tries => $num_tries, last_try => $reg_date @@ -50,7 +50,7 @@ sub run { } else { $db->insert( - 'pending_mails', + 'mail_blacklist', { email => $mail, num_tries => 1, @@ -69,6 +69,13 @@ sub run { printf( "Pruned %d pending password reset(s)\n", $rows ); } + $res = $db->delete( 'pending_mails', + { requested_at => { '<', $verification_deadline } } ); + + if ( my $rows = $res->rows ) { + printf( "Pruned %d pending mail change(s)\n", $rows ); + } + my $to_delete = $db->select( 'users', ['id'], { deletion_requested => { '<', $deletion_deadline } } ); my @uids_to_delete = $to_delete->arrays->map( sub { shift->[0] } )->each; diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index 259fb9e..962a33a 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -211,6 +211,88 @@ sub do_logout { $self->redirect_to('/login'); } +sub change_mail { + my ($self) = @_; + + my $action = $self->req->param('action'); + my $password = $self->req->param('password'); + my $email = $self->req->param('email'); + + if ( $action and $action eq 'update_mail' ) { + if ( $self->validation->csrf_protect->has_error('csrf_token') ) { + $self->render( + 'change_mail', + invalid => 'csrf', + ); + return; + } + + if ( not length($email) ) { + $self->render( 'change_mail', invalid => 'mail_empty' ); + return; + } + + if ( + not $self->authenticate( + $self->current_user->{name}, + $self->param('password') + ) + ) + { + $self->render( 'change_mail', invalid => 'password' ); + return; + } + + my $token = make_token(); + my $name = $self->current_user->{name}; + my $db = $self->pg->db; + my $tx = $db->begin; + + $self->mark_for_mail_change( $db, $self->current_user->{id}, + $email, $token ); + + my $ip = $self->req->headers->header('X-Forwarded-For'); + my $ua = $self->req->headers->user_agent; + my $date = DateTime->now( time_zone => 'Europe/Berlin' ) + ->strftime('%d.%m.%Y %H:%M:%S %z'); + + # In case Mojolicious is not running behind a reverse proxy + $ip + //= sprintf( '%s:%s', $self->tx->remote_address, + $self->tx->remote_port ); + my $confirm_url + = $self->url_for('confirm_mail')->to_abs->scheme('https'); + my $imprint_url = $self->url_for('impressum')->to_abs->scheme('https'); + + my $body = "Hallo ${name},\n\n"; + $body .= "Bitte bestätige unter <${confirm_url}/${token}>,\n"; + $body .= "dass du mit dieser Adresse E-Mail empfangen kannst.\n\n"; + $body + .= "Du erhältst diese Mail, da eine Änderung der deinem travelynx-Account\n"; + $body .= "zugeordneten Mail-Adresse beantragt wurde.\n\n"; + $body .= "Daten zur Anfrage:\n"; + $body .= " * Datum: ${date}\n"; + $body .= " * Client: ${ip}\n"; + $body .= " * UserAgent: ${ua}\n\n\n"; + $body .= "Impressum: ${imprint_url}\n"; + + my $success + = $self->sendmail->custom( $email, + 'travelynx: Mail-Adresse bestätigen', $body ); + + if ($success) { + $tx->commit; + $self->render( 'change_mail', success => 1 ); + } + else { + $self->render( 'change_mail', invalid => 'sendmail' ); + } + } + else { + $self->render('change_mail'); + } +} + sub password_form { my ($self) = @_; @@ -252,6 +334,7 @@ sub change_password { my $pw_hash = hash_password($password); $self->set_user_password( $self->current_user->{id}, $pw_hash ); + $self->flash( success => 'password' ); $self->redirect_to('account'); my $user = $self->current_user->{name}; @@ -361,7 +444,7 @@ sub request_password_reset { return; } if ( not $self->verify_password_token( $id, $token ) ) { - $self->render( 'recover_password', invalid => 'recovery token' ); + $self->render( 'recover_password', invalid => 'change token' ); return; } if ( $password ne $password2 ) { @@ -384,6 +467,7 @@ sub request_password_reset { invalid => 'Authentication failure – WTF?' ); } + $self->flash( success => 'password' ); $self->redirect_to('account'); $self->remove_password_token( $id, $token ); @@ -433,6 +517,20 @@ sub recover_password { } } +sub confirm_mail { + my ($self) = @_; + my $id = $self->current_user->{id}; + my $token = $self->stash('token'); + + if ( $self->change_mail_with_token( $id, $token ) ) { + $self->flash( success => 'mail' ); + $self->redirect_to('account'); + } + else { + $self->render( 'change_mail', invalid => 'change token' ); + } +} + sub account { my ($self) = @_; -- cgit v1.2.3