diff options
author | Matthew Somerville <matthew@mysociety.org> | 2016-01-13 20:11:16 +0000 |
---|---|---|
committer | Matthew Somerville <matthew@mysociety.org> | 2016-01-22 17:26:57 +0000 |
commit | a1da835f294dbb4014585c04716a88211d686e62 (patch) | |
tree | 041510d8eb45e1e5638882041e94270c658dd18d | |
parent | 2854503286a7dbb9e3098469d508b1a4d37362ef (diff) |
Add login by Facebook when reporting.
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Auth.pm | 108 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Report/New.pm | 125 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Tokens.pm | 1 | ||||
-rw-r--r-- | templates/web/base/js/validation_rules.html | 1 | ||||
-rw-r--r-- | templates/web/base/report/new/fill_in_details.html | 10 | ||||
-rw-r--r-- | templates/web/base/report/new/fill_in_details_form.html | 3 | ||||
-rw-r--r-- | templates/web/base/report/new/form_user_loggedout.html | 21 | ||||
-rw-r--r-- | templates/web/base/report/new/form_user_loggedout_email.html | 4 | ||||
-rw-r--r-- | templates/web/base/report/new/login_success_form.html | 18 | ||||
-rw-r--r-- | templates/web/base/report/new/oauth_email_form.html | 26 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/fixmystreet.js | 14 | ||||
-rw-r--r-- | web/cobrands/sass/_base.scss | 30 | ||||
-rw-r--r-- | web/i/facebook-icon-32.png | bin | 0 -> 211 bytes | |||
-rw-r--r-- | web/js/fixmystreet.js | 4 |
14 files changed, 312 insertions, 53 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Auth.pm b/perllib/FixMyStreet/App/Controller/Auth.pm index b6cd84cf0..cf728806c 100644 --- a/perllib/FixMyStreet/App/Controller/Auth.pm +++ b/perllib/FixMyStreet/App/Controller/Auth.pm @@ -8,6 +8,7 @@ use Email::Valid; use Net::Domain::TLD; use mySociety::AuthToken; use JSON::MaybeXS; +use Net::Facebook::Oauth2; =head1 NAME @@ -36,6 +37,8 @@ sub general : Path : Args(0) { return unless $c->req->method eq 'POST'; # decide which action to take + $c->detach('facebook_sign_in') if $c->get_param('facebook_sign_in'); + my $clicked_password = $c->get_param('sign_in'); my $clicked_email = $c->get_param('email_sign_in'); my $data_password = $c->get_param('password_sign_in'); @@ -182,6 +185,111 @@ sub token : Path('/M') : Args(1) { $c->detach( 'redirect_on_signin', [ $data->{r} ] ); } +=head2 facebook_sign_in + +Starts the Facebook authentication sequence. + +=cut + +sub fb : Private { + my ($self, $c) = @_; + Net::Facebook::Oauth2->new( + application_id => $c->config->{FACEBOOK_APP_ID}, + application_secret => $c->config->{FACEBOOK_APP_SECRET}, + callback => $c->uri_for('/auth/Facebook'), + ); +} + +sub facebook_sign_in : Private { + my( $self, $c ) = @_; + + my $fb = $c->forward('/auth/fb'); + my $url = $fb->get_authorization_url(scope => ['email']); + + my %oauth; + $oauth{return_url} = $c->get_param('r'); + $oauth{detach_to} = $c->stash->{detach_to}; + $oauth{detach_args} = $c->stash->{detach_args}; + $c->session->{oauth} = \%oauth; + $c->res->redirect($url); +} + +=head2 facebook_callback + +Handles the Facebook callback request and completes the authentication sequence. + +=cut + +sub facebook_callback: Path('/auth/Facebook') : Args(0) { + my( $self, $c ) = @_; + + if ( $c->get_param('error_code') ) { + $c->stash->{oauth_failure} = 1; + if ($c->session->{oauth}{detach_to}) { + $c->detach($c->session->{oauth}{detach_to}, $c->session->{oauth}{detach_args}); + } else { + $c->stash->{template} = 'auth/general.html'; + $c->detach; + } + } + + my $fb = $c->forward('/auth/fb'); + my $access_token; + eval { + $access_token = $fb->get_access_token(code => $c->get_param('code')); + }; + if ($@) { + ($c->stash->{message} = $@) =~ s/at [^ ]*Auth.pm.*//; + $c->stash->{template} = 'errors/generic.html'; + $c->detach; + } + + # save this token in session + $c->session->{oauth}{token} = $access_token; + + my $info = $fb->get('https://graph.facebook.com/me?fields=name,email')->as_hash(); + my $name = $info->{name}; + my $email = lc ($info->{email} || ""); + my $uid = $info->{id}; + + my $user; + if ($email) { + # We've got an ID and an email address + # Remove any existing mention of this ID + my $existing = $c->model('DB::User')->find( { facebook_id => $uid } ); + $existing->update( { facebook_id => undef } ) if $existing; + # Get or create a user, give it this Facebook ID + $user = $c->model('DB::User')->find_or_new( { email => $email } ); + $user->facebook_id($uid); + $user->name($name); + $user->in_storage() ? $user->update : $user->insert; + } else { + # We've got an ID, but no email + $user = $c->model('DB::User')->find( { facebook_id => $uid } ); + if ($user) { + # Matching Facebook ID in our database + $user->name($name); + $user->update; + } else { + # No matching ID, store ID for use later + $c->session->{oauth}{facebook_id} = $uid; + $c->stash->{oauth_need_email} = 1; + } + } + + # If we've got here with a full user, log in + if ($user) { + $c->authenticate( { email => $user->email }, 'no_password' ); + $c->stash->{login_success} = 1; + } + + if ($c->session->{oauth}{detach_to}) { + $c->detach($c->session->{oauth}{detach_to}, $c->session->{oauth}{detach_args}); + } else { + $c->detach( 'redirect_on_signin', [ $c->session->{oauth}{return_url} ] ); + } +} + =head2 redirect_on_signin Used after signing in to take the person back to where they were. diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index 1b4c1b295..bab0f0fd0 100644 --- a/perllib/FixMyStreet/App/Controller/Report/New.pm +++ b/perllib/FixMyStreet/App/Controller/Report/New.pm @@ -391,6 +391,12 @@ sub report_import : Path('/import') { return 1; } +sub oauth_callback : Private { + my ( $self, $c, $token_code ) = @_; + $c->stash->{oauth_report} = $token_code; + $c->detach('report_new'); +} + =head2 initialize_report Create the report and set up some basics in it. If there is a partial report @@ -436,9 +442,6 @@ sub initialize_report : Private { # save the token to delete at the end $c->stash->{partial_token} = $token if $report; - # Stash the photo IDs for "already got" display - $c->stash->{upload_fileid} = $report->get_photoset->data; - } else { # no point keeping it if it is done. $token->delete; @@ -446,18 +449,25 @@ sub initialize_report : Private { } } - if ( !$report ) { + if (!$report && $c->stash->{oauth_report}) { + my $auth_token = $c->forward( '/tokens/load_auth_token', + [ $c->stash->{oauth_report}, 'problem/social' ] ); + $report = $c->model("DB::Problem")->new($auth_token->data); + } - # If we didn't find a partial then create a new one + if ($report) { + # Stash the photo IDs for "already got" display + $c->stash->{upload_fileid} = $report->get_photoset->data; + } else { + # If we didn't find one otherwise, start with a blank report $report = $c->model('DB::Problem')->new( {} ); + } - # If we have a user logged in let's prefill some values for them. - if ( $c->user ) { - my $user = $c->user->obj; - $report->user($user); - $report->name( $user->name ); - } - + # If we have a user logged in let's prefill some values for them. + if (!$report->user && $c->user) { + my $user = $c->user->obj; + $report->user($user); + $report->name( $user->name ); } if ( $c->get_param('first_name') && $c->get_param('last_name') ) { @@ -989,6 +999,13 @@ sub check_for_errors : Private { delete $field_errors{name}; } + # if using social login then we don't care about name and email errors + $c->stash->{is_social_user} = $c->get_param('facebook_sign_in') || $c->get_param('twitter_sign_in'); + if ( $c->stash->{is_social_user} ) { + delete $field_errors{name}; + delete $field_errors{email}; + } + # add the photo error if there is one. if ( my $photo_error = delete $c->stash->{photo_error} ) { $field_errors{photo} = $photo_error; @@ -1002,6 +1019,19 @@ sub check_for_errors : Private { return; } +# Store changes in token for when token is validated. +sub tokenize_user : Private { + my ($self, $c, $report) = @_; + $c->stash->{token_data} = { + name => $report->user->name, + phone => $report->user->phone, + password => $report->user->password, + title => $report->user->title, + }; + $c->stash->{token_data}{facebook_id} = $c->session->{oauth}{facebook_id} + if $c->get_param('oauth_need_email') && $c->session->{oauth}{facebook_id}; +} + =head2 save_user_and_report Save the user and the report. @@ -1013,7 +1043,41 @@ before or they are currently logged in. Otherwise discard any changes. sub save_user_and_report : Private { my ( $self, $c ) = @_; - my $report = $c->stash->{report}; + my $report = $c->stash->{report}; + + # If there was a photo add that + if ( my $fileid = $c->stash->{upload_fileid} ) { + $report->photo($fileid); + } + + # Set a default if possible + $report->category( _('Other') ) unless $report->category; + + # Set unknown to DB unknown + $report->bodies_str( undef ) if $report->bodies_str eq '-1'; + + # if there is a Message Manager message ID, pass it back to the client view + if ($c->cobrand->moniker eq 'fixmybarangay' && $c->get_param('external_source_id') =~ /^\d+$/) { + $c->stash->{external_source_id} = $c->get_param('external_source_id'); + $report->external_source_id( $c->get_param('external_source_id') ); + $report->external_source( $c->config->{MESSAGE_MANAGER_URL} ) ; + } + + if ( $c->stash->{is_social_user} ) { + my $token = $c->model("DB::Token")->create( { + scope => 'problem/social', + data => { $report->get_inflated_columns }, + } ); + + $c->stash->{detach_to} = '/report/new/oauth_callback'; + $c->stash->{detach_args} = [$token->token]; + + if ( $c->get_param('facebook_sign_in') ) { + $c->detach('/auth/facebook_sign_in'); + } elsif ( $c->get_param('twitter_sign_in') ) { + $c->detach('/auth/twitter_sign_in'); + } + } # Save or update the user if appropriate if ( $c->cobrand->never_confirm_reports ) { @@ -1023,15 +1087,10 @@ sub save_user_and_report : Private { $report->user->insert(); } $report->confirm(); + } elsif ( !$report->user->in_storage ) { # User does not exist. - # Store changes in token for when token is validated. - $c->stash->{token_data} = { - name => $report->user->name, - phone => $report->user->phone, - password => $report->user->password, - title => $report->user->title, - }; + $c->forward('tokenize_user', [ $report ]); $report->user->name( undef ); $report->user->phone( undef ); $report->user->password( '', 1 ); @@ -1048,35 +1107,11 @@ sub save_user_and_report : Private { } else { # User exists and we are not logged in as them. - # Store changes in token for when token is validated. - $c->stash->{token_data} = { - name => $report->user->name, - phone => $report->user->phone, - password => $report->user->password, - title => $report->user->title, - }; + $c->forward('tokenize_user', [ $report ]); $report->user->discard_changes(); $c->log->info($report->user->id . ' exists, but is not logged in for this report'); } - # If there was a photo add that too - if ( my $fileid = $c->stash->{upload_fileid} ) { - $report->photo($fileid); - } - - # Set a default if possible - $report->category( _('Other') ) unless $report->category; - - # Set unknown to DB unknown - $report->bodies_str( undef ) if $report->bodies_str eq '-1'; - - # if there is a Message Manager message ID, pass it back to the client view - if ($c->cobrand->moniker eq 'fixmybarangay' && $c->get_param('external_source_id') =~ /^\d+$/) { - $c->stash->{external_source_id} = $c->get_param('external_source_id'); - $report->external_source_id( $c->get_param('external_source_id') ); - $report->external_source( $c->config->{MESSAGE_MANAGER_URL} ) ; - } - # save the report; $report->in_storage ? $report->update : $report->insert(); diff --git a/perllib/FixMyStreet/App/Controller/Tokens.pm b/perllib/FixMyStreet/App/Controller/Tokens.pm index c10904f8f..9057f217e 100644 --- a/perllib/FixMyStreet/App/Controller/Tokens.pm +++ b/perllib/FixMyStreet/App/Controller/Tokens.pm @@ -105,6 +105,7 @@ sub confirm_problem : Path('/P') { $problem->user->phone( $data->{phone} ) if $data->{phone}; $problem->user->password( $data->{password}, 1 ) if $data->{password}; $problem->user->title( $data->{title} ) if $data->{title}; + $problem->user->facebook_id( $data->{facebook_id} ) if $data->{facebook_id}; $problem->user->update; } $c->authenticate( { email => $problem->user->email }, 'no_password' ); diff --git a/templates/web/base/js/validation_rules.html b/templates/web/base/js/validation_rules.html index 409d0971f..5d55baff1 100644 --- a/templates/web/base/js/validation_rules.html +++ b/templates/web/base/js/validation_rules.html @@ -1,7 +1,6 @@ validation_rules = { title: { required: true }, detail: { required: true }, - email: { required: true }, update: { required: true }, rznvy: { required: true } }; diff --git a/templates/web/base/report/new/fill_in_details.html b/templates/web/base/report/new/fill_in_details.html index 9859fa4fd..55b3a5207 100644 --- a/templates/web/base/report/new/fill_in_details.html +++ b/templates/web/base/report/new/fill_in_details.html @@ -33,12 +33,14 @@ <div id="skipped-map"> [% END %] - [% IF login_success %] - <p class='form-success'>[% loc('You have successfully signed in; please check and confirm your details are accurate:') %]</p> - [% END %] - <div id="report-a-problem-main"> + [% IF login_success %] + [% PROCESS 'report/new/login_success_form.html' %] + [% ELSIF oauth_need_email %] + [% PROCESS 'report/new/oauth_email_form.html' %] + [% ELSE %] [% PROCESS 'report/new/fill_in_details_form.html' %] + [% END %] </div> </div> diff --git a/templates/web/base/report/new/fill_in_details_form.html b/templates/web/base/report/new/fill_in_details_form.html index cd8a0d814..f9da3753f 100644 --- a/templates/web/base/report/new/fill_in_details_form.html +++ b/templates/web/base/report/new/fill_in_details_form.html @@ -19,6 +19,9 @@ [% IF report.used_map && partial_token %] <p id="unknown">[% loc('Please note your report has <strong>not yet been sent</strong>. Choose a category and add further information below, then submit.') %]</p> [% END %] +[% IF oauth_failure %] + <p class="form-error">[% loc('Sorry, we could not log you in. Please fill in the form below.') %]</p> +[% END %] [% TRY %][% PROCESS 'report/new/sidebar.html' %][% CATCH file %][% END %] diff --git a/templates/web/base/report/new/form_user_loggedout.html b/templates/web/base/report/new/form_user_loggedout.html index 5f2f473df..6657c87a1 100644 --- a/templates/web/base/report/new/form_user_loggedout.html +++ b/templates/web/base/report/new/form_user_loggedout.html @@ -1,8 +1,25 @@ -[% PROCESS 'report/new/form_user_loggedout_email.html' %] +[% IF c.config.FACEBOOK_APP_ID %] + <h3>[% loc("Now to submit your report…") %]</h3> -<div id="form_sign_in"> + <div class="form-box"> + <button name="facebook_sign_in" id="facebook_sign_in" value="facebook_sign_in" class="btn btn--block btn--social btn--facebook"> + <img alt="" src="/i/facebook-icon-32.png" width="17" height="32"> + Log in with Facebook + </button> + </div> + <div id="js-social-email-hide"> + [% PROCESS 'report/new/form_user_loggedout_email.html' required = 0 %] +[% ELSE %] + [% PROCESS 'report/new/form_user_loggedout_email.html' required = 1 %] <h3>[% loc("Now to submit your report…") %]</h3> +[% END %] + +<div id="form_sign_in"> <h2>[% tprintf(loc("Do you have a %s password?", "%s is the site name"), site_name) %]</h2> [% PROCESS 'report/new/form_user_loggedout_password.html' %] [% PROCESS 'report/new/form_user_loggedout_by_email.html' %] </div> + +[% IF c.config.FACEBOOK_APP_ID %] + </div> +[% END %] diff --git a/templates/web/base/report/new/form_user_loggedout_email.html b/templates/web/base/report/new/form_user_loggedout_email.html index d7a1790e2..4f816f8cc 100644 --- a/templates/web/base/report/new/form_user_loggedout_email.html +++ b/templates/web/base/report/new/form_user_loggedout_email.html @@ -4,4 +4,6 @@ [% IF field_errors.email %] <p class='form-error'>[% field_errors.email %]</p> [% END %] -<input type="email" value="[% report.user.email | html %]" name="email" id="form_email" placeholder="[% loc('Please enter your email address') %]" required> +<input type="email" value="[% report.user.email | html %]" name="email" id="form_email" placeholder="[% loc('Please enter your email address') %]" + [% IF required %]required[% END %] + class="required"> diff --git a/templates/web/base/report/new/login_success_form.html b/templates/web/base/report/new/login_success_form.html new file mode 100644 index 000000000..45d0221a7 --- /dev/null +++ b/templates/web/base/report/new/login_success_form.html @@ -0,0 +1,18 @@ +<h1>[% loc('Report your problem') %]</h1> + +<p class='form-success'>[% loc('You have successfully signed in; please check and confirm your details are accurate:') %]</p> + +[% TRY %][% PROCESS 'report/new/sidebar.html' %][% CATCH file %][% END %] + +[% INCLUDE 'errors.html' %] + +<fieldset> + <div id="problem_form"> + [% IF c.user_exists %] + [% PROCESS "report/new/form_user_loggedin.html" %] + [% ELSE %] + [% PROCESS "report/new/form_user_loggedout.html" %] + [% END %] + [% PROCESS 'report/new/form_report.html' %] + </div> +</fieldset> diff --git a/templates/web/base/report/new/oauth_email_form.html b/templates/web/base/report/new/oauth_email_form.html new file mode 100644 index 000000000..c897aaeea --- /dev/null +++ b/templates/web/base/report/new/oauth_email_form.html @@ -0,0 +1,26 @@ +<h1>[% loc('Report your problem') %]</h1> + +<p class="form-error"> + [% loc('Please note your report has <strong>not yet been sent</strong>.') %] + [% loc('We need your email address, please give it below.') %] +</p> + +[% TRY %][% PROCESS 'report/new/sidebar.html' %][% CATCH file %][% END %] + +[% INCLUDE 'errors.html' %] + +<fieldset> + <div id="problem_form"> + [% PROCESS 'report/new/form_user_loggedout_email.html' required=1 %] + + <div id="form_sign_in"> + <h3>[% loc("Now to submit your report…") %]</h3> + <h2>[% tprintf(loc("Do you have a %s password?", "%s is the site name"), site_name) %]</h2> + [% PROCESS 'report/new/form_user_loggedout_by_email.html' %] + [% PROCESS 'report/new/form_user_loggedout_password.html' %] + </div> + + <input type="hidden" name="oauth_need_email" value="1"> + [% PROCESS 'report/new/form_report.html' %] + </div> +</fieldset> diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js index 3b81ad144..129cc73bc 100644 --- a/web/cobrands/fixmystreet/fixmystreet.js +++ b/web/cobrands/fixmystreet/fixmystreet.js @@ -299,6 +299,20 @@ $(function(){ }); } + /* Log in with email button */ + var email_form = $('#js-social-email-hide'), + button = $('<button class="btn btn--social btn--social-email">Log in with email</button>'), + form_box = $('<div class="form-box"></div>'); + button.click(function(e){ + e.preventDefault(); + email_form.fadeIn(500); + form_box.hide(); + }); + form_box.append(button).insertBefore(email_form); + if ($('.form-error').length) { + button.click(); + } + /* * Show on click - pretty generic */ diff --git a/web/cobrands/sass/_base.scss b/web/cobrands/sass/_base.scss index 79ed91d97..8992893bb 100644 --- a/web/cobrands/sass/_base.scss +++ b/web/cobrands/sass/_base.scss @@ -316,6 +316,9 @@ label{ font-size:1.25em; margin:0.5em 0; } + h2 { + margin: 0 0 0.5em; + } h5 { margin:0 0 1em; font: { @@ -738,6 +741,33 @@ input.final-submit { float: $right; } +.btn--facebook { + @include button-reset(#3b5998, darken(#3b5998, 10%), #3b5998, #fff, darken(#3b5998, 5%), darken(#3b5998, 10%), #3b5998, #fff); + + &:visited { + color: #fff; + } + + img { + margin-right: 0.5em; + vertical-align: -0.2em; + height: 1.3em; + width: auto; + } +} + +// Under the button to override its text transform and width +.btn--social { + display: block; + width: 100%; + text-transform: none; + text-align: center; +} + +.js #js-social-email-hide { + display: none; +} + .button-fwd { padding: flip(1em 3em 1em 1em, 1em 1em 1em 3em); background: inline-image("../fixmystreet/images/chevron-grey-#{$right}.svg") $right 50% no-repeat; diff --git a/web/i/facebook-icon-32.png b/web/i/facebook-icon-32.png Binary files differnew file mode 100644 index 000000000..460ddc8a6 --- /dev/null +++ b/web/i/facebook-icon-32.png diff --git a/web/js/fixmystreet.js b/web/js/fixmystreet.js index 51fa01559..ad3a8c570 100644 --- a/web/js/fixmystreet.js +++ b/web/js/fixmystreet.js @@ -128,6 +128,10 @@ $(function(){ $('#form_name').addClass('required').removeClass('valid'); } ); + $('#facebook_sign_in').click(function(e){ + $('#form_email').removeClass(); + }); + // Geolocation if (geo_position_js.init()) { var link = '<a href="#LINK" id="geolocate_link">… ' + translation_strings.geolocate + '</a>'; |