From 5d8398c8b8b51ee7363e7d45acc560f489783e34 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 2 Jun 2020 19:24:53 +0200 Subject: Add E2EE API (#13820) --- config/locales/en.yml | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'config/locales') diff --git a/config/locales/en.yml b/config/locales/en.yml index 116db4498..5943dd4f6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -721,6 +721,10 @@ en: hint_html: "Tip: We won't ask you for your password again for the next hour." invalid_password: Invalid password prompt: Confirm password to continue + crypto: + errors: + invalid_key: is not a valid Ed25519 or Curve25519 key + invalid_signature: is not a valid Ed25519 signature date: formats: default: "%b %d, %Y" -- cgit From bf949346232b3db731675c1342f50d97fd4a7ef8 Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 3 Jun 2020 20:18:19 +0200 Subject: Fix account redirect confirmation message talking about moved followers (#13950) Fixes #13949 --- app/controllers/settings/migration/redirects_controller.rb | 2 +- config/locales/en.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'config/locales') diff --git a/app/controllers/settings/migration/redirects_controller.rb b/app/controllers/settings/migration/redirects_controller.rb index 6e5b72ffb..97193ade0 100644 --- a/app/controllers/settings/migration/redirects_controller.rb +++ b/app/controllers/settings/migration/redirects_controller.rb @@ -18,7 +18,7 @@ class Settings::Migration::RedirectsController < Settings::BaseController if @redirect.valid_with_challenge?(current_user) current_account.update!(moved_to_account: @redirect.target_account) ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) - redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct) + redirect_to settings_migration_path, notice: I18n.t('migrations.redirected_msg', acct: current_account.moved_to_account.acct) else render :new end diff --git a/config/locales/en.yml b/config/locales/en.yml index 5943dd4f6..be29286f3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -922,6 +922,7 @@ en: on_cooldown: You have recently migrated your account. This function will become available again in %{count} days. past_migrations: Past migrations proceed_with_move: Move followers + redirected_msg: Your account is now redirecting to %{acct}. redirecting_to: Your account is redirecting to %{acct}. set_redirect: Set redirect warning: -- cgit From bf6745b9c326e64b819a381a0558cc87c99be4be Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 5 Jun 2020 15:23:27 +0200 Subject: Fix unpermitted operations on custom emojis leading to cryptic errors (#13951) * Display appropriate error when performing unpermitted operation on custom emoji Fixes #13897 * Remove links to custom emoji actions not performable by moderators --- app/controllers/admin/custom_emojis_controller.rb | 2 ++ app/views/admin/custom_emojis/index.html.haml | 10 ++++++---- config/locales/en.yml | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) (limited to 'config/locales') diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index efa8f2950..71efb543e 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -33,6 +33,8 @@ module Admin @form.save rescue ActionController::ParameterMissing flash[:alert] = I18n.t('admin.accounts.no_account_selected') + rescue Mastodon::NotPermittedError + flash[:alert] = I18n.t('admin.custom_emojis.not_permitted') ensure redirect_to admin_custom_emojis_path(filter_params) end diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/admin/custom_emojis/index.html.haml index 69aa5ae41..c96a1ce00 100644 --- a/app/views/admin/custom_emojis/index.html.haml +++ b/app/views/admin/custom_emojis/index.html.haml @@ -4,8 +4,9 @@ - content_for :header_tags do = javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous' -- content_for :heading_actions do - = link_to t('admin.custom_emojis.upload'), new_admin_custom_emoji_path, class: 'button' +- if can?(:create, :custom_emoji) + - content_for :heading_actions do + = link_to t('admin.custom_emojis.upload'), new_admin_custom_emoji_path, class: 'button' .filters .filter-subset @@ -58,9 +59,10 @@ = f.button safe_join([fa_icon('power-off'), t('admin.custom_emojis.disable')]), name: :disable, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('times'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + - if can?(:destroy, :custom_emoji) + = f.button safe_join([fa_icon('times'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - - unless params[:local] == '1' + - if can?(:copy, :custom_emoji) && params[:local] != '1' = f.button safe_join([fa_icon('copy'), t('admin.custom_emojis.copy')]), name: :copy, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - if params[:local] == '1' diff --git a/config/locales/en.yml b/config/locales/en.yml index be29286f3..20d87057f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -309,6 +309,7 @@ en: listed: Listed new: title: Add new custom emoji + not_permitted: You are not permitted to perform this action overwrite: Overwrite shortcode: Shortcode shortcode_hint: At least 2 characters, only alphanumeric characters and underscores -- cgit From 72a7cfaa395bbddabd0f0a712165fd7babf5d58c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 9 Jun 2020 10:23:06 +0200 Subject: Add e-mail-based sign in challenge for users with disabled 2FA (#14013) --- app/controllers/auth/sessions_controller.rb | 50 +--------- .../sign_in_token_authentication_concern.rb | 49 ++++++++++ .../concerns/two_factor_authentication_concern.rb | 47 +++++++++ app/mailers/user_mailer.rb | 17 ++++ app/models/user.rb | 28 +++++- app/views/auth/sessions/sign_in_token.html.haml | 14 +++ app/views/user_mailer/sign_in_token.html.haml | 105 +++++++++++++++++++++ app/views/user_mailer/sign_in_token.text.erb | 17 ++++ config/locales/en.yml | 9 ++ config/locales/simple_form.en.yml | 1 + .../20200608113046_add_sign_in_token_to_users.rb | 6 ++ db/schema.rb | 5 +- spec/controllers/auth/sessions_controller_spec.rb | 66 ++++++++++++- spec/mailers/previews/user_mailer_preview.rb | 5 + 14 files changed, 368 insertions(+), 51 deletions(-) create mode 100644 app/controllers/concerns/sign_in_token_authentication_concern.rb create mode 100644 app/controllers/concerns/two_factor_authentication_concern.rb create mode 100644 app/views/auth/sessions/sign_in_token.html.haml create mode 100644 app/views/user_mailer/sign_in_token.html.haml create mode 100644 app/views/user_mailer/sign_in_token.text.erb create mode 100644 db/migrate/20200608113046_add_sign_in_token_to_users.rb (limited to 'config/locales') diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index e95909447..2415e2ef3 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -8,7 +8,8 @@ class Auth::SessionsController < Devise::SessionsController skip_before_action :require_no_authentication, only: [:create] skip_before_action :require_functional! - prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create] + include TwoFactorAuthenticationConcern + include SignInTokenAuthenticationConcern before_action :set_instance_presenter, only: [:new] before_action :set_body_classes @@ -39,8 +40,8 @@ class Auth::SessionsController < Devise::SessionsController protected def find_user - if session[:otp_user_id] - User.find(session[:otp_user_id]) + if session[:attempt_user_id] + User.find(session[:attempt_user_id]) else user = User.authenticate_with_ldap(user_params) if Devise.ldap_authentication user ||= User.authenticate_with_pam(user_params) if Devise.pam_authentication @@ -49,7 +50,7 @@ class Auth::SessionsController < Devise::SessionsController end def user_params - params.require(:user).permit(:email, :password, :otp_attempt) + params.require(:user).permit(:email, :password, :otp_attempt, :sign_in_token_attempt) end def after_sign_in_path_for(resource) @@ -70,47 +71,6 @@ class Auth::SessionsController < Devise::SessionsController super end - def two_factor_enabled? - find_user&.otp_required_for_login? - end - - def valid_otp_attempt?(user) - user.validate_and_consume_otp!(user_params[:otp_attempt]) || - user.invalidate_otp_backup_code!(user_params[:otp_attempt]) - rescue OpenSSL::Cipher::CipherError - false - end - - def authenticate_with_two_factor - user = self.resource = find_user - - if user_params[:otp_attempt].present? && session[:otp_user_id] - authenticate_with_two_factor_via_otp(user) - elsif user.present? && (user.encrypted_password.blank? || user.valid_password?(user_params[:password])) - # If encrypted_password is blank, we got the user from LDAP or PAM, - # so credentials are already valid - - prompt_for_two_factor(user) - end - end - - def authenticate_with_two_factor_via_otp(user) - if valid_otp_attempt?(user) - session.delete(:otp_user_id) - remember_me(user) - sign_in(user) - else - flash.now[:alert] = I18n.t('users.invalid_otp_token') - prompt_for_two_factor(user) - end - end - - def prompt_for_two_factor(user) - session[:otp_user_id] = user.id - @body_classes = 'lighter' - render :two_factor - end - def require_no_authentication super # Delete flash message that isn't entirely useful and may be confusing in diff --git a/app/controllers/concerns/sign_in_token_authentication_concern.rb b/app/controllers/concerns/sign_in_token_authentication_concern.rb new file mode 100644 index 000000000..a177aacaf --- /dev/null +++ b/app/controllers/concerns/sign_in_token_authentication_concern.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module SignInTokenAuthenticationConcern + extend ActiveSupport::Concern + + included do + prepend_before_action :authenticate_with_sign_in_token, if: :sign_in_token_required?, only: [:create] + end + + def sign_in_token_required? + find_user&.suspicious_sign_in?(request.remote_ip) + end + + def valid_sign_in_token_attempt?(user) + Devise.secure_compare(user.sign_in_token, user_params[:sign_in_token_attempt]) + end + + def authenticate_with_sign_in_token + user = self.resource = find_user + + if user_params[:sign_in_token_attempt].present? && session[:attempt_user_id] + authenticate_with_sign_in_token_attempt(user) + elsif user.present? && user.external_or_valid_password?(user_params[:password]) + prompt_for_sign_in_token(user) + end + end + + def authenticate_with_sign_in_token_attempt(user) + if valid_sign_in_token_attempt?(user) + session.delete(:attempt_user_id) + remember_me(user) + sign_in(user) + else + flash.now[:alert] = I18n.t('users.invalid_sign_in_token') + prompt_for_sign_in_token(user) + end + end + + def prompt_for_sign_in_token(user) + if user.sign_in_token_expired? + user.generate_sign_in_token && user.save + UserMailer.sign_in_token(user, request.remote_ip, request.user_agent, Time.now.utc.to_s).deliver_later! + end + + session[:attempt_user_id] = user.id + @body_classes = 'lighter' + render :sign_in_token + end +end diff --git a/app/controllers/concerns/two_factor_authentication_concern.rb b/app/controllers/concerns/two_factor_authentication_concern.rb new file mode 100644 index 000000000..cdd8d14af --- /dev/null +++ b/app/controllers/concerns/two_factor_authentication_concern.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module TwoFactorAuthenticationConcern + extend ActiveSupport::Concern + + included do + prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create] + end + + def two_factor_enabled? + find_user&.otp_required_for_login? + end + + def valid_otp_attempt?(user) + user.validate_and_consume_otp!(user_params[:otp_attempt]) || + user.invalidate_otp_backup_code!(user_params[:otp_attempt]) + rescue OpenSSL::Cipher::CipherError + false + end + + def authenticate_with_two_factor + user = self.resource = find_user + + if user_params[:otp_attempt].present? && session[:attempt_user_id] + authenticate_with_two_factor_attempt(user) + elsif user.present? && user.external_or_valid_password?(user_params[:password]) + prompt_for_two_factor(user) + end + end + + def authenticate_with_two_factor_attempt(user) + if valid_otp_attempt?(user) + session.delete(:attempt_user_id) + remember_me(user) + sign_in(user) + else + flash.now[:alert] = I18n.t('users.invalid_otp_token') + prompt_for_two_factor(user) + end + end + + def prompt_for_two_factor(user) + session[:attempt_user_id] = user.id + @body_classes = 'lighter' + render :two_factor + end +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 88a11f761..2cd58e60a 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -126,4 +126,21 @@ class UserMailer < Devise::Mailer reply_to: Setting.site_contact_email end end + + def sign_in_token(user, remote_ip, user_agent, timestamp) + @resource = user + @instance = Rails.configuration.x.local_domain + @remote_ip = remote_ip + @user_agent = user_agent + @detection = Browser.new(user_agent) + @timestamp = timestamp.to_time.utc + + return if @resource.disabled? + + I18n.with_locale(@resource.locale || I18n.default_locale) do + mail to: @resource.email, + subject: I18n.t('user_mailer.sign_in_token.subject'), + reply_to: Setting.site_contact_email + end + end end diff --git a/app/models/user.rb b/app/models/user.rb index ae0cdf29d..306e2d435 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -38,6 +38,8 @@ # chosen_languages :string is an Array # created_by_application_id :bigint(8) # approved :boolean default(TRUE), not null +# sign_in_token :string +# sign_in_token_sent_at :datetime # class User < ApplicationRecord @@ -113,7 +115,7 @@ class User < ApplicationRecord :advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images, to: :settings, prefix: :setting, allow_nil: false - attr_reader :invite_code + attr_reader :invite_code, :sign_in_token_attempt attr_writer :external def confirmed? @@ -167,6 +169,10 @@ class User < ApplicationRecord true end + def suspicious_sign_in?(ip) + !otp_required_for_login? && current_sign_in_at.present? && current_sign_in_at < 2.weeks.ago && !recent_ip?(ip) + end + def functional? confirmed? && approved? && !disabled? && !account.suspended? && account.moved_to_account_id.nil? end @@ -269,6 +275,13 @@ class User < ApplicationRecord super end + def external_or_valid_password?(compare_password) + # If encrypted_password is blank, we got the user from LDAP or PAM, + # so credentials are already valid + + encrypted_password.blank? || valid_password?(compare_password) + end + def send_reset_password_instructions return false if encrypted_password.blank? @@ -304,6 +317,15 @@ class User < ApplicationRecord end end + def sign_in_token_expired? + sign_in_token_sent_at.nil? || sign_in_token_sent_at < 5.minutes.ago + end + + def generate_sign_in_token + self.sign_in_token = Devise.friendly_token(6) + self.sign_in_token_sent_at = Time.now.utc + end + protected def send_devise_notification(notification, *args) @@ -320,6 +342,10 @@ class User < ApplicationRecord private + def recent_ip?(ip) + recent_ips.any? { |(_, recent_ip)| recent_ip == ip } + end + def send_pending_devise_notifications pending_devise_notifications.each do |notification, args| render_and_send_devise_message(notification, *args) diff --git a/app/views/auth/sessions/sign_in_token.html.haml b/app/views/auth/sessions/sign_in_token.html.haml new file mode 100644 index 000000000..8923203cd --- /dev/null +++ b/app/views/auth/sessions/sign_in_token.html.haml @@ -0,0 +1,14 @@ +- content_for :page_title do + = t('auth.login') + += simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| + %p.hint.otp-hint= t('users.suspicious_sign_in_confirmation') + + .fields-group + = f.input :sign_in_token_attempt, type: :number, wrapper: :with_label, label: t('simple_form.labels.defaults.sign_in_token_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.sign_in_token_attempt'), :autocomplete => 'off' }, autofocus: true + + .actions + = f.button :button, t('auth.login'), type: :submit + + - if Setting.site_contact_email.present? + %p.hint.subtle-hint= t('users.generic_access_help_html', email: mail_to(Setting.site_contact_email, nil)) diff --git a/app/views/user_mailer/sign_in_token.html.haml b/app/views/user_mailer/sign_in_token.html.haml new file mode 100644 index 000000000..826b34e7c --- /dev/null +++ b/app/views/user_mailer/sign_in_token.html.haml @@ -0,0 +1,105 @@ +%table.email-table{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.email-body + .email-container + %table.content-section{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.content-cell.hero + .email-row + .col-6 + %table.column{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.column-cell.text-center.padded + %table.hero-icon.alert-icon{ align: 'center', cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td + = image_tag full_pack_url('media/images/mailer/icon_email.png'), alt: '' + + %h1= t 'user_mailer.sign_in_token.title' + %p.lead= t 'user_mailer.sign_in_token.explanation' + +%table.email-table{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.email-body + .email-container + %table.content-section{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.content-cell.content-start + %table.column{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.column-cell.input-cell + %table.input{ align: 'center', cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td= @resource.sign_in_token + +%table.email-table{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.email-body + .email-container + %table.content-section{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.content-cell + .email-row + .col-6 + %table.column{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.column-cell.text-center + %p= t 'user_mailer.sign_in_token.details' + %tr + %td.column-cell.text-center + %p + %strong= "#{t('sessions.ip')}:" + = @remote_ip + %br/ + %strong= "#{t('sessions.browser')}:" + %span{ title: @user_agent }= t 'sessions.description', browser: t("sessions.browsers.#{@detection.id}", default: "#{@detection.id}"), platform: t("sessions.platforms.#{@detection.platform.id}", default: "#{@detection.platform.id}") + %br/ + = l(@timestamp) + +%table.email-table{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.email-body + .email-container + %table.content-section{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.content-cell + .email-row + .col-6 + %table.column{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.column-cell.text-center + %p= t 'user_mailer.sign_in_token.further_actions' + +%table.email-table{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.email-body + .email-container + %table.content-section{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.content-cell + %table.column{ cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.column-cell.button-cell + %table.button{ align: 'center', cellspacing: 0, cellpadding: 0 } + %tbody + %tr + %td.button-primary + = link_to edit_user_registration_url do + %span= t 'settings.account_settings' diff --git a/app/views/user_mailer/sign_in_token.text.erb b/app/views/user_mailer/sign_in_token.text.erb new file mode 100644 index 000000000..2539ddaf6 --- /dev/null +++ b/app/views/user_mailer/sign_in_token.text.erb @@ -0,0 +1,17 @@ +<%= t 'user_mailer.sign_in_token.title' %> + +=== + +<%= t 'user_mailer.sign_in_token.explanation' %> + +=> <%= @resource.sign_in_token %> + +<%= t 'user_mailer.sign_in_token.details' %> + +<%= t('sessions.ip') %>: <%= @remote_ip %> +<%= t('sessions.browser') %>: <%= t('sessions.description', browser: t("sessions.browsers.#{@detection.id}", default: "#{@detection.id}"), platform: t("sessions.platforms.#{@detection.platform.id}", default: "#{@detection.platform.id}")) %> +<%= l(@timestamp) %> + +<%= t 'user_mailer.sign_in_token.further_actions' %> + +=> <%= edit_user_registration_url %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 20d87057f..a33605049 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1273,6 +1273,12 @@ en: explanation: You requested a full backup of your Mastodon account. It's now ready for download! subject: Your archive is ready for download title: Archive takeout + sign_in_token: + details: 'Here are details of the attempt:' + explanation: 'We detected an attempt to sign in to your account from an unrecognized IP address. If this is you, please enter the security code below on the sign in challenge page:' + further_actions: 'If this wasn''t you, please change your password and enable two-factor authentication on your account. You can do so here:' + subject: Please confirm attempted sign in + title: Sign in attempt warning: explanation: disable: While your account is frozen, your account data remains intact, but you cannot perform any actions until it is unlocked. @@ -1310,11 +1316,14 @@ en: title: Welcome aboard, %{name}! users: follow_limit_reached: You cannot follow more than %{limit} people + generic_access_help_html: Trouble accessing your account? You may get in touch with %{email} for assistance invalid_email: The e-mail address is invalid invalid_otp_token: Invalid two-factor code + invalid_sign_in_token: Invalid security code otp_lost_help_html: If you lost access to both, you may get in touch with %{email} seamless_external_login: You are logged in via an external service, so password and e-mail settings are not available. signed_in_as: 'Signed in as:' + suspicious_sign_in_confirmation: You appear to not have logged in from this device before, and you haven't logged in for a while, so we're sending a security code to your e-mail address to confirm that it's you. verification: explanation_html: 'You can verify yourself as the owner of the links in your profile metadata. For that, the linked website must contain a link back to your Mastodon profile. The link back must have a rel="me" attribute. The text content of the link does not matter. Here is an example:' verification: Verification diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index fd56a35bf..f84b6c884 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -151,6 +151,7 @@ en: setting_use_blurhash: Show colorful gradients for hidden media setting_use_pending_items: Slow mode severity: Severity + sign_in_token_attempt: Security code type: Import type username: Username username_or_email: Username or Email diff --git a/db/migrate/20200608113046_add_sign_in_token_to_users.rb b/db/migrate/20200608113046_add_sign_in_token_to_users.rb new file mode 100644 index 000000000..baa63c10f --- /dev/null +++ b/db/migrate/20200608113046_add_sign_in_token_to_users.rb @@ -0,0 +1,6 @@ +class AddSignInTokenToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :sign_in_token, :string + add_column :users, :sign_in_token_sent_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index beda93c01..df5d48f44 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,8 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_06_05_155027) do +ActiveRecord::Schema.define(version: 2020_06_08_113046) do + # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -869,6 +870,8 @@ ActiveRecord::Schema.define(version: 2020_06_05_155027) do t.string "chosen_languages", array: true t.bigint "created_by_application_id" t.boolean "approved", default: true, null: false + t.string "sign_in_token" + t.datetime "sign_in_token_sent_at" t.index ["account_id"], name: "index_users_on_account_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id" diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb index 1950c173a..c387842cd 100644 --- a/spec/controllers/auth/sessions_controller_spec.rb +++ b/spec/controllers/auth/sessions_controller_spec.rb @@ -215,7 +215,7 @@ RSpec.describe Auth::SessionsController, type: :controller do context 'using a valid OTP' do before do - post :create, params: { user: { otp_attempt: user.current_otp } }, session: { otp_user_id: user.id } + post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id } end it 'redirects to home' do @@ -230,7 +230,7 @@ RSpec.describe Auth::SessionsController, type: :controller do context 'when the server has an decryption error' do before do allow_any_instance_of(User).to receive(:validate_and_consume_otp!).and_raise(OpenSSL::Cipher::CipherError) - post :create, params: { user: { otp_attempt: user.current_otp } }, session: { otp_user_id: user.id } + post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id } end it 'shows a login error' do @@ -244,7 +244,7 @@ RSpec.describe Auth::SessionsController, type: :controller do context 'using a valid recovery code' do before do - post :create, params: { user: { otp_attempt: recovery_codes.first } }, session: { otp_user_id: user.id } + post :create, params: { user: { otp_attempt: recovery_codes.first } }, session: { attempt_user_id: user.id } end it 'redirects to home' do @@ -258,7 +258,7 @@ RSpec.describe Auth::SessionsController, type: :controller do context 'using an invalid OTP' do before do - post :create, params: { user: { otp_attempt: 'wrongotp' } }, session: { otp_user_id: user.id } + post :create, params: { user: { otp_attempt: 'wrongotp' } }, session: { attempt_user_id: user.id } end it 'shows a login error' do @@ -270,5 +270,63 @@ RSpec.describe Auth::SessionsController, type: :controller do end end end + + context 'when 2FA is disabled and IP is unfamiliar' do + let!(:user) { Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', current_sign_in_at: 3.weeks.ago, current_sign_in_ip: '0.0.0.0') } + + before do + request.remote_ip = '10.10.10.10' + request.user_agent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0' + + allow(UserMailer).to receive(:sign_in_token).and_return(double('email', deliver_later!: nil)) + end + + context 'using email and password' do + before do + post :create, params: { user: { email: user.email, password: user.password } } + end + + it 'renders sign in token authentication page' do + expect(controller).to render_template("sign_in_token") + end + + it 'generates sign in token' do + expect(user.reload.sign_in_token).to_not be_nil + end + + it 'sends sign in token e-mail' do + expect(UserMailer).to have_received(:sign_in_token) + end + end + + context 'using a valid sign in token' do + before do + user.generate_sign_in_token && user.save + post :create, params: { user: { sign_in_token_attempt: user.sign_in_token } }, session: { attempt_user_id: user.id } + end + + it 'redirects to home' do + expect(response).to redirect_to(root_path) + end + + it 'logs the user in' do + expect(controller.current_user).to eq user + end + end + + context 'using an invalid sign in token' do + before do + post :create, params: { user: { sign_in_token_attempt: 'wrongotp' } }, session: { attempt_user_id: user.id } + end + + it 'shows a login error' do + expect(flash[:alert]).to match I18n.t('users.invalid_sign_in_token') + end + + it "doesn't log the user in" do + expect(controller.current_user).to be_nil + end + end + end end end diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index 464f177d0..313666412 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -59,4 +59,9 @@ class UserMailerPreview < ActionMailer::Preview def warning UserMailer.warning(User.first, AccountWarning.new(text: '', action: :silence), [Status.first.id]) end + + # Preview this email at http://localhost:3000/rails/mailers/user_mailer/sign_in_token + def sign_in_token + UserMailer.sign_in_token(User.first.tap { |user| user.generate_sign_in_token }, '127.0.0.1', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0', Time.now.utc) + end end -- cgit From ac3c83ef6fe207a22d67ff8912e74c21c6b61daf Mon Sep 17 00:00:00 2001 From: Mélanie Chauvel Date: Tue, 9 Jun 2020 10:28:02 +0200 Subject: Improve wording and add titles on moderated servers section in /about/more (#13930) --- app/views/about/more.html.haml | 3 +++ config/locales/en.yml | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'config/locales') diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml index 7e156db61..4152a3601 100644 --- a/app/views/about/more.html.haml +++ b/app/views/about/more.html.haml @@ -56,12 +56,15 @@ %p= t('about.unavailable_content_html') - if (blocks = @blocks.select(&:reject_media?)) && !blocks.empty? + %h3= t('about.unavailable_content_description.rejecting_media_title') %p= t('about.unavailable_content_description.rejecting_media') = render partial: 'domain_blocks', locals: { domain_blocks: blocks } - if (blocks = @blocks.select(&:silence?)) && !blocks.empty? + %h3= t('about.unavailable_content_description.silenced_title') %p= t('about.unavailable_content_description.silenced') = render partial: 'domain_blocks', locals: { domain_blocks: blocks } - if (blocks = @blocks.select(&:suspend?)) && !blocks.empty? + %h3= t('about.unavailable_content_description.suspended_title') %p= t('about.unavailable_content_description.suspended') = render partial: 'domain_blocks', locals: { domain_blocks: blocks } diff --git a/config/locales/en.yml b/config/locales/en.yml index a33605049..aed96e3e1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -35,13 +35,16 @@ en: status_count_before: Who authored tagline: Follow friends and discover new ones terms: Terms of service - unavailable_content: Unavailable content + unavailable_content: Moderated servers unavailable_content_description: domain: Server reason: Reason rejecting_media: 'Media files from these servers will not be processed or stored, and no thumbnails will be displayed, requiring manual click-through to the original file:' - silenced: 'Posts from these servers will be hidden in public timelines and conversations, and no notifications will be generated from their users'' interactions, unless you are following them:' + rejecting_media_title: Filtered media + silenced: 'Posts from these servers will be hidden in public timelines and conversations, and no notifications will be generated from their users interactions, unless you are following them:' + silenced_title: Silenced servers suspended: 'No data from these servers will be processed, stored or exchanged, making any interaction or communication with users from these servers impossible:' + suspended_title: Suspended servers unavailable_content_html: Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server. user_count_after: one: user -- cgit