Browse Source

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.
Eugen Rochko 1 year ago
parent
commit
964ae8eee5
No account linked to committer's email address
35 changed files with 298 additions and 148 deletions
  1. 1
    1
      app/controllers/about_controller.rb
  2. 1
    1
      app/controllers/api/base_controller.rb
  3. 3
    3
      app/controllers/application_controller.rb
  4. 1
    20
      app/controllers/auth/confirmations_controller.rb
  5. 1
    1
      app/controllers/auth/omniauth_callbacks_controller.rb
  6. 8
    1
      app/controllers/auth/registrations_controller.rb
  7. 3
    1
      app/controllers/auth/sessions_controller.rb
  8. 58
    0
      app/controllers/auth/setup_controller.rb
  9. 2
    0
      app/controllers/oauth/authorized_applications_controller.rb
  10. 7
    0
      app/controllers/settings/deletes_controller.rb
  11. 2
    0
      app/controllers/settings/sessions_controller.rb
  12. 2
    0
      app/controllers/settings/two_factor_authentication/confirmations_controller.rb
  13. 2
    0
      app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
  14. 2
    0
      app/controllers/settings/two_factor_authentications_controller.rb
  15. 35
    23
      app/javascript/styles/mastodon/admin.scss
  16. 7
    0
      app/javascript/styles/mastodon/forms.scss
  17. 1
    1
      app/models/concerns/omniauthable.rb
  18. 5
    1
      app/models/user.rb
  19. 0
    15
      app/views/auth/confirmations/finish_signup.html.haml
  20. 3
    1
      app/views/auth/registrations/_sessions.html.haml
  21. 16
    0
      app/views/auth/registrations/_status.html.haml
  22. 19
    16
      app/views/auth/registrations/edit.html.haml
  23. 23
    0
      app/views/auth/setup/show.html.haml
  24. 1
    1
      app/views/oauth/authorized_applications/index.html.haml
  25. 8
    1
      config/locales/en.yml
  26. 4
    1
      config/routes.rb
  27. 1
    1
      db/seeds.rb
  28. 40
    2
      spec/controllers/api/base_controller_spec.rb
  29. 2
    2
      spec/controllers/application_controller_spec.rb
  30. 0
    41
      spec/controllers/auth/confirmations_controller_spec.rb
  31. 17
    8
      spec/controllers/auth/registrations_controller_spec.rb
  32. 2
    2
      spec/controllers/auth/sessions_controller_spec.rb
  33. 17
    0
      spec/controllers/settings/deletes_controller_spec.rb
  34. 2
    2
      spec/features/log_in_spec.rb
  35. 2
    2
      spec/models/user_spec.rb

+ 1
- 1
app/controllers/about_controller.rb View File

@@ -7,7 +7,7 @@ class AboutController < ApplicationController
7 7
   before_action :set_instance_presenter
8 8
   before_action :set_expires_in
9 9
 
10
-  skip_before_action :check_user_permissions, only: [:more, :terms]
10
+  skip_before_action :require_functional!, only: [:more, :terms]
11 11
 
12 12
   def show; end
13 13
 

+ 1
- 1
app/controllers/api/base_controller.rb View File

@@ -7,7 +7,7 @@ class Api::BaseController < ApplicationController
7 7
   include RateLimitHeaders
8 8
 
9 9
   skip_before_action :store_current_location
10
-  skip_before_action :check_user_permissions
10
+  skip_before_action :require_functional!
11 11
 
12 12
   before_action :set_cache_headers
13 13
 

+ 3
- 3
app/controllers/application_controller.rb View File

@@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base
25 25
   rescue_from Mastodon::NotPermittedError, with: :forbidden
26 26
 
27 27
   before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
28
-  before_action :check_user_permissions, if: :user_signed_in?
28
+  before_action :require_functional!, if: :user_signed_in?
29 29
 
30 30
   def raise_not_found
31 31
     raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}"
@@ -57,8 +57,8 @@ class ApplicationController < ActionController::Base
57 57
     forbidden unless current_user&.staff?
58 58
   end
59 59
 
