Skip to content

Commit

Permalink
fixed issues with Devise + Doorkeeper + Oauth2 sign in
Browse files Browse the repository at this point in the history
  • Loading branch information
briri committed Sep 23, 2024
1 parent 9d579ca commit 4987995
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 30 deletions.
61 changes: 55 additions & 6 deletions app/controllers/users/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,28 @@ def create
# redirect_to root_path, alert: _('Invalid email address!')

elsif sign_in_params[:org_id].present? && !@bypass_sso
# If there is an Org in the params then this is step 2 of the email+password workflow
# so just let Devise sign them in normally
super
# If this is part of an API V2 Oauth workflow, sign the user in and render the
# authorization page. If the authentication fails rerender the page with an error
if session['oauth-referer'].present?
new_resource = warden.authenticate!(auth_options)
oauth_hash = ApplicationService.decrypt(payload: session['oauth-referer'])

@client = ApiClient.find_by(uid: oauth_hash['client_id'])
@current_resource_owner = resource

@pre_auth ||= Doorkeeper::OAuth::PreAuthorization.new(
Doorkeeper.configuration,
pre_auth_params,
resource,
)
@pre_auth.send(:validate_client)

render 'doorkeeper/authorizations/new', layout: 'doorkeeper/application'
else
# If there is an Org in the params then this is step 2 of the email+password workflow
# so just let Devise sign them in normally
super
end

else
# If there is no Org then the user provided their email in step 1 so we need
Expand All @@ -47,7 +66,18 @@ def create
if session['oauth-referer'].present?
oauth_hash = ApplicationService.decrypt(payload: session['oauth-referer'])

@client = ApiClient.where(uid: oauth_hash['client_id'])
@client = ApiClient.find_by(uid: oauth_hash['client_id'])
@current_resource_owner = resource

@dmptool_oauth = generate_pre_auth(oauth_hash)

@pre_auth ||= Doorkeeper::OAuth::PreAuthorization.new(
Doorkeeper.configuration,
pre_auth_params,
resource,
)
@pre_auth.send(:validate_client)

render 'doorkeeper/authorizations/new', layout: 'doorkeeper/application'
else
render 'static_pages/auth'
Expand All @@ -70,6 +100,11 @@ def destroy

protected

def pre_auth_params
params.permit(:client_id, :code_challenge, :code_challenge_method, :response_type,
:response_mode, :redirect_uri, :scope, :state)
end

# If you have extra params to permit, append them to the sanitizer.
def configure_sign_in_params
devise_parameter_sanitizer.permit(:sign_in, keys: authentication_params(type: :sign_in))
Expand All @@ -78,8 +113,8 @@ def configure_sign_in_params
# The path used after sign in.
# rubocop:disable Metrics/AbcSize
def after_sign_in_path_for(resource)
# Determine if this was parft of an OAuth workflow for API V2
if session['oauth-referer'].present?
# Determine if this was part of an OAuth workflow for API V2
if session['oauth-referer'].present? &&
auth_hash = ApplicationService.decrypt(payload: session['oauth-referer']) || {}
oauth_path = auth_hash['path']

Expand All @@ -100,5 +135,19 @@ def after_sign_in_path_for(resource)
(oauth_path.presence || landing_page_path)
end
# rubocop:enable Metrics/AbcSize

def generate_pre_auth(oauth_hash)
pre_auth = {}
return {} unless oauth_hash.present?

oauth_path_parts = oauth_hash['path'].split('?').last&.split('&')

oauth_path_parts.each do |entry|
parts = entry.split('=')
pre_auth[parts.first] = parts.last
end

JSON.parse(pre_auth.to_json)
end
end
end
10 changes: 10 additions & 0 deletions app/helpers/authentication_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ def process_oauth
begin
# Fetch the oauth info from the session and decrypt it
oauth_hash = ApplicationService.decrypt(payload: session['oauth-referer'])

pre_auth = {}
oauth_path_parts = oauth_hash['path'].split('?').last&.split('&')

oauth_path_parts.each do |entry|
parts = entry.split('=')
pre_auth[parts.first] = parts.length > 1 ? parts.last : ''
end
@dmptool_oauth = JSON.parse(pre_auth.to_json)

{
path: oauth_hash['path'],
client: ApiClient.find_by(uid: oauth_hash['client_id'])
Expand Down
80 changes: 61 additions & 19 deletions app/views/doorkeeper/authorizations/_validate.html.erb
Original file line number Diff line number Diff line change
@@ -1,26 +1,68 @@
<%= form_with model: resource, url: new_user_session_path, method: :post, local: true, class: "novalidate" do |form| %>
<% if resource.email.present? %>
<%# The user does not have an account! %>
<% url = (@shibbolized && !Rails.env.development? && !@bypass_sso) ? users_shibboleth_path : user_session_path %>

<%= form_with model: resource, url: url, method: :post, local: true, class: "novalidate",
data: { turbo: false } do |form| %>
<% if (resource.email.present? && resource.id.nil?) %>
<%# The email address was not in the database! OR signin failed %>
<p class="red"><%= _('Invalid email or password.') %></p>
<p><%= sanitize(_('If you do not have a DMP Tool account, you will need to create one before authorizing %<application>s. To create an account, please visit the %<link_to_home_page>s. Once your account has been created, you may return to this page to finish the authotization.') % {
application: client.is_a?(String) ? client : client&.name&.humanize,
link_to_home_page: link_to(root_url, _('DMP Tool home page'), target: '_blank')
}, { attributes: %w[href target] }) %></p>
<% end %>

<%# Have the user enter their email address so we can figure out how to auth them %>
<div id="sign-in-sign-up-email" class="c-textfield js-textfield">
<%= form.label :email, _('Email address') %>
<%= form.email_field(:email,
class: "require-me",
aria: { describedby: "sign-in-sign-up-email-desc" },
autocomplete: 'email') %>
<div id="sign-in-sign-up-email-desc" class="c-textfield__general-description">
<%= _("For SSO, use institutional address.") %>
</div>
<div id="" class="c-textfield__invalid-description js-invalid-description" hidden>
</div>
<% if @dmptool_oauth.present? %>
<%= hidden_field_tag :client_id, @dmptool_oauth['client_id'] %>
<%= hidden_field_tag :redirect_uri, @dmptool_oauth['redirect_uri'] %>
<%= hidden_field_tag :state, @dmptool_oauth['state'] %>
<%= hidden_field_tag :response_type, @dmptool_oauth['response_type'] %>
<%= hidden_field_tag :scope, @dmptool_oauth['scope'].gsub('+', ' ') %>
<%= hidden_field_tag :code_challenge, @dmptool_oauth['code_challenge'] %>
<%= hidden_field_tag :code_challenge_method, @dmptool_oauth['code_challenge_method'] %>
<% end %>

<%= form.hidden_field :org_id, value: resource.org_id %>
<% if resource.id.nil? || resource.id.blank? %>
<%= form.label :email, _('Email address') %>
<%= form.email_field(:email,
class: "require-me",
aria: { describedby: "sign-in-sign-up-email-desc" },
autocomplete: 'email') %>

<div id="sign-in-sign-up-email-desc" class="c-textfield__general-description">
<%= _("For SSO, use institutional address.") %>
</div>
<div id="" class="c-textfield__invalid-description js-invalid-description" hidden>
</div>
<button type="submit" class="btn btn-primary"><%= _("Continue") %></button>

<% elsif @shibbolized && !Rails.env.development? && !@bypass_sso %>
<%# If the Org is @shibbolized and the user has an EPPN %>
<%= render partial: "users/shared/sso",
locals: { form: form, label: _("Sign in with Institution (SSO)") } %>

<div id="sign-in-bypass-sso" class="c-login__pseudo-description">
<%= link_to _('Sign in with non SSO'),
user_session_path(sso_bypass: true, user: {
email: resource.email, org_id: resource.org_id }),
method: :post %>
</div>
<% else %>
<%= form.hidden_field(:email) %>
<div id="sign-in-password" class="c-textfield js-textfield">
<%# Display the standard Sign in form %>
<%= form.label(:password, _('Password')) %>
<%= form.password_field(:password, autocomplete: 'password', minlength: 8,
maxlength: 80, class: "require-me") %>
<div id="" class="c-textfield__invalid-description js-invalid-description" hidden>
</div>
</div>

<div class="c-login__pseudo-description">
<%= link_to _('Forgot password?'), new_password_path('user') %>
</div>

<button type="submit" class="btn btn-primary"><%= _("Sign in") %></button>
<% end %>


</div>
<br>
<button type="submit" class="btn-primary"><%= _("Continue") %></button>
<% end %>
13 changes: 8 additions & 5 deletions app/views/doorkeeper/authorizations/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ oauth_hash = process_oauth
%>

<main role="main" id="oauth-authorization">
<% if @current_resource_owner.present? %>
<% if @current_resource_owner.present? && user_signed_in? %>
<p class="h4">
<%= sanitize(_("%{application} wants to access your DMPs. Please confirm this action.") % { application: "<strong class=\"text-info\">#{oauth_hash[:client]&.description&.capitalize}</strong>" }) %>
</p>
Expand All @@ -27,7 +27,7 @@ oauth_hash = process_oauth
<% end %>

<div class="actions">
<%= form_tag oauth_authorization_path, method: :post do %>
<%= form_tag oauth_authorization_path, method: :post, data: { turbo: false } do %>
<%= hidden_field_tag :client_id, @pre_auth.client.uid %>
<%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %>
<%= hidden_field_tag :state, @pre_auth.state %>
Expand All @@ -37,7 +37,7 @@ oauth_hash = process_oauth
<%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method %>
<%= submit_tag t('doorkeeper.authorizations.buttons.authorize'), class: "btn btn-success btn-lg btn-block" %>
<% end %>
<%= form_tag oauth_authorization_path, method: :delete do %>
<%= form_tag oauth_authorization_path, method: :delete, data: { turbo: false } do %>
<%= hidden_field_tag :client_id, @pre_auth.client.uid %>
<%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %>
<%= hidden_field_tag :state, @pre_auth.state %>
Expand All @@ -57,11 +57,14 @@ oauth_hash = process_oauth
<div class="signin-form">
<%# User has either not provided their email yet %>
<%= render partial: '/doorkeeper/authorizations/validate',
locals: { resource: resource, client: oauth_hash[:client]&.description&.capitalize } %>
locals: { resource: resource, client: oauth_hash[:client]&.description&.capitalize,
path: oauth_hash[:path] } %>
</div>
<% end %>

<p class="red"><%= flash[:alert] %></p>
<% unless flash[:alert] == 'You are already signed in.' %>
<p class="red"><%= flash[:alert] %></p>
<% end %>

<br>
<%= link_to(_('Go back'), oauth_hash[:path], rel: 'prev', class: 'c-serialnav hidden') %>
Expand Down

0 comments on commit 4987995

Please sign in to comment.