diff options
| author | Daniel Friesel <derf@finalrewind.org> | 2019-04-30 12:08:51 +0200 | 
|---|---|---|
| committer | Daniel Friesel <derf@finalrewind.org> | 2019-04-30 12:08:51 +0200 | 
| commit | 867a5d4afbb3a5c0ad0e5dfbc10d2516e3e9123f (patch) | |
| tree | 13830d604e23ed647c874bd201ec999eeb37585b /lib | |
| parent | 4ad1a1d20e52f7aa4b1ae2b7cc84c83c584d6725 (diff) | |
allow users to change their mail address0.19
Closes #6
Diffstat (limited to 'lib')
| -rwxr-xr-x | lib/Travelynx.pm | 56 | ||||
| -rw-r--r-- | lib/Travelynx/Command/database.pm | 21 | ||||
| -rw-r--r-- | lib/Travelynx/Command/maintenance.pm | 13 | ||||
| -rw-r--r-- | lib/Travelynx/Controller/Account.pm | 100 | 
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) = @_; | 
