Merge branch 'master' into glitch-soc/merge-upstream
This commit is contained in:
		
						commit
						c32625495f
					
				
					 14 changed files with 72 additions and 30 deletions
				
			
		|  | @ -27,7 +27,7 @@ module Admin | |||
|           ips       = [] | ||||
| 
 | ||||
|           Resolv::DNS.open do |dns| | ||||
|             dns.timeouts = 1 | ||||
|             dns.timeouts = 5 | ||||
| 
 | ||||
|             hostnames = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s } | ||||
| 
 | ||||
|  |  | |||
|  | @ -99,6 +99,28 @@ function main() { | |||
|       new Rellax('.parallax', { speed: -1 }); | ||||
|     } | ||||
| 
 | ||||
|     delegate(document, '#registration_user_password_confirmation,#registration_user_password', 'input', () => { | ||||
|       const password = document.getElementById('registration_user_password'); | ||||
|       const confirmation = document.getElementById('registration_user_password_confirmation'); | ||||
|       if (password.value && password.value !== confirmation.value) { | ||||
|         confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.mismatching'] || 'Password confirmation does not match', locale)).format()); | ||||
|       } else { | ||||
|         confirmation.setCustomValidity(''); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     delegate(document, '#user_password,#user_password_confirmation', 'input', () => { | ||||
|       const password = document.getElementById('user_password'); | ||||
|       const confirmation = document.getElementById('user_password_confirmation'); | ||||
|       if (!confirmation) return; | ||||
| 
 | ||||
|       if (password.value && password.value !== confirmation.value) { | ||||
|         confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.mismatching'] || 'Password confirmation does not match', locale)).format()); | ||||
|       } else { | ||||
|         confirmation.setCustomValidity(''); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original')); | ||||
|     delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); | ||||
| 
 | ||||
|  |  | |||
|  | @ -364,7 +364,8 @@ code { | |||
|       box-shadow: none; | ||||
|     } | ||||
| 
 | ||||
|     &:focus:invalid:not(:placeholder-shown) { | ||||
|     &:focus:invalid:not(:placeholder-shown), | ||||
|     &:required:invalid:not(:placeholder-shown) { | ||||
|       border-color: lighten($error-red, 12%); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ class BlacklistedEmailValidator < ActiveModel::Validator | |||
| 
 | ||||
|     @email = user.email | ||||
| 
 | ||||
|     user.errors.add(:email, I18n.t('users.invalid_email')) if blocked_email? | ||||
|     user.errors.add(:email, I18n.t('users.blocked_email_provider')) if blocked_email? | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
|  |  | |||
|  | @ -4,22 +4,38 @@ require 'resolv' | |||
| 
 | ||||
| class EmailMxValidator < ActiveModel::Validator | ||||
|   def validate(user) | ||||
|     user.errors.add(:email, I18n.t('users.invalid_email')) if invalid_mx?(user.email) | ||||
|     domain = get_domain(user.email) | ||||
| 
 | ||||
|     if domain.nil? | ||||
|       user.errors.add(:email, I18n.t('users.invalid_email')) | ||||
|     else | ||||
|       ips, hostnames = resolve_mx(domain) | ||||
|       if ips.empty? | ||||
|         user.errors.add(:email, I18n.t('users.invalid_email_mx')) | ||||
|       elsif on_blacklist?(hostnames + ips) | ||||
|         user.errors.add(:email, I18n.t('users.blocked_email_provider')) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   def invalid_mx?(value) | ||||
|   def get_domain(value) | ||||
|     _, domain = value.split('@', 2) | ||||
| 
 | ||||
|     return true if domain.nil? | ||||
|     return nil if domain.nil? | ||||
| 
 | ||||
|     domain    = TagManager.instance.normalize_domain(domain) | ||||
|     TagManager.instance.normalize_domain(domain) | ||||
|   rescue Addressable::URI::InvalidURIError | ||||
|     nil | ||||
|   end | ||||
| 
 | ||||
|   def resolve_mx(domain) | ||||
|     hostnames = [] | ||||
|     ips       = [] | ||||
| 
 | ||||
|     Resolv::DNS.open do |dns| | ||||
|       dns.timeouts = 1 | ||||
|       dns.timeouts = 5 | ||||
| 
 | ||||
|       hostnames = dns.getresources(domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s } | ||||
| 
 | ||||
|  | @ -29,9 +45,7 @@ class EmailMxValidator < ActiveModel::Validator | |||
|       end | ||||
|     end | ||||
| 
 | ||||
|     ips.empty? || on_blacklist?(hostnames + ips) | ||||
|   rescue Addressable::URI::InvalidURIError | ||||
|     true | ||||
|     [ips, hostnames] | ||||
|   end | ||||
| 
 | ||||
|   def on_blacklist?(values) | ||||
|  |  | |||
|  | @ -1,13 +1,13 @@ | |||
| .simple_form__overlay-area{ class: (closed_registrations? && @instance_presenter.closed_registrations_message.present?) ? 'simple_form__overlay-area__blurred' : '' } | ||||
|   = simple_form_for(new_user, url: user_registration_path, namespace: 'registration') do |f| | ||||
|   = simple_form_for(new_user, url: user_registration_path, namespace: 'registration', html: { novalidate: false }) do |f| | ||||
|     %p.lead= t('about.federation_hint_html', instance: content_tag(:strong, site_hostname)) | ||||
| 
 | ||||
|     .fields-group | ||||
|       = f.simple_fields_for :account do |account_fields| | ||||
|         = account_fields.input :username, wrapper: :with_label, 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? | ||||
|         = account_fields.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-z0-9_]+', maxlength: 30 }, 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: 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, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }, 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? | ||||
| 
 | ||||
|     - if approved_registrations? | ||||
|  | @ -16,7 +16,7 @@ | |||
|           = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: false | ||||
| 
 | ||||
|     .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: closed_registrations? | ||||
|       = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), required: true, disabled: closed_registrations? | ||||
| 
 | ||||
|     .actions | ||||
|       = f.button :button, sign_up_message, type: :submit, class: 'button button-primary', disabled: closed_registrations? | ||||
|  |  | |||
|  | @ -1,14 +1,14 @@ | |||
| - content_for :page_title do | ||||
|   = t('auth.set_new_password') | ||||
| 
 | ||||
| = simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| | ||||
| = simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, novalidate: false }) do |f| | ||||
|   = render 'shared/error_messages', object: resource | ||||
| 
 | ||||
|   - if !use_seamless_external_login? || resource.encrypted_password.present? | ||||
|     = f.input :reset_password_token, as: :hidden | ||||
| 
 | ||||
|     .fields-group | ||||
|       = f.input :password, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }, required: true | ||||
|       = f.input :password, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }, required: true | ||||
|     .fields-group | ||||
|       = 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' }, required: true | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| 
 | ||||
| %h3= t('auth.security') | ||||
| 
 | ||||
| = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f| | ||||
| = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit', novalidate: false }) do |f| | ||||
|   = render 'shared/error_messages', object: resource | ||||
| 
 | ||||
|   - if !use_seamless_external_login? || resource.encrypted_password.present? | ||||
|  | @ -17,7 +17,7 @@ | |||
| 
 | ||||
|     .fields-row | ||||
|       .fields-row__column.fields-group.fields-row__column-6 | ||||
|         = 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? | ||||
|         = 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', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended? | ||||
|       .fields-row__column.fields-group.fields-row__column-6 | ||||
|         = 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? | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| - content_for :header_tags do | ||||
|   = render partial: 'shared/og', locals: { description: description_for_sign_up } | ||||
| 
 | ||||
| = simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| | ||||
| = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { novalidate: false }) do |f| | ||||
|   = render 'shared/error_messages', object: resource | ||||
| 
 | ||||
