Skip to content
Snippets Groups Projects
Unverified Commit 964ae8ee authored by Eugen Rochko's avatar Eugen Rochko Committed by GitHub
Browse files

Change unconfirmed user login behaviour (#11375)

Allow access to account settings, 2FA, authorized applications, and
account deletions to unconfirmed and pending users, as well as
users who had their accounts disabled. Suspended users cannot update
their e-mail or password or delete their account.

Display account status on account settings page, for example, when
an account is frozen, limited, unconfirmed or pending review.

After sign up, login users straight away and show a simple page that
tells them the status of their account with links to account settings
and logout, to reduce onboarding friction and allow users to correct
wrongly typed e-mail addresses.

Move the final sign-up step of SSO integrations to be the same
as above to reduce code duplication.
parent fea903f5
No related branches found
No related tags found
No related merge requests found
Showing
with 144 additions and 69 deletions
......@@ -7,7 +7,7 @@ class AboutController < ApplicationController
before_action :set_instance_presenter
before_action :set_expires_in
skip_before_action :check_user_permissions, only: [:more, :terms]
skip_before_action :require_functional!, only: [:more, :terms]
def show; end
......
......@@ -7,7 +7,7 @@ class Api::BaseController < ApplicationController
include RateLimitHeaders
skip_before_action :store_current_location
skip_before_action :check_user_permissions
skip_before_action :require_functional!
before_action :set_cache_headers
......
......@@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base
rescue_from Mastodon::NotPermittedError, with: :forbidden
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
before_action :check_user_permissions, if: :user_signed_in?
before_action :require_functional!, if: :user_signed_in?
def raise_not_found
raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}"
......@@ -57,8 +57,8 @@ class ApplicationController < ActionController::Base
forbidden unless current_user&.staff?
end
def check_user_permissions
forbidden if current_user.disabled? || current_user.account.suspended?
def require_functional!
redirect_to edit_user_registration_path unless current_user.functional?
end
def after_sign_out_path_for(_resource_or_scope)
......
......@@ -4,34 +4,15 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
layout 'auth'
before_action :set_body_classes
before_action :set_user, only: [:finish_signup]
def finish_signup
return unless request.patch? && params[:user]
if @user.update(user_params)
@user.skip_reconfirmation!
bypass_sign_in(@user)
redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions')
else
@show_errors = true
end
end
skip_before_action :require_functional!
private
def set_user
@user = current_user
end
def set_body_classes
@body_classes = 'lighter'
end
def user_params
params.require(:user).permit(:email)
end
def after_confirmation_path_for(_resource_name, user)
if user.created_by_application && truthy_param?(:redirect_to_app)
user.created_by_application.redirect_uri
......
......@@ -27,7 +27,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
if resource.email_verified?
root_path
else
finish_signup_path
auth_setup_path(missing_email: '1')
end
end
end
......@@ -9,6 +9,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_sessions, only: [:edit, :update]
before_action :set_instance_presenter, only: [:new, :create, :update]
before_action :set_body_classes, only: [:new, :create, :edit, :update]
before_action :require_not_suspended!, only: [:update]
skip_before_action :require_functional!, only: [:edit, :update]
def new
super(&:build_invite_request)
......@@ -43,7 +46,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def after_sign_up_path_for(_resource)
new_user_session_path
auth_setup_path
end
def after_sign_in_path_for(_resource)
......@@ -102,4 +105,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def set_sessions
@sessions = current_user.session_activations
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end
......@@ -6,8 +6,10 @@ class Auth::SessionsController < Devise::SessionsController
layout 'auth'
skip_before_action :require_no_authentication, only: [:create]
skip_before_action :check_user_permissions, only: [:destroy]
skip_before_action :require_functional!
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
before_action :set_instance_presenter, only: [:new]
before_action :set_body_classes
......
# frozen_string_literal: true
class Auth::SetupController < ApplicationController
layout 'auth'
before_action :authenticate_user!
before_action :require_unconfirmed_or_pending!
before_action :set_body_classes
before_action :set_user
skip_before_action :require_functional!
def show
flash.now[:notice] = begin
if @user.pending?
I18n.t('devise.registrations.signed_up_but_pending')
else
I18n.t('devise.registrations.signed_up_but_unconfirmed')
end
end
end
def update
# This allows updating the e-mail without entering a password as is required
# on the account settings page; however, we only allow this for accounts
# that were not confirmed yet
if @user.update(user_params)
redirect_to auth_setup_path, notice: I18n.t('devise.confirmations.send_instructions')
else
render :show
end
end
helper_method :missing_email?
private
def require_unconfirmed_or_pending!
redirect_to root_path if current_user.confirmed? && current_user.approved?
end
def set_user
@user = current_user
end
def set_body_classes
@body_classes = 'lighter'
end
def user_params
params.require(:user).permit(:email)
end
def missing_email?
truthy_param?(:missing_email)
end
end
......@@ -7,6 +7,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :authenticate_resource_owner!
before_action :set_body_classes
skip_before_action :require_functional!
include Localized
def destroy
......
......@@ -5,6 +5,9 @@ class Settings::DeletesController < Settings::BaseController
before_action :check_enabled_deletion
before_action :authenticate_user!
before_action :require_not_suspended!
skip_before_action :require_functional!
def show
@confirmation = Form::DeleteConfirmation.new
......@@ -29,4 +32,8 @@ class Settings::DeletesController < Settings::BaseController
def delete_params
params.require(:form_delete_confirmation).permit(:password)
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end
......@@ -4,6 +4,8 @@ class Settings::SessionsController < Settings::BaseController
before_action :authenticate_user!
before_action :set_session, only: :destroy
skip_before_action :require_functional!
def destroy
@session.destroy!
flash[:notice] = I18n.t('sessions.revoke_success')
......
......@@ -8,6 +8,8 @@ module Settings
before_action :authenticate_user!
before_action :ensure_otp_secret
skip_before_action :require_functional!
def new
prepare_two_factor_form
end
......
......@@ -7,6 +7,8 @@ module Settings
before_action :authenticate_user!
skip_before_action :require_functional!
def create
@recovery_codes = current_user.generate_otp_backup_codes!
current_user.save!
......
......@@ -7,6 +7,8 @@ module Settings
before_action :authenticate_user!
before_action :verify_otp_required, only: [:create]
skip_before_action :require_functional!
def show
@confirmation = Form::TwoFactorConfirmation.new
end
......
......@@ -204,29 +204,6 @@ $content-width: 840px;
border: 0;
}
}
.muted-hint {
color: $darker-text-color;
a {
color: $highlight-text-color;
}
}
.positive-hint {
color: $valid-value-color;
font-weight: 500;
}
.negative-hint {
color: $error-value-color;
font-weight: 500;
}
.neutral-hint {
color: $dark-text-color;
font-weight: 500;
}
}
@media screen and (max-width: $no-columns-breakpoint) {
......@@ -249,6 +226,41 @@ $content-width: 840px;
}
}
hr.spacer {
width: 100%;
border: 0;
margin: 20px 0;
height: 1px;
}
.muted-hint {
color: $darker-text-color;
a {
color: $highlight-text-color;
}
}
.positive-hint {
color: $valid-value-color;
font-weight: 500;
}
.negative-hint {
color: $error-value-color;
font-weight: 500;
}
.neutral-hint {
color: $dark-text-color;
font-weight: 500;
}
.warning-hint {
color: $gold-star;
font-weight: 500;
}
.filters {
display: flex;
flex-wrap: wrap;
......
......@@ -300,6 +300,13 @@ code {
}
}
.input.static .label_input__wrapper {
font-size: 16px;
padding: 10px;
border: 1px solid $dark-text-color;
border-radius: 4px;
}
input[type=text],
input[type=number],
input[type=email],
......
......@@ -43,7 +43,7 @@ module Omniauthable
# Check if the user exists with provided email if the provider gives us a
# verified email. If no verified email was provided or the user already
# exists, we assign a temporary email and ask the user to verify it on
# the next step via Auth::ConfirmationsController.finish_signup
# the next step via Auth::SetupController.show
user = User.new(user_params_from_auth(auth))
user.account.avatar_remote_url = auth.info.image if auth.info.image =~ /\A#{URI.regexp(%w(http https))}\z/
......
......@@ -161,7 +161,11 @@ class User < ApplicationRecord
end
def active_for_authentication?
super && approved?
true
end
def functional?
confirmed? && approved? && !disabled? && !account.suspended?
end
def inactive_message
......
- content_for :page_title do
= t('auth.confirm_email')
= simple_form_for(current_user, as: 'user', url: finish_signup_path, html: { role: 'form'}) do |f|
- if @show_errors && current_user.errors.any?
#error_explanation
- current_user.errors.full_messages.each do |msg|
= msg
%br
.fields-group
= f.input :email, wrapper: :with_label, required: true, hint: false
.actions
= f.submit t('auth.confirm_email'), class: 'button'
%h4= t 'sessions.title'
%h3= t 'sessions.title'
%p.muted-hint= t 'sessions.explanation'
%hr.spacer/
.table-wrapper
%table.table.inline-table
%thead
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment