Allow @username@domain/@username in follow form, prevent duplicate accounts
created via remote look-up when domains differ but point to the same resource
This commit is contained in:
		| @@ -25,7 +25,7 @@ import { | ||||
| } from '../actions/statuses'; | ||||
| import Immutable from 'immutable'; | ||||
|  | ||||
| const normalizeAccount = (state, account) => state.set(account.get('id'), account); | ||||
| const normalizeAccount = (state, account) => state.set(account.id, Immutable.fromJS(account)); | ||||
|  | ||||
| const normalizeAccounts = (state, accounts) => { | ||||
|   accounts.forEach(account => { | ||||
| @@ -36,10 +36,10 @@ const normalizeAccounts = (state, accounts) => { | ||||
| }; | ||||
|  | ||||
| const normalizeAccountFromStatus = (state, status) => { | ||||
|   state = normalizeAccount(state, status.get('account')); | ||||
|   state = normalizeAccount(state, status.account); | ||||
|  | ||||
|   if (status.getIn(['reblog', 'account'])) { | ||||
|     state = normalizeAccount(state, status.getIn(['reblog', 'account'])); | ||||
|   if (status.reblog && status.reblog.account) { | ||||
|     state = normalizeAccount(state, status.reblog.account); | ||||
|   } | ||||
|  | ||||
|   return state; | ||||
| @@ -60,24 +60,24 @@ export default function accounts(state = initialState, action) { | ||||
|     case ACCOUNT_SET_SELF: | ||||
|     case ACCOUNT_FETCH_SUCCESS: | ||||
|     case FOLLOW_SUBMIT_SUCCESS: | ||||
|       return normalizeAccount(state, Immutable.fromJS(action.account)); | ||||
|       return normalizeAccount(state, action.account); | ||||
|     case SUGGESTIONS_FETCH_SUCCESS: | ||||
|     case FOLLOWERS_FETCH_SUCCESS: | ||||
|     case FOLLOWING_FETCH_SUCCESS: | ||||
|       return normalizeAccounts(state, Immutable.fromJS(action.accounts)); | ||||
|       return normalizeAccounts(state, action.accounts); | ||||
|     case TIMELINE_REFRESH_SUCCESS: | ||||
|     case TIMELINE_EXPAND_SUCCESS: | ||||
|     case ACCOUNT_TIMELINE_FETCH_SUCCESS: | ||||
|     case ACCOUNT_TIMELINE_EXPAND_SUCCESS: | ||||
|     case CONTEXT_FETCH_SUCCESS: | ||||
|       return normalizeAccountsFromStatuses(state, Immutable.fromJS(action.statuses)); | ||||
|       return normalizeAccountsFromStatuses(state, action.statuses); | ||||
|     case TIMELINE_UPDATE: | ||||
|     case REBLOG_SUCCESS: | ||||
|     case FAVOURITE_SUCCESS: | ||||
|     case UNREBLOG_SUCCESS: | ||||
|     case UNFAVOURITE_SUCCESS: | ||||
|     case STATUS_FETCH_SUCCESS: | ||||
|       return normalizeAccountFromStatus(state, Immutable.fromJS(action.status)); | ||||
|       return normalizeAccountFromStatus(state, action.status); | ||||
|     default: | ||||
|       return state; | ||||
|   } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import { | ||||
| } from '../actions/accounts'; | ||||
| import Immutable from 'immutable'; | ||||
|  | ||||
| const normalizeRelationship = (state, relationship) => state.set(relationship.get('id'), relationship); | ||||
| const normalizeRelationship = (state, relationship) => state.set(relationship.id, Immutable.fromJS(relationship)); | ||||
|  | ||||
| const normalizeRelationships = (state, relationships) => { | ||||
|   relationships.forEach(relationship => { | ||||
| @@ -25,9 +25,9 @@ export default function relationships(state = initialState, action) { | ||||
|     case ACCOUNT_UNFOLLOW_SUCCESS: | ||||
|     case ACCOUNT_BLOCK_SUCCESS: | ||||
|     case ACCOUNT_UNBLOCK_SUCCESS: | ||||
|       return normalizeRelationship(state, Immutable.fromJS(action.relationship)); | ||||
|       return normalizeRelationship(state, action.relationship); | ||||
|     case RELATIONSHIPS_FETCH_SUCCESS: | ||||
|       return normalizeRelationships(state, Immutable.fromJS(action.relationships)); | ||||
|       return normalizeRelationships(state, action.relationships); | ||||
|     default: | ||||
|       return state; | ||||
|   } | ||||
|   | ||||
| @@ -21,14 +21,14 @@ import { | ||||
| import Immutable from 'immutable'; | ||||
|  | ||||
| const normalizeStatus = (state, status) => { | ||||
|   status = status.set('account', status.getIn(['account', 'id'])); | ||||
|   status.account = status.account.id; | ||||
|  | ||||
|   if (status.getIn(['reblog', 'id'])) { | ||||
|     state  = normalizeStatus(state, status.get('reblog')); | ||||
|     status = status.set('reblog', status.getIn(['reblog', 'id'])); | ||||
|   if (status.reblog && status.reblog.id) { | ||||
|     state         = normalizeStatus(state, status.reblog); | ||||
|     status.reblog = status.reblog.id; | ||||
|   } | ||||
|  | ||||
|   return state.set(status.get('id'), status); | ||||
|   return state.set(status.id, Immutable.fromJS(status)); | ||||
| }; | ||||
|  | ||||
| const normalizeStatuses = (state, statuses) => { | ||||
| @@ -53,18 +53,18 @@ export default function statuses(state = initialState, action) { | ||||
|   switch(action.type) { | ||||
|     case TIMELINE_UPDATE: | ||||
|     case STATUS_FETCH_SUCCESS: | ||||
|       return normalizeStatus(state, Immutable.fromJS(action.status)); | ||||
|       return normalizeStatus(state, action.status); | ||||
|     case REBLOG_SUCCESS: | ||||
|     case UNREBLOG_SUCCESS: | ||||
|     case FAVOURITE_SUCCESS: | ||||
|     case UNFAVOURITE_SUCCESS: | ||||
|       return normalizeStatus(state, Immutable.fromJS(action.response)); | ||||
|       return normalizeStatus(state, action.response); | ||||
|     case TIMELINE_REFRESH_SUCCESS: | ||||
|     case TIMELINE_EXPAND_SUCCESS: | ||||
|     case ACCOUNT_TIMELINE_FETCH_SUCCESS: | ||||
|     case ACCOUNT_TIMELINE_EXPAND_SUCCESS: | ||||
|     case CONTEXT_FETCH_SUCCESS: | ||||
|       return normalizeStatuses(state, Immutable.fromJS(action.statuses)); | ||||
|       return normalizeStatuses(state, action.statuses); | ||||
|     case TIMELINE_DELETE: | ||||
|       return deleteStatus(state, action.id, action.references); | ||||
|     default: | ||||
|   | ||||
| @@ -5,7 +5,13 @@ class Api::V1::FollowsController < ApiController | ||||
|   def create | ||||
|     raise ActiveRecord::RecordNotFound if params[:uri].blank? | ||||
|  | ||||
|     @account = FollowService.new.call(current_user.account, params[:uri].strip).try(:target_account) | ||||
|     @account = FollowService.new.call(current_user.account, target_uri).try(:target_account) | ||||
|     render action: :show | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def target_uri | ||||
|     params[:uri].strip.gsub(/\A@/, '') | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -15,16 +15,25 @@ class FollowRemoteAccountService < BaseService | ||||
|     return nil if DomainBlock.blocked?(domain) | ||||
|  | ||||
|     account = Account.find_remote(username, domain) | ||||
|  | ||||
|     return account unless account.nil? | ||||
|  | ||||
|     Rails.logger.debug "Creating new remote account for #{uri}" | ||||
|     Rails.logger.debug "Looking up webfinger for #{uri}" | ||||
|  | ||||
|     account = Account.new(username: username, domain: domain) | ||||
|  | ||||
|     data = Goldfinger.finger("acct:#{uri}") | ||||
|  | ||||
|     raise Goldfinger::Error, 'Missing resource links' if data.link('http://schemas.google.com/g/2010#updates-from').nil? || data.link('salmon').nil? || data.link('http://webfinger.net/rel/profile-page').nil? || data.link('magic-public-key').nil? | ||||
|  | ||||
|     confirmed_username, confirmed_domain = data.subject.gsub(/\Aacct:/, '').split('@') | ||||
|  | ||||
|     return Account.find_local(confirmed_username) if TagManager.instance.local_domain?(confirmed_domain) | ||||
|  | ||||
|     confirmed_account = Account.find_remote(confirmed_username, confirmed_domain) | ||||
|     return confirmed_account unless confirmed_account.nil? | ||||
|  | ||||
|     Rails.logger.debug "Creating new remote account for #{uri}" | ||||
|  | ||||
|     account.remote_url  = data.link('http://schemas.google.com/g/2010#updates-from').href | ||||
|     account.salmon_url  = data.link('salmon').href | ||||
|     account.url         = data.link('http://webfinger.net/rel/profile-page').href | ||||
|   | ||||
		Reference in New Issue
	
	Block a user