60
-  def check_user_permissions
61
-    forbidden if current_user.disabled? || current_user.account.suspended?
60
+  def require_functional!
61
+    redirect_to edit_user_registration_path unless current_user.functional?
62 62
   end
63 63
 
64 64
   def after_sign_out_path_for(_resource_or_scope)

+ 1
- 20
app/controllers/auth/confirmations_controller.rb View File

@@ -4,34 +4,15 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
4 4
   layout 'auth'
5 5
 
6 6
   before_action :set_body_classes
7
-  before_action :set_user, only: [:finish_signup]
8 7
 
9
-  def finish_signup
10
-    return unless request.patch? && params[:user]
11
-
12
-    if @user.update(user_params)
13
-      @user.skip_reconfirmation!
14
-      bypass_sign_in(@user)
15
-      redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions')
16
-    else
17
-      @show_errors = true
18
-    end
19
-  end
8
+  skip_before_action :require_functional!
20 9
 
21 10
   private
22 11
 
23
-  def set_user
24
-    @user = current_user
25
-  end
26
-
27 12
   def set_body_classes
28 13
     @body_classes = 'lighter'
29 14
   end
30 15
 
31
-  def user_params
32
-    params.require(:user).permit(:email)
33
-  end
34
-
35 16
   def after_confirmation_path_for(_resource_name, user)
36 17
     if user.created_by_application && truthy_param?(:redirect_to_app)
37 18
       user.created_by_application.redirect_uri

+ 1
- 1
app/controllers/auth/omniauth_callbacks_controller.rb View File

@@ -27,7 +27,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
27 27
     if resource.email_verified?
28 28
       root_path
29 29
     else
30
-      finish_signup_path
30
+      auth_setup_path(missing_email: '1')
31 31
     end
32 32
   end
33 33
 end

+ 8
- 1
app/controllers/auth/registrations_controller.rb View File

@@ -9,6 +9,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController
9 9
   before_action :set_sessions, only: [:edit, :update]
10 10
   before_action :set_instance_presenter, only: [:new, :create, :update]
11 11
   before_action :set_body_classes, only: [:new, :create, :edit, :update]
12
+  before_action :require_not_suspended!, only: [:update]
13
+
14
+  skip_before_action :require_functional!, only: [:edit, :update]
12 15
 
13 16
   def new
14 17
     super(&:build_invite_request)
@@ -43,7 +46,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
43 46
   end
44 47
 
45 48
   def after_sign_up_path_for(_resource)
46
-    new_user_session_path
49
+    auth_setup_path
47 50
   end
48 51
 
49 52
   def after_sign_in_path_for(_resource)
@@ -102,4 +105,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
102 105
   def set_sessions
103 106
     @sessions = current_user.session_activations
104 107
   end
108
+
109
+  def require_not_suspended!
110
+    forbidden if current_account.suspended?
111
+  end
105 112
 end

+ 3
- 1
app/controllers/auth/sessions_controller.rb View File

@@ -6,8 +6,10 @@ class Auth::SessionsController < Devise::SessionsController
6 6
   layout 'auth'
7 7
 
8 8
   skip_before_action :require_no_authentication, only: [:create]
9
-  skip_before_action :check_user_permissions, only: [:destroy]
9
+  skip_before_action :require_functional!
10
+
10 11
   prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
12
+
11 13
   before_action :set_instance_presenter, only: [:new]
12 14
   before_action :set_body_classes
13 15
 

+ 58
- 0
app/controllers/auth/setup_controller.rb View File

