Add preferred provider social signup

- 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
This commit is contained in:
Milan Cvetkovic 2023-12-06 10:31:52 +00:00
parent d0e8f72311
commit 9649b192c0
11 changed files with 135 additions and 59 deletions

View file

@ -12,7 +12,7 @@ $(document).ready(function () {
$("#openid_open_url").click(function (e) { $("#openid_open_url").click(function (e) {
e.preventDefault(); e.preventDefault();
$("#openid_url").val("http://"); $("#openid_url").val("http://");
$("#login_auth_buttons").hide(); $("#login_auth_buttons").hide().removeClass("d-flex");
$("#login_openid_url").show(); $("#login_openid_url").show();
$("#openid_login_button").show(); $("#openid_login_button").show();
}); });

View 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 # return the URL to use for authentication
def auth_url(provider, uid, referer = nil) def auth_url(provider, uid, referer = nil)

View file

@ -15,6 +15,8 @@ class SessionsController < ApplicationController
override_content_security_policy_directives(:form_action => []) if Settings.csp_enforce || Settings.key?(:csp_report_url) 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] session[:referer] = safe_referer(params[:referer]) if params[:referer]
parse_oauth_referer session[:referer]
end end
def create def create

View file

@ -60,6 +60,8 @@ class UsersController < ApplicationController
session[:referer] session[:referer]
end end
parse_oauth_referer @referer
append_content_security_policy_directives( append_content_security_policy_directives(
:form_action => %w[accounts.google.com *.facebook.com login.live.com github.com meta.wikimedia.org] :form_action => %w[accounts.google.com *.facebook.com login.live.com github.com meta.wikimedia.org]
) )

View file

@ -60,11 +60,25 @@ module UserHelper
link_to( link_to(
image_tag("#{name}.svg", image_tag("#{name}.svg",
:alt => t("application.auth_providers.#{name}.alt"), :alt => t("application.auth_providers.#{name}.alt"),
:class => "rounded-3", :class => "rounded-1",
:size => "36"), :size => "24"),
auth_path(options.merge(:provider => provider)), auth_path(options.merge(:provider => provider)),
:method => :post, :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") :title => t("application.auth_providers.#{name}.title")
) )
end end

View file

@ -1,33 +1,44 @@
<div> <div>
<div class="mb-3"> <div class="list-inline justify-content-center d-flex align-items-center flex-wrap mb-3 gap-3" id="login_auth_buttons">
<label class="form-label"><%= t ".with external" %></label>
<ul class='list-inline' id="login_auth_buttons"> <% %w[google facebook microsoft github wikipedia].each do |provider| %>
<li class="list-inline-item me-3"> <% 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", <%= link_to image_tag("openid.png",
:alt => t("application.auth_providers.openid.title"), :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") %> :title => t("application.auth_providers.openid.title"),
</li> :class => "p-2 d-block" %>
<% %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 -%>
<% end -%>
</ul>
<%= 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> </div>
<%= submit_tag t(".openid_login_button"), :tabindex => 6, :id => "openid_login_button", :class => "btn btn-primary" %> <% %w[google facebook microsoft github wikipedia].each do |provider| %>
<% end %> <% unless @preferred_auth_provider == provider %>
<% if Settings.key?("#{provider}_auth_id".to_sym) -%>
<div><%= auth_button provider, provider %></div>
<% end -%>
<% end %>
<% end -%>
</div>
</div> </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 => 21, :id => "openid_login_button", :class => "btn btn-primary" %>
<% end %>
</div> </div>

View file

@ -6,6 +6,10 @@
<% content_for :heading_class, "p-0 mw-100" %> <% content_for :heading_class, "p-0 mw-100" %>
<% content_for :heading do %> <% 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"> <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"> <ul class="nav nav-tabs position-absolute bottom-0 px-3 fs-6 w-100">
<li class="nav-item"> <li class="nav-item">
@ -19,7 +23,14 @@
<% end %> <% end %>
<div id="login_login" class="auth-container mx-auto my-0"> <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| %> <%= bootstrap_form_tag(:action => "login", :html => { :id => "login_form" }) do |f| %>
<%= hidden_field_tag("referer", h(params[:referer]), :autocomplete => "off") %> <%= hidden_field_tag("referer", h(params[:referer]), :autocomplete => "off") %>
@ -40,10 +51,17 @@
<%= f.check_box :remember_me, { :label => t(".remember"), :tabindex => 3, :checked => (params[:remember_me] == "yes") }, "yes" %> <%= f.check_box :remember_me, { :label => t(".remember"), :tabindex => 3, :checked => (params[:remember_me] == "yes") }, "yes" %>
<% end %> <% end %>
<%= f.primary t(".login_button"), :tabindex => 4 %> <div class="mb-3">
<%= f.primary t(".login_button"), :tabindex => 4 %>
</div>
<% end %> <% end %>
<hr> <% unless @preferred_auth_provider %>
<div class="d-flex justify-content-center align-items-center">
<%= render :partial => "auth_providers" %> <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>

View file

@ -6,6 +6,10 @@
<% content_for :heading_class, "p-0 mw-100" %> <% content_for :heading_class, "p-0 mw-100" %>
<% content_for :heading do %> <% 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"> <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"> <ul class="nav nav-tabs position-absolute bottom-0 px-3 fs-6 w-100">
<li class="nav-item"> <li class="nav-item">
@ -24,6 +28,15 @@
<p><strong><%= t ".about.header" %></strong> <%= t ".about.paragraph_1" %></p> <p><strong><%= t ".about.header" %></strong> <%= t ".about.paragraph_1" %></p>
<p><%= t ".about.paragraph_2" %></p> <p><%= t ".about.paragraph_2" %></p>
</div> </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 %> <% else %>
<h4><%= t ".about.welcome" %></h4> <h4><%= t ".about.welcome" %></h4>
<% end %> <% end %>
@ -34,7 +47,7 @@
<%= f.hidden_field :auth_provider unless current_user.auth_provider.nil? %> <%= 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_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", <%= f.email_field :email, :help => t(".email_help_html",
:privacy_policy_link => link_to(t(".privacy_policy"), :privacy_policy_link => link_to(t(".privacy_policy"),
t(".privacy_policy_url"), t(".privacy_policy_url"),
@ -58,18 +71,17 @@
</div> </div>
<% end %> <% end %>
<p class="mb-3 text-muted"><%= t(".by_signing_up_html", <p class="mb-3 text-muted fs-6"><%= t(".by_signing_up_html",
:tou_link => link_to(t("layouts.tou"), :tou_link => link_to(t("layouts.tou"),
"https://wiki.osmfoundation.org/wiki/Terms_of_Use", "https://wiki.osmfoundation.org/wiki/Terms_of_Use",
:target => :new), :target => :new),
:privacy_policy_link => link_to(t(".privacy_policy"), :privacy_policy_link => link_to(t(".privacy_policy"),
t(".privacy_policy_url"), t(".privacy_policy_url"),
:title => t(".privacy_policy_title"), :title => t(".privacy_policy_title"),
:target => :new), :target => :new),
:contributor_terms_link => link_to(t(".contributor_terms"), :contributor_terms_link => link_to(t(".contributor_terms"),
t(".contributor_terms_url"), t(".contributor_terms_url"),
:target => :new)) %></p> :target => :new)) %></p>
<%= f.form_group do %> <%= f.form_group do %>
<%= f.check_box :consider_pd, <%= f.check_box :consider_pd,
:tabindex => 5, :tabindex => 5,
@ -84,8 +96,12 @@
</div> </div>
<% end %> <% end %>
<% if current_user.auth_uid.nil? %> <% if current_user.auth_uid.nil? and @preferred_auth_provider.nil? %>
<hr> <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" %> <%= render :partial => "auth_providers" %>
<% end %> <% end %>
</div> </div>

View file

@ -1848,15 +1848,15 @@ en:
new: new:
title: "Log in" title: "Log in"
tab_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" email or username: "Email Address or Username"
password: "Password" password: "Password"
remember: "Remember me" remember: "Remember me"
lost password link: "Lost your password?" lost password link: "Lost your password?"
login_button: "Log in" login_button: "Log in"
register now: Register now register now: Register now
with external: "Alternatively, use a third party to log in:" with external: "or log in with a third party"
no account: Don't have an account? or: "or"
auth failure: "Sorry, could not log in with those details." auth failure: "Sorry, could not log in with those details."
destroy: destroy:
title: "Logout" title: "Logout"
@ -2576,7 +2576,6 @@ en:
openid_logo_alt: "Log in with an OpenID" openid_logo_alt: "Log in with an OpenID"
openid_html: "%{logo} OpenID" openid_html: "%{logo} OpenID"
openid_login_button: "Continue" openid_login_button: "Continue"
with external: "Alternatively, use a third party to login:"
openid: openid:
title: Log in with OpenID title: Log in with OpenID
alt: Log in with an OpenID URL alt: Log in with an OpenID URL
@ -2730,22 +2729,22 @@ en:
new: new:
title: "Sign Up" title: "Sign Up"
tab_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." 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.' 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 support: support
about: about:
header: Free and editable. 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_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" 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." 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." 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 continue: Sign Up
terms accepted: "Thanks for accepting the new contributor terms!" 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.' 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_html: "I consider my contributions to be in the %{consider_pd_link}."
consider_pd: "public domain" 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_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: terms:
title: "Terms" title: "Terms"
heading: "Terms" heading: "Terms"

View file

@ -116,8 +116,8 @@ class UserHelperTest < ActionView::TestCase
def test_auth_button def test_auth_button
button = auth_button("google", "google") 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\" />" 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\" title=\"Log in with Google\" rel=\"nofollow\" data-method=\"post\" href=\"/auth/google\">#{img_tag}</a>", button) 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 end
private private

View file

@ -4,7 +4,7 @@ class UserSignupTest < ApplicationSystemTestCase
test "Sign up from login page" do test "Sign up from login page" do
visit login_path visit login_path
click_on "Register now" click_on "Sign up"
assert_content "Confirm Password" assert_content "Confirm Password"
end end