Skip to content

Commit

Permalink
Add instructions to set password for OAuth users (#346)
Browse files Browse the repository at this point in the history
  • Loading branch information
riggraz authored May 14, 2024
1 parent 57ed8c3 commit 5a162c6
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 9 deletions.
19 changes: 19 additions & 0 deletions app/controllers/passwords_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class PasswordsController < Devise::PasswordsController
# Needed to have Current.tenant available in Devise's controllers
prepend_before_action :load_tenant_data

# Needed for OAuth users (otherwise an already logged in user wouldn't be able to reset their password)
skip_before_action :require_no_authentication, :only => [:edit, :update]

def update
super

if resource.errors.empty?
resource.update!(has_set_password: true) unless resource.has_set_password?

# See: https://stackoverflow.com/a/22110985/1857435
sign_out(resource_name)
sign_in(resource_name, resource)
end
end
end
10 changes: 10 additions & 0 deletions app/controllers/registrations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ def destroy
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
end

def send_set_password_instructions
user = User.find_by_email(params[:email])

if user.present?
user.send_reset_password_instructions
end

render json: { success: true } # always return true, even if user not found
end

private

def set_page_title_new
Expand Down
1 change: 1 addition & 0 deletions app/controllers/tenants_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def create
full_name: params[:user][:full_name] || I18n.t('defaults.user_full_name'),
email: params[:user][:email],
password: is_o_auth_login ? Devise.friendly_token : params[:user][:password],
has_set_password: !is_o_auth_login,
role: "owner"
)

Expand Down
2 changes: 1 addition & 1 deletion app/javascript/components/Board/NewPost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class NewPost extends React.Component<Props, State> {

} catch (e) {
this.setState({
error: I18n.t('board.new_post.submit_error')
error: I18n.t('common.errors.unknown')
});
}
}
Expand Down
41 changes: 41 additions & 0 deletions app/javascript/components/UserProfile/SetPasswordDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from 'react';
import I18n from 'i18n-js';

import Button from '../common/Button';
import buildRequestHeaders from '../../helpers/buildRequestHeaders';

interface Props {
sendSetPasswordInstructionsEndpoint: string;
authenticityToken: string;
}

const SetPasswordDialog = ({ sendSetPasswordInstructionsEndpoint, authenticityToken }: Props) => {


return (
<div style={{margin: 8, padding: 16, backgroundColor: '#cce5ff', borderRadius: 4}}>
<p>
{ I18n.t('common.forms.auth.no_password_set') }
</p>

<Button
type='button'
onClick={async (event) => {
const res = await fetch(sendSetPasswordInstructionsEndpoint, {
method: 'GET',
headers: buildRequestHeaders(authenticityToken)
});

if (res.ok) {
alert(I18n.t('devise.passwords.send_instructions'));
} else {
alert(I18n.t('common.errors.unknown'));
}
}}>
{ I18n.t('common.forms.auth.set_password') }
</Button>
</div>
)
};

export default SetPasswordDialog;
4 changes: 3 additions & 1 deletion app/javascript/components/common/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import * as React from 'react';
interface Props {
children: any;
onClick(e: React.FormEvent): void;
type?: 'button' | 'submit';
className?: string;
outline?: boolean;
disabled?: boolean;
}

const Button = ({ children, onClick, className = '', outline = false, disabled = false}: Props) => (
const Button = ({ children, onClick, type="submit", className = '', outline = false, disabled = false}: Props) => (
<button
onClick={onClick}
type={type}
className={`${className} btn${outline ? 'Outline' : ''}Primary`}
disabled={disabled}
>
Expand Down
22 changes: 21 additions & 1 deletion app/views/devise/registrations/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@

<%= render "devise/shared/error_messages", resource: resource %>

<% if not current_user.has_set_password? %>
<%=
react_component(
'UserProfile/SetPasswordDialog',
{
sendSetPasswordInstructionsEndpoint: send_set_password_instructions_path(email: current_user.email),
authenticityToken: form_authenticity_token
},
)
%>

<fieldset disabled="disabled">
<% end %>

<div class="form-group">
<%= f.label :full_name, t('common.forms.auth.full_name') %>
<%= f.text_field :full_name, autocomplete: "full-name", class: "form-control" %>
Expand Down Expand Up @@ -42,9 +56,11 @@
<%= f.password_field :password_confirmation, autocomplete: "new-password", class: "form-control" %>
</div>

<hr />

<div class="form-group">
<%= f.label :current_password, t('common.forms.auth.current_password') %>
<%= f.password_field :current_password, autocomplete: "current-password", class: "form-control" %>
<%= f.password_field :current_password, autocomplete: "current-password", required: true, class: "form-control" %>
<small id="currentPasswordHelp" class="form-text text-muted">
<%= t('common.forms.auth.current_password_required_help') %>
</small>
Expand All @@ -53,6 +69,10 @@
<div class="actions">
<%= f.submit t('common.forms.auth.update_profile'), class: "btnPrimary btn-block" %>
</div>

<% if not current_user.has_set_password? %>
</fieldset>
<% end %>
<% end %>

<br />
Expand Down
1 change: 1 addition & 0 deletions app/workflows/o_auth_sign_in_user_workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def run
email: email,
full_name: full_name,
password: Devise.friendly_token,
has_set_password: false,
status: 'active'
)
user.skip_confirmation
Expand Down
5 changes: 4 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
en:
common:
errors:
unknown: 'An unknown error occurred, please try again'
validations:
required: '%{attribute} is required'
email: 'Invalid email'
Expand All @@ -18,6 +20,8 @@ en:
notifications_enabled: 'Notifications enabled'
notifications_enabled_help: "if disabled, you won't receive any notification"
waiting_confirmation: 'Currently waiting confirmation for %{email}'
no_password_set: 'You must set a password to update your profile'
set_password: 'Set password'
password_leave_blank_help: "leave blank if you don't want to change your password"
current_password_required_help: 'we need your current password to confirm your changes'
remember_me: 'Remember me'
Expand Down Expand Up @@ -92,7 +96,6 @@ en:
description: 'Description (optional)'
no_title: 'Title field is mandatory'
submit_success: 'Feedback published! You will be redirected soon...'
submit_error: 'An unknown error occurred, try again'
search_box:
title: 'Search'
filter_box:
Expand Down
7 changes: 6 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@

devise_for :users, :controllers => {
:registrations => 'registrations',
:sessions => 'sessions'
:sessions => 'sessions',
:passwords => 'passwords'
}

devise_scope :user do
get '/users/send_set_password_instructions', to: 'registrations#send_set_password_instructions', as: :send_set_password_instructions
end

resources :tenants, only: [:show, :update]
resources :users, only: [:index, :update]
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20240514112836_add_has_set_password_to_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddHasSetPasswordToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :has_set_password, :boolean, default: true, null: false
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2024_04_27_140300) do
ActiveRecord::Schema.define(version: 2024_05_14_112836) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -198,6 +198,7 @@
t.integer "status"
t.bigint "tenant_id", null: false
t.string "oauth_token"
t.boolean "has_set_password", default: true, null: false
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email", "tenant_id"], name: "index_users_on_email_and_tenant_id", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
Expand Down
5 changes: 2 additions & 3 deletions spec/system/user_edit_profile_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,11 @@
full_name = user.full_name

visit edit_user_registration_path
fill_in 'Full name', with: 'New fantastic full name'
fill_in 'Full name', with: user.full_name + ' New'
# do not fill current password textbox
click_button 'Update profile'
visit edit_user_registration_path

expect(page).to have_css('#error_explanation')
expect(page).to have_css('.field_with_errors')
expect(user.reload.full_name).to eq(full_name)
end

Expand Down

0 comments on commit 5a162c6

Please sign in to comment.