@@ -0,0 +1,58 @@
1
+# frozen_string_literal: true
2
+
3
+class Auth::SetupController < ApplicationController
4
+  layout 'auth'
5
+
6
+  before_action :authenticate_user!
7
+  before_action :require_unconfirmed_or_pending!
8
+  before_action :set_body_classes
9
+  before_action :set_user
10
+
11
+  skip_before_action :require_functional!
12
+
13
+  def show
14
+    flash.now[:notice] = begin
15
+      if @user.pending?
16
+        I18n.t('devise.registrations.signed_up_but_pending')
17
+      else
18
+        I18n.t('devise.registrations.signed_up_but_unconfirmed')
19
+      end
20
+    end
21
+  end
22
+
23
+  def update
24
+    # This allows updating the e-mail without entering a password as is required
25
+    # on the account settings page; however, we only allow this for accounts
26
+    # that were not confirmed yet
27
+
28
+    if @user.update(user_params)
29
+      redirect_to auth_setup_path, notice: I18n.t('devise.confirmations.send_instructions')
30
+    else
31
+      render :show
32
+    end
33
+  end
34
+
35
+  helper_method :missing_email?
36
+
37
+  private
38
+
39
+  def require_unconfirmed_or_pending!
40
+    redirect_to root_path if current_user.confirmed? && current_user.approved?
41
+  end
42
+
43
+  def set_user
44
+    @user = current_user
45
+  end
46
+
47
+  def set_body_classes
48
+    @body_classes = 'lighter'
49
+  end
50
+
51
+  def user_params
52
+    params.require(:user).permit(:email)
53
+  end
54
+
55
+  def missing_email?
56
+    truthy_param?(:missing_email)
57
+  end
58
+end

+ 2
- 0
app/controllers/oauth/authorized_applications_controller.rb View File

@@ -7,6 +7,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
7 7
   before_action :authenticate_resource_owner!
8 8
   before_action :set_body_classes
9 9
 
10
+  skip_before_action :require_functional!
11
+
10 12
   include Localized
11 13
 
12 14
   def destroy

+ 7
- 0
app/controllers/settings/deletes_controller.rb View File

@@ -5,6 +5,9 @@ class Settings::DeletesController < Settings::BaseController
5 5
 
6 6
   before_action :check_enabled_deletion
7 7
   before_action :authenticate_user!
8
+  before_action :require_not_suspended!
9
+
10
+  skip_before_action :require_functional!
8 11
 
9 12
   def show
10 13
     @confirmation = Form::DeleteConfirmation.new
@@ -29,4 +32,8 @@ class Settings::DeletesController < Settings::BaseController
29 32
   def delete_params
30 33
     params.require(:form_delete_confirmation).permit(:password)
31 34
   end
35
+
36
+  def require_not_suspended!
37
+    forbidden if current_account.suspended?
38
+  end
32 39
 end

+ 2
- 0
app/controllers/settings/sessions_controller.rb View File

@@ -4,6 +4,8 @@ class Settings::SessionsController < Settings::BaseController
4 4
   before_action :authenticate_user!
5 5
   before_action :set_session, only: :destroy
6 6
 
7
+  skip_before_action :require_functional!
8
+
7 9
   def destroy
8 10
     @session.destroy!
9 11
     flash[:notice] = I18n.t('sessions.revoke_success')

+ 2
- 0
app/controllers/settings/two_factor_authentication/confirmations_controller.rb View File

@@ -8,6 +8,8 @@ module Settings
8 8
       before_action :authenticate_user!
9 9
       before_action :ensure_otp_secret
10 10
 
11
+      skip_before_action :require_functional!
12
+
11 13
       def new
12 14
         prepare_two_factor_form
13 15
       end

+ 2
- 0
app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb View File

@@ -7,6 +7,8 @@ module Settings
7 7
 
8 8
       before_action :authenticate_user!
9 9
 
10
+      skip_before_action :require_functional!
11
+
10 12
       def create
11 13
         @recovery_codes = current_user.generate_otp_backup_codes!
12 14
         current_user.save!

+ 2
- 0
app/controllers/settings/two_factor_authentications_controller.rb View File

@@ -7,6 +7,8 @@ module Settings
7 7
     before_action :authenticate_user!
8 8
     before_action :verify_otp_required, only: [:create]
9 9
 
10
+    skip_before_action :require_functional!
11
+
10 12
     def show
11 13
       @confirmation = Form::TwoFactorConfirmation.new
12 14
     end

+ 35
- 23
app/javascript/styles/mastodon/admin.scss View File

@@ -204,29 +204,6 @@ $content-width: 840px;
204 204
         border: 0;
205 205
       }
206 206
     }
207
-
208
-    .muted-hint {
209
-      color: $darker-text-color;
210
-
211
-      a {
212
-        color: $highlight-text-color;
213
-      }
214
-    }
215
-
216
-    .positive-hint {
217
-      color: $valid-value-color;
218
-      font-weight: 500;
219
-    }
220
-
221
-    .negative-hint {
222
-      color: $error-value-color;
223
-      font-weight: 500;
224
-    }
225
-
226
-    .neutral-hint {
227
-      color: $dark-text-color;
228
-      font-weight: 500;
229
-    }
230 207
   }
231 208
 
232 209
   @media screen and (max-width: $no-columns-breakpoint) {
@@ -249,6 +226,41 @@ $content-width: 840px;
249 226
   }
250 227
 }
251 228
 
229
+hr.spacer {
230
+  width: 100%;
231
+  border: 0;
232
+  margin: 20px 0;
233
+  height: 1px;
234
+}
235
+
236
+.muted-hint {
237
+  color: $darker-text-color;
238
+
239
+  a {
240
+    color: $highlight-text-color;
241
+  }
242
+}
243
+
244
+.positive-hint {
245
+  color: $valid-value-color;
246
+  font-weight: 500;
247
+}
248
+
249
+.negative-hint {
250
+  color: $error-value-color;
251
+  font-weight: 500;
252
+}
253
+
254
+.neutral-hint {
255
+  color: $dark-text-color;
256
+  font-weight: 500;
257
+}
258
+
259
+.warning-hint {
260
+  color: $gold-star;
261
+  font-weight: 500;
262
+}
263
+
252 264
 .filters {
253 265
   display: flex;
254 266
   flex-wrap: wrap;

+ 7
- 0
app/javascript/styles/mastodon/forms.scss View File

@@ -300,6 +300,13 @@ code {
300 300
     }
301 301
   }
302 302
 
303
+  .input.static .label_input__wrapper {
304
+    font-size: 16px;
305
+    padding: 10px;
306
+    border: 1px solid $dark-text-color;
307
+    border-radius: 4px;
308
+  }
309
+
303 310
   input[type=text],
304 311
   input[type=number],
305 312
   input[type=email],

+ 1
- 1
app/models/concerns/omniauthable.rb View File

@@ -43,7 +43,7 @@ module Omniauthable
43 43
       # Check if the user exists with provided email if the provider gives us a
44 44
       # verified email.  If no verified email was provided or the user already
45 45
       # exists, we assign a temporary email and ask the user to verify it on
46
-      # the next step via Auth::ConfirmationsController.finish_signup
46
+      # the next step via Auth::SetupController.show
47 47
 
48 48
       user = User.new(user_params_from_auth(auth))
49 49
       user.account.avatar_remote_url = auth.info.image if auth.info.image =~ /\A#{URI.regexp(%w(http https))}\z/

+ 5
- 1
app/models/user.rb View File

@@ -161,7 +161,11 @@ class User < ApplicationRecord
161 161
   end
162 162
 
163 163
   def active_for_authentication?
164
-    super && approved?
164
+    true
165
+  end
166
+
167
+  def functional?
168
+    confirmed? && approved? && !disabled? && !account.suspended?
165 169
   end
166 170
 
167 171
   def inactive_message

+ 0
- 15
app/views/auth/confirmations/finish_signup.html.haml View File

@@ -1,15 +0,0 @@
1
-- content_for :page_title do
2
-  = t('auth.confirm_email')
3
-
4
-= simple_form_for(current_user, as: 'user', url: finish_signup_path, html: { role: 'form'}) do |f|
5
-  - if @show_errors && current_user.errors.any?
6
-    #error_explanation
7
-      - current_user.errors.full_messages.each do |msg|
8
-        = msg
9
-        %br
10
-
11
-  .fields-group
12
-    = f.input :email, wrapper: :with_label, required: true, hint: false
13
-
14
-  .actions
15
-    = f.submit t('auth.confirm_email'), class: 'button'

+ 3
- 1
app/views/auth/registrations/_sessions.html.haml View File

@@ -1,6 +1,8 @@
1
-%h4= t 'sessions.title'
1
+%h3= t 'sessions.title'
2 2
 %p.muted-hint= t 'sessions.explanation'
3 3
 
4
+%hr.spacer/
5
+
4 6
 .table-wrapper
5 7
   %table.table.inline-table
6 8
     %thead

+ 16
- 0
app/views/auth/registrations/_status.html.haml View File

@@ -0,0 +1,16 @@
1
+%h3= t('auth.status.account_status')
2
+
3
+- if @user.account.suspended?
4
+  %span.negative-hint= t('user_mailer.warning.explanation.suspend')
5
+- elsif @user.disabled?
6
+  %span.negative-hint= t('user_mailer.warning.explanation.disable')
7
+- elsif @user.account.silenced?
8
+  %span.warning-hint= t('user_mailer.warning.explanation.silence')
9
+- elsif !@user.confirmed?
10
+  %span.warning-hint= t('auth.status.confirming')
11
+- elsif !@user.approved?
12
+  %span.warning-hint= t('auth.status.pending')
13
+- else
14
+  %span.positive-hint= t('auth.status.functional')
15
+
16
+%hr.spacer/

+ 19
- 16
app/views/auth/registrations/edit.html.haml View File

@@ -1,25 +1,28 @@
1 1
 - content_for :page_title do
2
-  = t('auth.security')
2
+  = t('settings.account_settings')
3
+
4
+= render 'status'
5
+
6
+%h3= t('auth.security')
3 7
 
4 8
 = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f|
5 9
   = render 'shared/error_messages', object: resource
6 10
 
7 11
   - if !use_seamless_external_login? || resource.encrypted_password.present?
8
-    .fields-group
9
-      = f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, hint: false
10
-
11
-    .fields-group
12
-      = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, required: true
13
-
14
-    .fields-group
15
-      = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }, hint: false
16
-
17
-    .fields-group
18
-      = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
19
-
12
+    .fields-row
13
+      .fields-row__column.fields-group.fields-row__column-6
14
+        = f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended?
15
+      .fields-row__column.fields-group.fields-row__column-6
16
+        = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, required: true, disabled: current_account.suspended?
17
+
18
+    .fields-row
19
+      .fields-row__column.fields-group.fields-row__column-6
20
+        = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
21
+      .fields-row__column.fields-group.fields-row__column-6
22
+        = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }, disabled: current_account.suspended?
20 23
 
21 24
     .actions
22
-      = f.button :button, t('generic.save_changes'), type: :submit
25
+      = f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended?
23 26
   - else
24 27
     %p.hint= t('users.seamless_external_login')
25 28
 
@@ -27,7 +30,7 @@
27 30
 
28 31
 = render 'sessions'
29 32
 
30
-- if open_deletion?
33
+- if open_deletion? && !current_account.suspended?
31 34
   %hr.spacer/
32
-  %h4= t('auth.delete_account')
35
+  %h3= t('auth.delete_account')
33 36
   %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)

+ 23
- 0
app/views/auth/setup/show.html.haml View File

@@ -0,0 +1,23 @@
1
+- content_for :page_title do
2
+  = t('auth.setup.title')
3
+
4
+- if missing_email?
5
+  = simple_form_for(@user, url: auth_setup_path) do |f|
6
+    = render 'shared/error_messages', object: @user
7
+
8
+    .fields-group
9
+      %p.hint= t('auth.setup.email_below_hint_html')
10
+
11
+    .fields-group
12
+      = f.input :email, required: true, hint: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }
13
+
14
+    .actions
15
+      = f.submit t('admin.accounts.change_email.label'), class: 'button'
16
+- else
17
+  .simple_form
18
+    %p.hint= t('auth.setup.email_settings_hint_html', email: content_tag(:strong, @user.email))
19
+
20
+.form-footer
21
+  %ul.no-list
22
+    %li= link_to t('settings.account_settings'), edit_user_registration_path
23
+    %li= link_to t('auth.logout'), destroy_user_session_path, data: { method: :delete }

+ 1
- 1
app/views/oauth/authorized_applications/index.html.haml View File

@@ -17,7 +17,7 @@
17 17
               = application.name
18 18
             - else
19 19
               = link_to application.name, application.website, target: '_blank', rel: 'noopener'
20
-          %th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('<br />')
20
+          %th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join(', ')
21 21
           %td= l application.created_at
22 22
           %td
23 23
             - unless application.superapp?

+ 8
- 1
config/locales/en.yml View File

@@ -524,7 +524,6 @@ en:
524 524
     apply_for_account: Request an invite
525 525
     change_password: Password
526 526
     checkbox_agreement_html: I agree to the <a href="%{rules_path}" target="_blank">server rules</a> and <a href="%{terms_path}" target="_blank">terms of service</a>
527
-    confirm_email: Confirm email
528 527
     delete_account: Delete account
529 528
     delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
530 529
     didnt_get_confirmation: Didn't receive confirmation instructions?
@@ -544,6 +543,14 @@ en:
544 543
     reset_password: Reset password
545 544
     security: Security
546 545
     set_new_password: Set new password
546
+    setup:
547
+      email_below_hint_html: If the below e-mail address is incorrect, you can change it here and receive a new confirmation e-mail.
548
+      email_settings_hint_html: The confirmation e-mail was sent to %{email}. If that e-mail address is not correct, you can change it in account settings.
549
+      title: Setup
550
+    status:
551
+      account_status: Account status
552
+      confirming: Waiting for e-mail confirmation to be completed.
553
+      pending: Your application is pending review by our staff. This may take some time. You will receive an e-mail if your application is approved.
547 554
     trouble_logging_in: Trouble logging in?
548 555
   authorize_follow:
549 556
     already_following: You are already following this account

+ 4
- 1
config/routes.rb View File

@@ -34,7 +34,10 @@ Rails.application.routes.draw do
34 34
 
35 35
   devise_scope :user do
36 36
     get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite
37
-    match '/auth/finish_signup' => 'auth/confirmations#finish_signup', via: [:get, :patch], as: :finish_signup
37
+
38
+    namespace :auth do
39
+      resource :setup, only: [:show, :update], controller: :setup
40
+    end
38 41
   end
39 42
 