|   - if @invite.present? && @invite.autofollow? | ||||
|  | @ -14,13 +14,13 @@ | |||
| 
 | ||||
|   = f.simple_fields_for :account do |ff| | ||||
|     .fields-group | ||||
|       = ff.input :username, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off' }, append: "@#{site_hostname}", hint: t('simple_form.hints.defaults.username', domain: site_hostname) | ||||
|       = ff.input :username, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', pattern: '[a-z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: t('simple_form.hints.defaults.username', domain: site_hostname) | ||||
| 
 | ||||
|   .fields-group | ||||
|     = f.input :email, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' } | ||||
| 
 | ||||
|   .fields-group | ||||
|     = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' } | ||||
|     = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last } | ||||
| 
 | ||||
|   .fields-group | ||||
|     = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' } | ||||
|  | @ -33,7 +33,7 @@ | |||
|   = f.input :invite_code, as: :hidden | ||||
| 
 | ||||
|   .fields-group | ||||
|     = f.input :agreement, as: :boolean, wrapper: :with_label, label: whitelist_mode? ? t('auth.checkbox_agreement_without_rules_html', terms_path: terms_path) : t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path) | ||||
|     = f.input :agreement, as: :boolean, wrapper: :with_label, label: whitelist_mode? ? t('auth.checkbox_agreement_without_rules_html', terms_path: terms_path) : t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), required: true | ||||
| 
 | ||||
|   .actions | ||||
|     = f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ | |||
|         %span.display-name | ||||
|           %bdi | ||||
|             %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay) | ||||
|             | ||||
|           = ' ' | ||||
|           %span.display-name__account | ||||
|             = acct(status.account) | ||||
|             = fa_icon('lock') if status.account.locked? | ||||
|  |  | |||
|  | @ -1325,9 +1325,11 @@ en: | |||
|       tips: Tips | ||||
|       title: Welcome aboard, %{name}! | ||||
|   users: | ||||
|     blocked_email_provider: This e-mail provider isn't allowed | ||||
|     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_email_mx: The e-mail address does not seem to exist | ||||
|     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} | ||||
|  |  | |||
|  | @ -63,7 +63,7 @@ module Mastodon | |||
|         ips       = [] | ||||
| 
 | ||||
|         Resolv::DNS.open do |dns| | ||||
|           dns.timeouts = 1 | ||||
|           dns.timeouts = 5 | ||||
|           hostnames = dns.getresources(email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s } | ||||
| 
 | ||||
|           ([email_domain_block.domain] + hostnames).uniq.each do |hostname| | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do | |||
|       let(:blocked_email) { true } | ||||
| 
 | ||||
|       it 'calls errors.add' do | ||||
|         expect(errors).to have_received(:add).with(:email, I18n.t('users.invalid_email')) | ||||
|         expect(errors).to have_received(:add).with(:email, I18n.t('users.blocked_email_provider')) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|  | @ -25,7 +25,7 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do | |||
|       let(:blocked_email) { false } | ||||
| 
 | ||||
|       it 'not calls errors.add' do | ||||
|         expect(errors).not_to have_received(:add).with(:email, I18n.t('users.invalid_email')) | ||||
|         expect(errors).not_to have_received(:add).with(:email, I18n.t('users.blocked_email_provider')) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -210,6 +210,7 @@ const startWorker = (workerId) => { | |||
|     if (subs[channel].length === 0) { | ||||
|       log.verbose(`Unsubscribe ${channel}`); | ||||
|       redisSubscribeClient.unsubscribe(channel); | ||||
|       delete subs[channel]; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|  | @ -888,19 +889,21 @@ const startWorker = (workerId) => { | |||
|     channelNameToIds(request, channelName, params).then(({ channelIds }) => { | ||||
|       log.verbose(request.requestId, `Ending stream from ${channelIds.join(', ')} for ${request.accountId}`); | ||||
| 
 | ||||
|       const { listener, stopHeartbeat } = subscriptions[channelIds.join(';')]; | ||||
|       const subscription = subscriptions[channelIds.join(';')]; | ||||
| 
 | ||||
|       if (!listener) { | ||||
|       if (!subscription) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       const { listener, stopHeartbeat } = subscription; | ||||
| 
 | ||||
|       channelIds.forEach(channelId => { | ||||
|         unsubscribe(`${redisPrefix}${channelId}`, listener); | ||||
|       }); | ||||
| 
 | ||||
|       stopHeartbeat(); | ||||
| 
 | ||||
|       subscriptions[channelIds.join(';')] = undefined; | ||||
|       delete subscriptions[channelIds.join(';')]; | ||||
|     }).catch(err => { | ||||
|       log.verbose(request.requestId, 'Unsubscription error:', err); | ||||
|       socket.send(JSON.stringify({ error: err.toString() })); | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue