diff options
Diffstat (limited to 'app')
44 files changed, 394 insertions, 200 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 022355bd0..157ea8569 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -90,8 +90,8 @@ class AccountsController < ApplicationController end end - def set_account - @account = Account.find_local!(params[:username]) + def username_param + params[:username] end def older_url diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index 562fba996..e160c603a 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -2,9 +2,9 @@ module Admin class AccountsController < BaseController - before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize] + before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject] before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload] - before_action :require_local_account!, only: [:enable, :memorialize] + before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject] def index authorize :account, :index? @@ -45,6 +45,18 @@ module Admin redirect_to admin_account_path(@account.id) end + def approve + authorize @account.user, :approve? + @account.user.approve! + redirect_to admin_accounts_path(pending: '1') + end + + def reject + authorize @account.user, :reject? + SuspendAccountService.new.call(@account, including_user: true, destroy: true) + redirect_to admin_accounts_path(pending: '1') + end + def unsilence authorize @account, :unsilence? @account.unsilence! @@ -114,6 +126,7 @@ module Admin :remote, :by_domain, :active, + :pending, :silenced, :suspended, :username, diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index bb923c185..22bbcec19 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -10,7 +10,7 @@ module Admin @interactions_week = Redis.current.get("activity:interactions:#{current_week}") || 0 @relay_enabled = Relay.enabled.exists? @single_user_mode = Rails.configuration.x.single_user_mode - @registrations_enabled = Setting.open_registrations + @registrations_enabled = Setting.registrations_mode != 'none' @deletions_enabled = Setting.open_deletion @invites_enabled = Setting.min_invite_role == 'user' @search_enabled = Chewy.enabled? diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index 9624df96b..a64e98868 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -10,7 +10,7 @@ module Admin site_description site_extended_description site_terms - open_registrations + registrations_mode closed_registrations_message open_deletion timeline_preview @@ -33,7 +33,6 @@ module Admin ).freeze BOOLEAN_SETTINGS = %w( - open_registrations open_deletion timeline_preview show_staff_badge diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index a1dd30918..3a92ee4e4 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -73,7 +73,9 @@ class Api::BaseController < ApplicationController elsif current_user.disabled? render json: { error: 'Your login is currently disabled' }, status: 403 elsif !current_user.confirmed? - render json: { error: 'Email confirmation is not completed' }, status: 403 + render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403 + elsif !current_user.approved? + render json: { error: 'Your login is currently pending approval' }, status: 403 else set_user_activity end diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 2ccbc3cbb..b0c62778e 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -80,6 +80,10 @@ class Api::V1::AccountsController < Api::BaseController end def check_enabled_registrations - forbidden if single_user_mode? || !Setting.open_registrations + forbidden if single_user_mode? || !allowed_registrations? + end + + def allowed_registrations? + Setting.registrations_mode != 'none' end end diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index efe29b53f..74dd7ff34 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -66,7 +66,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController end def allowed_registrations? - Setting.open_registrations || @invite&.valid_for_use? + Setting.registrations_mode != 'none' || @invite&.valid_for_use? end def invite_code diff --git a/app/controllers/concerns/account_controller_concern.rb b/app/controllers/concerns/account_controller_concern.rb index 6c27ef330..8817fd7de 100644 --- a/app/controllers/concerns/account_controller_concern.rb +++ b/app/controllers/concerns/account_controller_concern.rb @@ -7,16 +7,18 @@ module AccountControllerConcern included do layout 'public' + before_action :set_account + before_action :check_account_approval + before_action :check_account_suspension before_action :set_instance_presenter before_action :set_link_headers - before_action :check_account_suspension end private def set_account - @account = Account.find_local!(params[:account_username]) + @account = Account.find_local!(username_param) end def set_instance_presenter @@ -33,6 +35,10 @@ module AccountControllerConcern ) end + def username_param + params[:account_username] + end + def webfinger_account_link [ webfinger_account_url, @@ -58,6 +64,10 @@ module AccountControllerConcern webfinger_url(resource: @account.to_webfinger_s) end + def check_account_approval + not_found if @account.user_pending? + end + def check_account_suspension gone if @account.suspended? end diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb index 275b5f2fe..8f78bf5f8 100644 --- a/app/helpers/admin/filter_helper.rb +++ b/app/helpers/admin/filter_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Admin::FilterHelper - ACCOUNT_FILTERS = %i(local remote by_domain active silenced suspended username display_name email ip staff).freeze + ACCOUNT_FILTERS = %i(local remote by_domain active pending silenced suspended username display_name email ip staff).freeze REPORT_FILTERS = %i(resolved account_id target_account_id).freeze INVITE_FILTER = %i(available expired).freeze CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fc006d777..f70c37522 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -20,7 +20,23 @@ module ApplicationHelper end def open_registrations? - Setting.open_registrations + Setting.registrations_mode == 'open' + end + + def approved_registrations? + Setting.registrations_mode == 'approved' + end + + def closed_registrations? + Setting.registrations_mode == 'none' + end + + def available_sign_up_path + if closed_registrations? + 'https://joinmastodon.org/#getting-started' + else + new_user_registration_path + end end def open_deletion? diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index 1f648649f..df60b7dd7 100644 --- a/app/helpers/home_helper.rb +++ b/app/helpers/home_helper.rb @@ -64,4 +64,14 @@ module HomeHelper content_tag(:div, &block) end end + + def sign_up_message + if closed_registrations? + t('auth.registration_closed', instance: site_hostname) + elsif open_registrations? + t('auth.register') + elsif approved_registrations? + t('auth.apply_for_account') + end + end end diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 4dbbaa1e8..42f53f525 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -705,3 +705,11 @@ a.name-tag, overflow: hidden; text-overflow: ellipsis; } + +.ellipsized-ip { + display: inline-block; + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; +} diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index adb75afe5..9e8785679 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -82,6 +82,10 @@ } } } + + &--invites tbody td { + vertical-align: middle; + } } .table-wrapper { diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb index a30468eb8..db154cad5 100644 --- a/app/mailers/admin_mailer.rb +++ b/app/mailers/admin_mailer.rb @@ -14,4 +14,14 @@ class AdminMailer < ApplicationMailer mail to: @me.user_email, subject: I18n.t('admin_mailer.new_report.subject', instance: @instance, id: @report.id) end end + + def new_pending_account(recipient, user) + @account = user.account + @me = recipient + @instance = Rails.configuration.x.local_domain + + locale_for_account(@me) do + mail to: @me.user_email, subject: I18n.t('admin_mailer.new_pending_account.subject', instance: @instance, username: @account.username) + end + end end diff --git a/app/models/account.rb b/app/models/account.rb index 0bb20b2f2..6b539f004 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -108,6 +108,8 @@ class Account < ApplicationRecord :current_sign_in_ip, :current_sign_in_at, :confirmed?, + :approved?, + :pending?, :admin?, :moderator?, :staff?, diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index b10f50db7..d2503100c 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -22,7 +22,7 @@ class AccountFilter def set_defaults! params['local'] = '1' if params['remote'].blank? - params['active'] = '1' if params['suspended'].blank? && params['silenced'].blank? + params['active'] = '1' if params['suspended'].blank? && params['silenced'].blank? && params['pending'].blank? end def scope_for(key, value) @@ -35,6 +35,8 @@ class AccountFilter Account.where(domain: value) when 'active' Account.without_suspended + when 'pending' + accounts_with_users.merge User.pending when 'silenced' Account.silenced when 'suspended' diff --git a/app/models/concerns/ldap_authenticable.rb b/app/models/concerns/ldap_authenticable.rb new file mode 100644 index 000000000..e1b5e3832 --- /dev/null +++ b/app/models/concerns/ldap_authenticable.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module LdapAuthenticable + extend ActiveSupport::Concern + + def ldap_setup(_attributes) + self.confirmed_at = Time.now.utc + self.admin = false + + save! + end + + class_methods do + def ldap_get_user(attributes = {}) + resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first }) + + if resource.blank? + resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first }) + resource.ldap_setup(attributes) + end + + resource + end + end +end diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb index 4dd2e9383..1b28b8162 100644 --- a/app/models/concerns/omniauthable.rb +++ b/app/models/concerns/omniauthable.rb @@ -7,6 +7,8 @@ module Omniauthable TEMP_EMAIL_REGEX = /\Achange@me/ included do + devise :omniauthable + def omniauth_providers Devise.omniauth_configs.keys end diff --git a/app/models/concerns/pam_authenticable.rb b/app/models/concerns/pam_authenticable.rb new file mode 100644 index 000000000..2f651c1a3 --- /dev/null +++ b/app/models/concerns/pam_authenticable.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module PamAuthenticable + extend ActiveSupport::Concern + + included do + devise :pam_authenticatable if ENV['PAM_ENABLED'] == 'true' + + def pam_conflict(_attributes) + # Block pam login tries on traditional account + end + + def pam_conflict? + if Devise.pam_authentication + encrypted_password.present? && pam_managed_user? + else + false + end + end + + def pam_get_name + if account.present? + account.username + else + super + end + end + + def pam_setup(_attributes) + account = Account.new(username: pam_get_name) + account.save!(validate: false) + + self.email = "#{account.username}@#{find_pam_suffix}" if email.nil? && find_pam_suffix + self.confirmed_at = Time.now.utc + self.admin = false + self.account = account + + account.destroy! unless save + end + + def self.pam_get_user(attributes = {}) + return nil unless attributes[:email] + + resource = begin + if Devise.check_at_sign && !attributes[:email].index('@') + joins(:account).find_by(accounts: { username: attributes[:email] }) + else + find_by(email: attributes[:email]) + end + end + + if resource.nil? + resource = new(email: attributes[:email], agreement: true) + + if Devise.check_at_sign && !resource[:email].index('@') + resource[:email] = Rpam2.getenv(resource.find_pam_service, attributes[:email], attributes[:password], 'email', false) + resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}" unless resource[:email] + end + end + + resource + end + + def self.authenticate_with_pam(attributes = {}) + super if Devise.pam_authentication + end + end +end diff --git a/app/models/concerns/user_roles.rb b/app/models/concerns/user_roles.rb new file mode 100644 index 000000000..58dffdc46 --- /dev/null +++ b/app/models/concerns/user_roles.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module UserRoles + extend ActiveSupport::Concern + + included do + scope :admins, -> { where(admin: true) } + scope :moderators, -> { where(moderator: true) } + scope :staff, -> { admins.or(moderators) } + end + + def staff? + admin? || moderator? + end + + def role + if admin? + 'admin' + elsif moderator? + 'moderator' + else + 'user' + end + end + + def role?(role) + case role + when 'user' + true + when 'moderator' + staff? + when 'admin' + admin? + else + false + end + end + + def promote! + if moderator? + update!(moderator: false, admin: true) + elsif !admin? + update!(moderator: true) + end + end + + def demote! + if admin? + update!(admin: false, moderator: true) + elsif moderator? + update!(moderator: false) + end + end +end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index d568200ed..929c65793 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -18,8 +18,8 @@ class Form::AdminSettings :site_extended_description=, :site_terms, :site_terms=, - :open_registrations, - :open_registrations=, + :registrations_mode, + :registrations_mode=, :closed_registrations_message, :closed_registrations_message=, :open_deletion, diff --git a/app/models/user.rb b/app/models/user.rb index f91ed7015..47657a670 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -37,11 +37,12 @@ # remember_token :string # chosen_languages :string is an Array # created_by_application_id :bigint(8) +# approved :boolean default(TRUE), not null # class User < ApplicationRecord include Settings::Extend - include Omniauthable + include UserRoles # The home and list feeds will be stored in Redis for this amount # of time, and status fan-out to followers will include only people @@ -61,9 +62,9 @@ class User < ApplicationRecord devise :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable - devise :pam_authenticatable if ENV['PAM_ENABLED'] == 'true' - - devise :omniauthable + include Omniauthable + include PamAuthenticable + include LdapAuthenticable belongs_to :account, inverse_of: :user belongs_to :invite, counter_cache: :uses, optional: true @@ -79,9 +80,8 @@ class User < ApplicationRecord validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create scope :recent, -> { order(id: :desc) } - scope :admins, -> { where(admin: true) } - scope :moderators, -> { where(moderator: true) } - scope :staff, -> { admins.or(moderators) } + scope :pending, -> { where(approved: false) } + scope :approved, -> { where(approved: true) } scope :confirmed, -> { where.not(confirmed_at: nil) } scope :enabled, -> { where(disabled: false) } scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) } @@ -90,6 +90,7 @@ class User < ApplicationRecord scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) } before_validation :sanitize_languages + before_create :set_approved # This avoids a deprecation warning from Rails 5.1 # It seems possible that a future release of devise-two-factor will @@ -104,39 +105,6 @@ class User < ApplicationRecord attr_reader :invite_code - def pam_conflict(_) - # block pam login tries on traditional account - nil - end - - def pam_conflict? - return false unless Devise.pam_authentication - encrypted_password.present? && pam_managed_user? - end - - def pam_get_name - return account.username if account.present? - super - end - - def pam_setup(_attributes) - acc = Account.new(username: pam_get_name) - acc.save!(validate: false) - - self.email = "#{acc.username}@#{find_pam_suffix}" if email.nil? && find_pam_suffix - self.confirmed_at = Time.now.utc - self.admin = false - self.account = acc - - acc.destroy! unless save - end - - def ldap_setup(_attributes) - self.confirmed_at = Time.now.utc - self.admin = false - save! - end - def confirmed? confirmed_at.present? end @@ -145,33 +113,6 @@ class User < ApplicationRecord invite_id.present? end - def staff? - admin? || moderator? - end - - def role - if admin? - 'admin' - elsif moderator? - 'moderator' - else - 'user' - end - end - - def role?(role) - case role - when 'user' - true - when 'moderator' - staff? - when 'admin' - admin? - else - false - end - end - def disable! update!(disabled: true, last_sign_in_at: current_sign_in_at, @@ -186,7 +127,12 @@ class User < ApplicationRecord new_user = !confirmed? super - prepare_new_user! if new_user + + if new_user && approved? + prepare_new_user! + elsif new_user + notify_staff_about_pending_account! + end end def confirm! @@ -194,28 +140,32 @@ class User < ApplicationRecord skip_confirmation! save! - prepare_new_user! if new_user + + prepare_new_user! if new_user && approved? end - def update_tracked_fields!(request) - super - prepare_returning_user! + def pending? + !approved? end - def promote! - if moderator? - update!(moderator: false, admin: true) - elsif !admin? - update!(moderator: true) - end + def active_for_authentication? + super && approved? end - def demote! - if admin? - update!(admin: false, moderator: true) - elsif moderator? - update!(moderator: false) - end + def inactive_message + !approved? ? :pending : super + end + + def approve! + return if approved? + + update!(approved: true) + prepare_new_user! + end + + def update_tracked_fields!(request) + super + prepare_returning_user! end def disable_two_factor! @@ -297,43 +247,6 @@ class User < ApplicationRecord super end - def self.pam_get_user(attributes = {}) - return nil unless attributes[:email] - - resource = - if Devise.check_at_sign && !attributes[:email].index('@') - joins(:account).find_by(accounts: { username: attributes[:email] }) - else - find_by(email: attributes[:email]) - end - - if resource.blank? - resource = new(email: attributes[:email], agreement: true) - - if Devise.check_at_sign && !resource[:email].index('@') - resource[:email] = Rpam2.getenv(resource.find_pam_service, attributes[:email], attributes[:password], 'email', false) - resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}" unless resource[:email] - end - end - resource - end - - def self.ldap_get_user(attributes = {}) - resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first }) - - if resource.blank? - resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first }) - resource.ldap_setup(attributes) - end - - resource - end - - def self.authenticate_with_pam(attributes = {}) - return nil unless Devise.pam_authentication - super - end - def show_all_media? setting_display_media == 'show_all' end @@ -350,6 +263,10 @@ class User < ApplicationRecord private + def set_approved + self.approved = Setting.registrations_mode == 'open' || invited? + end + def sanitize_languages return if chosen_languages.nil? chosen_languages.reject!(&:blank?) @@ -367,6 +284,13 @@ class User < ApplicationRecord regenerate_feed! if needs_feed_update? end + def notify_staff_about_pending_account! + User.staff.includes(:account).each do |u| + next unless u.allows_report_emails? + AdminMailer.new_pending_account(u.account, self).deliver_later + end + end + def regenerate_feed! return unless Redis.current.setnx("account:#{account_id}:regeneration", true) Redis.current.expire("account:#{account_id}:regeneration", 1.day.seconds) diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index 57af5c61c..d832bff75 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -21,6 +21,14 @@ class UserPolicy < ApplicationPolicy staff? end + def approve? + staff? && !record.approved? + end + + def reject? + staff? && !record.approved? + end + def disable? staff? && !record.admin? end @@ -36,7 +44,7 @@ class UserPolicy < ApplicationPolicy private def promoteable? - !record.staff? || !record.admin? + record.approved? && (!record.staff? || !record.admin?) end def demoteable? diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb index 58e3541c1..94a2c1692 100644 --- a/app/presenters/instance_presenter.rb +++ b/app/presenters/instance_presenter.rb @@ -2,9 +2,7 @@ class InstancePresenter delegate( - :closed_registrations_message, :site_contact_email, - :open_registrations, :site_title, :site_short_description, :site_description, diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index 30e8dcbc1..97fed63d1 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -65,7 +65,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer end def registrations - Setting.open_registrations && !Rails.configuration.x.single_user_mode + Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode end private diff --git a/app/services/app_sign_up_service.rb b/app/services/app_sign_up_service.rb index d621cc462..6dee9cd81 100644 --- a/app/services/app_sign_up_service.rb +++ b/app/services/app_sign_up_service.rb @@ -18,6 +18,6 @@ class AppSignUpService < BaseService private def allowed_registrations? - Setting.open_registrations && !Rails.configuration.x.single_user_mode + Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode end end diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml index 715bcd37c..9cb4eb2bc 100644 --- a/app/views/about/_registration.html.haml +++ b/app/views/about/_registration.html.haml @@ -3,14 +3,14 @@ .fields-group = f.simple_fields_for :account do |account_fields| - = account_fields.input :username, wrapper: :with_label, autofocus: true, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username') }, append: "@#{site_hostname}", hint: false, disabled: !Setting.open_registrations + = account_fields.input :username, wrapper: :with_label, autofocus: true, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username') }, append: "@#{site_hostname}", hint: false, disabled: closed_registrations? - = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false, disabled: !Setting.open_registrations - = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false, disabled: !Setting.open_registrations - = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }, hint: false, disabled: !Setting.open_registrations + = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations? + = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations? + = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations? .fields-group - = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), disabled: !Setting.open_registrations + = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), disabled: closed_registrations? .actions - = f.button :button, Setting.open_registrations ? t('auth.register') : t('auth.registration_closed', instance: site_hostname), type: :submit, class: 'button button-primary', disabled: !Setting.open_registrations + = f.button :button, sign_up_message, type: :submit, class: 'button button-primary', disabled: closed_registrations? diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml index 1e1bb1812..eba3ad804 100644 --- a/app/views/admin/accounts/_account.html.haml +++ b/app/views/admin/accounts/_account.html.haml @@ -5,7 +5,7 @@ %div{ style: 'margin: -2px 0' }= account_badge(account, all: true) %td - if account.user_current_sign_in_ip - %samp= account.user_current_sign_in_ip + %samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= account.user_current_sign_in_ip - else \- %td @@ -14,5 +14,9 @@ - else \- %td - = table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}") - = table_link_to 'globe', t('admin.accounts.public'), TagManager.instance.url_for(account) + - if account.local? && account.user_pending? + = table_link_to 'check', t('admin.accounts.approve'), approve_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:approve, account.user) + = table_link_to 'times', t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:reject, account.user) + - else + = table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}") + = table_link_to 'globe', t('admin.accounts.public'), TagManager.instance.url_for(account) diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml index 345f74f90..66808add7 100644 --- a/app/views/admin/accounts/index.html.haml +++ b/app/views/admin/accounts/index.html.haml @@ -10,9 +10,10 @@ .filter-subset %strong= t('admin.accounts.moderation.title') %ul - %li= filter_link_to t('admin.accounts.moderation.active'), silenced: nil, suspended: nil - %li= filter_link_to t('admin.accounts.moderation.silenced'), silenced: '1', suspended: nil - %li= filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1', silenced: nil + %li= filter_link_to t('admin.accounts.moderation.pending'), pending: '1', silenced: nil, suspended: nil + %li= filter_link_to t('admin.accounts.moderation.active'), silenced: nil, suspended: nil, pending: nil + %li= filter_link_to t('admin.accounts.moderation.silenced'), silenced: '1', suspended: nil, pending: nil + %li= filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1', silenced: nil, pending: nil .filter-subset %strong= t('admin.accounts.role') %ul diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 7ac73bd07..7494c9fa2 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -37,6 +37,8 @@ %span.red= t('admin.accounts.disabled') - elsif @account.local? && !@account.user&.confirmed? %span.neutral= t('admin.accounts.confirming') + - elsif @account.local? && !@account.user_approved? + %span.neutral= t('admin.accounts.pending') - else %span.neutral= t('admin.accounts.no_limits_imposed') .dashboard__counters__label= t 'admin.accounts.login_status' @@ -95,7 +97,7 @@ %td - if @account.user&.disabled? = table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post if can?(:enable, @account.user) - - else + - elsif @account.user_approved? = table_link_to 'lock', t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable') if can?(:disable, @account.user) %tr @@ -144,26 +146,30 @@ = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user) - if @account.user&.otp_required_for_login? = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user) - - unless @account.memorial? + - if !@account.memorial? && @account.user_approved? = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account) - else = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) %div{ style: 'float: left' } - - if @account.local? + - if @account.local? && @account.user_approved? = link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account) - if @account.silenced? = link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account) - - else + - elsif !@account.local? || @account.user_approved? = link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button button--destructive' if can?(:silence, @account) - if @account.local? + - if @account.user_pending? + = link_to t('admin.accounts.approve'), approve_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, @account.user) + = link_to t('admin.accounts.reject'), reject_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, @account.user) + - unless @account.user_confirmed? = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user) - if @account.suspended? = link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account) - - else + - elsif !@account.local? || @account.user_approved? = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button button--destructive' if can?(:suspend, @account) - unless @account.local? diff --git a/app/views/admin/invites/_invite.html.haml b/app/views/admin/invites/_invite.html.haml index d7b697286..ee0eacaf5 100644 --- a/app/views/admin/invites/_invite.html.haml +++ b/app/views/admin/invites/_invite.html.haml @@ -1,21 +1,29 @@ %tr %td + .input-copy + .input-copy__wrapper + %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: public_invite_url(invite_code: invite.code) } + %button{ type: :button }= t('generic.copy') + + %td .name-tag = image_tag invite.user.account.avatar.url(:original), alt: '', width: 16, height: 16, class: 'avatar' %span.username= invite.user.account.username - %td - = invite.uses - = " / #{invite.max_uses}" unless invite.max_uses.nil? - %td - - if invite.expired? + + - if invite.expired? + %td{ colspan: 2 } = t('invites.expired') - - else + - else + %td + = fa_icon 'user fw' + = invite.uses + = " / #{invite.max_uses}" unless invite.max_uses.nil? + %td - if invite.expires_at.nil? ∞ - else %time.formatted{ datetime: invite.expires_at.iso8601, title: l(invite.expires_at) } = l invite.expires_at - %td= table_link_to 'link', public_invite_url(invite_code: invite.code), public_invite_url(invite_code: invite.code) %td - if !invite.expired? && policy(invite).destroy? = table_link_to 'times', t('invites.delete'), admin_invite_path(invite), method: :delete diff --git a/app/views/admin/invites/index.html.haml b/app/views/admin/invites/index.html.haml index 42159e9f3..ee6ba0f57 100644 --- a/app/views/admin/invites/index.html.haml +++ b/app/views/admin/invites/index.html.haml @@ -18,15 +18,15 @@ %hr.spacer/ -.table-wrapper - %table.table +.table-wrapper.simple_form + %table.table.table--invites %thead %tr %th + %th %th= t('invites.table.uses') %th= t('invites.table.expires_at') %th - %th %tbody = render @invites diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml index e3ceb4344..9995e0b2a 100644 --- a/app/views/admin/settings/edit.html.haml +++ b/app/views/admin/settings/edit.html.haml @@ -6,8 +6,11 @@ .fields-group = f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title') - .fields-group - = f.input :flavour_and_skin, collection: Themes.instance.flavours_and_skins, group_label_method: lambda { |(flavour, _)| I18n.t("flavours.#{flavour}.name", default: flavour) }, wrapper: :with_label, include_blank: false, as: :grouped_select, label_method: :last, value_method: lambda { |value| value.join('/') }, group_method: :last + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :flavour_and_skin, collection: Themes.instance.flavours_and_skins, group_label_method: lambda { |(flavour, _)| I18n.t("flavours.#{flavour}.name", default: flavour) }, wrapper: :with_label, include_blank: false, as: :grouped_select, label_method: :last, value_method: lambda { |value| value.join('/') }, group_method: :last + .fields-row__column.fields-row__column-6.fields-group + = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, label: t('admin.settings.registrations_mode.title'), include_blank: false, label_method: lambda { |mode| I18n.t("admin.settings.registrations_mode.modes.#{mode}") } .fields-row .fields-row__column.fields-row__column-6.fields-group @@ -48,9 +51,6 @@ = f.input :show_staff_badge, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_staff_badge.title'), hint: t('admin.settings.show_staff_badge.desc_html') .fields-group - = f.input :open_registrations, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.open.title'), hint: t('admin.settings.registrations.open.desc_html') - - .fields-group = f.input :open_deletion, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.deletion.title'), hint: t('admin.settings.registrations.deletion.desc_html') .fields-group diff --git a/app/views/admin_mailer/new_pending_account.text.erb b/app/views/admin_mailer/new_pending_account.text.erb new file mode 100644 index 000000000..ed31ae2eb --- /dev/null +++ b/app/views/admin_mailer/new_pending_account.text.erb @@ -0,0 +1,8 @@ +<%= raw t('application_mailer.salutation', name: display_name(@me)) %> + +<%= raw t('admin_mailer.new_pending_account.body') %> + +<%= raw t('admin.accounts.email') %>: <%= @account.user_email %> +<%= raw t('admin.accounts.most_recent_ip') %>: <%= @account.user_current_sign_in_ip %> + +<%= raw t('application_mailer.view')%> <%= admin_account_url(@account.id) %> diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml index 72ce8e531..1caf2b401 100644 --- a/app/views/auth/registrations/new.html.haml +++ b/app/views/auth/registrations/new.html.haml @@ -29,6 +29,6 @@ %p.hint= t('auth.agreement_html', rules_path: about_more_path, terms_path: terms_path) .actions - = f.button :button, t('auth.register'), type: :submit + = f.button :button, sign_up_message, type: :submit .form-footer= render 'auth/shared/links' diff --git a/app/views/auth/shared/_links.html.haml b/app/views/auth/shared/_links.html.haml index 516c625a6..3c68ccd22 100644 --- a/app/views/auth/shared/_links.html.haml +++ b/app/views/auth/shared/_links.html.haml @@ -3,7 +3,7 @@ %li= link_to t('auth.login'), new_session_path(resource_name) - if devise_mapping.registerable? && controller_name != 'registrations' - %li= link_to t('auth.register'), open_registrations? ? new_registration_path(resource_name) : 'https://joinmastodon.org/#getting-started' + %li= link_to t('auth.register'), available_sign_up_path - if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %li= link_to t('auth.forgot_password'), new_password_path(resource_name) diff --git a/app/views/invites/_invite.html.haml b/app/views/invites/_invite.html.haml index 1c7ec311d..4240aa3e7 100644 --- a/app/views/invites/_invite.html.haml +++ b/app/views/invites/_invite.html.haml @@ -1,17 +1,25 @@ %tr %td - = invite.uses - = " / #{invite.max_uses}" unless invite.max_uses.nil? - %td - - if invite.expired? + .input-copy + .input-copy__wrapper + %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: public_invite_url(invite_code: invite.code) } + %button{ type: :button }= t('generic.copy') + + - if invite.expired? + %td{ colspan: 2 } = t('invites.expired') - - else + - else + %td + = fa_icon 'user fw' + = invite.uses + = " / #{invite.max_uses}" unless invite.max_uses.nil? + %td - if invite.expires_at.nil? ∞ - else %time.formatted{ datetime: invite.expires_at.iso8601, title: l(invite.expires_at) } = l invite.expires_at - %td= table_link_to 'link', public_invite_url(invite_code: invite.code), public_invite_url(invite_code: invite.code) + %td - if !invite.expired? && policy(invite).destroy? = table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete diff --git a/app/views/invites/index.html.haml b/app/views/invites/index.html.haml index fb827f6e6..61420ab1e 100644 --- a/app/views/invites/index.html.haml +++ b/app/views/invites/index.html.haml @@ -8,12 +8,13 @@ %hr.spacer/ -%table.table - %thead - %tr - %th= t('invites.table.uses') - %th= t('invites.table.expires_at') - %th - %th - %tbody - = render @invites +.simple_form + %table.table.table--invites + %thead + %tr + %th + %th= t('invites.table.uses') + %th= t('invites.table.expires_at') + %th + %tbody + = render @invites diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml index f26271cdc..1d3519b8a 100644 --- a/app/views/layouts/public.html.haml +++ b/app/views/layouts/public.html.haml @@ -7,8 +7,7 @@ = link_to root_url, class: 'brand' do = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' - - if Setting.profile_directory - = link_to t('directories.directory'), explore_path, class: 'nav-link optional' + = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory = link_to t('about.about_this'), about_more_path, class: 'nav-link optional' = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional' .nav-center @@ -17,7 +16,7 @@ = link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn' - else = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn nav-link nav-button' - = link_to t('auth.register'), open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started', class: 'webapp-btn nav-link nav-button' + = link_to t('auth.register'), available_sign_up_path, class: 'webapp-btn nav-link nav-button' .container= yield diff --git a/app/views/remote_follow/new.html.haml b/app/views/remote_follow/new.html.haml index c90793842..4e9601f6a 100644 --- a/app/views/remote_follow/new.html.haml +++ b/app/views/remote_follow/new.html.haml @@ -17,4 +17,4 @@ %p.hint.subtle-hint = t('remote_follow.reason_html', instance: site_hostname) - = t('remote_follow.no_account_html', sign_up_path: open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started') + = t('remote_follow.no_account_html', sign_up_path: available_sign_up_path) diff --git a/app/views/remote_interaction/new.html.haml b/app/views/remote_interaction/new.html.haml index b2b7826c4..c8c08991f 100644 --- a/app/views/remote_interaction/new.html.haml +++ b/app/views/remote_interaction/new.html.haml @@ -21,4 +21,4 @@ %p.hint.subtle-hint = t('remote_follow.reason_html', instance: site_hostname) - = t('remote_follow.no_account_html', sign_up_path: open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started') + = t('remote_follow.no_account_html', sign_up_path: available_sign_up_path) diff --git a/app/views/user_mailer/confirmation_instructions.html.haml b/app/views/user_mailer/confirmation_instructions.html.haml index f75f7529a..70d0f5a24 100644 --- a/app/views/user_mailer/confirmation_instructions.html.haml +++ b/app/views/user_mailer/confirmation_instructions.html.haml @@ -36,7 +36,7 @@ %tbody %tr %td.column-cell.text-center - %p= t 'devise.mailer.confirmation_instructions.explanation', host: site_hostname + %p= t @resource.approved? ? 'devise.mailer.confirmation_instructions.explanation' : 'devise.mailer.confirmation_instructions.explanation_when_pending', host: site_hostname %table.email-table{ cellspacing: 0, cellpadding: 0 } %tbody diff --git a/app/views/user_mailer/confirmation_instructions.text.erb b/app/views/user_mailer/confirmation_instructions.text.erb index 65b4626c6..aad91cd9d 100644 --- a/app/views/user_mailer/confirmation_instructions.text.erb +++ b/app/views/user_mailer/confirmation_instructions.text.erb @@ -2,7 +2,7 @@ === -<%= t 'devise.mailer.confirmation_instructions.explanation', host: site_hostname %> +<%= t @resource.approved? ? 'devise.mailer.confirmation_instructions.explanation' : 'devise.mailer.confirmation_instructions.explanation_when_pending', host: site_hostname %> => <%= confirmation_url(@resource, confirmation_token: @token, redirect_to_app: @resource.created_by_application ? 'true' : nil) %> diff --git a/app/workers/poll_expiration_notify_worker.rb b/app/workers/poll_expiration_notify_worker.rb index ae72298b8..e08f0c249 100644 --- a/app/workers/poll_expiration_notify_worker.rb +++ b/app/workers/poll_expiration_notify_worker.rb @@ -15,7 +15,7 @@ class PollExpirationNotifyWorker end # Notify local voters - poll.votes.includes(:account).map(&:account).filter(&:local?).each do |account| + poll.votes.includes(:account).map(&:account).select(&:local?).each do |account| NotifyService.new.call(account, poll) end rescue ActiveRecord::RecordNotFound |