40 43
   devise_for :users, path: 'auth', controllers: {

+ 1
- 1
db/seeds.rb View File

@@ -1,4 +1,4 @@
1
-Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow')
1
+Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push')
2 2
 
3 3
 domain = ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain
4 4
 account = Account.find_or_initialize_by(id: -99, actor_type: 'Application', locked: true, username: domain)

+ 40
- 2
spec/controllers/api/base_controller_spec.rb View File

@@ -15,7 +15,7 @@ describe Api::BaseController do
15 15
     end
16 16
   end
17 17
 
18
-  describe 'Forgery protection' do
18
+  describe 'forgery protection' do
19 19
     before do
20 20
       routes.draw { post 'success' => 'api/base#success' }
21 21
     end
@@ -27,7 +27,45 @@ describe Api::BaseController do
27 27
     end
28 28
   end
29 29
 
30
-  describe 'Error handling' do
30
+  describe 'non-functional accounts handling' do
31
+    let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
32
+    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
33
+
34
+    controller do
35
+      before_action :require_user!
36
+    end
37
+
38
+    before do
39
+      routes.draw { post 'success' => 'api/base#success' }
40
+      allow(controller).to receive(:doorkeeper_token) { token }
41
+    end
42
+
43
+    it 'returns http forbidden for unconfirmed accounts' do
44
+      user.update(confirmed_at: nil)
45
+      post 'success'
46
+      expect(response).to have_http_status(403)
47
+    end
48
+
49
+    it 'returns http forbidden for pending accounts' do
50
+      user.update(approved: false)
51
+      post 'success'
52
+      expect(response).to have_http_status(403)
53
+    end
54
+
55
+    it 'returns http forbidden for disabled accounts' do
56
+      user.update(disabled: true)
57
+      post 'success'
58
+      expect(response).to have_http_status(403)
59
+    end
60
+
61
+    it 'returns http forbidden for suspended accounts' do
62
+      user.account.suspend!
63
+      post 'success'
64
+      expect(response).to have_http_status(403)
65
+    end
66
+  end
67
+
68
+  describe 'error handling' do
31 69
     ERRORS_WITH_CODES = {
32 70
       ActiveRecord::RecordInvalid => 422,
33 71
       Mastodon::ValidationError => 422,

+ 2
- 2
spec/controllers/application_controller_spec.rb View File

@@ -187,10 +187,10 @@ describe ApplicationController, type: :controller do
187 187
       expect(response).to have_http_status(200)
188 188
     end
189 189
 
190
-    it 'returns http 403 if user who signed in is suspended' do
190
+    it 'redirects to account status page' do
191 191
       sign_in(Fabricate(:user, account: Fabricate(:account, suspended: true)))
192 192
       get 'success'
193
-      expect(response).to have_http_status(403)
193
+      expect(response).to redirect_to(edit_user_registration_path)
194 194
     end
195 195
   end
196 196
 

+ 0
- 41
spec/controllers/auth/confirmations_controller_spec.rb View File

@@ -50,45 +50,4 @@ describe Auth::ConfirmationsController, type: :controller do
50 50
       end
51 51
     end
52 52
   end
53
-
54
-  describe 'GET #finish_signup' do
55
-    subject { get :finish_signup }
56
-
57
-    let(:user) { Fabricate(:user) }
58
-    before do
59
-      sign_in user, scope: :user
60
-      @request.env['devise.mapping'] = Devise.mappings[:user]
61
-    end
62
-
63
-    it 'renders finish_signup' do
64
-      is_expected.to render_template :finish_signup
65
-      expect(assigns(:user)).to have_attributes id: user.id
66
-    end
67
-  end
68
-
69
-  describe 'PATCH #finish_signup' do
70
-    subject { patch :finish_signup, params: { user: { email: email } } }
71
-
72
-    let(:user) { Fabricate(:user) }
73
-    before do
74
-      sign_in user, scope: :user
75
-      @request.env['devise.mapping'] = Devise.mappings[:user]
76
-    end
77
-
78
-    context 'when email is valid' do
79
-      let(:email) { 'new_' + user.email }
80
-
81
-      it 'redirects to root_path' do
82
-        is_expected.to redirect_to root_path
83
-      end
84
-    end
85
-
86
-    context 'when email is invalid' do
87
-      let(:email) { '' }
88
-
89
-      it 'renders finish_signup' do
90
-        is_expected.to render_template :finish_signup
91
-      end
92
-    end
93
-  end
94 53
 end

+ 17
- 8
spec/controllers/auth/registrations_controller_spec.rb View File

@@ -46,6 +46,15 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
46 46
       post :update
47 47
       expect(response).to have_http_status(200)
48 48
     end
49
+
50
+    context 'when suspended' do
51
+      it 'returns http forbidden' do
52
+        request.env["devise.mapping"] = Devise.mappings[:user]
53
+        sign_in(Fabricate(:user, account_attributes: { username: 'test', suspended_at: Time.now.utc }), scope: :user)
54
+        post :update
55
+        expect(response).to have_http_status(403)
56
+      end
57
+    end
49 58
   end
50 59
 
51 60
   describe 'GET #new' do
@@ -94,9 +103,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
94 103
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
95 104
       end
96 105
 
97
-      it 'redirects to login page' do
106
+      it 'redirects to setup' do
98 107
         subject
99
-        expect(response).to redirect_to new_user_session_path
108
+        expect(response).to redirect_to auth_setup_path
100 109
       end
101 110
 
102 111
       it 'creates user' do
@@ -120,9 +129,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
120 129
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
121 130
       end
122 131
 
123
-      it 'redirects to login page' do
132
+      it 'redirects to setup' do
124 133
         subject
125
-        expect(response).to redirect_to new_user_session_path
134
+        expect(response).to redirect_to auth_setup_path
126 135
       end
127 136
 
128 137
       it 'creates user' do
@@ -148,9 +157,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
148 157
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
149 158
       end
150 159
 
151
-      it 'redirects to login page' do
160
+      it 'redirects to setup' do
152 161
         subject
153
-        expect(response).to redirect_to new_user_session_path
162
+        expect(response).to redirect_to auth_setup_path
154 163
       end
155 164
 
156 165
       it 'creates user' do
@@ -176,9 +185,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
176 185
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
177 186
       end
178 187
 
179
-      it 'redirects to login page' do
188
+      it 'redirects to setup' do
180 189
         subject
181
-        expect(response).to redirect_to new_user_session_path
190
+        expect(response).to redirect_to auth_setup_path
182 191
       end
183 192
 
184 193
       it 'creates user' do

+ 2
- 2
spec/controllers/auth/sessions_controller_spec.rb View File

@@ -160,8 +160,8 @@ RSpec.describe Auth::SessionsController, type: :controller do
160 160
         let(:unconfirmed_user) { user.tap { |u| u.update!(confirmed_at: nil) } }
161 161
         let(:accept_language) { 'fr' }
162 162
 
163
-        it 'shows a translated login error' do
164
-          expect(flash[:alert]).to eq(I18n.t('devise.failure.unconfirmed', locale: accept_language))
163
+        it 'redirects to home' do
164
+          expect(response).to redirect_to(root_path)
165 165
         end
166 166
       end
167 167
 

+ 17
- 0
spec/controllers/settings/deletes_controller_spec.rb View File

@@ -15,6 +15,15 @@ describe Settings::DeletesController do
15 15
         get :show
16 16
         expect(response).to have_http_status(200)
17 17
       end
18
+
19
+      context 'when suspended' do
20
+        let(:user) { Fabricate(:user, account_attributes: { username: 'alice', suspended_at: Time.now.utc }) }
21
+
22
+        it 'returns http forbidden' do
23
+          get :show
24
+          expect(response).to have_http_status(403)
25
+        end
26
+      end
18 27
     end
19 28
 
20 29
     context 'when not signed in' do
@@ -49,6 +58,14 @@ describe Settings::DeletesController do
49 58
         it 'marks account as suspended' do
50 59
           expect(user.account.reload).to be_suspended
51 60
         end
61
+
62
+        context 'when suspended' do
63
+          let(:user) { Fabricate(:user, account_attributes: { username: 'alice', suspended_at: Time.now.utc }) }
64
+
65
+          it 'returns http forbidden' do
66
+            expect(response).to have_http_status(403)
67
+          end
68
+        end
52 69
       end
53 70
 
54 71
       context 'with incorrect password' do

+ 2
- 2
spec/features/log_in_spec.rb View File

@@ -31,12 +31,12 @@ feature "Log in" do
31 31
   context do
32 32
     given(:confirmed_at) { nil }
33 33
 
34
-    scenario "A unconfirmed user is not able to log in" do
34
+    scenario "A unconfirmed user is able to log in" do
35 35
       fill_in "user_email", with: email
36 36
       fill_in "user_password", with: password
37 37
       click_on I18n.t('auth.login')
38 38
 
39
-      is_expected.to have_css(".flash-message", text: failure_message("unconfirmed"))
39
+      is_expected.to have_css("div.admin-wrapper")
40 40
     end
41 41
   end
42 42
 

+ 2
- 2
spec/models/user_spec.rb View File

@@ -506,7 +506,7 @@ RSpec.describe User, type: :model do
506 506
       context 'when user is not confirmed' do
507 507
         let(:confirmed_at) { nil }
508 508
 
509
-        it { is_expected.to be false }
509
+        it { is_expected.to be true }
510 510
       end
511 511
     end
512 512
 
@@ -522,7 +522,7 @@ RSpec.describe User, type: :model do
522 522
       context 'when user is not confirmed' do
523 523
         let(:confirmed_at) { nil }
524 524
 
525
-        it { is_expected.to be false }
525
+        it { is_expected.to be true }
526 526
       end
527 527
     end
528 528
   end

Loading…
Cancel
Save