mirror of
https://github.com/astuto/astuto.git
synced 2025-12-16 19:57:52 +01:00
Add instructions to set password for OAuth users (#346)
This commit is contained in:
committed by
GitHub
parent
57ed8c338d
commit
5a162c6f4f
19
app/controllers/passwords_controller.rb
Normal file
19
app/controllers/passwords_controller.rb
Normal file
@@ -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
|
||||||
@@ -17,6 +17,16 @@ class RegistrationsController < Devise::RegistrationsController
|
|||||||
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
|
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def set_page_title_new
|
def set_page_title_new
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class TenantsController < ApplicationController
|
|||||||
full_name: params[:user][:full_name] || I18n.t('defaults.user_full_name'),
|
full_name: params[:user][:full_name] || I18n.t('defaults.user_full_name'),
|
||||||
email: params[:user][:email],
|
email: params[:user][:email],
|
||||||
password: is_o_auth_login ? Devise.friendly_token : params[:user][:password],
|
password: is_o_auth_login ? Devise.friendly_token : params[:user][:password],
|
||||||
|
has_set_password: !is_o_auth_login,
|
||||||
role: "owner"
|
role: "owner"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class NewPost extends React.Component<Props, State> {
|
|||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: I18n.t('board.new_post.submit_error')
|
error: I18n.t('common.errors.unknown')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
app/javascript/components/UserProfile/SetPasswordDialog.tsx
Normal file
41
app/javascript/components/UserProfile/SetPasswordDialog.tsx
Normal file
@@ -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;
|
||||||
@@ -3,14 +3,16 @@ import * as React from 'react';
|
|||||||
interface Props {
|
interface Props {
|
||||||
children: any;
|
children: any;
|
||||||
onClick(e: React.FormEvent): void;
|
onClick(e: React.FormEvent): void;
|
||||||
|
type?: 'button' | 'submit';
|
||||||
className?: string;
|
className?: string;
|
||||||
outline?: boolean;
|
outline?: boolean;
|
||||||
disabled?: 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
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
type={type}
|
||||||
className={`${className} btn${outline ? 'Outline' : ''}Primary`}
|
className={`${className} btn${outline ? 'Outline' : ''}Primary`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,6 +3,20 @@
|
|||||||
|
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
<%= 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">
|
<div class="form-group">
|
||||||
<%= f.label :full_name, t('common.forms.auth.full_name') %>
|
<%= f.label :full_name, t('common.forms.auth.full_name') %>
|
||||||
<%= f.text_field :full_name, autocomplete: "full-name", class: "form-control" %>
|
<%= f.text_field :full_name, autocomplete: "full-name", class: "form-control" %>
|
||||||
@@ -42,9 +56,11 @@
|
|||||||
<%= f.password_field :password_confirmation, autocomplete: "new-password", class: "form-control" %>
|
<%= f.password_field :password_confirmation, autocomplete: "new-password", class: "form-control" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<%= f.label :current_password, t('common.forms.auth.current_password') %>
|
<%= 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">
|
<small id="currentPasswordHelp" class="form-text text-muted">
|
||||||
<%= t('common.forms.auth.current_password_required_help') %>
|
<%= t('common.forms.auth.current_password_required_help') %>
|
||||||
</small>
|
</small>
|
||||||
@@ -53,6 +69,10 @@
|
|||||||
<div class="actions">
|
<div class="actions">
|
||||||
<%= f.submit t('common.forms.auth.update_profile'), class: "btnPrimary btn-block" %>
|
<%= f.submit t('common.forms.auth.update_profile'), class: "btnPrimary btn-block" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<% if not current_user.has_set_password? %>
|
||||||
|
</fieldset>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class OAuthSignInUserWorkflow
|
|||||||
email: email,
|
email: email,
|
||||||
full_name: full_name,
|
full_name: full_name,
|
||||||
password: Devise.friendly_token,
|
password: Devise.friendly_token,
|
||||||
|
has_set_password: false,
|
||||||
status: 'active'
|
status: 'active'
|
||||||
)
|
)
|
||||||
user.skip_confirmation
|
user.skip_confirmation
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
en:
|
en:
|
||||||
common:
|
common:
|
||||||
|
errors:
|
||||||
|
unknown: 'An unknown error occurred, please try again'
|
||||||
validations:
|
validations:
|
||||||
required: '%{attribute} is required'
|
required: '%{attribute} is required'
|
||||||
email: 'Invalid email'
|
email: 'Invalid email'
|
||||||
@@ -18,6 +20,8 @@ en:
|
|||||||
notifications_enabled: 'Notifications enabled'
|
notifications_enabled: 'Notifications enabled'
|
||||||
notifications_enabled_help: "if disabled, you won't receive any notification"
|
notifications_enabled_help: "if disabled, you won't receive any notification"
|
||||||
waiting_confirmation: 'Currently waiting confirmation for %{email}'
|
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"
|
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'
|
current_password_required_help: 'we need your current password to confirm your changes'
|
||||||
remember_me: 'Remember me'
|
remember_me: 'Remember me'
|
||||||
@@ -92,7 +96,6 @@ en:
|
|||||||
description: 'Description (optional)'
|
description: 'Description (optional)'
|
||||||
no_title: 'Title field is mandatory'
|
no_title: 'Title field is mandatory'
|
||||||
submit_success: 'Feedback published! You will be redirected soon...'
|
submit_success: 'Feedback published! You will be redirected soon...'
|
||||||
submit_error: 'An unknown error occurred, try again'
|
|
||||||
search_box:
|
search_box:
|
||||||
title: 'Search'
|
title: 'Search'
|
||||||
filter_box:
|
filter_box:
|
||||||
|
|||||||
@@ -31,9 +31,14 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
devise_for :users, :controllers => {
|
devise_for :users, :controllers => {
|
||||||
:registrations => 'registrations',
|
: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 :tenants, only: [:show, :update]
|
||||||
resources :users, only: [:index, :update]
|
resources :users, only: [:index, :update]
|
||||||
resources :o_auths, only: [:index, :create, :update, :destroy] do
|
resources :o_auths, only: [:index, :create, :update, :destroy] do
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
@@ -198,6 +198,7 @@ ActiveRecord::Schema.define(version: 2024_04_27_140300) do
|
|||||||
t.integer "status"
|
t.integer "status"
|
||||||
t.bigint "tenant_id", null: false
|
t.bigint "tenant_id", null: false
|
||||||
t.string "oauth_token"
|
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 ["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 ["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
|
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||||
|
|||||||
@@ -93,12 +93,11 @@ feature 'edit user profile settings', type: :system, js: true do
|
|||||||
full_name = user.full_name
|
full_name = user.full_name
|
||||||
|
|
||||||
visit edit_user_registration_path
|
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
|
# do not fill current password textbox
|
||||||
click_button 'Update profile'
|
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)
|
expect(user.reload.full_name).to eq(full_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user