summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDaniel Friesel <derf@finalrewind.org>2019-04-30 12:08:51 +0200
committerDaniel Friesel <derf@finalrewind.org>2019-04-30 12:08:51 +0200
commit867a5d4afbb3a5c0ad0e5dfbc10d2516e3e9123f (patch)
tree13830d604e23ed647c874bd201ec999eeb37585b /lib
parent4ad1a1d20e52f7aa4b1ae2b7cc84c83c584d6725 (diff)
allow users to change their mail address0.19
Closes #6
Diffstat (limited to 'lib')
-rwxr-xr-xlib/Travelynx.pm56
-rw-r--r--lib/Travelynx/Command/database.pm21
-rw-r--r--lib/Travelynx/Command/maintenance.pm13
-rw-r--r--lib/Travelynx/Controller/Account.pm100
4 files changed, 184 insertions, 6 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm
index 01515e9..d828f84 100755
--- a/lib/Travelynx.pm
+++ b/lib/Travelynx.pm
@@ -801,6 +801,57 @@ sub startup {
);
$self->helper(
+ 'mark_for_mail_change' => sub {
+ my ( $self, $db, $uid, $email, $token ) = @_;
+
+ $db->insert(
+ 'pending_mails',
+ {
+ user_id => $uid,
+ email => $email,
+ token => $token,
+ requested_at =>
+ DateTime->now( time_zone => 'Europe/Berlin' )
+ },
+ {
+ on_conflict => \
+'(user_id) do update set email = EXCLUDED.email, token = EXCLUDED.token, requested_at = EXCLUDED.requested_at'
+ },
+ );
+ }
+ );
+
+ $self->helper(
+ 'change_mail_with_token' => sub {
+ my ( $self, $uid, $token ) = @_;
+
+ my $db = $self->pg->db;
+ my $tx = $db->begin;
+
+ my $res_h = $db->select(
+ 'pending_mails',
+ ['email'],
+ {
+ user_id => $uid,
+ token => $token
+ }
+ )->hash;
+
+ if ($res_h) {
+ $db->update(
+ 'users',
+ { email => $res_h->{email} },
+ { id => $uid }
+ );
+ $db->delete( 'pending_mails', { user_id => $uid } );
+ $tx->commit;
+ return 1;
+ }
+ return;
+ }
+ );
+
+ $self->helper(
'remove_password_token' => sub {
my ( $self, $uid, $token ) = @_;
@@ -1004,7 +1055,7 @@ sub startup {
}
$count = $self->pg->db->select(
- 'pending_mails',
+ 'mail_blacklist',
'count(*) as count',
{
email => $mail,
@@ -1638,6 +1689,7 @@ sub startup {
$authed_r->get('/ajax/status_card.html')->to('traveling#status_card');
$authed_r->get('/cancelled')->to('traveling#cancelled');
$authed_r->get('/change_password')->to('account#password_form');
+ $authed_r->get('/change_mail')->to('account#change_mail');
$authed_r->get('/export.json')->to('account#json_export');
$authed_r->get('/history.json')->to('traveling#json_history');
$authed_r->get('/history')->to('traveling#history');
@@ -1646,9 +1698,11 @@ sub startup {
$authed_r->get('/journey/add')->to('traveling#add_journey_form');
$authed_r->get('/journey/:id')->to('traveling#journey_details');
$authed_r->get('/s/*station')->to('traveling#station');
+ $authed_r->get('/confirm_mail/:token')->to('account#confirm_mail');
$authed_r->post('/journey/add')->to('traveling#add_journey_form');
$authed_r->post('/journey/edit')->to('traveling#edit_journey');
$authed_r->post('/change_password')->to('account#change_password');
+ $authed_r->post('/change_mail')->to('account#change_mail');
$authed_r->post('/delete')->to('account#delete');
$authed_r->post('/logout')->to('account#do_logout');
$authed_r->post('/set_token')->to('api#set_token');
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) = @_;