Improve: Support search and adding federated users through regular endpoints (#13936)
parent
be8ae64209
commit
65b5be3a83
@ -0,0 +1,100 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { HTTP } from 'meteor/http'; |
||||
|
||||
import { logger } from '../logger'; |
||||
import { Federation } from '../'; |
||||
|
||||
import { skipRetryOnSpecificError, delay } from './utils'; |
||||
|
||||
export class PeerHTTP { |
||||
constructor() { |
||||
this.config = {}; |
||||
} |
||||
|
||||
setConfig(config) { |
||||
// General
|
||||
this.config = config; |
||||
} |
||||
|
||||
log(message) { |
||||
logger.http.info(message); |
||||
} |
||||
|
||||
//
|
||||
// Direct request
|
||||
simpleRequest(peer, method, uri, body, headers) { |
||||
this.log(`Request: ${ method } ${ uri }`); |
||||
|
||||
const { url: serverBaseURL } = peer; |
||||
|
||||
const url = `${ serverBaseURL }${ uri }`; |
||||
|
||||
let data = null; |
||||
|
||||
if (method === 'POST' || method === 'PUT') { |
||||
data = body; |
||||
} |
||||
|
||||
this.log(`Sending request: ${ method } - ${ uri }`); |
||||
|
||||
return HTTP.call(method, url, { data, timeout: 2000, headers: { ...headers, 'x-federation-domain': this.config.peer.domain } }); |
||||
} |
||||
|
||||
//
|
||||
// Request trying to find DNS entries
|
||||
request(peer, method, uri, body, retryInfo = {}, headers = {}) { |
||||
// Normalize retry info
|
||||
retryInfo = { |
||||
total: retryInfo.total || 1, |
||||
stepSize: retryInfo.stepSize || 100, |
||||
stepMultiplier: retryInfo.stepMultiplier || 1, |
||||
tryToUpdateDNS: retryInfo.tryToUpdateDNS === undefined ? true : retryInfo.tryToUpdateDNS, |
||||
DNSUpdated: false, |
||||
}; |
||||
|
||||
for (let i = 0; i <= retryInfo.total; i++) { |
||||
try { |
||||
return this.simpleRequest(peer, method, uri, body, headers); |
||||
} catch (err) { |
||||
try { |
||||
if (retryInfo.tryToUpdateDNS && !retryInfo.DNSUpdated) { |
||||
i--; |
||||
|
||||
retryInfo.DNSUpdated = true; |
||||
|
||||
this.log(`Trying to update local DNS cache for peer:${ peer.domain }`); |
||||
|
||||
peer = Federation.peerDNS.updatePeerDNS(peer.domain); |
||||
|
||||
continue; |
||||
} |
||||
} catch (err) { |
||||
if (err.response && err.response.statusCode === 404) { |
||||
throw new Meteor.Error('federation-peer-does-not-exist', 'Peer does not exist'); |
||||
} |
||||
} |
||||
|
||||
// Check if we need to skip due to specific error
|
||||
if (skipRetryOnSpecificError(err)) { |
||||
this.log('Retry: skipping due to specific error'); |
||||
|
||||
throw err; |
||||
} |
||||
|
||||
if (i === retryInfo.total - 1) { |
||||
// Throw the error, as we could not fulfill the request
|
||||
this.log('Retry: could not fulfill the request'); |
||||
|
||||
throw err; |
||||
} |
||||
|
||||
const timeToRetry = retryInfo.stepSize * (i + 1) * retryInfo.stepMultiplier; |
||||
|
||||
this.log(`Trying again in ${ timeToRetry / 1000 }s: ${ method } - ${ uri }`); |
||||
|
||||
// Otherwise, wait and try again
|
||||
delay(timeToRetry); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1 @@ |
||||
export { PeerHTTP } from './PeerHTTP'; |
||||
@ -0,0 +1,19 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
|
||||
// Should skip the retry if the error is one of the below?
|
||||
const errorsToSkipRetrying = [ |
||||
'error-app-prevented-sending', |
||||
]; |
||||
|
||||
export function skipRetryOnSpecificError(err) { |
||||
err = err && err.response && err.response.data; |
||||
return errorsToSkipRetrying.includes(err && err.errorType); |
||||
} |
||||
|
||||
// Delay method to wait a little bit before retrying
|
||||
export const delay = Meteor.wrapAsync(function(ms, callback) { |
||||
Meteor.setTimeout(function() { |
||||
callback(null); |
||||
}, ms); |
||||
}); |
||||
|
||||
@ -1,8 +1,6 @@ |
||||
import peerServer from './peerServer'; |
||||
|
||||
// Setup routes
|
||||
import './routes/events'; |
||||
import './routes/uploads'; |
||||
import './routes/users'; |
||||
|
||||
export default peerServer; |
||||
export { PeerServer } from './PeerServer'; |
||||
@ -1,4 +1,4 @@ |
||||
export { default as FederatedMessage } from './FederatedMessage'; |
||||
export { default as FederatedResource } from './FederatedResource'; |
||||
export { default as FederatedRoom } from './FederatedRoom'; |
||||
export { default as FederatedUser } from './FederatedUser'; |
||||
export { FederatedMessage } from './FederatedMessage'; |
||||
export { FederatedResource } from './FederatedResource'; |
||||
export { FederatedRoom } from './FederatedRoom'; |
||||
export { FederatedUser } from './FederatedUser'; |
||||
|
||||
@ -0,0 +1,45 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { Users } from '../../../models'; |
||||
|
||||
import { Federation } from '../'; |
||||
import { logger } from '../logger'; |
||||
|
||||
export function addUser(identifier) { |
||||
if (!Meteor.userId()) { |
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'Federation.addUser' }); |
||||
} |
||||
|
||||
if (!Federation.peerServer.enabled) { |
||||
throw new Meteor.Error('error-federation-disabled', 'Federation disabled', { method: 'Federation.addUser' }); |
||||
} |
||||
|
||||
// Make sure the federated user still exists, and get the unique one, by email address
|
||||
const [federatedUser] = Federation.peerClient.findUsers(identifier, { usernameOnly: true }); |
||||
|
||||
if (!federatedUser) { |
||||
throw new Meteor.Error('federation-invalid-user', 'There is no user to add.'); |
||||
} |
||||
|
||||
let user = null; |
||||
|
||||
const localUser = federatedUser.getLocalUser(); |
||||
|
||||
localUser.name += `@${ federatedUser.user.federation.peer }`; |
||||
|
||||
// Delete the _id
|
||||
delete localUser._id; |
||||
|
||||
try { |
||||
// Create the local user
|
||||
user = Users.create(localUser); |
||||
} catch (err) { |
||||
// If the user already exists, return the existing user
|
||||
if (err.code === 11000) { |
||||
user = Users.findOne({ 'federation._id': localUser.federation._id }); |
||||
} |
||||
|
||||
logger.error(err); |
||||
} |
||||
|
||||
return user; |
||||
} |
||||
@ -1,48 +0,0 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { Users } from '../../../models'; |
||||
|
||||
import { logger } from '../logger'; |
||||
import peerClient from '../peerClient'; |
||||
import peerServer from '../peerClient'; |
||||
|
||||
Meteor.methods({ |
||||
federationAddUser(emailAddress, domainOverride) { |
||||
if (!Meteor.userId()) { |
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'federationAddUser' }); |
||||
} |
||||
|
||||
if (!peerServer.enabled) { |
||||
throw new Meteor.Error('error-federation-disabled', 'Federation disabled', { method: 'federationAddUser' }); |
||||
} |
||||
|
||||
// Make sure the federated user still exists, and get the unique one, by email address
|
||||
const [federatedUser] = peerClient.findUsers(emailAddress, { domainOverride, emailOnly: true }); |
||||
|
||||
if (!federatedUser) { |
||||
throw new Meteor.Error('federation-invalid-user', 'There is no user to add.'); |
||||
} |
||||
|
||||
let user = null; |
||||
|
||||
const localUser = federatedUser.getLocalUser(); |
||||
|
||||
localUser.name += `@${ federatedUser.user.federation.peer }`; |
||||
|
||||
// Delete the _id
|
||||
delete localUser._id; |
||||
|
||||
try { |
||||
// Create the local user
|
||||
user = Users.create(localUser); |
||||
} catch (err) { |
||||
// If the user already exists, return the existing user
|
||||
if (err.code === 11000) { |
||||
user = Users.findOne({ 'federation._id': localUser.federation._id }); |
||||
} |
||||
|
||||
logger.error(err); |
||||
} |
||||
|
||||
return user; |
||||
}, |
||||
}); |
||||
@ -1,24 +0,0 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
|
||||
import peerClient from '../peerClient'; |
||||
import peerServer from '../peerServer'; |
||||
|
||||
Meteor.methods({ |
||||
federationSearchUsers(email) { |
||||
if (!Meteor.userId()) { |
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'federationSearchUsers' }); |
||||
} |
||||
|
||||
if (!peerServer.enabled) { |
||||
throw new Meteor.Error('error-federation-disabled', 'Federation disabled', { method: 'federationAddUser' }); |
||||
} |
||||
|
||||
const federatedUsers = peerClient.findUsers(email); |
||||
|
||||
if (!federatedUsers.length) { |
||||
throw new Meteor.Error('federation-user-not-found', `Could not find federated users using "${ email }"`); |
||||
} |
||||
|
||||
return federatedUsers; |
||||
}, |
||||
}); |
||||
@ -1,2 +0,0 @@ |
||||
import './federationSearchUsers'; |
||||
import './federationAddUser'; |
||||
@ -0,0 +1,21 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
|
||||
import { Federation } from '../'; |
||||
|
||||
export function searchUsers(identifier) { |
||||
if (!Meteor.userId()) { |
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'federationSearchUsers' }); |
||||
} |
||||
|
||||
if (!Federation.peerClient.enabled) { |
||||
throw new Meteor.Error('error-federation-disabled', 'Federation disabled', { method: 'federationSearchUsers' }); |
||||
} |
||||
|
||||
const federatedUsers = Federation.peerClient.findUsers(identifier); |
||||
|
||||
if (!federatedUsers.length) { |
||||
throw new Meteor.Error('federation-user-not-found', `Could not find federated users using "${ identifier }"`); |
||||
} |
||||
|
||||
return federatedUsers; |
||||
} |
||||
@ -1,126 +0,0 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { HTTP } from 'meteor/http'; |
||||
|
||||
import { logger } from './logger'; |
||||
import peerDNS from './peerDNS'; |
||||
|
||||
// Should skip the retry if the error is one of the below?
|
||||
const errorsToSkipRetrying = [ |
||||
'error-app-prevented-sending', |
||||
]; |
||||
|
||||
function skipRetryOnSpecificError(err) { |
||||
return errorsToSkipRetrying.includes(err && err.errorType); |
||||
} |
||||
|
||||
// Delay method to wait a little bit before retrying
|
||||
const delay = Meteor.wrapAsync(function(ms, callback) { |
||||
Meteor.setTimeout(function() { |
||||
callback(null); |
||||
}, ms); |
||||
}); |
||||
|
||||
function doSimpleRequest(peer, method, uri, body, headers = {}) { |
||||
this.log(`Request: ${ method } ${ uri }`); |
||||
|
||||
const { url: serverBaseURL } = peer; |
||||
|
||||
const url = `${ serverBaseURL }${ uri }`; |
||||
|
||||
let data = null; |
||||
|
||||
if (method === 'POST' || method === 'PUT') { |
||||
data = body; |
||||
} |
||||
|
||||
this.log(`Sending request: ${ method } - ${ uri }`); |
||||
|
||||
return HTTP.call(method, url, { data, timeout: 2000, headers: { ...headers, 'x-federation-domain': this.config.peer.domain } }); |
||||
} |
||||
|
||||
//
|
||||
// Actually does the request, handling retries and everything
|
||||
function doRequest(peer, method, uri, body, retryInfo = {}, headers = {}) { |
||||
// Normalize retry info
|
||||
retryInfo = { |
||||
total: retryInfo.total || 1, |
||||
stepSize: retryInfo.stepSize || 100, |
||||
stepMultiplier: retryInfo.stepMultiplier || 1, |
||||
tryToUpdateDNS: retryInfo.tryToUpdateDNS === undefined ? true : retryInfo.tryToUpdateDNS, |
||||
DNSUpdated: false, |
||||
}; |
||||
|
||||
for (let i = 0; i <= retryInfo.total; i++) { |
||||
try { |
||||
return doSimpleRequest.call(this, peer, method, uri, body, headers); |
||||
} catch (err) { |
||||
try { |
||||
if (retryInfo.tryToUpdateDNS && !retryInfo.DNSUpdated) { |
||||
i--; |
||||
|
||||
retryInfo.DNSUpdated = true; |
||||
|
||||
this.log(`Trying to update local DNS cache for peer:${ peer.domain }`); |
||||
|
||||
peer = peerDNS.updatePeerDNS(peer.domain); |
||||
|
||||
continue; |
||||
} |
||||
} catch (err) { |
||||
if (err.response && err.response.statusCode === 404) { |
||||
throw new Meteor.Error('federation-peer-does-not-exist', 'Peer does not exist'); |
||||
} |
||||
} |
||||
|
||||
// Check if we need to skip due to specific error
|
||||
if (skipRetryOnSpecificError(err && err.response && err.response.data)) { |
||||
this.log('Retry: skipping due to specific error'); |
||||
|
||||
throw err; |
||||
} |
||||
|
||||
if (i === retryInfo.total - 1) { |
||||
// Throw the error, as we could not fulfill the request
|
||||
this.log('Retry: could not fulfill the request'); |
||||
|
||||
throw err; |
||||
} |
||||
|
||||
const timeToRetry = retryInfo.stepSize * (i + 1) * retryInfo.stepMultiplier; |
||||
|
||||
this.log(`Trying again in ${ timeToRetry / 1000 }s: ${ method } - ${ uri }`); |
||||
|
||||
// Otherwise, wait and try again
|
||||
delay(timeToRetry); |
||||
} |
||||
} |
||||
} |
||||
|
||||
class PeerHTTP { |
||||
constructor() { |
||||
this.config = {}; |
||||
} |
||||
|
||||
setConfig(config) { |
||||
// General
|
||||
this.config = config; |
||||
} |
||||
|
||||
log(message) { |
||||
logger.http.info(message); |
||||
} |
||||
|
||||
//
|
||||
// Direct request
|
||||
simpleRequest(peer, method, uri, body, headers) { |
||||
return doSimpleRequest.call(this, peer, method, uri, body, headers); |
||||
} |
||||
|
||||
//
|
||||
// Request trying to find DNS entries
|
||||
request(peer, method, uri, body, retryInfo = {}, headers = {}) { |
||||
return doRequest.call(this, peer, method, uri, body, retryInfo, headers); |
||||
} |
||||
} |
||||
|
||||
export default new PeerHTTP(); |
||||
Loading…
Reference in new issue