Follow works
This commit is contained in:
parent
e34c85e527
commit
350e31d6b6
39 changed files with 431 additions and 169 deletions
|
@ -4,12 +4,12 @@
|
|||
|
||||
<p-dataTable
|
||||
[value]="friends" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
|
||||
sortField="id" (onLazyLoad)="loadLazy($event)"
|
||||
sortField="createdAt" (onLazyLoad)="loadLazy($event)"
|
||||
>
|
||||
<p-column field="id" header="ID" [sortable]="true"></p-column>
|
||||
<p-column field="host" header="Host" [sortable]="true"></p-column>
|
||||
<p-column field="id" header="ID"></p-column>
|
||||
<p-column field="host" header="Host"></p-column>
|
||||
<p-column field="email" header="Email"></p-column>
|
||||
<p-column field="score" header="Score" [sortable]="true"></p-column>
|
||||
<p-column field="score" header="Score"></p-column>
|
||||
<p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
|
||||
<p-column header="Delete" styleClass="action-cell">
|
||||
<ng-template pTemplate="body" let-pod="rowData">
|
||||
|
|
|
@ -17,7 +17,7 @@ export class FriendListComponent extends RestTable implements OnInit {
|
|||
friends: Pod[] = []
|
||||
totalRecords = 0
|
||||
rowsPerPage = 10
|
||||
sort: SortMeta = { field: 'id', order: 1 }
|
||||
sort: SortMeta = { field: 'createdAt', order: 1 }
|
||||
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
||||
|
||||
constructor (
|
||||
|
|
|
@ -23,7 +23,7 @@ export class FriendService {
|
|||
let params = new HttpParams()
|
||||
params = this.restService.addRestGetParams(params, pagination, sort)
|
||||
|
||||
return this.authHttp.get<ResultList<Account>>(API_URL + '/followers', { params })
|
||||
return this.authHttp.get<ResultList<Account>>(API_URL + '/api/v1/pods/followers', { params })
|
||||
.map(res => this.restExtractor.convertResultListDateToHuman(res))
|
||||
.catch(res => this.restExtractor.handleError(res))
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export class FriendService {
|
|||
hosts: notEmptyHosts
|
||||
}
|
||||
|
||||
return this.authHttp.post(API_URL + '/follow', body)
|
||||
return this.authHttp.post(API_URL + '/api/v1/pods/follow', body)
|
||||
.map(this.restExtractor.extractDataBool)
|
||||
.catch(res => this.restExtractor.handleError(res))
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ db.init(false).then(() => onDatabaseInitDone())
|
|||
// ----------- PeerTube modules -----------
|
||||
import { migrate, installApplication } from './server/initializers'
|
||||
import { httpRequestJobScheduler, transcodingJobScheduler, VideosPreviewCache } from './server/lib'
|
||||
import { apiRouter, clientsRouter, staticRouter, servicesRouter } from './server/controllers'
|
||||
import { apiRouter, clientsRouter, staticRouter, servicesRouter, webfingerRouter, activityPubRouter } from './server/controllers'
|
||||
|
||||
// ----------- Command line -----------
|
||||
|
||||
|
@ -115,6 +115,9 @@ app.use(apiRoute, apiRouter)
|
|||
// Services (oembed...)
|
||||
app.use('/services', servicesRouter)
|
||||
|
||||
app.use('/', webfingerRouter)
|
||||
app.use('/', activityPubRouter)
|
||||
|
||||
// Client files
|
||||
app.use('/', clientsRouter)
|
||||
|
||||
|
|
|
@ -16,12 +16,12 @@ activityPubClientRouter.get('/account/:name',
|
|||
executeIfActivityPub(asyncMiddleware(accountController))
|
||||
)
|
||||
|
||||
activityPubClientRouter.get('/account/:nameWithHost/followers',
|
||||
activityPubClientRouter.get('/account/:name/followers',
|
||||
executeIfActivityPub(localAccountValidator),
|
||||
executeIfActivityPub(asyncMiddleware(accountFollowersController))
|
||||
)
|
||||
|
||||
activityPubClientRouter.get('/account/:nameWithHost/following',
|
||||
activityPubClientRouter.get('/account/:name/following',
|
||||
executeIfActivityPub(localAccountValidator),
|
||||
executeIfActivityPub(asyncMiddleware(accountFollowingController))
|
||||
)
|
||||
|
|
|
@ -30,7 +30,7 @@ inboxRouter.post('/inbox',
|
|||
asyncMiddleware(inboxController)
|
||||
)
|
||||
|
||||
inboxRouter.post('/:nameWithHost/inbox',
|
||||
inboxRouter.post('/account/:name/inbox',
|
||||
signatureValidator,
|
||||
asyncMiddleware(checkSignature),
|
||||
localAccountValidator,
|
||||
|
@ -59,7 +59,9 @@ async function inboxController (req: express.Request, res: express.Response, nex
|
|||
}
|
||||
|
||||
// Only keep activities we are able to process
|
||||
logger.debug('Filtering activities...', { activities })
|
||||
activities = activities.filter(a => isActivityValid(a))
|
||||
logger.debug('We keep %d activities.', activities.length, { activities })
|
||||
|
||||
await processActivities(activities, res.locals.account)
|
||||
|
||||
|
|
|
@ -4,14 +4,14 @@ import { badRequest } from '../../helpers'
|
|||
import { inboxRouter } from './inbox'
|
||||
import { activityPubClientRouter } from './client'
|
||||
|
||||
const remoteRouter = express.Router()
|
||||
const activityPubRouter = express.Router()
|
||||
|
||||
remoteRouter.use('/', inboxRouter)
|
||||
remoteRouter.use('/', activityPubClientRouter)
|
||||
remoteRouter.use('/*', badRequest)
|
||||
activityPubRouter.use('/', inboxRouter)
|
||||
activityPubRouter.use('/', activityPubClientRouter)
|
||||
activityPubRouter.use('/*', badRequest)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
remoteRouter
|
||||
activityPubRouter
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import * as Bluebird from 'bluebird'
|
||||
import * as express from 'express'
|
||||
import { UserRight } from '../../../shared/models/users/user-right.enum'
|
||||
import { getFormattedObjects } from '../../helpers'
|
||||
import { getOrCreateAccount } from '../../helpers/activitypub'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { getApplicationAccount } from '../../helpers/utils'
|
||||
import { REMOTE_SCHEME } from '../../initializers/constants'
|
||||
import { getAccountFromWebfinger } from '../../helpers/webfinger'
|
||||
import { SERVER_ACCOUNT_NAME } from '../../initializers/constants'
|
||||
import { database as db } from '../../initializers/database'
|
||||
import { sendFollow } from '../../lib/activitypub/send-request'
|
||||
import { asyncMiddleware, paginationValidator, setFollowersSort, setPagination } from '../../middlewares'
|
||||
import { authenticate } from '../../middlewares/oauth'
|
||||
import { setBodyHostsPort } from '../../middlewares/pods'
|
||||
import { setFollowingSort } from '../../middlewares/sort'
|
||||
import { ensureUserHasRight } from '../../middlewares/user-right'
|
||||
import { followValidator } from '../../middlewares/validators/pods'
|
||||
import { followersSortValidator, followingSortValidator } from '../../middlewares/validators/sort'
|
||||
import { sendFollow } from '../../lib/activitypub/send-request'
|
||||
import { authenticate } from '../../middlewares/oauth'
|
||||
import { ensureUserHasRight } from '../../middlewares/user-right'
|
||||
import { UserRight } from '../../../shared/models/users/user-right.enum'
|
||||
|
||||
const podsRouter = express.Router()
|
||||
|
||||
|
@ -67,22 +67,43 @@ async function follow (req: express.Request, res: express.Response, next: expres
|
|||
const hosts = req.body.hosts as string[]
|
||||
const fromAccount = await getApplicationAccount()
|
||||
|
||||
const tasks: Bluebird<any>[] = []
|
||||
const tasks: Promise<any>[] = []
|
||||
const accountName = SERVER_ACCOUNT_NAME
|
||||
|
||||
for (const host of hosts) {
|
||||
const url = REMOTE_SCHEME.HTTP + '://' + host
|
||||
const targetAccount = await getOrCreateAccount(url)
|
||||
|
||||
// We process each host in a specific transaction
|
||||
// First, we add the follow request in the database
|
||||
// Then we send the follow request to other account
|
||||
const p = db.sequelize.transaction(async t => {
|
||||
return db.AccountFollow.create({
|
||||
accountId: fromAccount.id,
|
||||
targetAccountId: targetAccount.id,
|
||||
state: 'pending'
|
||||
const p = loadLocalOrGetAccountFromWebfinger(accountName, host)
|
||||
.then(accountResult => {
|
||||
let targetAccount = accountResult.account
|
||||
|
||||
return db.sequelize.transaction(async t => {
|
||||
if (accountResult.loadedFromDB === false) {
|
||||
targetAccount = await targetAccount.save({ transaction: t })
|
||||
}
|
||||
|
||||
const [ accountFollow ] = await db.AccountFollow.findOrCreate({
|
||||
where: {
|
||||
accountId: fromAccount.id,
|
||||
targetAccountId: targetAccount.id
|
||||
},
|
||||
defaults: {
|
||||
state: 'pending',
|
||||
accountId: fromAccount.id,
|
||||
targetAccountId: targetAccount.id
|
||||
},
|
||||
transaction: t
|
||||
})
|
||||
|
||||
// Send a notification to remote server
|
||||
if (accountFollow.state === 'pending') {
|
||||
await sendFollow(fromAccount, targetAccount, t)
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(() => sendFollow(fromAccount, targetAccount, t))
|
||||
})
|
||||
.catch(err => logger.warn('Cannot follow server %s.', `${accountName}@${host}`, err))
|
||||
|
||||
tasks.push(p)
|
||||
}
|
||||
|
@ -91,3 +112,16 @@ async function follow (req: express.Request, res: express.Response, next: expres
|
|||
|
||||
return res.status(204).end()
|
||||
}
|
||||
|
||||
async function loadLocalOrGetAccountFromWebfinger (name: string, host: string) {
|
||||
let loadedFromDB = true
|
||||
let account = await db.Account.loadByNameAndHost(name, host)
|
||||
|
||||
if (!account) {
|
||||
const nameWithDomain = name + '@' + host
|
||||
account = await getAccountFromWebfinger(nameWithDomain)
|
||||
loadedFromDB = false
|
||||
}
|
||||
|
||||
return { account, loadedFromDB }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export * from './activitypub'
|
||||
export * from './static'
|
||||
export * from './client'
|
||||
export * from './services'
|
||||
export * from './api'
|
||||
export * from './webfinger'
|
||||
|
|
39
server/controllers/webfinger.ts
Normal file
39
server/controllers/webfinger.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import * as express from 'express'
|
||||
|
||||
import { CONFIG, PREVIEWS_SIZE, EMBED_SIZE } from '../initializers'
|
||||
import { oembedValidator } from '../middlewares'
|
||||
import { VideoInstance } from '../models'
|
||||
import { webfingerValidator } from '../middlewares/validators/webfinger'
|
||||
import { AccountInstance } from '../models/account/account-interface'
|
||||
|
||||
const webfingerRouter = express.Router()
|
||||
|
||||
webfingerRouter.use('/.well-known/webfinger',
|
||||
webfingerValidator,
|
||||
webfingerController
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
webfingerRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const account: AccountInstance = res.locals.account
|
||||
|
||||
const json = {
|
||||
subject: req.query.resource,
|
||||
aliases: [ account.url ],
|
||||
links: [
|
||||
{
|
||||
rel: 'self',
|
||||
href: account.url
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return res.json(json).end()
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -10,14 +10,14 @@ import { logger } from '../logger'
|
|||
import { isUserUsernameValid } from './users'
|
||||
import { isHostValid } from './pods'
|
||||
|
||||
function isVideoAccountNameValid (value: string) {
|
||||
function isAccountNameValid (value: string) {
|
||||
return isUserUsernameValid(value)
|
||||
}
|
||||
|
||||
function isAccountNameWithHostValid (value: string) {
|
||||
const [ name, host ] = value.split('@')
|
||||
|
||||
return isVideoAccountNameValid(name) && isHostValid(host)
|
||||
return isAccountNameValid(name) && isHostValid(host)
|
||||
}
|
||||
|
||||
function checkVideoAccountExists (id: string, res: express.Response, callback: () => void) {
|
||||
|
@ -38,10 +38,10 @@ function checkVideoAccountExists (id: string, res: express.Response, callback: (
|
|||
res.locals.account = account
|
||||
callback()
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Error in video account request validator.', err)
|
||||
return res.sendStatus(500)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Error in video account request validator.', err)
|
||||
return res.sendStatus(500)
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -49,5 +49,5 @@ function checkVideoAccountExists (id: string, res: express.Response, callback: (
|
|||
export {
|
||||
checkVideoAccountExists,
|
||||
isAccountNameWithHostValid,
|
||||
isVideoAccountNameValid
|
||||
isAccountNameValid
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -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'
|
||||
|
|
25
server/helpers/custom-validators/webfinger.ts
Normal file
25
server/helpers/custom-validators/webfinger.ts
Normal file
|
@ -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
|
||||
}
|
|
@ -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<WebFingerData>((res, rej) => {
|
||||
webfinger.lookup(url, (err, p) => {
|
||||
webfinger.lookup(nameWithHost, (err, p) => {
|
||||
if (err) return rej(err)
|
||||
|
||||
return p
|
||||
return res(p.object)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as config from 'config'
|
||||
|
||||
import { promisify0 } from '../helpers/core-utils'
|
||||
import { OAuthClientModel } from '../models/oauth/oauth-client-interface'
|
||||
import { UserModel } from '../models/account/user-interface'
|
||||
import { ApplicationModel } from '../models/application/application-interface'
|
||||
import { OAuthClientModel } from '../models/oauth/oauth-client-interface'
|
||||
|
||||
// Some checks on configuration files
|
||||
function checkConfig () {
|
||||
|
@ -70,6 +70,13 @@ async function usersExist (User: UserModel) {
|
|||
return totalUsers !== 0
|
||||
}
|
||||
|
||||
// We get db by param to not import it in this file (import orders)
|
||||
async function applicationExist (Application: ApplicationModel) {
|
||||
const totalApplication = await Application.countTotal()
|
||||
|
||||
return totalApplication !== 0
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -77,5 +84,6 @@ export {
|
|||
checkFFmpeg,
|
||||
checkMissedConfig,
|
||||
clientsExist,
|
||||
usersExist
|
||||
usersExist,
|
||||
applicationExist
|
||||
}
|
||||
|
|
|
@ -226,6 +226,9 @@ const FRIEND_SCORE = {
|
|||
MAX: 1000
|
||||
}
|
||||
|
||||
const SERVER_ACCOUNT_NAME = 'peertube'
|
||||
const ACTIVITY_PUB_ACCEPT_HEADER = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
|
||||
const ACTIVITY_PUB = {
|
||||
COLLECTION_ITEMS_PER_PAGE: 10,
|
||||
VIDEO_URL_MIME_TYPES: [
|
||||
|
@ -352,8 +355,10 @@ export {
|
|||
PODS_SCORE,
|
||||
PREVIEWS_SIZE,
|
||||
REMOTE_SCHEME,
|
||||
ACTIVITY_PUB_ACCEPT_HEADER,
|
||||
FOLLOW_STATES,
|
||||
SEARCHABLE_COLUMNS,
|
||||
SERVER_ACCOUNT_NAME,
|
||||
PRIVATE_RSA_KEY_SIZE,
|
||||
SORTABLE_COLUMNS,
|
||||
STATIC_MAX_AGE,
|
||||
|
|
|
@ -3,8 +3,8 @@ import { UserRole } from '../../shared'
|
|||
import { logger, mkdirpPromise, rimrafPromise } from '../helpers'
|
||||
import { createUserAccountAndChannel } from '../lib'
|
||||
import { createLocalAccount } from '../lib/user'
|
||||
import { clientsExist, usersExist } from './checker'
|
||||
import { CACHE, CONFIG, LAST_MIGRATION_VERSION } from './constants'
|
||||
import { applicationExist, clientsExist, usersExist } from './checker'
|
||||
import { CACHE, CONFIG, LAST_MIGRATION_VERSION, SERVER_ACCOUNT_NAME } from './constants'
|
||||
|
||||
import { database as db } from './database'
|
||||
|
||||
|
@ -128,9 +128,13 @@ async function createOAuthAdminIfNotExist () {
|
|||
}
|
||||
|
||||
async function createApplicationIfNotExist () {
|
||||
const exist = await applicationExist(db.Application)
|
||||
// Nothing to do, application already exist
|
||||
if (exist === true) return undefined
|
||||
|
||||
logger.info('Creating Application table.')
|
||||
const applicationInstance = await db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION })
|
||||
|
||||
logger.info('Creating application account.')
|
||||
return createLocalAccount('peertube', null, applicationInstance.id, undefined)
|
||||
return createLocalAccount(SERVER_ACCOUNT_NAME, null, applicationInstance.id, undefined)
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ async function addRemoteVideo (account: AccountInstance, videoChannelUrl: string
|
|||
|
||||
// Don't block on request
|
||||
generateThumbnailFromUrl(video, videoToCreateData.icon)
|
||||
.catch(err => logger.warning('Cannot generate thumbnail of %s.', videoToCreateData.id, err))
|
||||
.catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err))
|
||||
|
||||
const videoCreated = await video.save(sequelizeOptions)
|
||||
|
||||
|
|
|
@ -36,14 +36,18 @@ async function follow (account: AccountInstance, targetAccountURL: string) {
|
|||
if (targetAccount === undefined) throw new Error('Unknown account')
|
||||
if (targetAccount.isOwned() === false) throw new Error('This is not a local account.')
|
||||
|
||||
const sequelizeOptions = {
|
||||
await db.AccountFollow.findOrCreate({
|
||||
where: {
|
||||
accountId: account.id,
|
||||
targetAccountId: targetAccount.id
|
||||
},
|
||||
defaults: {
|
||||
accountId: account.id,
|
||||
targetAccountId: targetAccount.id,
|
||||
state: 'accepted'
|
||||
},
|
||||
transaction: t
|
||||
}
|
||||
await db.AccountFollow.create({
|
||||
accountId: account.id,
|
||||
targetAccountId: targetAccount.id,
|
||||
state: 'accepted'
|
||||
}, sequelizeOptions)
|
||||
})
|
||||
|
||||
// Target sends to account he accepted the follow request
|
||||
return sendAccept(targetAccount, account, t)
|
||||
|
|
|
@ -10,60 +10,60 @@ import { httpRequestJobScheduler } from '../jobs'
|
|||
import { signObject, activityPubContextify } from '../../helpers'
|
||||
import { Activity } from '../../../shared'
|
||||
|
||||
function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
|
||||
async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
|
||||
const videoChannelObject = videoChannel.toActivityPubObject()
|
||||
const data = createActivityData(videoChannel.url, videoChannel.Account, videoChannelObject)
|
||||
const data = await createActivityData(videoChannel.url, videoChannel.Account, videoChannelObject)
|
||||
|
||||
return broadcastToFollowers(data, videoChannel.Account, t)
|
||||
}
|
||||
|
||||
function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
|
||||
async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
|
||||
const videoChannelObject = videoChannel.toActivityPubObject()
|
||||
const data = updateActivityData(videoChannel.url, videoChannel.Account, videoChannelObject)
|
||||
const data = await updateActivityData(videoChannel.url, videoChannel.Account, videoChannelObject)
|
||||
|
||||
return broadcastToFollowers(data, videoChannel.Account, t)
|
||||
}
|
||||
|
||||
function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
|
||||
const data = deleteActivityData(videoChannel.url, videoChannel.Account)
|
||||
async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
|
||||
const data = await deleteActivityData(videoChannel.url, videoChannel.Account)
|
||||
|
||||
return broadcastToFollowers(data, videoChannel.Account, t)
|
||||
}
|
||||
|
||||
function sendAddVideo (video: VideoInstance, t: Sequelize.Transaction) {
|
||||
async function sendAddVideo (video: VideoInstance, t: Sequelize.Transaction) {
|
||||
const videoObject = video.toActivityPubObject()
|
||||
const data = addActivityData(video.url, video.VideoChannel.Account, video.VideoChannel.url, videoObject)
|
||||
const data = await addActivityData(video.url, video.VideoChannel.Account, video.VideoChannel.url, videoObject)
|
||||
|
||||
return broadcastToFollowers(data, video.VideoChannel.Account, t)
|
||||
}
|
||||
|
||||
function sendUpdateVideo (video: VideoInstance, t: Sequelize.Transaction) {
|
||||
async function sendUpdateVideo (video: VideoInstance, t: Sequelize.Transaction) {
|
||||
const videoObject = video.toActivityPubObject()
|
||||
const data = updateActivityData(video.url, video.VideoChannel.Account, videoObject)
|
||||
const data = await updateActivityData(video.url, video.VideoChannel.Account, videoObject)
|
||||
|
||||
return broadcastToFollowers(data, video.VideoChannel.Account, t)
|
||||
}
|
||||
|
||||
function sendDeleteVideo (video: VideoInstance, t: Sequelize.Transaction) {
|
||||
const data = deleteActivityData(video.url, video.VideoChannel.Account)
|
||||
async function sendDeleteVideo (video: VideoInstance, t: Sequelize.Transaction) {
|
||||
const data = await deleteActivityData(video.url, video.VideoChannel.Account)
|
||||
|
||||
return broadcastToFollowers(data, video.VideoChannel.Account, t)
|
||||
}
|
||||
|
||||
function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transaction) {
|
||||
const data = deleteActivityData(account.url, account)
|
||||
async function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transaction) {
|
||||
const data = await deleteActivityData(account.url, account)
|
||||
|
||||
return broadcastToFollowers(data, account, t)
|
||||
}
|
||||
|
||||
function sendAccept (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) {
|
||||
const data = acceptActivityData(fromAccount)
|
||||
async function sendAccept (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) {
|
||||
const data = await acceptActivityData(fromAccount)
|
||||
|
||||
return unicastTo(data, toAccount, t)
|
||||
}
|
||||
|
||||
function sendFollow (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) {
|
||||
const data = followActivityData(toAccount.url, fromAccount)
|
||||
async function sendFollow (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) {
|
||||
const data = await followActivityData(toAccount.url, fromAccount)
|
||||
|
||||
return unicastTo(data, toAccount, t)
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t:
|
|||
|
||||
async function unicastTo (data: any, toAccount: AccountInstance, t: Sequelize.Transaction) {
|
||||
const jobPayload = {
|
||||
uris: [ toAccount.url ],
|
||||
uris: [ toAccount.inboxUrl ],
|
||||
body: data
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ async function process (payload: HTTPRequestPayload, jobId: number) {
|
|||
logger.info('Processing broadcast in job %d.', jobId)
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
uri: '',
|
||||
json: payload.body
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ async function process (payload: HTTPRequestPayload, jobId: number) {
|
|||
|
||||
const uri = payload.uris[0]
|
||||
const options = {
|
||||
method: 'POST',
|
||||
uri,
|
||||
json: payload.body
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { JobCategory } from '../../../shared'
|
|||
import { logger } from '../../helpers'
|
||||
import { database as db, JOB_STATES, JOBS_FETCH_LIMIT_PER_CYCLE, JOBS_FETCHING_INTERVAL } from '../../initializers'
|
||||
import { JobInstance } from '../../models'
|
||||
import { error } from 'util'
|
||||
|
||||
export interface JobHandler<P, T> {
|
||||
process (data: object, jobId: number): Promise<T>
|
||||
|
@ -80,8 +81,12 @@ class JobScheduler<P, T> {
|
|||
private async processJob (job: JobInstance, callback: (err: Error) => void) {
|
||||
const jobHandler = this.jobHandlers[job.handlerName]
|
||||
if (jobHandler === undefined) {
|
||||
logger.error('Unknown job handler for job %s.', job.handlerName)
|
||||
return callback(null)
|
||||
const errorString = 'Unknown job handler ' + job.handlerName + ' for job ' + job.id
|
||||
logger.error(errorString)
|
||||
|
||||
const error = new Error(errorString)
|
||||
await this.onJobError(jobHandler, job, error)
|
||||
return callback(error)
|
||||
}
|
||||
|
||||
logger.info('Processing job %d with handler %s.', job.id, job.handlerName)
|
||||
|
@ -103,7 +108,7 @@ class JobScheduler<P, T> {
|
|||
}
|
||||
}
|
||||
|
||||
callback(null)
|
||||
return callback(null)
|
||||
}
|
||||
|
||||
private async onJobError (jobHandler: JobHandler<P, T>, job: JobInstance, err: Error) {
|
||||
|
@ -111,7 +116,7 @@ class JobScheduler<P, T> {
|
|||
|
||||
try {
|
||||
await job.save()
|
||||
await jobHandler.onError(err, job.id)
|
||||
if (jobHandler) await jobHandler.onError(err, job.id)
|
||||
} catch (err) {
|
||||
this.cannotSaveJobError(err)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import { Request, Response, NextFunction } from 'express'
|
||||
|
||||
import { database as db } from '../initializers'
|
||||
import {
|
||||
logger,
|
||||
getAccountFromWebfinger,
|
||||
isSignatureVerified
|
||||
} from '../helpers'
|
||||
import { NextFunction, Request, Response, RequestHandler } from 'express'
|
||||
import { ActivityPubSignature } from '../../shared'
|
||||
import { isSignatureVerified, logger } from '../helpers'
|
||||
import { fetchRemoteAccountAndCreatePod } from '../helpers/activitypub'
|
||||
import { database as db, ACTIVITY_PUB_ACCEPT_HEADER } from '../initializers'
|
||||
import { each, eachSeries, waterfall } from 'async'
|
||||
|
||||
async function checkSignature (req: Request, res: Response, next: NextFunction) {
|
||||
const signatureObject: ActivityPubSignature = req.body.signature
|
||||
|
@ -17,35 +14,40 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
|
|||
|
||||
// We don't have this account in our database, fetch it on remote
|
||||
if (!account) {
|
||||
account = await getAccountFromWebfinger(signatureObject.creator)
|
||||
const accountResult = await fetchRemoteAccountAndCreatePod(signatureObject.creator)
|
||||
|
||||
if (!account) {
|
||||
if (!accountResult) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
// Save our new account in database
|
||||
account = accountResult.account
|
||||
await account.save()
|
||||
}
|
||||
|
||||
const verified = await isSignatureVerified(account, req.body)
|
||||
if (verified === false) return res.sendStatus(403)
|
||||
|
||||
res.locals.signature.account = account
|
||||
res.locals.signature = {
|
||||
account
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
function executeIfActivityPub (fun: any | any[]) {
|
||||
function executeIfActivityPub (fun: RequestHandler | RequestHandler[]) {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
if (req.header('Accept') !== 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"') {
|
||||
if (req.header('Accept') !== ACTIVITY_PUB_ACCEPT_HEADER) {
|
||||
return next()
|
||||
}
|
||||
|
||||
if (Array.isArray(fun) === true) {
|
||||
fun[0](req, res, next) // FIXME: doesn't work
|
||||
return eachSeries(fun as RequestHandler[], (f, cb) => {
|
||||
f(req, res, cb)
|
||||
}, next)
|
||||
}
|
||||
|
||||
return fun(req, res, next)
|
||||
return (fun as RequestHandler)(req, res, next)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,13 +8,13 @@ import {
|
|||
isUserVideoQuotaValid,
|
||||
logger
|
||||
} from '../../helpers'
|
||||
import { isAccountNameWithHostValid } from '../../helpers/custom-validators/video-accounts'
|
||||
import { isAccountNameValid } from '../../helpers/custom-validators/accounts'
|
||||
import { database as db } from '../../initializers/database'
|
||||
import { AccountInstance } from '../../models'
|
||||
import { checkErrors } from './utils'
|
||||
|
||||
const localAccountValidator = [
|
||||
param('nameWithHost').custom(isAccountNameWithHostValid).withMessage('Should have a valid account with domain name (myuser@domain.tld)'),
|
||||
param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking localAccountValidator parameters', { parameters: req.params })
|
||||
|
@ -33,10 +33,8 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function checkLocalAccountExists (nameWithHost: string, res: express.Response, callback: (err: Error, account: AccountInstance) => void) {
|
||||
const [ name, host ] = nameWithHost.split('@')
|
||||
|
||||
db.Account.loadLocalAccountByNameAndPod(name, host)
|
||||
function checkLocalAccountExists (name: string, res: express.Response, callback: (err: Error, account: AccountInstance) => void) {
|
||||
db.Account.loadLocalByName(name)
|
||||
.then(account => {
|
||||
if (!account) {
|
||||
return res.status(404)
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { body } from 'express-validator/check'
|
||||
import * as express from 'express'
|
||||
|
||||
import { logger, isRootActivityValid } from '../../../helpers'
|
||||
import { body } from 'express-validator/check'
|
||||
import { isRootActivityValid, logger } from '../../../helpers'
|
||||
import { checkErrors } from '../utils'
|
||||
|
||||
const activityPubValidator = [
|
||||
body('data').custom(isRootActivityValid),
|
||||
body('').custom((value, { req }) => isRootActivityValid(req.body)),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking activity pub parameters', { parameters: req.body })
|
||||
|
|
|
@ -8,3 +8,4 @@ export * from './users'
|
|||
export * from './videos'
|
||||
export * from './video-blacklist'
|
||||
export * from './video-channels'
|
||||
export * from './webfinger'
|
||||
|
|
42
server/middlewares/validators/webfinger.ts
Normal file
42
server/middlewares/validators/webfinger.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { query } from 'express-validator/check'
|
||||
import * as express from 'express'
|
||||
|
||||
import { checkErrors } from './utils'
|
||||
import { logger, isWebfingerResourceValid } from '../../helpers'
|
||||
import { database as db } from '../../initializers'
|
||||
|
||||
const webfingerValidator = [
|
||||
query('resource').custom(isWebfingerResourceValid).withMessage('Should have a valid webfinger resource'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking webfinger parameters', { parameters: req.query })
|
||||
|
||||
checkErrors(req, res, () => {
|
||||
// Remove 'acct:' from the beginning of the string
|
||||
const nameWithHost = req.query.resource.substr(5)
|
||||
const [ name, ] = nameWithHost.split('@')
|
||||
|
||||
db.Account.loadLocalByName(name)
|
||||
.then(account => {
|
||||
if (!account) {
|
||||
return res.status(404)
|
||||
.send({ error: 'Account not found' })
|
||||
.end()
|
||||
}
|
||||
|
||||
res.locals.account = account
|
||||
return next()
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Error in webfinger validator.', err)
|
||||
return res.sendStatus(500)
|
||||
})
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
webfingerValidator
|
||||
}
|
|
@ -19,11 +19,13 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
{
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'accountId' ],
|
||||
unique: true
|
||||
fields: [ 'accountId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'targetAccountId' ],
|
||||
fields: [ 'targetAccountId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'accountId', 'targetAccountId' ],
|
||||
unique: true
|
||||
}
|
||||
]
|
||||
|
@ -31,7 +33,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
)
|
||||
|
||||
const classMethods = [
|
||||
associate
|
||||
associate,
|
||||
loadByAccountAndTarget
|
||||
]
|
||||
addMethodsToModel(AccountFollow, classMethods)
|
||||
|
||||
|
@ -46,7 +49,7 @@ function associate (models) {
|
|||
name: 'accountId',
|
||||
allowNull: false
|
||||
},
|
||||
as: 'followers',
|
||||
as: 'accountFollowers',
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
|
||||
|
@ -55,7 +58,7 @@ function associate (models) {
|
|||
name: 'targetAccountId',
|
||||
allowNull: false
|
||||
},
|
||||
as: 'following',
|
||||
as: 'accountFollowing',
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ export namespace AccountMethods {
|
|||
export type LoadByUUID = (uuid: string) => Bluebird<AccountInstance>
|
||||
export type LoadByUrl = (url: string, transaction?: Sequelize.Transaction) => Bluebird<AccountInstance>
|
||||
export type LoadAccountByPodAndUUID = (uuid: string, podId: number, transaction: Sequelize.Transaction) => Bluebird<AccountInstance>
|
||||
export type LoadLocalAccountByNameAndPod = (name: string, host: string) => Bluebird<AccountInstance>
|
||||
export type LoadLocalByName = (name: string) => Bluebird<AccountInstance>
|
||||
export type LoadByNameAndHost = (name: string, host: string) => Bluebird<AccountInstance>
|
||||
export type ListOwned = () => Bluebird<AccountInstance[]>
|
||||
export type ListAcceptedFollowerUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> >
|
||||
export type ListAcceptedFollowingUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> >
|
||||
|
@ -34,7 +35,8 @@ export interface AccountClass {
|
|||
load: AccountMethods.Load
|
||||
loadByUUID: AccountMethods.LoadByUUID
|
||||
loadByUrl: AccountMethods.LoadByUrl
|
||||
loadLocalAccountByNameAndPod: AccountMethods.LoadLocalAccountByNameAndPod
|
||||
loadLocalByName: AccountMethods.LoadLocalByName
|
||||
loadByNameAndHost: AccountMethods.LoadByNameAndHost
|
||||
listOwned: AccountMethods.ListOwned
|
||||
listAcceptedFollowerUrlsForApi: AccountMethods.ListAcceptedFollowerUrlsForApi
|
||||
listAcceptedFollowingUrlsForApi: AccountMethods.ListAcceptedFollowingUrlsForApi
|
||||
|
|
|
@ -31,7 +31,8 @@ let load: AccountMethods.Load
|
|||
let loadApplication: AccountMethods.LoadApplication
|
||||
let loadByUUID: AccountMethods.LoadByUUID
|
||||
let loadByUrl: AccountMethods.LoadByUrl
|
||||
let loadLocalAccountByNameAndPod: AccountMethods.LoadLocalAccountByNameAndPod
|
||||
let loadLocalByName: AccountMethods.LoadLocalByName
|
||||
let loadByNameAndHost: AccountMethods.LoadByNameAndHost
|
||||
let listOwned: AccountMethods.ListOwned
|
||||
let listAcceptedFollowerUrlsForApi: AccountMethods.ListAcceptedFollowerUrlsForApi
|
||||
let listAcceptedFollowingUrlsForApi: AccountMethods.ListAcceptedFollowingUrlsForApi
|
||||
|
@ -88,7 +89,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
|
|||
},
|
||||
privateKey: {
|
||||
type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max),
|
||||
allowNull: false,
|
||||
allowNull: true,
|
||||
validate: {
|
||||
privateKeyValid: value => {
|
||||
const res = isAccountPrivateKeyValid(value)
|
||||
|
@ -199,7 +200,8 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
|
|||
load,
|
||||
loadByUUID,
|
||||
loadByUrl,
|
||||
loadLocalAccountByNameAndPod,
|
||||
loadLocalByName,
|
||||
loadByNameAndHost,
|
||||
listOwned,
|
||||
listAcceptedFollowerUrlsForApi,
|
||||
listAcceptedFollowingUrlsForApi,
|
||||
|
@ -330,6 +332,8 @@ getFollowerSharedInboxUrls = function (this: AccountInstance) {
|
|||
include: [
|
||||
{
|
||||
model: Account['sequelize'].models.AccountFollow,
|
||||
required: true,
|
||||
as: 'followers',
|
||||
where: {
|
||||
targetAccountId: this.id
|
||||
}
|
||||
|
@ -387,7 +391,7 @@ listFollowingForApi = function (id: number, start: number, count: number, sort:
|
|||
include: [
|
||||
{
|
||||
model: Account['sequelize'].models.Account,
|
||||
as: 'following',
|
||||
as: 'accountFollowing',
|
||||
required: true,
|
||||
include: [ Account['sequelize'].models.Pod ]
|
||||
}
|
||||
|
@ -418,7 +422,7 @@ listFollowersForApi = function (id: number, start: number, count: number, sort:
|
|||
include: [
|
||||
{
|
||||
model: Account['sequelize'].models.Account,
|
||||
as: 'followers',
|
||||
as: 'accountFollowers',
|
||||
required: true,
|
||||
include: [ Account['sequelize'].models.Pod ]
|
||||
}
|
||||
|
@ -439,7 +443,7 @@ loadApplication = function () {
|
|||
return Account.findOne({
|
||||
include: [
|
||||
{
|
||||
model: Account['sequelize'].model.Application,
|
||||
model: Account['sequelize'].models.Application,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
|
@ -460,17 +464,37 @@ loadByUUID = function (uuid: string) {
|
|||
return Account.findOne(query)
|
||||
}
|
||||
|
||||
loadLocalAccountByNameAndPod = function (name: string, host: string) {
|
||||
loadLocalByName = function (name: string) {
|
||||
const query: Sequelize.FindOptions<AccountAttributes> = {
|
||||
where: {
|
||||
name,
|
||||
userId: {
|
||||
[Sequelize.Op.ne]: null
|
||||
}
|
||||
[Sequelize.Op.or]: [
|
||||
{
|
||||
userId: {
|
||||
[Sequelize.Op.ne]: null
|
||||
}
|
||||
},
|
||||
{
|
||||
applicationId: {
|
||||
[Sequelize.Op.ne]: null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return Account.findOne(query)
|
||||
}
|
||||
|
||||
loadByNameAndHost = function (name: string, host: string) {
|
||||
const query: Sequelize.FindOptions<AccountAttributes> = {
|
||||
where: {
|
||||
name
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Account['sequelize'].models.Pod,
|
||||
required: true,
|
||||
where: {
|
||||
host
|
||||
}
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import * as Promise from 'bluebird'
|
||||
import * as Bluebird from 'bluebird'
|
||||
|
||||
export namespace ApplicationMethods {
|
||||
export type LoadMigrationVersion = () => Promise<number>
|
||||
export type LoadMigrationVersion = () => Bluebird<number>
|
||||
|
||||
export type UpdateMigrationVersion = (
|
||||
newVersion: number,
|
||||
transaction: Sequelize.Transaction
|
||||
) => Promise<[ number, ApplicationInstance[] ]>
|
||||
) => Bluebird<[ number, ApplicationInstance[] ]>
|
||||
|
||||
export type CountTotal = () => Bluebird<number>
|
||||
}
|
||||
|
||||
export interface ApplicationClass {
|
||||
loadMigrationVersion: ApplicationMethods.LoadMigrationVersion
|
||||
updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion
|
||||
countTotal: ApplicationMethods.CountTotal
|
||||
}
|
||||
|
||||
export interface ApplicationAttributes {
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
let Application: Sequelize.Model<ApplicationInstance, ApplicationAttributes>
|
||||
let loadMigrationVersion: ApplicationMethods.LoadMigrationVersion
|
||||
let updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion
|
||||
let countTotal: ApplicationMethods.CountTotal
|
||||
|
||||
export default function defineApplication (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
|
||||
Application = sequelize.define<ApplicationInstance, ApplicationAttributes>('Application',
|
||||
|
@ -26,7 +27,11 @@ export default function defineApplication (sequelize: Sequelize.Sequelize, DataT
|
|||
}
|
||||
)
|
||||
|
||||
const classMethods = [ loadMigrationVersion, updateMigrationVersion ]
|
||||
const classMethods = [
|
||||
countTotal,
|
||||
loadMigrationVersion,
|
||||
updateMigrationVersion
|
||||
]
|
||||
addMethodsToModel(Application, classMethods)
|
||||
|
||||
return Application
|
||||
|
@ -34,6 +39,10 @@ export default function defineApplication (sequelize: Sequelize.Sequelize, DataT
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
countTotal = function () {
|
||||
return this.count()
|
||||
}
|
||||
|
||||
loadMigrationVersion = function () {
|
||||
const query = {
|
||||
attributes: [ 'migrationVersion' ]
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
|
||||
JobMethods
|
||||
} from './job-interface'
|
||||
import { JobState } from '../../../shared/models/job.model'
|
||||
import { JobCategory, JobState } from '../../../shared/models/job.model'
|
||||
|
||||
let Job: Sequelize.Model<JobInstance, JobAttributes>
|
||||
let listWithLimitByCategory: JobMethods.ListWithLimitByCategory
|
||||
|
@ -38,7 +38,7 @@ export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Se
|
|||
{
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'state' ]
|
||||
fields: [ 'state', 'category' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -52,14 +52,15 @@ export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Se
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
listWithLimitByCategory = function (limit: number, state: JobState) {
|
||||
listWithLimitByCategory = function (limit: number, state: JobState, jobCategory: JobCategory) {
|
||||
const query = {
|
||||
order: [
|
||||
[ 'id', 'ASC' ]
|
||||
],
|
||||
limit: limit,
|
||||
where: {
|
||||
state
|
||||
state,
|
||||
category: jobCategory
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue