about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/javascript/mastodon/actions/compose.js12
-rw-r--r--app/lib/request.rb13
-rw-r--r--app/models/account.rb6
-rw-r--r--app/services/activitypub/process_account_service.rb2
-rw-r--r--app/validators/unique_username_validator.rb14
5 files changed, 41 insertions, 6 deletions
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index 130b4af23..1371f22b2 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -1,4 +1,5 @@
 import api from '../api';
+import { CancelToken } from 'axios';
 import { throttle } from 'lodash';
 import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
 import { tagHistory } from '../settings';
@@ -11,6 +12,8 @@ import {
   refreshPublicTimeline,
 } from './timelines';
 
+let cancelFetchComposeSuggestionsAccounts;
+
 export const COMPOSE_CHANGE          = 'COMPOSE_CHANGE';
 export const COMPOSE_SUBMIT_REQUEST  = 'COMPOSE_SUBMIT_REQUEST';
 export const COMPOSE_SUBMIT_SUCCESS  = 'COMPOSE_SUBMIT_SUCCESS';
@@ -257,13 +260,22 @@ export function undoUploadCompose(media_id) {
 };
 
 export function clearComposeSuggestions() {
+  if (cancelFetchComposeSuggestionsAccounts) {
+    cancelFetchComposeSuggestionsAccounts();
+  }
   return {
     type: COMPOSE_SUGGESTIONS_CLEAR,
   };
 };
 
 const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
+  if (cancelFetchComposeSuggestionsAccounts) {
+    cancelFetchComposeSuggestionsAccounts();
+  }
   api(getState).get('/api/v1/accounts/search', {
+    cancelToken: new CancelToken(cancel => {
+      cancelFetchComposeSuggestionsAccounts = cancel;
+    }),
     params: {
       q: token.slice(1),
       resolve: false,
diff --git a/app/lib/request.rb b/app/lib/request.rb
index 5776b3d78..298fb9528 100644
--- a/app/lib/request.rb
+++ b/app/lib/request.rb
@@ -94,9 +94,16 @@ class Request
   class Socket < TCPSocket
     class << self
       def open(host, *args)
-        address = IPSocket.getaddress(host)
-        raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address)
-        super address, *args
+        outer_e = nil
+        Addrinfo.foreach(host, nil, nil, :SOCK_STREAM) do |address|
+          begin
+            raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address.ip_address)
+            return super address.ip_address, *args
+          rescue => e
+            outer_e = e
+          end
+        end
+        raise outer_e if outer_e
       end
 
       alias new open
diff --git a/app/models/account.rb b/app/models/account.rb
index 61f81ab70..16a256bfc 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -47,7 +47,8 @@
 #
 
 class Account < ApplicationRecord
-  MENTION_RE = /(?<=^|[^\/[:word:]])@(([a-z0-9_]+)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
+  USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.]+[a-z0-9_]+)?/i
+  MENTION_RE  = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE}?)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
 
   include AccountAvatar
   include AccountFinderConcern
@@ -70,7 +71,8 @@ class Account < ApplicationRecord
   validates :username, uniqueness: { scope: :domain, case_sensitive: true }, if: -> { !local? && will_save_change_to_username? }
 
   # Local user validations
-  validates :username, format: { with: /\A[a-z0-9_]+\z/i }, uniqueness: { scope: :domain, case_sensitive: false }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? }
+  validates :username, format: { with: /\A#{USERNAME_RE}\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? }
+  validates_with UniqueUsernameValidator, if: -> { local? && will_save_change_to_username? }
   validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? }
   validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
   validate :note_length_does_not_exceed_length_limit, if: -> { local? && will_save_change_to_note? }
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index 68e9db766..7d8dc1369 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -16,7 +16,7 @@ class ActivityPub::ProcessAccountService < BaseService
 
     RedisLock.acquire(lock_options) do |lock|
       if lock.acquired?
-        @account        = Account.find_by(uri: @uri)
+        @account        = Account.find_remote(@username, @domain)
         @old_public_key = @account&.public_key
         @old_protocol   = @account&.protocol
 
diff --git a/app/validators/unique_username_validator.rb b/app/validators/unique_username_validator.rb
new file mode 100644
index 000000000..c76407b16
--- /dev/null
+++ b/app/validators/unique_username_validator.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class UniqueUsernameValidator < ActiveModel::Validator
+  def validate(account)
+    return if account.username.nil?
+
+    normalized_username = account.username.downcase.delete('.')
+
+    scope = Account.where(domain: nil, username: normalized_username)
+    scope = scope.where.not(id: account.id) if account.persisted?
+
+    account.errors.add(:username, :taken) if scope.exists?
+  end
+end