mirror of
https://github.com/astuto/astuto.git
synced 2025-12-15 03:07: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) }
|
||||
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
|
||||
|
||||
@@ -42,6 +42,7 @@ class TenantsController < ApplicationController
|
||||
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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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}
|
||||
>
|
||||
|
||||
@@ -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" %>
|
||||
@@ -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>
|
||||
@@ -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 />
|
||||
|
||||
@@ -44,6 +44,7 @@ class OAuthSignInUserWorkflow
|
||||
email: email,
|
||||
full_name: full_name,
|
||||
password: Devise.friendly_token,
|
||||
has_set_password: false,
|
||||
status: 'active'
|
||||
)
|
||||
user.skip_confirmation
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
en:
|
||||
common:
|
||||
errors:
|
||||
unknown: 'An unknown error occurred, please try again'
|
||||
validations:
|
||||
required: '%{attribute} is required'
|
||||
email: 'Invalid email'
|
||||
@@ -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'
|
||||
@@ -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:
|
||||
|
||||
@@ -31,8 +31,13 @@ Rails.application.routes.draw do
|
||||
|
||||
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]
|
||||
|
||||
@@ -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.
|
||||
|
||||
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"
|
||||
@@ -198,6 +198,7 @@ ActiveRecord::Schema.define(version: 2024_04_27_140300) do
|
||||
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
|
||||
|
||||
@@ -93,12 +93,11 @@ feature 'edit user profile settings', type: :system, js: true do
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user