From 350e31d6b64e4973dfa5e9f7b46841cb09aeb1ad Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 14 Nov 2017 17:31:26 +0100 Subject: Follow works --- server/helpers/activitypub.ts | 29 ++++++++---- server/helpers/custom-validators/accounts.ts | 53 ++++++++++++++++++++++ .../custom-validators/activitypub/account.ts | 31 +++++++++---- .../custom-validators/activitypub/activity.ts | 19 ++++++-- .../helpers/custom-validators/activitypub/misc.ts | 10 ++-- .../custom-validators/activitypub/videos.ts | 14 +++++- server/helpers/custom-validators/index.ts | 3 +- server/helpers/custom-validators/video-accounts.ts | 53 ---------------------- server/helpers/custom-validators/webfinger.ts | 25 ++++++++++ server/helpers/webfinger.ts | 21 +++++---- 10 files changed, 164 insertions(+), 94 deletions(-) create mode 100644 server/helpers/custom-validators/accounts.ts delete mode 100644 server/helpers/custom-validators/video-accounts.ts create mode 100644 server/helpers/custom-validators/webfinger.ts (limited to 'server/helpers') diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index a1493e5c1..b91490a0b 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -5,7 +5,7 @@ import { ActivityIconObject } from '../../shared/index' import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor' import { ResultList } from '../../shared/models/result-list.model' import { database as db, REMOTE_SCHEME } from '../initializers' -import { CONFIG, STATIC_PATHS } from '../initializers/constants' +import { ACTIVITY_PUB_ACCEPT_HEADER, CONFIG, STATIC_PATHS } from '../initializers/constants' import { VideoInstance } from '../models/video/video-interface' import { isRemoteAccountValid } from './custom-validators' import { logger } from './logger' @@ -35,11 +35,11 @@ async function getOrCreateAccount (accountUrl: string) { // We don't have this account in our database, fetch it on remote if (!account) { - const { account } = await fetchRemoteAccountAndCreatePod(accountUrl) - - if (!account) throw new Error('Cannot fetch remote account.') + const res = await fetchRemoteAccountAndCreatePod(accountUrl) + if (res === undefined) throw new Error('Cannot fetch remote account.') // Save our new account in database + const account = res.account await account.save() } @@ -49,19 +49,27 @@ async function getOrCreateAccount (accountUrl: string) { async function fetchRemoteAccountAndCreatePod (accountUrl: string) { const options = { uri: accountUrl, - method: 'GET' + method: 'GET', + headers: { + 'Accept': ACTIVITY_PUB_ACCEPT_HEADER + } } + logger.info('Fetching remote account %s.', accountUrl) + let requestResult try { requestResult = await doRequest(options) } catch (err) { - logger.warning('Cannot fetch remote account %s.', accountUrl, err) + logger.warn('Cannot fetch remote account %s.', accountUrl, err) return undefined } - const accountJSON: ActivityPubActor = requestResult.body - if (isRemoteAccountValid(accountJSON) === false) return undefined + const accountJSON: ActivityPubActor = JSON.parse(requestResult.body) + if (isRemoteAccountValid(accountJSON) === false) { + logger.debug('Remote account JSON is not valid.', { accountJSON }) + return undefined + } const followersCount = await fetchAccountCount(accountJSON.followers) const followingCount = await fetchAccountCount(accountJSON.following) @@ -90,7 +98,8 @@ async function fetchRemoteAccountAndCreatePod (accountUrl: string) { host: accountHost } } - const pod = await db.Pod.findOrCreate(podOptions) + const [ pod ] = await db.Pod.findOrCreate(podOptions) + account.set('podId', pod.id) return { account, pod } } @@ -176,7 +185,7 @@ async function fetchAccountCount (url: string) { try { requestResult = await doRequest(options) } catch (err) { - logger.warning('Cannot fetch remote account count %s.', url, err) + logger.warn('Cannot fetch remote account count %s.', url, err) return undefined } diff --git a/server/helpers/custom-validators/accounts.ts b/server/helpers/custom-validators/accounts.ts new file mode 100644 index 000000000..6d6219a95 --- /dev/null +++ b/server/helpers/custom-validators/accounts.ts @@ -0,0 +1,53 @@ +import * as Promise from 'bluebird' +import * as validator from 'validator' +import * as express from 'express' +import 'express-validator' + +import { database as db } from '../../initializers' +import { AccountInstance } from '../../models' +import { logger } from '../logger' + +import { isUserUsernameValid } from './users' +import { isHostValid } from './pods' + +function isAccountNameValid (value: string) { + return isUserUsernameValid(value) +} + +function isAccountNameWithHostValid (value: string) { + const [ name, host ] = value.split('@') + + return isAccountNameValid(name) && isHostValid(host) +} + +function checkVideoAccountExists (id: string, res: express.Response, callback: () => void) { + let promise: Promise + if (validator.isInt(id)) { + promise = db.Account.load(+id) + } else { // UUID + promise = db.Account.loadByUUID(id) + } + + promise.then(account => { + if (!account) { + return res.status(404) + .json({ error: 'Video account not found' }) + .end() + } + + res.locals.account = account + callback() + }) + .catch(err => { + logger.error('Error in video account request validator.', err) + return res.sendStatus(500) + }) +} + +// --------------------------------------------------------------------------- + +export { + checkVideoAccountExists, + isAccountNameWithHostValid, + isAccountNameValid +} diff --git a/server/helpers/custom-validators/activitypub/account.ts b/server/helpers/custom-validators/activitypub/account.ts index acd2b8058..645f55a5a 100644 --- a/server/helpers/custom-validators/activitypub/account.ts +++ b/server/helpers/custom-validators/activitypub/account.ts @@ -1,9 +1,8 @@ import * as validator from 'validator' - -import { exists, isUUIDValid } from '../misc' -import { isActivityPubUrlValid } from './misc' -import { isUserUsernameValid } from '../users' import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' +import { isAccountNameValid } from '../accounts' +import { exists, isUUIDValid } from '../misc' +import { isActivityPubUrlValid, isBaseActivityValid } from './misc' function isAccountEndpointsObjectValid (endpointObject: any) { return isAccountSharedInboxValid(endpointObject.sharedInbox) @@ -59,10 +58,6 @@ function isAccountOutboxValid (outbox: string) { return isActivityPubUrlValid(outbox) } -function isAccountNameValid (name: string) { - return isUserUsernameValid(name) -} - function isAccountPreferredUsernameValid (preferredUsername: string) { return isAccountNameValid(preferredUsername) } @@ -90,7 +85,7 @@ function isRemoteAccountValid (remoteAccount: any) { isAccountPreferredUsernameValid(remoteAccount.preferredUsername) && isAccountUrlValid(remoteAccount.url) && isAccountPublicKeyObjectValid(remoteAccount.publicKey) && - isAccountEndpointsObjectValid(remoteAccount.endpoint) + isAccountEndpointsObjectValid(remoteAccount.endpoints) } function isAccountFollowingCountValid (value: string) { @@ -101,6 +96,19 @@ function isAccountFollowersCountValid (value: string) { return exists(value) && validator.isInt('' + value, { min: 0 }) } +function isAccountDeleteActivityValid (activity: any) { + return isBaseActivityValid(activity, 'Delete') +} + +function isAccountFollowActivityValid (activity: any) { + return isBaseActivityValid(activity, 'Follow') && + isActivityPubUrlValid(activity.object) +} + +function isAccountAcceptActivityValid (activity: any) { + return isBaseActivityValid(activity, 'Accept') +} + // --------------------------------------------------------------------------- export { @@ -122,5 +130,8 @@ export { isRemoteAccountValid, isAccountFollowingCountValid, isAccountFollowersCountValid, - isAccountNameValid + isAccountNameValid, + isAccountFollowActivityValid, + isAccountAcceptActivityValid, + isAccountDeleteActivityValid } diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts index dd671c4cf..b5ba0f7af 100644 --- a/server/helpers/custom-validators/activitypub/activity.ts +++ b/server/helpers/custom-validators/activitypub/activity.ts @@ -1,9 +1,13 @@ import * as validator from 'validator' +import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account' +import { isActivityPubUrlValid } from './misc' import { isVideoChannelCreateActivityValid, + isVideoChannelDeleteActivityValid, + isVideoChannelUpdateActivityValid, isVideoTorrentAddActivityValid, - isVideoTorrentUpdateActivityValid, - isVideoChannelUpdateActivityValid + isVideoTorrentDeleteActivityValid, + isVideoTorrentUpdateActivityValid } from './videos' function isRootActivityValid (activity: any) { @@ -14,8 +18,8 @@ function isRootActivityValid (activity: any) { Array.isArray(activity.items) ) || ( - validator.isURL(activity.id) && - validator.isURL(activity.actor) + isActivityPubUrlValid(activity.id) && + isActivityPubUrlValid(activity.actor) ) } @@ -23,7 +27,12 @@ function isActivityValid (activity: any) { return isVideoTorrentAddActivityValid(activity) || isVideoChannelCreateActivityValid(activity) || isVideoTorrentUpdateActivityValid(activity) || - isVideoChannelUpdateActivityValid(activity) + isVideoChannelUpdateActivityValid(activity) || + isVideoTorrentDeleteActivityValid(activity) || + isVideoChannelDeleteActivityValid(activity) || + isAccountDeleteActivityValid(activity) || + isAccountFollowActivityValid(activity) || + isAccountAcceptActivityValid(activity) } // --------------------------------------------------------------------------- diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts index a94c36b51..665a63a73 100644 --- a/server/helpers/custom-validators/activitypub/misc.ts +++ b/server/helpers/custom-validators/activitypub/misc.ts @@ -23,10 +23,12 @@ function isActivityPubUrlValid (url: string) { function isBaseActivityValid (activity: any, type: string) { return Array.isArray(activity['@context']) && activity.type === type && - validator.isURL(activity.id) && - validator.isURL(activity.actor) && - Array.isArray(activity.to) && - activity.to.every(t => validator.isURL(t)) + isActivityPubUrlValid(activity.id) && + isActivityPubUrlValid(activity.actor) && + ( + activity.to === undefined || + (Array.isArray(activity.to) && activity.to.every(t => isActivityPubUrlValid(t))) + ) } export { diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index 8f6d50f50..c9ecf1f3d 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts @@ -14,7 +14,7 @@ import { isVideoUrlValid } from '../videos' import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' -import { isBaseActivityValid } from './misc' +import { isActivityPubUrlValid, isBaseActivityValid } from './misc' function isVideoTorrentAddActivityValid (activity: any) { return isBaseActivityValid(activity, 'Add') && @@ -26,6 +26,10 @@ function isVideoTorrentUpdateActivityValid (activity: any) { isVideoTorrentObjectValid(activity.object) } +function isVideoTorrentDeleteActivityValid (activity: any) { + return isBaseActivityValid(activity, 'Delete') +} + function isVideoTorrentObjectValid (video: any) { return video.type === 'Video' && isVideoNameValid(video.name) && @@ -54,6 +58,10 @@ function isVideoChannelUpdateActivityValid (activity: any) { isVideoChannelObjectValid(activity.object) } +function isVideoChannelDeleteActivityValid (activity: any) { + return isBaseActivityValid(activity, 'Delete') +} + function isVideoChannelObjectValid (videoChannel: any) { return videoChannel.type === 'VideoChannel' && isVideoChannelNameValid(videoChannel.name) && @@ -67,7 +75,9 @@ export { isVideoTorrentAddActivityValid, isVideoChannelCreateActivityValid, isVideoTorrentUpdateActivityValid, - isVideoChannelUpdateActivityValid + isVideoChannelUpdateActivityValid, + isVideoChannelDeleteActivityValid, + isVideoTorrentDeleteActivityValid } // --------------------------------------------------------------------------- diff --git a/server/helpers/custom-validators/index.ts b/server/helpers/custom-validators/index.ts index 33922b8fe..1c475e301 100644 --- a/server/helpers/custom-validators/index.ts +++ b/server/helpers/custom-validators/index.ts @@ -3,6 +3,7 @@ export * from './misc' export * from './pods' export * from './pods' export * from './users' -export * from './video-accounts' +export * from './accounts' export * from './video-channels' export * from './videos' +export * from './webfinger' diff --git a/server/helpers/custom-validators/video-accounts.ts b/server/helpers/custom-validators/video-accounts.ts deleted file mode 100644 index 31808ae1e..000000000 --- a/server/helpers/custom-validators/video-accounts.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as Promise from 'bluebird' -import * as validator from 'validator' -import * as express from 'express' -import 'express-validator' - -import { database as db } from '../../initializers' -import { AccountInstance } from '../../models' -import { logger } from '../logger' - -import { isUserUsernameValid } from './users' -import { isHostValid } from './pods' - -function isVideoAccountNameValid (value: string) { - return isUserUsernameValid(value) -} - -function isAccountNameWithHostValid (value: string) { - const [ name, host ] = value.split('@') - - return isVideoAccountNameValid(name) && isHostValid(host) -} - -function checkVideoAccountExists (id: string, res: express.Response, callback: () => void) { - let promise: Promise - if (validator.isInt(id)) { - promise = db.Account.load(+id) - } else { // UUID - promise = db.Account.loadByUUID(id) - } - - promise.then(account => { - if (!account) { - return res.status(404) - .json({ error: 'Video account not found' }) - .end() - } - - res.locals.account = account - callback() - }) - .catch(err => { - logger.error('Error in video account request validator.', err) - return res.sendStatus(500) - }) -} - -// --------------------------------------------------------------------------- - -export { - checkVideoAccountExists, - isAccountNameWithHostValid, - isVideoAccountNameValid -} diff --git a/server/helpers/custom-validators/webfinger.ts b/server/helpers/custom-validators/webfinger.ts new file mode 100644 index 000000000..e93115d81 --- /dev/null +++ b/server/helpers/custom-validators/webfinger.ts @@ -0,0 +1,25 @@ +import 'express-validator' +import 'multer' +import { CONFIG } from '../../initializers/constants' +import { exists } from './misc' + +function isWebfingerResourceValid (value: string) { + if (!exists(value)) return false + if (value.startsWith('acct:') === false) return false + + const accountWithHost = value.substr(5) + const accountParts = accountWithHost.split('@') + if (accountParts.length !== 2) return false + + const host = accountParts[1] + + if (host !== CONFIG.WEBSERVER.HOST) return false + + return true +} + +// --------------------------------------------------------------------------- + +export { + isWebfingerResourceValid +} diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts index 164ae4951..0155e5f3e 100644 --- a/server/helpers/webfinger.ts +++ b/server/helpers/webfinger.ts @@ -12,17 +12,20 @@ const webfinger = new WebFinger({ request_timeout: 3000 }) -async function getAccountFromWebfinger (url: string) { - const webfingerData: WebFingerData = await webfingerLookup(url) +async function getAccountFromWebfinger (nameWithHost: string) { + const webfingerData: WebFingerData = await webfingerLookup(nameWithHost) - if (Array.isArray(webfingerData.links) === false) return undefined + if (Array.isArray(webfingerData.links) === false) throw new Error('WebFinger links is not an array.') const selfLink = webfingerData.links.find(l => l.rel === 'self') - if (selfLink === undefined || isActivityPubUrlValid(selfLink.href) === false) return undefined + if (selfLink === undefined || isActivityPubUrlValid(selfLink.href) === false) { + throw new Error('Cannot find self link or href is not a valid URL.') + } - const { account } = await fetchRemoteAccountAndCreatePod(selfLink.href) + const res = await fetchRemoteAccountAndCreatePod(selfLink.href) + if (res === undefined) throw new Error('Cannot fetch and create pod of remote account ' + selfLink.href) - return account + return res.account } // --------------------------------------------------------------------------- @@ -33,12 +36,12 @@ export { // --------------------------------------------------------------------------- -function webfingerLookup (url: string) { +function webfingerLookup (nameWithHost: string) { return new Promise((res, rej) => { - webfinger.lookup(url, (err, p) => { + webfinger.lookup(nameWithHost, (err, p) => { if (err) return rej(err) - return p + return res(p.object) }) }) } -- cgit v1.2.3