]> git.openstreetmap.org Git - rails.git/commitdiff
Add preferred provider social signup
authorMilan Cvetkovic <mcvetkovic@microsoft.com>
Wed, 6 Dec 2023 10:31:52 +0000 (10:31 +0000)
committerMilan Cvetkovic <mcvetkovic@microsoft.com>
Mon, 29 Apr 2024 11:32:54 +0000 (11:32 +0000)
- Add preferred provider for authorization to login and signup pages.
  To use, the 3rd party application would have to add `preferred_provider=...`
  parameter to OAuth2 authorization request.
- Resize 3rd party provider icons
- Add "login to authorize" heading to login and signup screens

app/assets/javascripts/auth_providers.js
app/controllers/concerns/session_methods.rb
app/controllers/sessions_controller.rb
app/controllers/users_controller.rb
app/helpers/user_helper.rb
app/views/application/_auth_providers.html.erb
app/views/sessions/new.html.erb
app/views/users/new.html.erb
config/locales/en.yml
test/helpers/user_helper_test.rb
test/system/user_signup_test.rb

index 35da7b5e4c9679f4bbc67abd4947d05615b3dc3c..975c57a9b611a87738dc51d0c2da09bac91d9ff5 100644 (file)
@@ -12,7 +12,7 @@ $(document).ready(function () {
   $("#openid_open_url").click(function (e) {
     e.preventDefault();
     $("#openid_url").val("http://");
   $("#openid_open_url").click(function (e) {
     e.preventDefault();
     $("#openid_url").val("http://");
-    $("#login_auth_buttons").hide();
+    $("#login_auth_buttons").hide().removeClass("d-flex");
     $("#login_openid_url").show();
     $("#openid_login_button").show();
   });
     $("#login_openid_url").show();
     $("#openid_login_button").show();
   });
index cebe932fc9862fae21c1cfdb13d93ffa82b4cd46..5dcddb82debb5e4849039c28e2d4cf128d6f5c5f 100644 (file)
@@ -3,6 +3,18 @@ module SessionMethods
 
   private
 
 
   private
 
+  ##
+  # Read @preferred_auth_provider and @client_app_name from oauth2 authorization request's referer
+  def parse_oauth_referer(referer)
+    referer_query = URI(referer).query if referer
+    return unless referer_query
+
+    ref_params = CGI.parse referer_query
+    preferred = ref_params["preferred_auth_provider"].first
+    @preferred_auth_provider = preferred if preferred && Settings.key?(:"#{preferred}_auth_id")
+    @client_app_name = Oauth2Application.where(:uid => ref_params["client_id"].first).pick(:name)
+  end
+
   ##
   # return the URL to use for authentication
   def auth_url(provider, uid, referer = nil)
   ##
   # return the URL to use for authentication
   def auth_url(provider, uid, referer = nil)
index e57ffc06aac0617b65100bc5fe1dab3b9ecbda2e..fdf2df6a753cf51f1e639495ebe5d9b3c3477ba1 100644 (file)
@@ -15,6 +15,8 @@ class SessionsController < ApplicationController
     override_content_security_policy_directives(:form_action => []) if Settings.csp_enforce || Settings.key?(:csp_report_url)
 
     session[:referer] = safe_referer(params[:referer]) if params[:referer]
     override_content_security_policy_directives(:form_action => []) if Settings.csp_enforce || Settings.key?(:csp_report_url)
 
     session[:referer] = safe_referer(params[:referer]) if params[:referer]
+
+    parse_oauth_referer session[:referer]
   end
 
   def create
   end
 
   def create
index 74ec5c0ec323f911af2017181b19cc16767cf408..e022ff0c14915322faf0e7045d38ed803a0c34f8 100644 (file)
@@ -60,6 +60,8 @@ class UsersController < ApplicationController
                  session[:referer]
                end
 
                  session[:referer]
                end
 
+    parse_oauth_referer @referer
+
     append_content_security_policy_directives(
       :form_action => %w[accounts.google.com *.facebook.com login.live.com github.com meta.wikimedia.org]
     )
     append_content_security_policy_directives(
       :form_action => %w[accounts.google.com *.facebook.com login.live.com github.com meta.wikimedia.org]
     )
index d0b2f0be550a47874e150eef4be3c1cb94d722e5..0a68e608e2ae354000df043f10220a257013ed3a 100644 (file)
@@ -60,11 +60,25 @@ module UserHelper
     link_to(
       image_tag("#{name}.svg",
                 :alt => t("application.auth_providers.#{name}.alt"),
     link_to(
       image_tag("#{name}.svg",
                 :alt => t("application.auth_providers.#{name}.alt"),
-                :class => "rounded-3",
-                :size => "36"),
+                :class => "rounded-1",
+                :size => "24"),
       auth_path(options.merge(:provider => provider)),
       :method => :post,
       auth_path(options.merge(:provider => provider)),
       :method => :post,
-      :class => "auth_button",
+      :class => "auth_button p-2 d-block",
+      :title => t("application.auth_providers.#{name}.title")
+    )
+  end
+
+  def auth_button_preferred(name, provider, options = {})
+    link_to(
+      image_tag("#{name}.svg",
+                :alt => t("application.auth_providers.#{name}.alt"),
+                :class => "rounded-1 me-3",
+                :width => "24px",
+                :height => "24px") + t("application.auth_providers.#{name}.title"),
+      auth_path(options.merge(:provider => provider)),
+      :method => :post,
+      :class => "auth_button fs-6 border rounded text-muted text-decoration-none py-2 px-4 d-flex justify-content-center align-items-center",
       :title => t("application.auth_providers.#{name}.title")
     )
   end
       :title => t("application.auth_providers.#{name}.title")
     )
   end
index 9c72d7aa09fd68dfdc891b1d7039a7469dd69001..a79e7b5ce3cf75e920d1e91d11664b590cf50a21 100644 (file)
@@ -1,33 +1,44 @@
 <div>
 <div>
-  <div class="mb-3">
-    <label class="form-label"><%= t ".with external" %></label>
+  <div class="list-inline justify-content-center d-flex align-items-center flex-wrap mb-3 gap-3" id="login_auth_buttons">
 
 
-    <ul class='list-inline' id="login_auth_buttons">
-      <li class="list-inline-item me-3">
+    <% %w[google facebook microsoft github wikipedia].each do |provider| %>
+      <% if Settings.key?("#{provider}_auth_id".to_sym) -%>
+        <% if @preferred_auth_provider == provider %>
+          <div class="mx-2"><%= auth_button_preferred provider, provider %></div>
+        <% end %>
+      <% end -%>
+    <% end -%>
+
+    <div class="justify-content-center d-flex gap-1">
+      <div>
         <%= link_to image_tag("openid.png",
                               :alt => t("application.auth_providers.openid.title"),
         <%= link_to image_tag("openid.png",
                               :alt => t("application.auth_providers.openid.title"),
-                              :size => "36"),
+                              :size => "24"),
                     "#",
                     :id => "openid_open_url",
                     "#",
                     :id => "openid_open_url",
-                    :title => t("application.auth_providers.openid.title") %>
-      </li>
+                    :title => t("application.auth_providers.openid.title"),
+                    :class => "p-2 d-block" %>
+      </div>
 
       <% %w[google facebook microsoft github wikipedia].each do |provider| %>
 
       <% %w[google facebook microsoft github wikipedia].each do |provider| %>
-        <% if Settings.key?("#{provider}_auth_id".to_sym) -%>
-          <li class="list-inline-item me-3"><%= auth_button provider, provider %></li>
-        <% end -%>
+        <% unless @preferred_auth_provider == provider %>
+          <% if Settings.key?("#{provider}_auth_id".to_sym) -%>
+            <div><%= auth_button provider, provider %></div>
+          <% end -%>
+        <% end %>
       <% end -%>
       <% end -%>
-    </ul>
+    </div>
+  </div>
 
 
-    <%= form_tag(auth_path(:provider => "openid"), :id => "openid_login_form") do %>
-      <div id='login_openid_url' class="mb-3">
-        <label for='openid_url' class="form-label"><%= t ".openid_html", :logo => openid_logo %></label>
-        <%= hidden_field_tag("referer", params[:referer], :autocomplete => "off") %>
-        <%= text_field_tag("openid_url", "", :tabindex => 5, :autocomplete => "on", :class => "openid_url form-control") %>
-        <span class="form-text text-muted">(<a href="<%= t "accounts.edit.openid.link" %>" target="_new"><%= t "accounts.edit.openid.link text" %></a>)</span>
-      </div>
+  <%# :tabindex starts high to allow rendering at the bottom of the template %>
+  <%= form_tag(auth_path(:provider => "openid"), :id => "openid_login_form") do %>
+    <div id="login_openid_url" class="mb-3">
+      <label for="openid_url" class="form-label"><%= t ".openid_html", :logo => openid_logo %></label>
+      <%= hidden_field_tag("referer", params[:referer], :autocomplete => "off") %>
+      <%= text_field_tag("openid_url", "", :tabindex => 20, :autocomplete => "on", :class => "openid_url form-control") %>
+      <span class="form-text text-muted">(<a href="<%= t "accounts.edit.openid.link" %>" target="_new"><%= t "accounts.edit.openid.link text" %></a>)</span>
+    </div>
 
 
-      <%= submit_tag t(".openid_login_button"), :tabindex => 6, :id => "openid_login_button", :class => "btn btn-primary" %>
-    <% end %>
-  </div>
+    <%= submit_tag t(".openid_login_button"), :tabindex => 21, :id => "openid_login_button", :class => "btn btn-primary" %>
+  <% end %>
 </div>
 </div>
index 90611f214c595b8ff410417ab098e96c66829856..d30eb6697505ecf8dd1f989a4febe346fef9d388 100644 (file)
@@ -6,6 +6,10 @@
 <% content_for :heading_class, "p-0 mw-100" %>
 
 <% content_for :heading do %>
 <% content_for :heading_class, "p-0 mw-100" %>
 
 <% content_for :heading do %>
+  <% if @client_app_name %>
+    <p class="text-center text-muted fs-6 py-2 mb-0 bg-white"><%= t(".login_to_authorize_html", :client_app_name => @client_app_name) %></p>
+  <% end %>
+
   <div class="header-illustration new-user-main auth-container mx-auto">
     <ul class="nav nav-tabs position-absolute bottom-0 px-3 fs-6 w-100">
       <li class="nav-item">
   <div class="header-illustration new-user-main auth-container mx-auto">
     <ul class="nav nav-tabs position-absolute bottom-0 px-3 fs-6 w-100">
       <li class="nav-item">
 <% end %>
 
 <div id="login_login" class="auth-container mx-auto my-0">
 <% end %>
 
 <div id="login_login" class="auth-container mx-auto my-0">
-  <p class='text-muted'><%= t ".no account" %> <%= link_to t(".register now"), user_new_path(:referer => params[:referer]) %></p>
+  <% if @preferred_auth_provider %>
+    <%= render :partial => "auth_providers" %>
+    <div class="d-flex justify-content-center align-items-center">
+      <div class="border-bottom border-1 flex-grow-1"></div>
+      <div class="text-secondary mx-3"><%= t ".or" %></div>
+      <div class="border-bottom border-1 flex-grow-1"></div>
+    </div>
+  <% end %>
 
   <%= bootstrap_form_tag(:action => "login", :html => { :id => "login_form" }) do |f| %>
     <%= hidden_field_tag("referer", h(params[:referer]), :autocomplete => "off") %>
 
   <%= bootstrap_form_tag(:action => "login", :html => { :id => "login_form" }) do |f| %>
     <%= hidden_field_tag("referer", h(params[:referer]), :autocomplete => "off") %>
       <%= f.check_box :remember_me, { :label => t(".remember"), :tabindex => 3, :checked => (params[:remember_me] == "yes") }, "yes" %>
     <% end %>
 
       <%= f.check_box :remember_me, { :label => t(".remember"), :tabindex => 3, :checked => (params[:remember_me] == "yes") }, "yes" %>
     <% end %>
 
-    <%= f.primary t(".login_button"), :tabindex => 4 %>
+    <div class="mb-3">
+      <%= f.primary t(".login_button"), :tabindex => 4 %>
+    </div>
   <% end %>
 
   <% end %>
 
-  <hr>
-
-  <%= render :partial => "auth_providers" %>
+  <% unless @preferred_auth_provider %>
+    <div class="d-flex justify-content-center align-items-center">
+      <div class="border-bottom border-1 flex-grow-1"></div>
+      <div class="text-secondary mx-3"><%= t ".with external" %></div>
+      <div class="border-bottom border-1 flex-grow-1"></div>
+    </div>
+    <%= render :partial => "auth_providers" %>
+  <% end %>
 </div>
 </div>
index c3020180ca47266ba1e6311eb5d15a2bb5b02efc..6e9c915240bd34d64ba299d677d7cb9085dadedf 100644 (file)
@@ -6,6 +6,10 @@
 <% content_for :heading_class, "p-0 mw-100" %>
 
 <% content_for :heading do %>
 <% content_for :heading_class, "p-0 mw-100" %>
 
 <% content_for :heading do %>
+  <% if @client_app_name %>
+    <p class="text-center text-muted fs-6 py-2 mb-0 bg-white"><%= t(".signup_to_authorize_html", :client_app_name => @client_app_name) %></p>
+  <% end %>
+
   <div class="header-illustration new-user-main auth-container mx-auto">
     <ul class="nav nav-tabs position-absolute bottom-0 px-3 fs-6 w-100">
       <li class="nav-item">
   <div class="header-illustration new-user-main auth-container mx-auto">
     <ul class="nav nav-tabs position-absolute bottom-0 px-3 fs-6 w-100">
       <li class="nav-item">
       <p><strong><%= t ".about.header" %></strong> <%= t ".about.paragraph_1" %></p>
       <p><%= t ".about.paragraph_2" %></p>
     </div>
       <p><strong><%= t ".about.header" %></strong> <%= t ".about.paragraph_1" %></p>
       <p><%= t ".about.paragraph_2" %></p>
     </div>
+
+    <% unless @preferred_auth_provider.nil? %>
+      <%= render :partial => "auth_providers" %>
+      <div class="d-flex justify-content-center align-items-center">
+        <div class="border-bottom border-1 flex-grow-1"></div>
+        <div class="text-secondary mx-3"><%= t ".or" %></div>
+        <div class="border-bottom border-1 flex-grow-1"></div>
+      </div>
+    <% end %>
   <% else %>
     <h4><%= t ".about.welcome" %></h4>
   <% end %>
   <% else %>
     <h4><%= t ".about.welcome" %></h4>
   <% end %>
@@ -34,7 +47,7 @@
     <%= f.hidden_field :auth_provider unless current_user.auth_provider.nil? %>
     <%= f.hidden_field :auth_uid unless current_user.auth_uid.nil? %>
 
     <%= f.hidden_field :auth_provider unless current_user.auth_provider.nil? %>
     <%= f.hidden_field :auth_uid unless current_user.auth_uid.nil? %>
 
-    <% if current_user.auth_uid.nil? or not current_user.errors[:email].empty? %>
+    <% if current_user.auth_uid.nil? or @verified_email.nil? or not current_user.errors[:email].empty? %>
       <%= f.email_field :email, :help => t(".email_help_html",
                                            :privacy_policy_link => link_to(t(".privacy_policy"),
                                                                            t(".privacy_policy_url"),
       <%= f.email_field :email, :help => t(".email_help_html",
                                            :privacy_policy_link => link_to(t(".privacy_policy"),
                                                                            t(".privacy_policy_url"),
       </div>
     <% end %>
 
       </div>
     <% end %>
 
-    <p class="mb-3 text-muted"><%= t(".by_signing_up_html",
-                                     :tou_link => link_to(t("layouts.tou"),
-                                                          "https://wiki.osmfoundation.org/wiki/Terms_of_Use",
-                                                          :target => :new),
-                                     :privacy_policy_link => link_to(t(".privacy_policy"),
-                                                                     t(".privacy_policy_url"),
-                                                                     :title => t(".privacy_policy_title"),
-                                                                     :target => :new),
-                                     :contributor_terms_link => link_to(t(".contributor_terms"),
-                                                                        t(".contributor_terms_url"),
-                                                                        :target => :new)) %></p>
-
+    <p class="mb-3 text-muted fs-6"><%= t(".by_signing_up_html",
+                                          :tou_link => link_to(t("layouts.tou"),
+                                                               "https://wiki.osmfoundation.org/wiki/Terms_of_Use",
+                                                               :target => :new),
+                                          :privacy_policy_link => link_to(t(".privacy_policy"),
+                                                                          t(".privacy_policy_url"),
+                                                                          :title => t(".privacy_policy_title"),
+                                                                          :target => :new),
+                                          :contributor_terms_link => link_to(t(".contributor_terms"),
+                                                                             t(".contributor_terms_url"),
+                                                                             :target => :new)) %></p>
     <%= f.form_group do %>
       <%= f.check_box :consider_pd,
                       :tabindex => 5,
     <%= f.form_group do %>
       <%= f.check_box :consider_pd,
                       :tabindex => 5,
     </div>
   <% end %>
 
     </div>
   <% end %>
 
-  <% if current_user.auth_uid.nil? %>
-    <hr>
+  <% if current_user.auth_uid.nil? and @preferred_auth_provider.nil? %>
+    <div class="d-flex justify-content-center align-items-center">
+      <div class="border-bottom border-1 flex-grow-1"></div>
+      <div class="text-secondary mx-3"><%= t ".use external auth" %></div>
+      <div class="border-bottom border-1 flex-grow-1"></div>
+    </div>
     <%= render :partial => "auth_providers" %>
   <% end %>
 </div>
     <%= render :partial => "auth_providers" %>
   <% end %>
 </div>
index 677ff618cfa32235c1dcbe1851ba49f2147c63fb..4570183d0c8fe9a675fdc6517c84ef7287d8a13e 100644 (file)
@@ -1848,15 +1848,15 @@ en:
     new:
       title: "Log in"
       tab_title: "Log in"
     new:
       title: "Log in"
       tab_title: "Log in"
-      heading: "Log in"
+      login_to_authorize_html: "Log in to OpenStreetMap to access %{client_app_name}."
       email or username: "Email Address or Username"
       password: "Password"
       remember: "Remember me"
       lost password link: "Lost your password?"
       login_button: "Log in"
       register now: Register now
       email or username: "Email Address or Username"
       password: "Password"
       remember: "Remember me"
       lost password link: "Lost your password?"
       login_button: "Log in"
       register now: Register now
-      with external: "Alternatively, use a third party to log in:"
-      no account: Don't have an account?
+      with external: "or log in with a third party"
+      or: "or"
       auth failure: "Sorry, could not log in with those details."
     destroy:
       title: "Logout"
       auth failure: "Sorry, could not log in with those details."
     destroy:
       title: "Logout"
@@ -2576,7 +2576,6 @@ en:
       openid_logo_alt: "Log in with an OpenID"
       openid_html: "%{logo} OpenID"
       openid_login_button: "Continue"
       openid_logo_alt: "Log in with an OpenID"
       openid_html: "%{logo} OpenID"
       openid_login_button: "Continue"
-      with external: "Alternatively, use a third party to login:"
       openid:
         title: Log in with OpenID
         alt: Log in with an OpenID URL
       openid:
         title: Log in with OpenID
         alt: Log in with an OpenID URL
@@ -2730,22 +2729,22 @@ en:
     new:
       title: "Sign Up"
       tab_title: "Sign up"
     new:
       title: "Sign Up"
       tab_title: "Sign up"
+      signup_to_authorize_html: "Sign up with OpenStreetMap to access %{client_app_name}."
       no_auto_account_create: "Unfortunately we are not currently able to create an account for you automatically."
       please_contact_support_html: 'Please contact %{support_link} to arrange for an account to be created - we will try and deal with the request as quickly as possible.'
       support: support
       about:
         header: Free and editable.
         paragraph_1: Unlike other maps, OpenStreetMap is completely created by people like you, and it's free for anyone to fix, update, download and use.
       no_auto_account_create: "Unfortunately we are not currently able to create an account for you automatically."
       please_contact_support_html: 'Please contact %{support_link} to arrange for an account to be created - we will try and deal with the request as quickly as possible.'
       support: support
       about:
         header: Free and editable.
         paragraph_1: Unlike other maps, OpenStreetMap is completely created by people like you, and it's free for anyone to fix, update, download and use.
-        paragraph_2: Sign up to get started contributing. We'll send an email to confirm your account.
+        paragraph_2: Sign up to get started contributing.
         welcome: "Welcome to OpenStreetMap"
       duplicate_social_email: "If you already have an OpenStreetMap account and wish to use a 3rd party identity provider, please log in using your password and modify the settings of your account."
       display name description: "Your publicly displayed username. You can change this later in the preferences."
         welcome: "Welcome to OpenStreetMap"
       duplicate_social_email: "If you already have an OpenStreetMap account and wish to use a 3rd party identity provider, please log in using your password and modify the settings of your account."
       display name description: "Your publicly displayed username. You can change this later in the preferences."
-      by_signing_up_html: "By signing up, you agree to our %{contributor_terms_link} and %{tou_link}."
+      by_signing_up_html: "By signing up, you agree to our %{tou_link}, %{privacy_policy_link} and %{contributor_terms_link}."
+      tou: "terms of use"
       contributor_terms_url: "https://wiki.osmfoundation.org/wiki/Licence/Contributor_Terms"
       contributor_terms_url: "https://wiki.osmfoundation.org/wiki/Licence/Contributor_Terms"
-      contributor_terms: "Contributor terms"
+      contributor_terms: "contributor terms"
       external auth: "Third Party Authentication:"
       external auth: "Third Party Authentication:"
-      use external auth: "Alternatively, use a third party to log in"
-      auth no password: "With third party authentication a password is not required, but some extra tools or server may still need one."
       continue: Sign Up
       terms accepted: "Thanks for accepting the new contributor terms!"
       email_help_html: 'Your address is not displayed publicly, see our %{privacy_policy_link} for more information.'
       continue: Sign Up
       terms accepted: "Thanks for accepting the new contributor terms!"
       email_help_html: 'Your address is not displayed publicly, see our %{privacy_policy_link} for more information.'
@@ -2755,6 +2754,8 @@ en:
       consider_pd_html: "I consider my contributions to be in the %{consider_pd_link}."
       consider_pd: "public domain"
       consider_pd_url: https://wiki.osmfoundation.org/wiki/Licence_and_Legal_FAQ/Why_would_I_want_my_contributions_to_be_public_domain
       consider_pd_html: "I consider my contributions to be in the %{consider_pd_link}."
       consider_pd: "public domain"
       consider_pd_url: https://wiki.osmfoundation.org/wiki/Licence_and_Legal_FAQ/Why_would_I_want_my_contributions_to_be_public_domain
+      or: "or"
+      use external auth: "or sign up with a third party"
     terms:
       title: "Terms"
       heading: "Terms"
     terms:
       title: "Terms"
       heading: "Terms"
index f7d2726dbf96667654b6eb89bbad377a8476b088..c2883c2c09e4dffd85e51120e8852c793ec039ed 100644 (file)
@@ -116,8 +116,8 @@ class UserHelperTest < ActionView::TestCase
 
   def test_auth_button
     button = auth_button("google", "google")
 
   def test_auth_button
     button = auth_button("google", "google")
-    img_tag = "<img alt=\"Log in with a Google OpenID\" class=\"rounded-3\" src=\"/images/google.svg\" width=\"36\" height=\"36\" />"
-    assert_equal("<a class=\"auth_button\" title=\"Log in with Google\" rel=\"nofollow\" data-method=\"post\" href=\"/auth/google\">#{img_tag}</a>", button)
+    img_tag = "<img alt=\"Log in with a Google OpenID\" class=\"rounded-1\" src=\"/images/google.svg\" width=\"24\" height=\"24\" />"
+    assert_equal("<a class=\"auth_button p-2 d-block\" title=\"Log in with Google\" rel=\"nofollow\" data-method=\"post\" href=\"/auth/google\">#{img_tag}</a>", button)
   end
 
   private
   end
 
   private
index 7e2c6ba546c8761376dbb4cd22e994768261e38f..0835df741779eb05d306ae670639d0ea163d5e1d 100644 (file)
@@ -4,7 +4,7 @@ class UserSignupTest < ApplicationSystemTestCase
   test "Sign up from login page" do
     visit login_path
 
   test "Sign up from login page" do
     visit login_path
 
-    click_on "Register now"
+    click_on "Sign up"
 
     assert_content "Confirm Password"
   end
 
     assert_content "Confirm Password"
   end