diff options
39 files changed, 431 insertions, 169 deletions
diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.html b/client/src/app/+admin/friends/friend-list/friend-list.component.html index 7e92ced54..df5a570fd 100644 --- a/client/src/app/+admin/friends/friend-list/friend-list.component.html +++ b/client/src/app/+admin/friends/friend-list/friend-list.component.html | |||
@@ -4,12 +4,12 @@ | |||
4 | 4 | ||
5 | <p-dataTable | 5 | <p-dataTable |
6 | [value]="friends" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" | 6 | [value]="friends" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" |
7 | sortField="id" (onLazyLoad)="loadLazy($event)" | 7 | sortField="createdAt" (onLazyLoad)="loadLazy($event)" |
8 | > | 8 | > |
9 | <p-column field="id" header="ID" [sortable]="true"></p-column> | 9 | <p-column field="id" header="ID"></p-column> |
10 | <p-column field="host" header="Host" [sortable]="true"></p-column> | 10 | <p-column field="host" header="Host"></p-column> |
11 | <p-column field="email" header="Email"></p-column> | 11 | <p-column field="email" header="Email"></p-column> |
12 | <p-column field="score" header="Score" [sortable]="true"></p-column> | 12 | <p-column field="score" header="Score"></p-column> |
13 | <p-column field="createdAt" header="Created date" [sortable]="true"></p-column> | 13 | <p-column field="createdAt" header="Created date" [sortable]="true"></p-column> |
14 | <p-column header="Delete" styleClass="action-cell"> | 14 | <p-column header="Delete" styleClass="action-cell"> |
15 | <ng-template pTemplate="body" let-pod="rowData"> | 15 | <ng-template pTemplate="body" let-pod="rowData"> |
diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.ts b/client/src/app/+admin/friends/friend-list/friend-list.component.ts index 0323ae96d..3fa8ef19f 100644 --- a/client/src/app/+admin/friends/friend-list/friend-list.component.ts +++ b/client/src/app/+admin/friends/friend-list/friend-list.component.ts | |||
@@ -17,7 +17,7 @@ export class FriendListComponent extends RestTable implements OnInit { | |||
17 | friends: Pod[] = [] | 17 | friends: Pod[] = [] |
18 | totalRecords = 0 | 18 | totalRecords = 0 |
19 | rowsPerPage = 10 | 19 | rowsPerPage = 10 |
20 | sort: SortMeta = { field: 'id', order: 1 } | 20 | sort: SortMeta = { field: 'createdAt', order: 1 } |
21 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 21 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
22 | 22 | ||
23 | constructor ( | 23 | constructor ( |
diff --git a/client/src/app/+admin/friends/shared/friend.service.ts b/client/src/app/+admin/friends/shared/friend.service.ts index 083a2fce0..867656a53 100644 --- a/client/src/app/+admin/friends/shared/friend.service.ts +++ b/client/src/app/+admin/friends/shared/friend.service.ts | |||
@@ -23,7 +23,7 @@ export class FriendService { | |||
23 | let params = new HttpParams() | 23 | let params = new HttpParams() |
24 | params = this.restService.addRestGetParams(params, pagination, sort) | 24 | params = this.restService.addRestGetParams(params, pagination, sort) |
25 | 25 | ||
26 | return this.authHttp.get<ResultList<Account>>(API_URL + '/followers', { params }) | 26 | return this.authHttp.get<ResultList<Account>>(API_URL + '/api/v1/pods/followers', { params }) |
27 | .map(res => this.restExtractor.convertResultListDateToHuman(res)) | 27 | .map(res => this.restExtractor.convertResultListDateToHuman(res)) |
28 | .catch(res => this.restExtractor.handleError(res)) | 28 | .catch(res => this.restExtractor.handleError(res)) |
29 | } | 29 | } |
@@ -33,7 +33,7 @@ export class FriendService { | |||
33 | hosts: notEmptyHosts | 33 | hosts: notEmptyHosts |
34 | } | 34 | } |
35 | 35 | ||
36 | return this.authHttp.post(API_URL + '/follow', body) | 36 | return this.authHttp.post(API_URL + '/api/v1/pods/follow', body) |
37 | .map(this.restExtractor.extractDataBool) | 37 | .map(this.restExtractor.extractDataBool) |
38 | .catch(res => this.restExtractor.handleError(res)) | 38 | .catch(res => this.restExtractor.handleError(res)) |
39 | } | 39 | } |
@@ -47,7 +47,7 @@ db.init(false).then(() => onDatabaseInitDone()) | |||
47 | // ----------- PeerTube modules ----------- | 47 | // ----------- PeerTube modules ----------- |
48 | import { migrate, installApplication } from './server/initializers' | 48 | import { migrate, installApplication } from './server/initializers' |
49 | import { httpRequestJobScheduler, transcodingJobScheduler, VideosPreviewCache } from './server/lib' | 49 | import { httpRequestJobScheduler, transcodingJobScheduler, VideosPreviewCache } from './server/lib' |
50 | import { apiRouter, clientsRouter, staticRouter, servicesRouter } from './server/controllers' | 50 | import { apiRouter, clientsRouter, staticRouter, servicesRouter, webfingerRouter, activityPubRouter } from './server/controllers' |
51 | 51 | ||
52 | // ----------- Command line ----------- | 52 | // ----------- Command line ----------- |
53 | 53 | ||
@@ -115,6 +115,9 @@ app.use(apiRoute, apiRouter) | |||
115 | // Services (oembed...) | 115 | // Services (oembed...) |
116 | app.use('/services', servicesRouter) | 116 | app.use('/services', servicesRouter) |
117 | 117 | ||
118 | app.use('/', webfingerRouter) | ||
119 | app.use('/', activityPubRouter) | ||
120 | |||
118 | // Client files | 121 | // Client files |
119 | app.use('/', clientsRouter) | 122 | app.use('/', clientsRouter) |
120 | 123 | ||
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 461a619dd..56a4054fa 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts | |||
@@ -16,12 +16,12 @@ activityPubClientRouter.get('/account/:name', | |||
16 | executeIfActivityPub(asyncMiddleware(accountController)) | 16 | executeIfActivityPub(asyncMiddleware(accountController)) |
17 | ) | 17 | ) |
18 | 18 | ||
19 | activityPubClientRouter.get('/account/:nameWithHost/followers', | 19 | activityPubClientRouter.get('/account/:name/followers', |
20 | executeIfActivityPub(localAccountValidator), | 20 | executeIfActivityPub(localAccountValidator), |
21 | executeIfActivityPub(asyncMiddleware(accountFollowersController)) | 21 | executeIfActivityPub(asyncMiddleware(accountFollowersController)) |
22 | ) | 22 | ) |
23 | 23 | ||
24 | activityPubClientRouter.get('/account/:nameWithHost/following', | 24 | activityPubClientRouter.get('/account/:name/following', |
25 | executeIfActivityPub(localAccountValidator), | 25 | executeIfActivityPub(localAccountValidator), |
26 | executeIfActivityPub(asyncMiddleware(accountFollowingController)) | 26 | executeIfActivityPub(asyncMiddleware(accountFollowingController)) |
27 | ) | 27 | ) |
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts index eedb518b9..e62125d85 100644 --- a/server/controllers/activitypub/inbox.ts +++ b/server/controllers/activitypub/inbox.ts | |||
@@ -30,7 +30,7 @@ inboxRouter.post('/inbox', | |||
30 | asyncMiddleware(inboxController) | 30 | asyncMiddleware(inboxController) |
31 | ) | 31 | ) |
32 | 32 | ||
33 | inboxRouter.post('/:nameWithHost/inbox', | 33 | inboxRouter.post('/account/:name/inbox', |
34 | signatureValidator, | 34 | signatureValidator, |
35 | asyncMiddleware(checkSignature), | 35 | asyncMiddleware(checkSignature), |
36 | localAccountValidator, | 36 | localAccountValidator, |
@@ -59,7 +59,9 @@ async function inboxController (req: express.Request, res: express.Response, nex | |||
59 | } | 59 | } |
60 | 60 | ||
61 | // Only keep activities we are able to process | 61 | // Only keep activities we are able to process |
62 | logger.debug('Filtering activities...', { activities }) | ||
62 | activities = activities.filter(a => isActivityValid(a)) | 63 | activities = activities.filter(a => isActivityValid(a)) |
64 | logger.debug('We keep %d activities.', activities.length, { activities }) | ||
63 | 65 | ||
64 | await processActivities(activities, res.locals.account) | 66 | await processActivities(activities, res.locals.account) |
65 | 67 | ||
diff --git a/server/controllers/activitypub/index.ts b/server/controllers/activitypub/index.ts index 6c7bafc6e..0c8574ef7 100644 --- a/server/controllers/activitypub/index.ts +++ b/server/controllers/activitypub/index.ts | |||
@@ -4,14 +4,14 @@ import { badRequest } from '../../helpers' | |||
4 | import { inboxRouter } from './inbox' | 4 | import { inboxRouter } from './inbox' |
5 | import { activityPubClientRouter } from './client' | 5 | import { activityPubClientRouter } from './client' |
6 | 6 | ||
7 | const remoteRouter = express.Router() | 7 | const activityPubRouter = express.Router() |
8 | 8 | ||
9 | remoteRouter.use('/', inboxRouter) | 9 | activityPubRouter.use('/', inboxRouter) |
10 | remoteRouter.use('/', activityPubClientRouter) | 10 | activityPubRouter.use('/', activityPubClientRouter) |
11 | remoteRouter.use('/*', badRequest) | 11 | activityPubRouter.use('/*', badRequest) |
12 | 12 | ||
13 | // --------------------------------------------------------------------------- | 13 | // --------------------------------------------------------------------------- |
14 | 14 | ||
15 | export { | 15 | export { |
16 | remoteRouter | 16 | activityPubRouter |
17 | } | 17 | } |
diff --git a/server/controllers/api/pods.ts b/server/controllers/api/pods.ts index 2231a05fa..0bd6971bb 100644 --- a/server/controllers/api/pods.ts +++ b/server/controllers/api/pods.ts | |||
@@ -1,19 +1,19 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight } from '../../../shared/models/users/user-right.enum' | ||
3 | import { getFormattedObjects } from '../../helpers' | 3 | import { getFormattedObjects } from '../../helpers' |
4 | import { getOrCreateAccount } from '../../helpers/activitypub' | 4 | import { logger } from '../../helpers/logger' |
5 | import { getApplicationAccount } from '../../helpers/utils' | 5 | import { getApplicationAccount } from '../../helpers/utils' |
6 | import { REMOTE_SCHEME } from '../../initializers/constants' | 6 | import { getAccountFromWebfinger } from '../../helpers/webfinger' |
7 | import { SERVER_ACCOUNT_NAME } from '../../initializers/constants' | ||
7 | import { database as db } from '../../initializers/database' | 8 | import { database as db } from '../../initializers/database' |
9 | import { sendFollow } from '../../lib/activitypub/send-request' | ||
8 | import { asyncMiddleware, paginationValidator, setFollowersSort, setPagination } from '../../middlewares' | 10 | import { asyncMiddleware, paginationValidator, setFollowersSort, setPagination } from '../../middlewares' |
11 | import { authenticate } from '../../middlewares/oauth' | ||
9 | import { setBodyHostsPort } from '../../middlewares/pods' | 12 | import { setBodyHostsPort } from '../../middlewares/pods' |
10 | import { setFollowingSort } from '../../middlewares/sort' | 13 | import { setFollowingSort } from '../../middlewares/sort' |
14 | import { ensureUserHasRight } from '../../middlewares/user-right' | ||
11 | import { followValidator } from '../../middlewares/validators/pods' | 15 | import { followValidator } from '../../middlewares/validators/pods' |
12 | import { followersSortValidator, followingSortValidator } from '../../middlewares/validators/sort' | 16 | import { followersSortValidator, followingSortValidator } from '../../middlewares/validators/sort' |
13 | import { sendFollow } from '../../lib/activitypub/send-request' | ||
14 | import { authenticate } from '../../middlewares/oauth' | ||
15 | import { ensureUserHasRight } from '../../middlewares/user-right' | ||
16 | import { UserRight } from '../../../shared/models/users/user-right.enum' | ||
17 | 17 | ||
18 | const podsRouter = express.Router() | 18 | const podsRouter = express.Router() |
19 | 19 | ||
@@ -67,22 +67,43 @@ async function follow (req: express.Request, res: express.Response, next: expres | |||
67 | const hosts = req.body.hosts as string[] | 67 | const hosts = req.body.hosts as string[] |
68 | const fromAccount = await getApplicationAccount() | 68 | const fromAccount = await getApplicationAccount() |
69 | 69 | ||
70 | const tasks: Bluebird<any>[] = [] | 70 | const tasks: Promise<any>[] = [] |
71 | const accountName = SERVER_ACCOUNT_NAME | ||
72 | |||
71 | for (const host of hosts) { | 73 | for (const host of hosts) { |
72 | const url = REMOTE_SCHEME.HTTP + '://' + host | ||
73 | const targetAccount = await getOrCreateAccount(url) | ||
74 | 74 | ||
75 | // We process each host in a specific transaction | 75 | // We process each host in a specific transaction |
76 | // First, we add the follow request in the database | 76 | // First, we add the follow request in the database |
77 | // Then we send the follow request to other account | 77 | // Then we send the follow request to other account |
78 | const p = db.sequelize.transaction(async t => { | 78 | const p = loadLocalOrGetAccountFromWebfinger(accountName, host) |
79 | return db.AccountFollow.create({ | 79 | .then(accountResult => { |
80 | accountId: fromAccount.id, | 80 | let targetAccount = accountResult.account |
81 | targetAccountId: targetAccount.id, | 81 | |
82 | state: 'pending' | 82 | return db.sequelize.transaction(async t => { |
83 | if (accountResult.loadedFromDB === false) { | ||
84 | targetAccount = await targetAccount.save({ transaction: t }) | ||
85 | } | ||
86 | |||
87 | const [ accountFollow ] = await db.AccountFollow.findOrCreate({ | ||
88 | where: { | ||
89 | accountId: fromAccount.id, | ||
90 | targetAccountId: targetAccount.id | ||
91 | }, | ||
92 | defaults: { | ||
93 | state: 'pending', | ||
94 | accountId: fromAccount.id, | ||
95 | targetAccountId: targetAccount.id | ||
96 | }, | ||
97 | transaction: t | ||
98 | }) | ||
99 | |||
100 | // Send a notification to remote server | ||
101 | if (accountFollow.state === 'pending') { | ||
102 | await sendFollow(fromAccount, targetAccount, t) | ||
103 | } | ||
104 | }) | ||
83 | }) | 105 | }) |
84 | .then(() => sendFollow(fromAccount, targetAccount, t)) | 106 | .catch(err => logger.warn('Cannot follow server %s.', `${accountName}@${host}`, err)) |
85 | }) | ||
86 | 107 | ||
87 | tasks.push(p) | 108 | tasks.push(p) |
88 | } | 109 | } |
@@ -91,3 +112,16 @@ async function follow (req: express.Request, res: express.Response, next: expres | |||
91 | 112 | ||
92 | return res.status(204).end() | 113 | return res.status(204).end() |
93 | } | 114 | } |
115 | |||
116 | async function loadLocalOrGetAccountFromWebfinger (name: string, host: string) { | ||
117 | let loadedFromDB = true | ||
118 | let account = await db.Account.loadByNameAndHost(name, host) | ||
119 | |||
120 | if (!account) { | ||
121 | const nameWithDomain = name + '@' + host | ||
122 | account = await getAccountFromWebfinger(nameWithDomain) | ||
123 | loadedFromDB = false | ||
124 | } | ||
125 | |||
126 | return { account, loadedFromDB } | ||
127 | } | ||
diff --git a/server/controllers/index.ts b/server/controllers/index.ts index 51cb480a3..457d0a12e 100644 --- a/server/controllers/index.ts +++ b/server/controllers/index.ts | |||
@@ -1,4 +1,6 @@ | |||
1 | export * from './activitypub' | ||
1 | export * from './static' | 2 | export * from './static' |
2 | export * from './client' | 3 | export * from './client' |
3 | export * from './services' | 4 | export * from './services' |
4 | export * from './api' | 5 | export * from './api' |
6 | export * from './webfinger' | ||
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts new file mode 100644 index 000000000..1c726f0cb --- /dev/null +++ b/server/controllers/webfinger.ts | |||
@@ -0,0 +1,39 @@ | |||
1 | import * as express from 'express' | ||
2 | |||
3 | import { CONFIG, PREVIEWS_SIZE, EMBED_SIZE } from '../initializers' | ||
4 | import { oembedValidator } from '../middlewares' | ||
5 | import { VideoInstance } from '../models' | ||
6 | import { webfingerValidator } from '../middlewares/validators/webfinger' | ||
7 | import { AccountInstance } from '../models/account/account-interface' | ||
8 | |||
9 | const webfingerRouter = express.Router() | ||
10 | |||
11 | webfingerRouter.use('/.well-known/webfinger', | ||
12 | webfingerValidator, | ||
13 | webfingerController | ||
14 | ) | ||
15 | |||
16 | // --------------------------------------------------------------------------- | ||
17 | |||
18 | export { | ||
19 | webfingerRouter | ||
20 | } | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
25 | const account: AccountInstance = res.locals.account | ||
26 | |||
27 | const json = { | ||
28 | subject: req.query.resource, | ||
29 | aliases: [ account.url ], | ||
30 | links: [ | ||
31 | { | ||
32 | rel: 'self', | ||
33 | href: account.url | ||
34 | } | ||
35 | ] | ||
36 | } | ||
37 | |||
38 | return res.json(json).end() | ||
39 | } | ||
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' | |||
5 | import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor' | 5 | import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor' |
6 | import { ResultList } from '../../shared/models/result-list.model' | 6 | import { ResultList } from '../../shared/models/result-list.model' |
7 | import { database as db, REMOTE_SCHEME } from '../initializers' | 7 | import { database as db, REMOTE_SCHEME } from '../initializers' |
8 | import { CONFIG, STATIC_PATHS } from '../initializers/constants' | 8 | import { ACTIVITY_PUB_ACCEPT_HEADER, CONFIG, STATIC_PATHS } from '../initializers/constants' |
9 | import { VideoInstance } from '../models/video/video-interface' | 9 | import { VideoInstance } from '../models/video/video-interface' |
10 | import { isRemoteAccountValid } from './custom-validators' | 10 | import { isRemoteAccountValid } from './custom-validators' |
11 | import { logger } from './logger' | 11 | import { logger } from './logger' |
@@ -35,11 +35,11 @@ async function getOrCreateAccount (accountUrl: string) { | |||
35 | 35 | ||
36 | // We don't have this account in our database, fetch it on remote | 36 | // We don't have this account in our database, fetch it on remote |
37 | if (!account) { | 37 | if (!account) { |
38 | const { account } = await fetchRemoteAccountAndCreatePod(accountUrl) | 38 | const res = await fetchRemoteAccountAndCreatePod(accountUrl) |
39 | 39 | if (res === undefined) throw new Error('Cannot fetch remote account.') | |
40 | if (!account) throw new Error('Cannot fetch remote account.') | ||
41 | 40 | ||
42 | // Save our new account in database | 41 | // Save our new account in database |
42 | const account = res.account | ||
43 | await account.save() | 43 | await account.save() |
44 | } | 44 | } |
45 | 45 | ||
@@ -49,19 +49,27 @@ async function getOrCreateAccount (accountUrl: string) { | |||
49 | async function fetchRemoteAccountAndCreatePod (accountUrl: string) { | 49 | async function fetchRemoteAccountAndCreatePod (accountUrl: string) { |
50 | const options = { | 50 | const options = { |
51 | uri: accountUrl, | 51 | uri: accountUrl, |
52 | method: 'GET' | 52 | method: 'GET', |
53 | headers: { | ||
54 | 'Accept': ACTIVITY_PUB_ACCEPT_HEADER | ||
55 | } | ||
53 | } | 56 | } |
54 | 57 | ||
58 | logger.info('Fetching remote account %s.', accountUrl) | ||
59 | |||
55 | let requestResult | 60 | let requestResult |
56 | try { | 61 | try { |
57 | requestResult = await doRequest(options) | 62 | requestResult = await doRequest(options) |
58 | } catch (err) { | 63 | } catch (err) { |
59 | logger.warning('Cannot fetch remote account %s.', accountUrl, err) | 64 | logger.warn('Cannot fetch remote account %s.', accountUrl, err) |
60 | return undefined | 65 | return undefined |
61 | } | 66 | } |
62 | 67 | ||
63 | const accountJSON: ActivityPubActor = requestResult.body | 68 | const accountJSON: ActivityPubActor = JSON.parse(requestResult.body) |
64 | if (isRemoteAccountValid(accountJSON) === false) return undefined | 69 | if (isRemoteAccountValid(accountJSON) === false) { |
70 | logger.debug('Remote account JSON is not valid.', { accountJSON }) | ||
71 | return undefined | ||
72 | } | ||
65 | 73 | ||
66 | const followersCount = await fetchAccountCount(accountJSON.followers) | 74 | const followersCount = await fetchAccountCount(accountJSON.followers) |
67 | const followingCount = await fetchAccountCount(accountJSON.following) | 75 | const followingCount = await fetchAccountCount(accountJSON.following) |
@@ -90,7 +98,8 @@ async function fetchRemoteAccountAndCreatePod (accountUrl: string) { | |||
90 | host: accountHost | 98 | host: accountHost |
91 | } | 99 | } |
92 | } | 100 | } |
93 | const pod = await db.Pod.findOrCreate(podOptions) | 101 | const [ pod ] = await db.Pod.findOrCreate(podOptions) |
102 | account.set('podId', pod.id) | ||
94 | 103 | ||
95 | return { account, pod } | 104 | return { account, pod } |
96 | } | 105 | } |
@@ -176,7 +185,7 @@ async function fetchAccountCount (url: string) { | |||
176 | try { | 185 | try { |
177 | requestResult = await doRequest(options) | 186 | requestResult = await doRequest(options) |
178 | } catch (err) { | 187 | } catch (err) { |
179 | logger.warning('Cannot fetch remote account count %s.', url, err) | 188 | logger.warn('Cannot fetch remote account count %s.', url, err) |
180 | return undefined | 189 | return undefined |
181 | } | 190 | } |
182 | 191 | ||
diff --git a/server/helpers/custom-validators/video-accounts.ts b/server/helpers/custom-validators/accounts.ts index 31808ae1e..6d6219a95 100644 --- a/server/helpers/custom-validators/video-accounts.ts +++ b/server/helpers/custom-validators/accounts.ts | |||
@@ -10,14 +10,14 @@ import { logger } from '../logger' | |||
10 | import { isUserUsernameValid } from './users' | 10 | import { isUserUsernameValid } from './users' |
11 | import { isHostValid } from './pods' | 11 | import { isHostValid } from './pods' |
12 | 12 | ||
13 | function isVideoAccountNameValid (value: string) { | 13 | function isAccountNameValid (value: string) { |
14 | return isUserUsernameValid(value) | 14 | return isUserUsernameValid(value) |
15 | } | 15 | } |
16 | 16 | ||
17 | function isAccountNameWithHostValid (value: string) { | 17 | function isAccountNameWithHostValid (value: string) { |
18 | const [ name, host ] = value.split('@') | 18 | const [ name, host ] = value.split('@') |
19 | 19 | ||
20 | return isVideoAccountNameValid(name) && isHostValid(host) | 20 | return isAccountNameValid(name) && isHostValid(host) |
21 | } | 21 | } |
22 | 22 | ||
23 | function checkVideoAccountExists (id: string, res: express.Response, callback: () => void) { | 23 | function checkVideoAccountExists (id: string, res: express.Response, callback: () => void) { |
@@ -38,10 +38,10 @@ function checkVideoAccountExists (id: string, res: express.Response, callback: ( | |||
38 | res.locals.account = account | 38 | res.locals.account = account |
39 | callback() | 39 | callback() |
40 | }) | 40 | }) |
41 | .catch(err => { | 41 | .catch(err => { |
42 | logger.error('Error in video account request validator.', err) | 42 | logger.error('Error in video account request validator.', err) |
43 | return res.sendStatus(500) | 43 | return res.sendStatus(500) |
44 | }) | 44 | }) |
45 | } | 45 | } |
46 | 46 | ||
47 | // --------------------------------------------------------------------------- | 47 | // --------------------------------------------------------------------------- |
@@ -49,5 +49,5 @@ function checkVideoAccountExists (id: string, res: express.Response, callback: ( | |||
49 | export { | 49 | export { |
50 | checkVideoAccountExists, | 50 | checkVideoAccountExists, |
51 | isAccountNameWithHostValid, | 51 | isAccountNameWithHostValid, |
52 | isVideoAccountNameValid | 52 | isAccountNameValid |
53 | } | 53 | } |
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 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | |||
3 | import { exists, isUUIDValid } from '../misc' | ||
4 | import { isActivityPubUrlValid } from './misc' | ||
5 | import { isUserUsernameValid } from '../users' | ||
6 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | 2 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
3 | import { isAccountNameValid } from '../accounts' | ||
4 | import { exists, isUUIDValid } from '../misc' | ||
5 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' | ||
7 | 6 | ||
8 | function isAccountEndpointsObjectValid (endpointObject: any) { | 7 | function isAccountEndpointsObjectValid (endpointObject: any) { |
9 | return isAccountSharedInboxValid(endpointObject.sharedInbox) | 8 | return isAccountSharedInboxValid(endpointObject.sharedInbox) |
@@ -59,10 +58,6 @@ function isAccountOutboxValid (outbox: string) { | |||
59 | return isActivityPubUrlValid(outbox) | 58 | return isActivityPubUrlValid(outbox) |
60 | } | 59 | } |
61 | 60 | ||
62 | function isAccountNameValid (name: string) { | ||
63 | return isUserUsernameValid(name) | ||
64 | } | ||
65 | |||
66 | function isAccountPreferredUsernameValid (preferredUsername: string) { | 61 | function isAccountPreferredUsernameValid (preferredUsername: string) { |
67 | return isAccountNameValid(preferredUsername) | 62 | return isAccountNameValid(preferredUsername) |
68 | } | 63 | } |
@@ -90,7 +85,7 @@ function isRemoteAccountValid (remoteAccount: any) { | |||
90 | isAccountPreferredUsernameValid(remoteAccount.preferredUsername) && | 85 | isAccountPreferredUsernameValid(remoteAccount.preferredUsername) && |
91 | isAccountUrlValid(remoteAccount.url) && | 86 | isAccountUrlValid(remoteAccount.url) && |
92 | isAccountPublicKeyObjectValid(remoteAccount.publicKey) && | 87 | isAccountPublicKeyObjectValid(remoteAccount.publicKey) && |
93 | isAccountEndpointsObjectValid(remoteAccount.endpoint) | 88 | isAccountEndpointsObjectValid(remoteAccount.endpoints) |
94 | } | 89 | } |
95 | 90 | ||
96 | function isAccountFollowingCountValid (value: string) { | 91 | function isAccountFollowingCountValid (value: string) { |
@@ -101,6 +96,19 @@ function isAccountFollowersCountValid (value: string) { | |||
101 | return exists(value) && validator.isInt('' + value, { min: 0 }) | 96 | return exists(value) && validator.isInt('' + value, { min: 0 }) |
102 | } | 97 | } |
103 | 98 | ||
99 | function isAccountDeleteActivityValid (activity: any) { | ||
100 | return isBaseActivityValid(activity, 'Delete') | ||
101 | } | ||
102 | |||
103 | function isAccountFollowActivityValid (activity: any) { | ||
104 | return isBaseActivityValid(activity, 'Follow') && | ||
105 | isActivityPubUrlValid(activity.object) | ||
106 | } | ||
107 | |||
108 | function isAccountAcceptActivityValid (activity: any) { | ||
109 | return isBaseActivityValid(activity, 'Accept') | ||
110 | } | ||
111 | |||
104 | // --------------------------------------------------------------------------- | 112 | // --------------------------------------------------------------------------- |
105 | 113 | ||
106 | export { | 114 | export { |
@@ -122,5 +130,8 @@ export { | |||
122 | isRemoteAccountValid, | 130 | isRemoteAccountValid, |
123 | isAccountFollowingCountValid, | 131 | isAccountFollowingCountValid, |
124 | isAccountFollowersCountValid, | 132 | isAccountFollowersCountValid, |
125 | isAccountNameValid | 133 | isAccountNameValid, |
134 | isAccountFollowActivityValid, | ||
135 | isAccountAcceptActivityValid, | ||
136 | isAccountDeleteActivityValid | ||
126 | } | 137 | } |
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 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account' | ||
3 | import { isActivityPubUrlValid } from './misc' | ||
2 | import { | 4 | import { |
3 | isVideoChannelCreateActivityValid, | 5 | isVideoChannelCreateActivityValid, |
6 | isVideoChannelDeleteActivityValid, | ||
7 | isVideoChannelUpdateActivityValid, | ||
4 | isVideoTorrentAddActivityValid, | 8 | isVideoTorrentAddActivityValid, |
5 | isVideoTorrentUpdateActivityValid, | 9 | isVideoTorrentDeleteActivityValid, |
6 | isVideoChannelUpdateActivityValid | 10 | isVideoTorrentUpdateActivityValid |
7 | } from './videos' | 11 | } from './videos' |
8 | 12 | ||
9 | function isRootActivityValid (activity: any) { | 13 | function isRootActivityValid (activity: any) { |
@@ -14,8 +18,8 @@ function isRootActivityValid (activity: any) { | |||
14 | Array.isArray(activity.items) | 18 | Array.isArray(activity.items) |
15 | ) || | 19 | ) || |
16 | ( | 20 | ( |
17 | validator.isURL(activity.id) && | 21 | isActivityPubUrlValid(activity.id) && |
18 | validator.isURL(activity.actor) | 22 | isActivityPubUrlValid(activity.actor) |
19 | ) | 23 | ) |
20 | } | 24 | } |
21 | 25 | ||
@@ -23,7 +27,12 @@ function isActivityValid (activity: any) { | |||
23 | return isVideoTorrentAddActivityValid(activity) || | 27 | return isVideoTorrentAddActivityValid(activity) || |
24 | isVideoChannelCreateActivityValid(activity) || | 28 | isVideoChannelCreateActivityValid(activity) || |
25 | isVideoTorrentUpdateActivityValid(activity) || | 29 | isVideoTorrentUpdateActivityValid(activity) || |
26 | isVideoChannelUpdateActivityValid(activity) | 30 | isVideoChannelUpdateActivityValid(activity) || |
31 | isVideoTorrentDeleteActivityValid(activity) || | ||
32 | isVideoChannelDeleteActivityValid(activity) || | ||
33 | isAccountDeleteActivityValid(activity) || | ||
34 | isAccountFollowActivityValid(activity) || | ||
35 | isAccountAcceptActivityValid(activity) | ||
27 | } | 36 | } |
28 | 37 | ||
29 | // --------------------------------------------------------------------------- | 38 | // --------------------------------------------------------------------------- |
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) { | |||
23 | function isBaseActivityValid (activity: any, type: string) { | 23 | function isBaseActivityValid (activity: any, type: string) { |
24 | return Array.isArray(activity['@context']) && | 24 | return Array.isArray(activity['@context']) && |
25 | activity.type === type && | 25 | activity.type === type && |
26 | validator.isURL(activity.id) && | 26 | isActivityPubUrlValid(activity.id) && |
27 | validator.isURL(activity.actor) && | 27 | isActivityPubUrlValid(activity.actor) && |
28 | Array.isArray(activity.to) && | 28 | ( |
29 | activity.to.every(t => validator.isURL(t)) | 29 | activity.to === undefined || |
30 | (Array.isArray(activity.to) && activity.to.every(t => isActivityPubUrlValid(t))) | ||
31 | ) | ||
30 | } | 32 | } |
31 | 33 | ||
32 | export { | 34 | 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 { | |||
14 | isVideoUrlValid | 14 | isVideoUrlValid |
15 | } from '../videos' | 15 | } from '../videos' |
16 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' | 16 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' |
17 | import { isBaseActivityValid } from './misc' | 17 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' |
18 | 18 | ||
19 | function isVideoTorrentAddActivityValid (activity: any) { | 19 | function isVideoTorrentAddActivityValid (activity: any) { |
20 | return isBaseActivityValid(activity, 'Add') && | 20 | return isBaseActivityValid(activity, 'Add') && |
@@ -26,6 +26,10 @@ function isVideoTorrentUpdateActivityValid (activity: any) { | |||
26 | isVideoTorrentObjectValid(activity.object) | 26 | isVideoTorrentObjectValid(activity.object) |
27 | } | 27 | } |
28 | 28 | ||
29 | function isVideoTorrentDeleteActivityValid (activity: any) { | ||
30 | return isBaseActivityValid(activity, 'Delete') | ||
31 | } | ||
32 | |||
29 | function isVideoTorrentObjectValid (video: any) { | 33 | function isVideoTorrentObjectValid (video: any) { |
30 | return video.type === 'Video' && | 34 | return video.type === 'Video' && |
31 | isVideoNameValid(video.name) && | 35 | isVideoNameValid(video.name) && |
@@ -54,6 +58,10 @@ function isVideoChannelUpdateActivityValid (activity: any) { | |||
54 | isVideoChannelObjectValid(activity.object) | 58 | isVideoChannelObjectValid(activity.object) |
55 | } | 59 | } |
56 | 60 | ||
61 | function isVideoChannelDeleteActivityValid (activity: any) { | ||
62 | return isBaseActivityValid(activity, 'Delete') | ||
63 | } | ||
64 | |||
57 | function isVideoChannelObjectValid (videoChannel: any) { | 65 | function isVideoChannelObjectValid (videoChannel: any) { |
58 | return videoChannel.type === 'VideoChannel' && | 66 | return videoChannel.type === 'VideoChannel' && |
59 | isVideoChannelNameValid(videoChannel.name) && | 67 | isVideoChannelNameValid(videoChannel.name) && |
@@ -67,7 +75,9 @@ export { | |||
67 | isVideoTorrentAddActivityValid, | 75 | isVideoTorrentAddActivityValid, |
68 | isVideoChannelCreateActivityValid, | 76 | isVideoChannelCreateActivityValid, |
69 | isVideoTorrentUpdateActivityValid, | 77 | isVideoTorrentUpdateActivityValid, |
70 | isVideoChannelUpdateActivityValid | 78 | isVideoChannelUpdateActivityValid, |
79 | isVideoChannelDeleteActivityValid, | ||
80 | isVideoTorrentDeleteActivityValid | ||
71 | } | 81 | } |
72 | 82 | ||
73 | // --------------------------------------------------------------------------- | 83 | // --------------------------------------------------------------------------- |
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' | |||
3 | export * from './pods' | 3 | export * from './pods' |
4 | export * from './pods' | 4 | export * from './pods' |
5 | export * from './users' | 5 | export * from './users' |
6 | export * from './video-accounts' | 6 | export * from './accounts' |
7 | export * from './video-channels' | 7 | export * from './video-channels' |
8 | export * from './videos' | 8 | export * from './videos' |
9 | export * from './webfinger' | ||
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 @@ | |||
1 | import 'express-validator' | ||
2 | import 'multer' | ||
3 | import { CONFIG } from '../../initializers/constants' | ||
4 | import { exists } from './misc' | ||
5 | |||
6 | function isWebfingerResourceValid (value: string) { | ||
7 | if (!exists(value)) return false | ||
8 | if (value.startsWith('acct:') === false) return false | ||
9 | |||
10 | const accountWithHost = value.substr(5) | ||
11 | const accountParts = accountWithHost.split('@') | ||
12 | if (accountParts.length !== 2) return false | ||
13 | |||
14 | const host = accountParts[1] | ||
15 | |||
16 | if (host !== CONFIG.WEBSERVER.HOST) return false | ||
17 | |||
18 | return true | ||
19 | } | ||
20 | |||
21 | // --------------------------------------------------------------------------- | ||
22 | |||
23 | export { | ||
24 | isWebfingerResourceValid | ||
25 | } | ||
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({ | |||
12 | request_timeout: 3000 | 12 | request_timeout: 3000 |
13 | }) | 13 | }) |
14 | 14 | ||
15 | async function getAccountFromWebfinger (url: string) { | 15 | async function getAccountFromWebfinger (nameWithHost: string) { |
16 | const webfingerData: WebFingerData = await webfingerLookup(url) | 16 | const webfingerData: WebFingerData = await webfingerLookup(nameWithHost) |
17 | 17 | ||
18 | if (Array.isArray(webfingerData.links) === false) return undefined | 18 | if (Array.isArray(webfingerData.links) === false) throw new Error('WebFinger links is not an array.') |
19 | 19 | ||
20 | const selfLink = webfingerData.links.find(l => l.rel === 'self') | 20 | const selfLink = webfingerData.links.find(l => l.rel === 'self') |
21 | if (selfLink === undefined || isActivityPubUrlValid(selfLink.href) === false) return undefined | 21 | if (selfLink === undefined || isActivityPubUrlValid(selfLink.href) === false) { |
22 | throw new Error('Cannot find self link or href is not a valid URL.') | ||
23 | } | ||
22 | 24 | ||
23 | const { account } = await fetchRemoteAccountAndCreatePod(selfLink.href) | 25 | const res = await fetchRemoteAccountAndCreatePod(selfLink.href) |
26 | if (res === undefined) throw new Error('Cannot fetch and create pod of remote account ' + selfLink.href) | ||
24 | 27 | ||
25 | return account | 28 | return res.account |
26 | } | 29 | } |
27 | 30 | ||
28 | // --------------------------------------------------------------------------- | 31 | // --------------------------------------------------------------------------- |
@@ -33,12 +36,12 @@ export { | |||
33 | 36 | ||
34 | // --------------------------------------------------------------------------- | 37 | // --------------------------------------------------------------------------- |
35 | 38 | ||
36 | function webfingerLookup (url: string) { | 39 | function webfingerLookup (nameWithHost: string) { |
37 | return new Promise<WebFingerData>((res, rej) => { | 40 | return new Promise<WebFingerData>((res, rej) => { |
38 | webfinger.lookup(url, (err, p) => { | 41 | webfinger.lookup(nameWithHost, (err, p) => { |
39 | if (err) return rej(err) | 42 | if (err) return rej(err) |
40 | 43 | ||
41 | return p | 44 | return res(p.object) |
42 | }) | 45 | }) |
43 | }) | 46 | }) |
44 | } | 47 | } |
diff --git a/server/initializers/checker.ts b/server/initializers/checker.ts index b69188f7e..317d59423 100644 --- a/server/initializers/checker.ts +++ b/server/initializers/checker.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as config from 'config' | 1 | import * as config from 'config' |
2 | |||
3 | import { promisify0 } from '../helpers/core-utils' | 2 | import { promisify0 } from '../helpers/core-utils' |
4 | import { OAuthClientModel } from '../models/oauth/oauth-client-interface' | ||
5 | import { UserModel } from '../models/account/user-interface' | 3 | import { UserModel } from '../models/account/user-interface' |
4 | import { ApplicationModel } from '../models/application/application-interface' | ||
5 | import { OAuthClientModel } from '../models/oauth/oauth-client-interface' | ||
6 | 6 | ||
7 | // Some checks on configuration files | 7 | // Some checks on configuration files |
8 | function checkConfig () { | 8 | function checkConfig () { |
@@ -70,6 +70,13 @@ async function usersExist (User: UserModel) { | |||
70 | return totalUsers !== 0 | 70 | return totalUsers !== 0 |
71 | } | 71 | } |
72 | 72 | ||
73 | // We get db by param to not import it in this file (import orders) | ||
74 | async function applicationExist (Application: ApplicationModel) { | ||
75 | const totalApplication = await Application.countTotal() | ||
76 | |||
77 | return totalApplication !== 0 | ||
78 | } | ||
79 | |||
73 | // --------------------------------------------------------------------------- | 80 | // --------------------------------------------------------------------------- |
74 | 81 | ||
75 | export { | 82 | export { |
@@ -77,5 +84,6 @@ export { | |||
77 | checkFFmpeg, | 84 | checkFFmpeg, |
78 | checkMissedConfig, | 85 | checkMissedConfig, |
79 | clientsExist, | 86 | clientsExist, |
80 | usersExist | 87 | usersExist, |
88 | applicationExist | ||
81 | } | 89 | } |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index e27d011fa..4a49c1ab3 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -226,6 +226,9 @@ const FRIEND_SCORE = { | |||
226 | MAX: 1000 | 226 | MAX: 1000 |
227 | } | 227 | } |
228 | 228 | ||
229 | const SERVER_ACCOUNT_NAME = 'peertube' | ||
230 | const ACTIVITY_PUB_ACCEPT_HEADER = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' | ||
231 | |||
229 | const ACTIVITY_PUB = { | 232 | const ACTIVITY_PUB = { |
230 | COLLECTION_ITEMS_PER_PAGE: 10, | 233 | COLLECTION_ITEMS_PER_PAGE: 10, |
231 | VIDEO_URL_MIME_TYPES: [ | 234 | VIDEO_URL_MIME_TYPES: [ |
@@ -352,8 +355,10 @@ export { | |||
352 | PODS_SCORE, | 355 | PODS_SCORE, |
353 | PREVIEWS_SIZE, | 356 | PREVIEWS_SIZE, |
354 | REMOTE_SCHEME, | 357 | REMOTE_SCHEME, |
358 | ACTIVITY_PUB_ACCEPT_HEADER, | ||
355 | FOLLOW_STATES, | 359 | FOLLOW_STATES, |
356 | SEARCHABLE_COLUMNS, | 360 | SEARCHABLE_COLUMNS, |
361 | SERVER_ACCOUNT_NAME, | ||
357 | PRIVATE_RSA_KEY_SIZE, | 362 | PRIVATE_RSA_KEY_SIZE, |
358 | SORTABLE_COLUMNS, | 363 | SORTABLE_COLUMNS, |
359 | STATIC_MAX_AGE, | 364 | STATIC_MAX_AGE, |
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts index 5221b81a5..c3521a9e4 100644 --- a/server/initializers/installer.ts +++ b/server/initializers/installer.ts | |||
@@ -3,8 +3,8 @@ import { UserRole } from '../../shared' | |||
3 | import { logger, mkdirpPromise, rimrafPromise } from '../helpers' | 3 | import { logger, mkdirpPromise, rimrafPromise } from '../helpers' |
4 | import { createUserAccountAndChannel } from '../lib' | 4 | import { createUserAccountAndChannel } from '../lib' |
5 | import { createLocalAccount } from '../lib/user' | 5 | import { createLocalAccount } from '../lib/user' |
6 | import { clientsExist, usersExist } from './checker' | 6 | import { applicationExist, clientsExist, usersExist } from './checker' |
7 | import { CACHE, CONFIG, LAST_MIGRATION_VERSION } from './constants' | 7 | import { CACHE, CONFIG, LAST_MIGRATION_VERSION, SERVER_ACCOUNT_NAME } from './constants' |
8 | 8 | ||
9 | import { database as db } from './database' | 9 | import { database as db } from './database' |
10 | 10 | ||
@@ -128,9 +128,13 @@ async function createOAuthAdminIfNotExist () { | |||
128 | } | 128 | } |
129 | 129 | ||
130 | async function createApplicationIfNotExist () { | 130 | async function createApplicationIfNotExist () { |
131 | const exist = await applicationExist(db.Application) | ||
132 | // Nothing to do, application already exist | ||
133 | if (exist === true) return undefined | ||
134 | |||
131 | logger.info('Creating Application table.') | 135 | logger.info('Creating Application table.') |
132 | const applicationInstance = await db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION }) | 136 | const applicationInstance = await db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION }) |
133 | 137 | ||
134 | logger.info('Creating application account.') | 138 | logger.info('Creating application account.') |
135 | return createLocalAccount('peertube', null, applicationInstance.id, undefined) | 139 | return createLocalAccount(SERVER_ACCOUNT_NAME, null, applicationInstance.id, undefined) |
136 | } | 140 | } |
diff --git a/server/lib/activitypub/process-add.ts b/server/lib/activitypub/process-add.ts index 024dee559..06d23a2ea 100644 --- a/server/lib/activitypub/process-add.ts +++ b/server/lib/activitypub/process-add.ts | |||
@@ -54,7 +54,7 @@ async function addRemoteVideo (account: AccountInstance, videoChannelUrl: string | |||
54 | 54 | ||
55 | // Don't block on request | 55 | // Don't block on request |
56 | generateThumbnailFromUrl(video, videoToCreateData.icon) | 56 | generateThumbnailFromUrl(video, videoToCreateData.icon) |
57 | .catch(err => logger.warning('Cannot generate thumbnail of %s.', videoToCreateData.id, err)) | 57 | .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err)) |
58 | 58 | ||
59 | const videoCreated = await video.save(sequelizeOptions) | 59 | const videoCreated = await video.save(sequelizeOptions) |
60 | 60 | ||
diff --git a/server/lib/activitypub/process-follow.ts b/server/lib/activitypub/process-follow.ts index ee5d97a0b..a805c0757 100644 --- a/server/lib/activitypub/process-follow.ts +++ b/server/lib/activitypub/process-follow.ts | |||
@@ -36,14 +36,18 @@ async function follow (account: AccountInstance, targetAccountURL: string) { | |||
36 | if (targetAccount === undefined) throw new Error('Unknown account') | 36 | if (targetAccount === undefined) throw new Error('Unknown account') |
37 | if (targetAccount.isOwned() === false) throw new Error('This is not a local account.') | 37 | if (targetAccount.isOwned() === false) throw new Error('This is not a local account.') |
38 | 38 | ||
39 | const sequelizeOptions = { | 39 | await db.AccountFollow.findOrCreate({ |
40 | where: { | ||
41 | accountId: account.id, | ||
42 | targetAccountId: targetAccount.id | ||
43 | }, | ||
44 | defaults: { | ||
45 | accountId: account.id, | ||
46 | targetAccountId: targetAccount.id, | ||
47 | state: 'accepted' | ||
48 | }, | ||
40 | transaction: t | 49 | transaction: t |
41 | } | 50 | }) |
42 | await db.AccountFollow.create({ | ||
43 | accountId: account.id, | ||
44 | targetAccountId: targetAccount.id, | ||
45 | state: 'accepted' | ||
46 | }, sequelizeOptions) | ||
47 | 51 | ||
48 | // Target sends to account he accepted the follow request | 52 | // Target sends to account he accepted the follow request |
49 | return sendAccept(targetAccount, account, t) | 53 | return sendAccept(targetAccount, account, t) |
diff --git a/server/lib/activitypub/send-request.ts b/server/lib/activitypub/send-request.ts index c18a69784..d47040d6d 100644 --- a/server/lib/activitypub/send-request.ts +++ b/server/lib/activitypub/send-request.ts | |||
@@ -10,60 +10,60 @@ import { httpRequestJobScheduler } from '../jobs' | |||
10 | import { signObject, activityPubContextify } from '../../helpers' | 10 | import { signObject, activityPubContextify } from '../../helpers' |
11 | import { Activity } from '../../../shared' | 11 | import { Activity } from '../../../shared' |
12 | 12 | ||
13 | function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { | 13 | async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { |
14 | const videoChannelObject = videoChannel.toActivityPubObject() | 14 | const videoChannelObject = videoChannel.toActivityPubObject() |
15 | const data = createActivityData(videoChannel.url, videoChannel.Account, videoChannelObject) | 15 | const data = await createActivityData(videoChannel.url, videoChannel.Account, videoChannelObject) |
16 | 16 | ||
17 | return broadcastToFollowers(data, videoChannel.Account, t) | 17 | return broadcastToFollowers(data, videoChannel.Account, t) |
18 | } | 18 | } |
19 | 19 | ||
20 | function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { | 20 | async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { |
21 | const videoChannelObject = videoChannel.toActivityPubObject() | 21 | const videoChannelObject = videoChannel.toActivityPubObject() |
22 | const data = updateActivityData(videoChannel.url, videoChannel.Account, videoChannelObject) | 22 | const data = await updateActivityData(videoChannel.url, videoChannel.Account, videoChannelObject) |
23 | 23 | ||
24 | return broadcastToFollowers(data, videoChannel.Account, t) | 24 | return broadcastToFollowers(data, videoChannel.Account, t) |
25 | } | 25 | } |
26 | 26 | ||
27 | function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { | 27 | async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { |
28 | const data = deleteActivityData(videoChannel.url, videoChannel.Account) | 28 | const data = await deleteActivityData(videoChannel.url, videoChannel.Account) |
29 | 29 | ||
30 | return broadcastToFollowers(data, videoChannel.Account, t) | 30 | return broadcastToFollowers(data, videoChannel.Account, t) |
31 | } | 31 | } |
32 | 32 | ||
33 | function sendAddVideo (video: VideoInstance, t: Sequelize.Transaction) { | 33 | async function sendAddVideo (video: VideoInstance, t: Sequelize.Transaction) { |
34 | const videoObject = video.toActivityPubObject() | 34 | const videoObject = video.toActivityPubObject() |
35 | const data = addActivityData(video.url, video.VideoChannel.Account, video.VideoChannel.url, videoObject) | 35 | const data = await addActivityData(video.url, video.VideoChannel.Account, video.VideoChannel.url, videoObject) |
36 | 36 | ||
37 | return broadcastToFollowers(data, video.VideoChannel.Account, t) | 37 | return broadcastToFollowers(data, video.VideoChannel.Account, t) |
38 | } | 38 | } |
39 | 39 | ||
40 | function sendUpdateVideo (video: VideoInstance, t: Sequelize.Transaction) { | 40 | async function sendUpdateVideo (video: VideoInstance, t: Sequelize.Transaction) { |
41 | const videoObject = video.toActivityPubObject() | 41 | const videoObject = video.toActivityPubObject() |
42 | const data = updateActivityData(video.url, video.VideoChannel.Account, videoObject) | 42 | const data = await updateActivityData(video.url, video.VideoChannel.Account, videoObject) |
43 | 43 | ||
44 | return broadcastToFollowers(data, video.VideoChannel.Account, t) | 44 | return broadcastToFollowers(data, video.VideoChannel.Account, t) |
45 | } | 45 | } |
46 | 46 | ||
47 | function sendDeleteVideo (video: VideoInstance, t: Sequelize.Transaction) { | 47 | async function sendDeleteVideo (video: VideoInstance, t: Sequelize.Transaction) { |
48 | const data = deleteActivityData(video.url, video.VideoChannel.Account) | 48 | const data = await deleteActivityData(video.url, video.VideoChannel.Account) |
49 | 49 | ||
50 | return broadcastToFollowers(data, video.VideoChannel.Account, t) | 50 | return broadcastToFollowers(data, video.VideoChannel.Account, t) |
51 | } | 51 | } |
52 | 52 | ||
53 | function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transaction) { | 53 | async function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transaction) { |
54 | const data = deleteActivityData(account.url, account) | 54 | const data = await deleteActivityData(account.url, account) |
55 | 55 | ||
56 | return broadcastToFollowers(data, account, t) | 56 | return broadcastToFollowers(data, account, t) |
57 | } | 57 | } |
58 | 58 | ||
59 | function sendAccept (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) { | 59 | async function sendAccept (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) { |
60 | const data = acceptActivityData(fromAccount) | 60 | const data = await acceptActivityData(fromAccount) |
61 | 61 | ||
62 | return unicastTo(data, toAccount, t) | 62 | return unicastTo(data, toAccount, t) |
63 | } | 63 | } |
64 | 64 | ||
65 | function sendFollow (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) { | 65 | async function sendFollow (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) { |
66 | const data = followActivityData(toAccount.url, fromAccount) | 66 | const data = await followActivityData(toAccount.url, fromAccount) |
67 | 67 | ||
68 | return unicastTo(data, toAccount, t) | 68 | return unicastTo(data, toAccount, t) |
69 | } | 69 | } |
@@ -97,7 +97,7 @@ async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t: | |||
97 | 97 | ||
98 | async function unicastTo (data: any, toAccount: AccountInstance, t: Sequelize.Transaction) { | 98 | async function unicastTo (data: any, toAccount: AccountInstance, t: Sequelize.Transaction) { |
99 | const jobPayload = { | 99 | const jobPayload = { |
100 | uris: [ toAccount.url ], | 100 | uris: [ toAccount.inboxUrl ], |
101 | body: data | 101 | body: data |
102 | } | 102 | } |
103 | 103 | ||
diff --git a/server/lib/jobs/http-request-job-scheduler/http-request-broadcast-handler.ts b/server/lib/jobs/http-request-job-scheduler/http-request-broadcast-handler.ts index 799b86e1c..2f1d9ee92 100644 --- a/server/lib/jobs/http-request-job-scheduler/http-request-broadcast-handler.ts +++ b/server/lib/jobs/http-request-job-scheduler/http-request-broadcast-handler.ts | |||
@@ -6,6 +6,7 @@ async function process (payload: HTTPRequestPayload, jobId: number) { | |||
6 | logger.info('Processing broadcast in job %d.', jobId) | 6 | logger.info('Processing broadcast in job %d.', jobId) |
7 | 7 | ||
8 | const options = { | 8 | const options = { |
9 | method: 'POST', | ||
9 | uri: '', | 10 | uri: '', |
10 | json: payload.body | 11 | json: payload.body |
11 | } | 12 | } |
diff --git a/server/lib/jobs/http-request-job-scheduler/http-request-unicast-handler.ts b/server/lib/jobs/http-request-job-scheduler/http-request-unicast-handler.ts index 13451f042..3a1a7fabf 100644 --- a/server/lib/jobs/http-request-job-scheduler/http-request-unicast-handler.ts +++ b/server/lib/jobs/http-request-job-scheduler/http-request-unicast-handler.ts | |||
@@ -7,6 +7,7 @@ async function process (payload: HTTPRequestPayload, jobId: number) { | |||
7 | 7 | ||
8 | const uri = payload.uris[0] | 8 | const uri = payload.uris[0] |
9 | const options = { | 9 | const options = { |
10 | method: 'POST', | ||
10 | uri, | 11 | uri, |
11 | json: payload.body | 12 | json: payload.body |
12 | } | 13 | } |
diff --git a/server/lib/jobs/job-scheduler.ts b/server/lib/jobs/job-scheduler.ts index f10f745b3..b25bb7ab3 100644 --- a/server/lib/jobs/job-scheduler.ts +++ b/server/lib/jobs/job-scheduler.ts | |||
@@ -4,6 +4,7 @@ import { JobCategory } from '../../../shared' | |||
4 | import { logger } from '../../helpers' | 4 | import { logger } from '../../helpers' |
5 | import { database as db, JOB_STATES, JOBS_FETCH_LIMIT_PER_CYCLE, JOBS_FETCHING_INTERVAL } from '../../initializers' | 5 | import { database as db, JOB_STATES, JOBS_FETCH_LIMIT_PER_CYCLE, JOBS_FETCHING_INTERVAL } from '../../initializers' |
6 | import { JobInstance } from '../../models' | 6 | import { JobInstance } from '../../models' |
7 | import { error } from 'util' | ||
7 | 8 | ||
8 | export interface JobHandler<P, T> { | 9 | export interface JobHandler<P, T> { |
9 | process (data: object, jobId: number): Promise<T> | 10 | process (data: object, jobId: number): Promise<T> |
@@ -80,8 +81,12 @@ class JobScheduler<P, T> { | |||
80 | private async processJob (job: JobInstance, callback: (err: Error) => void) { | 81 | private async processJob (job: JobInstance, callback: (err: Error) => void) { |
81 | const jobHandler = this.jobHandlers[job.handlerName] | 82 | const jobHandler = this.jobHandlers[job.handlerName] |
82 | if (jobHandler === undefined) { | 83 | if (jobHandler === undefined) { |
83 | logger.error('Unknown job handler for job %s.', job.handlerName) | 84 | const errorString = 'Unknown job handler ' + job.handlerName + ' for job ' + job.id |
84 | return callback(null) | 85 | logger.error(errorString) |
86 | |||
87 | const error = new Error(errorString) | ||
88 | await this.onJobError(jobHandler, job, error) | ||
89 | return callback(error) | ||
85 | } | 90 | } |
86 | 91 | ||
87 | logger.info('Processing job %d with handler %s.', job.id, job.handlerName) | 92 | logger.info('Processing job %d with handler %s.', job.id, job.handlerName) |
@@ -103,7 +108,7 @@ class JobScheduler<P, T> { | |||
103 | } | 108 | } |
104 | } | 109 | } |
105 | 110 | ||
106 | callback(null) | 111 | return callback(null) |
107 | } | 112 | } |
108 | 113 | ||
109 | private async onJobError (jobHandler: JobHandler<P, T>, job: JobInstance, err: Error) { | 114 | private async onJobError (jobHandler: JobHandler<P, T>, job: JobInstance, err: Error) { |
@@ -111,7 +116,7 @@ class JobScheduler<P, T> { | |||
111 | 116 | ||
112 | try { | 117 | try { |
113 | await job.save() | 118 | await job.save() |
114 | await jobHandler.onError(err, job.id) | 119 | if (jobHandler) await jobHandler.onError(err, job.id) |
115 | } catch (err) { | 120 | } catch (err) { |
116 | this.cannotSaveJobError(err) | 121 | this.cannotSaveJobError(err) |
117 | } | 122 | } |
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts index 6cf8eea6f..bed2bfeab 100644 --- a/server/middlewares/activitypub.ts +++ b/server/middlewares/activitypub.ts | |||
@@ -1,12 +1,9 @@ | |||
1 | import { Request, Response, NextFunction } from 'express' | 1 | import { NextFunction, Request, Response, RequestHandler } from 'express' |
2 | |||
3 | import { database as db } from '../initializers' | ||
4 | import { | ||
5 | logger, | ||
6 | getAccountFromWebfinger, | ||
7 | isSignatureVerified | ||
8 | } from '../helpers' | ||
9 | import { ActivityPubSignature } from '../../shared' | 2 | import { ActivityPubSignature } from '../../shared' |
3 | import { isSignatureVerified, logger } from '../helpers' | ||
4 | import { fetchRemoteAccountAndCreatePod } from '../helpers/activitypub' | ||
5 | import { database as db, ACTIVITY_PUB_ACCEPT_HEADER } from '../initializers' | ||
6 | import { each, eachSeries, waterfall } from 'async' | ||
10 | 7 | ||
11 | async function checkSignature (req: Request, res: Response, next: NextFunction) { | 8 | async function checkSignature (req: Request, res: Response, next: NextFunction) { |
12 | const signatureObject: ActivityPubSignature = req.body.signature | 9 | const signatureObject: ActivityPubSignature = req.body.signature |
@@ -17,35 +14,40 @@ async function checkSignature (req: Request, res: Response, next: NextFunction) | |||
17 | 14 | ||
18 | // We don't have this account in our database, fetch it on remote | 15 | // We don't have this account in our database, fetch it on remote |
19 | if (!account) { | 16 | if (!account) { |
20 | account = await getAccountFromWebfinger(signatureObject.creator) | 17 | const accountResult = await fetchRemoteAccountAndCreatePod(signatureObject.creator) |
21 | 18 | ||
22 | if (!account) { | 19 | if (!accountResult) { |
23 | return res.sendStatus(403) | 20 | return res.sendStatus(403) |
24 | } | 21 | } |
25 | 22 | ||
26 | // Save our new account in database | 23 | // Save our new account in database |
24 | account = accountResult.account | ||
27 | await account.save() | 25 | await account.save() |
28 | } | 26 | } |
29 | 27 | ||
30 | const verified = await isSignatureVerified(account, req.body) | 28 | const verified = await isSignatureVerified(account, req.body) |
31 | if (verified === false) return res.sendStatus(403) | 29 | if (verified === false) return res.sendStatus(403) |
32 | 30 | ||
33 | res.locals.signature.account = account | 31 | res.locals.signature = { |
32 | account | ||
33 | } | ||
34 | 34 | ||
35 | return next() | 35 | return next() |
36 | } | 36 | } |
37 | 37 | ||
38 | function executeIfActivityPub (fun: any | any[]) { | 38 | function executeIfActivityPub (fun: RequestHandler | RequestHandler[]) { |
39 | return (req: Request, res: Response, next: NextFunction) => { | 39 | return (req: Request, res: Response, next: NextFunction) => { |
40 | if (req.header('Accept') !== 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"') { | 40 | if (req.header('Accept') !== ACTIVITY_PUB_ACCEPT_HEADER) { |
41 | return next() | 41 | return next() |
42 | } | 42 | } |
43 | 43 | ||
44 | if (Array.isArray(fun) === true) { | 44 | if (Array.isArray(fun) === true) { |
45 | fun[0](req, res, next) // FIXME: doesn't work | 45 | return eachSeries(fun as RequestHandler[], (f, cb) => { |
46 | f(req, res, cb) | ||
47 | }, next) | ||
46 | } | 48 | } |
47 | 49 | ||
48 | return fun(req, res, next) | 50 | return (fun as RequestHandler)(req, res, next) |
49 | } | 51 | } |
50 | } | 52 | } |
51 | 53 | ||
diff --git a/server/middlewares/validators/account.ts b/server/middlewares/validators/account.ts index 3ccf2ea21..58eeed3cc 100644 --- a/server/middlewares/validators/account.ts +++ b/server/middlewares/validators/account.ts | |||
@@ -8,13 +8,13 @@ import { | |||
8 | isUserVideoQuotaValid, | 8 | isUserVideoQuotaValid, |
9 | logger | 9 | logger |
10 | } from '../../helpers' | 10 | } from '../../helpers' |
11 | import { isAccountNameWithHostValid } from '../../helpers/custom-validators/video-accounts' | 11 | import { isAccountNameValid } from '../../helpers/custom-validators/accounts' |
12 | import { database as db } from '../../initializers/database' | 12 | import { database as db } from '../../initializers/database' |
13 | import { AccountInstance } from '../../models' | 13 | import { AccountInstance } from '../../models' |
14 | import { checkErrors } from './utils' | 14 | import { checkErrors } from './utils' |
15 | 15 | ||
16 | const localAccountValidator = [ | 16 | const localAccountValidator = [ |
17 | param('nameWithHost').custom(isAccountNameWithHostValid).withMessage('Should have a valid account with domain name (myuser@domain.tld)'), | 17 | param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'), |
18 | 18 | ||
19 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 19 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
20 | logger.debug('Checking localAccountValidator parameters', { parameters: req.params }) | 20 | logger.debug('Checking localAccountValidator parameters', { parameters: req.params }) |
@@ -33,10 +33,8 @@ export { | |||
33 | 33 | ||
34 | // --------------------------------------------------------------------------- | 34 | // --------------------------------------------------------------------------- |
35 | 35 | ||
36 | function checkLocalAccountExists (nameWithHost: string, res: express.Response, callback: (err: Error, account: AccountInstance) => void) { | 36 | function checkLocalAccountExists (name: string, res: express.Response, callback: (err: Error, account: AccountInstance) => void) { |
37 | const [ name, host ] = nameWithHost.split('@') | 37 | db.Account.loadLocalByName(name) |
38 | |||
39 | db.Account.loadLocalAccountByNameAndPod(name, host) | ||
40 | .then(account => { | 38 | .then(account => { |
41 | if (!account) { | 39 | if (!account) { |
42 | return res.status(404) | 40 | return res.status(404) |
diff --git a/server/middlewares/validators/activitypub/activity.ts b/server/middlewares/validators/activitypub/activity.ts index 78a6d1444..0de8b2d85 100644 --- a/server/middlewares/validators/activitypub/activity.ts +++ b/server/middlewares/validators/activitypub/activity.ts | |||
@@ -1,11 +1,10 @@ | |||
1 | import { body } from 'express-validator/check' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
3 | 2 | import { body } from 'express-validator/check' | |
4 | import { logger, isRootActivityValid } from '../../../helpers' | 3 | import { isRootActivityValid, logger } from '../../../helpers' |
5 | import { checkErrors } from '../utils' | 4 | import { checkErrors } from '../utils' |
6 | 5 | ||
7 | const activityPubValidator = [ | 6 | const activityPubValidator = [ |
8 | body('data').custom(isRootActivityValid), | 7 | body('').custom((value, { req }) => isRootActivityValid(req.body)), |
9 | 8 | ||
10 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 9 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
11 | logger.debug('Checking activity pub parameters', { parameters: req.body }) | 10 | logger.debug('Checking activity pub parameters', { parameters: req.body }) |
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts index 46c00d679..92a4bad28 100644 --- a/server/middlewares/validators/index.ts +++ b/server/middlewares/validators/index.ts | |||
@@ -8,3 +8,4 @@ export * from './users' | |||
8 | export * from './videos' | 8 | export * from './videos' |
9 | export * from './video-blacklist' | 9 | export * from './video-blacklist' |
10 | export * from './video-channels' | 10 | export * from './video-channels' |
11 | export * from './webfinger' | ||
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts new file mode 100644 index 000000000..068e03ad7 --- /dev/null +++ b/server/middlewares/validators/webfinger.ts | |||
@@ -0,0 +1,42 @@ | |||
1 | import { query } from 'express-validator/check' | ||
2 | import * as express from 'express' | ||
3 | |||
4 | import { checkErrors } from './utils' | ||
5 | import { logger, isWebfingerResourceValid } from '../../helpers' | ||
6 | import { database as db } from '../../initializers' | ||
7 | |||
8 | const webfingerValidator = [ | ||
9 | query('resource').custom(isWebfingerResourceValid).withMessage('Should have a valid webfinger resource'), | ||
10 | |||
11 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
12 | logger.debug('Checking webfinger parameters', { parameters: req.query }) | ||
13 | |||
14 | checkErrors(req, res, () => { | ||
15 | // Remove 'acct:' from the beginning of the string | ||
16 | const nameWithHost = req.query.resource.substr(5) | ||
17 | const [ name, ] = nameWithHost.split('@') | ||
18 | |||
19 | db.Account.loadLocalByName(name) | ||
20 | .then(account => { | ||
21 | if (!account) { | ||
22 | return res.status(404) | ||
23 | .send({ error: 'Account not found' }) | ||
24 | .end() | ||
25 | } | ||
26 | |||
27 | res.locals.account = account | ||
28 | return next() | ||
29 | }) | ||
30 | .catch(err => { | ||
31 | logger.error('Error in webfinger validator.', err) | ||
32 | return res.sendStatus(500) | ||
33 | }) | ||
34 | }) | ||
35 | } | ||
36 | ] | ||
37 | |||
38 | // --------------------------------------------------------------------------- | ||
39 | |||
40 | export { | ||
41 | webfingerValidator | ||
42 | } | ||
diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts index e6abc893a..7c129ab9d 100644 --- a/server/models/account/account-follow.ts +++ b/server/models/account/account-follow.ts | |||
@@ -19,11 +19,13 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
19 | { | 19 | { |
20 | indexes: [ | 20 | indexes: [ |
21 | { | 21 | { |
22 | fields: [ 'accountId' ], | 22 | fields: [ 'accountId' ] |
23 | unique: true | 23 | }, |
24 | { | ||
25 | fields: [ 'targetAccountId' ] | ||
24 | }, | 26 | }, |
25 | { | 27 | { |
26 | fields: [ 'targetAccountId' ], | 28 | fields: [ 'accountId', 'targetAccountId' ], |
27 | unique: true | 29 | unique: true |
28 | } | 30 | } |
29 | ] | 31 | ] |
@@ -31,7 +33,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
31 | ) | 33 | ) |
32 | 34 | ||
33 | const classMethods = [ | 35 | const classMethods = [ |
34 | associate | 36 | associate, |
37 | loadByAccountAndTarget | ||
35 | ] | 38 | ] |
36 | addMethodsToModel(AccountFollow, classMethods) | 39 | addMethodsToModel(AccountFollow, classMethods) |
37 | 40 | ||
@@ -46,7 +49,7 @@ function associate (models) { | |||
46 | name: 'accountId', | 49 | name: 'accountId', |
47 | allowNull: false | 50 | allowNull: false |
48 | }, | 51 | }, |
49 | as: 'followers', | 52 | as: 'accountFollowers', |
50 | onDelete: 'CASCADE' | 53 | onDelete: 'CASCADE' |
51 | }) | 54 | }) |
52 | 55 | ||
@@ -55,7 +58,7 @@ function associate (models) { | |||
55 | name: 'targetAccountId', | 58 | name: 'targetAccountId', |
56 | allowNull: false | 59 | allowNull: false |
57 | }, | 60 | }, |
58 | as: 'following', | 61 | as: 'accountFollowing', |
59 | onDelete: 'CASCADE' | 62 | onDelete: 'CASCADE' |
60 | }) | 63 | }) |
61 | } | 64 | } |
diff --git a/server/models/account/account-interface.ts b/server/models/account/account-interface.ts index 2468dc6e1..6fc36ae9d 100644 --- a/server/models/account/account-interface.ts +++ b/server/models/account/account-interface.ts | |||
@@ -12,7 +12,8 @@ export namespace AccountMethods { | |||
12 | export type LoadByUUID = (uuid: string) => Bluebird<AccountInstance> | 12 | export type LoadByUUID = (uuid: string) => Bluebird<AccountInstance> |
13 | export type LoadByUrl = (url: string, transaction?: Sequelize.Transaction) => Bluebird<AccountInstance> | 13 | export type LoadByUrl = (url: string, transaction?: Sequelize.Transaction) => Bluebird<AccountInstance> |
14 | export type LoadAccountByPodAndUUID = (uuid: string, podId: number, transaction: Sequelize.Transaction) => Bluebird<AccountInstance> | 14 | export type LoadAccountByPodAndUUID = (uuid: string, podId: number, transaction: Sequelize.Transaction) => Bluebird<AccountInstance> |
15 | export type LoadLocalAccountByNameAndPod = (name: string, host: string) => Bluebird<AccountInstance> | 15 | export type LoadLocalByName = (name: string) => Bluebird<AccountInstance> |
16 | export type LoadByNameAndHost = (name: string, host: string) => Bluebird<AccountInstance> | ||
16 | export type ListOwned = () => Bluebird<AccountInstance[]> | 17 | export type ListOwned = () => Bluebird<AccountInstance[]> |
17 | export type ListAcceptedFollowerUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> > | 18 | export type ListAcceptedFollowerUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> > |
18 | export type ListAcceptedFollowingUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> > | 19 | export type ListAcceptedFollowingUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> > |
@@ -34,7 +35,8 @@ export interface AccountClass { | |||
34 | load: AccountMethods.Load | 35 | load: AccountMethods.Load |
35 | loadByUUID: AccountMethods.LoadByUUID | 36 | loadByUUID: AccountMethods.LoadByUUID |
36 | loadByUrl: AccountMethods.LoadByUrl | 37 | loadByUrl: AccountMethods.LoadByUrl |
37 | loadLocalAccountByNameAndPod: AccountMethods.LoadLocalAccountByNameAndPod | 38 | loadLocalByName: AccountMethods.LoadLocalByName |
39 | loadByNameAndHost: AccountMethods.LoadByNameAndHost | ||
38 | listOwned: AccountMethods.ListOwned | 40 | listOwned: AccountMethods.ListOwned |
39 | listAcceptedFollowerUrlsForApi: AccountMethods.ListAcceptedFollowerUrlsForApi | 41 | listAcceptedFollowerUrlsForApi: AccountMethods.ListAcceptedFollowerUrlsForApi |
40 | listAcceptedFollowingUrlsForApi: AccountMethods.ListAcceptedFollowingUrlsForApi | 42 | listAcceptedFollowingUrlsForApi: AccountMethods.ListAcceptedFollowingUrlsForApi |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index cd6c822f1..d2293a939 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -31,7 +31,8 @@ let load: AccountMethods.Load | |||
31 | let loadApplication: AccountMethods.LoadApplication | 31 | let loadApplication: AccountMethods.LoadApplication |
32 | let loadByUUID: AccountMethods.LoadByUUID | 32 | let loadByUUID: AccountMethods.LoadByUUID |
33 | let loadByUrl: AccountMethods.LoadByUrl | 33 | let loadByUrl: AccountMethods.LoadByUrl |
34 | let loadLocalAccountByNameAndPod: AccountMethods.LoadLocalAccountByNameAndPod | 34 | let loadLocalByName: AccountMethods.LoadLocalByName |
35 | let loadByNameAndHost: AccountMethods.LoadByNameAndHost | ||
35 | let listOwned: AccountMethods.ListOwned | 36 | let listOwned: AccountMethods.ListOwned |
36 | let listAcceptedFollowerUrlsForApi: AccountMethods.ListAcceptedFollowerUrlsForApi | 37 | let listAcceptedFollowerUrlsForApi: AccountMethods.ListAcceptedFollowerUrlsForApi |
37 | let listAcceptedFollowingUrlsForApi: AccountMethods.ListAcceptedFollowingUrlsForApi | 38 | let listAcceptedFollowingUrlsForApi: AccountMethods.ListAcceptedFollowingUrlsForApi |
@@ -88,7 +89,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes | |||
88 | }, | 89 | }, |
89 | privateKey: { | 90 | privateKey: { |
90 | type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max), | 91 | type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max), |
91 | allowNull: false, | 92 | allowNull: true, |
92 | validate: { | 93 | validate: { |
93 | privateKeyValid: value => { | 94 | privateKeyValid: value => { |
94 | const res = isAccountPrivateKeyValid(value) | 95 | const res = isAccountPrivateKeyValid(value) |
@@ -199,7 +200,8 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes | |||
199 | load, | 200 | load, |
200 | loadByUUID, | 201 | loadByUUID, |
201 | loadByUrl, | 202 | loadByUrl, |
202 | loadLocalAccountByNameAndPod, | 203 | loadLocalByName, |
204 | loadByNameAndHost, | ||
203 | listOwned, | 205 | listOwned, |
204 | listAcceptedFollowerUrlsForApi, | 206 | listAcceptedFollowerUrlsForApi, |
205 | listAcceptedFollowingUrlsForApi, | 207 | listAcceptedFollowingUrlsForApi, |
@@ -330,6 +332,8 @@ getFollowerSharedInboxUrls = function (this: AccountInstance) { | |||
330 | include: [ | 332 | include: [ |
331 | { | 333 | { |
332 | model: Account['sequelize'].models.AccountFollow, | 334 | model: Account['sequelize'].models.AccountFollow, |
335 | required: true, | ||
336 | as: 'followers', | ||
333 | where: { | 337 | where: { |
334 | targetAccountId: this.id | 338 | targetAccountId: this.id |
335 | } | 339 | } |
@@ -387,7 +391,7 @@ listFollowingForApi = function (id: number, start: number, count: number, sort: | |||
387 | include: [ | 391 | include: [ |
388 | { | 392 | { |
389 | model: Account['sequelize'].models.Account, | 393 | model: Account['sequelize'].models.Account, |
390 | as: 'following', | 394 | as: 'accountFollowing', |
391 | required: true, | 395 | required: true, |
392 | include: [ Account['sequelize'].models.Pod ] | 396 | include: [ Account['sequelize'].models.Pod ] |
393 | } | 397 | } |
@@ -418,7 +422,7 @@ listFollowersForApi = function (id: number, start: number, count: number, sort: | |||
418 | include: [ | 422 | include: [ |
419 | { | 423 | { |
420 | model: Account['sequelize'].models.Account, | 424 | model: Account['sequelize'].models.Account, |
421 | as: 'followers', | 425 | as: 'accountFollowers', |
422 | required: true, | 426 | required: true, |
423 | include: [ Account['sequelize'].models.Pod ] | 427 | include: [ Account['sequelize'].models.Pod ] |
424 | } | 428 | } |
@@ -439,7 +443,7 @@ loadApplication = function () { | |||
439 | return Account.findOne({ | 443 | return Account.findOne({ |
440 | include: [ | 444 | include: [ |
441 | { | 445 | { |
442 | model: Account['sequelize'].model.Application, | 446 | model: Account['sequelize'].models.Application, |
443 | required: true | 447 | required: true |
444 | } | 448 | } |
445 | ] | 449 | ] |
@@ -460,17 +464,37 @@ loadByUUID = function (uuid: string) { | |||
460 | return Account.findOne(query) | 464 | return Account.findOne(query) |
461 | } | 465 | } |
462 | 466 | ||
463 | loadLocalAccountByNameAndPod = function (name: string, host: string) { | 467 | loadLocalByName = function (name: string) { |
464 | const query: Sequelize.FindOptions<AccountAttributes> = { | 468 | const query: Sequelize.FindOptions<AccountAttributes> = { |
465 | where: { | 469 | where: { |
466 | name, | 470 | name, |
467 | userId: { | 471 | [Sequelize.Op.or]: [ |
468 | [Sequelize.Op.ne]: null | 472 | { |
469 | } | 473 | userId: { |
474 | [Sequelize.Op.ne]: null | ||
475 | } | ||
476 | }, | ||
477 | { | ||
478 | applicationId: { | ||
479 | [Sequelize.Op.ne]: null | ||
480 | } | ||
481 | } | ||
482 | ] | ||
483 | } | ||
484 | } | ||
485 | |||
486 | return Account.findOne(query) | ||
487 | } | ||
488 | |||
489 | loadByNameAndHost = function (name: string, host: string) { | ||
490 | const query: Sequelize.FindOptions<AccountAttributes> = { | ||
491 | where: { | ||
492 | name | ||
470 | }, | 493 | }, |
471 | include: [ | 494 | include: [ |
472 | { | 495 | { |
473 | model: Account['sequelize'].models.Pod, | 496 | model: Account['sequelize'].models.Pod, |
497 | required: true, | ||
474 | where: { | 498 | where: { |
475 | host | 499 | host |
476 | } | 500 | } |
diff --git a/server/models/application/application-interface.ts b/server/models/application/application-interface.ts index 33254ba2d..2c391dba3 100644 --- a/server/models/application/application-interface.ts +++ b/server/models/application/application-interface.ts | |||
@@ -1,18 +1,21 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | 2 | import * as Bluebird from 'bluebird' |
3 | 3 | ||
4 | export namespace ApplicationMethods { | 4 | export namespace ApplicationMethods { |
5 | export type LoadMigrationVersion = () => Promise<number> | 5 | export type LoadMigrationVersion = () => Bluebird<number> |
6 | 6 | ||
7 | export type UpdateMigrationVersion = ( | 7 | export type UpdateMigrationVersion = ( |
8 | newVersion: number, | 8 | newVersion: number, |
9 | transaction: Sequelize.Transaction | 9 | transaction: Sequelize.Transaction |
10 | ) => Promise<[ number, ApplicationInstance[] ]> | 10 | ) => Bluebird<[ number, ApplicationInstance[] ]> |
11 | |||
12 | export type CountTotal = () => Bluebird<number> | ||
11 | } | 13 | } |
12 | 14 | ||
13 | export interface ApplicationClass { | 15 | export interface ApplicationClass { |
14 | loadMigrationVersion: ApplicationMethods.LoadMigrationVersion | 16 | loadMigrationVersion: ApplicationMethods.LoadMigrationVersion |
15 | updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion | 17 | updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion |
18 | countTotal: ApplicationMethods.CountTotal | ||
16 | } | 19 | } |
17 | 20 | ||
18 | export interface ApplicationAttributes { | 21 | export interface ApplicationAttributes { |
diff --git a/server/models/application/application.ts b/server/models/application/application.ts index 507b7a843..8ba40a895 100644 --- a/server/models/application/application.ts +++ b/server/models/application/application.ts | |||
@@ -11,6 +11,7 @@ import { | |||
11 | let Application: Sequelize.Model<ApplicationInstance, ApplicationAttributes> | 11 | let Application: Sequelize.Model<ApplicationInstance, ApplicationAttributes> |
12 | let loadMigrationVersion: ApplicationMethods.LoadMigrationVersion | 12 | let loadMigrationVersion: ApplicationMethods.LoadMigrationVersion |
13 | let updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion | 13 | let updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion |
14 | let countTotal: ApplicationMethods.CountTotal | ||
14 | 15 | ||
15 | export default function defineApplication (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | 16 | export default function defineApplication (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { |
16 | Application = sequelize.define<ApplicationInstance, ApplicationAttributes>('Application', | 17 | Application = sequelize.define<ApplicationInstance, ApplicationAttributes>('Application', |
@@ -26,7 +27,11 @@ export default function defineApplication (sequelize: Sequelize.Sequelize, DataT | |||
26 | } | 27 | } |
27 | ) | 28 | ) |
28 | 29 | ||
29 | const classMethods = [ loadMigrationVersion, updateMigrationVersion ] | 30 | const classMethods = [ |
31 | countTotal, | ||
32 | loadMigrationVersion, | ||
33 | updateMigrationVersion | ||
34 | ] | ||
30 | addMethodsToModel(Application, classMethods) | 35 | addMethodsToModel(Application, classMethods) |
31 | 36 | ||
32 | return Application | 37 | return Application |
@@ -34,6 +39,10 @@ export default function defineApplication (sequelize: Sequelize.Sequelize, DataT | |||
34 | 39 | ||
35 | // --------------------------------------------------------------------------- | 40 | // --------------------------------------------------------------------------- |
36 | 41 | ||
42 | countTotal = function () { | ||
43 | return this.count() | ||
44 | } | ||
45 | |||
37 | loadMigrationVersion = function () { | 46 | loadMigrationVersion = function () { |
38 | const query = { | 47 | const query = { |
39 | attributes: [ 'migrationVersion' ] | 48 | attributes: [ 'migrationVersion' ] |
diff --git a/server/models/job/job.ts b/server/models/job/job.ts index ce1203e5a..c2d088090 100644 --- a/server/models/job/job.ts +++ b/server/models/job/job.ts | |||
@@ -10,7 +10,7 @@ import { | |||
10 | 10 | ||
11 | JobMethods | 11 | JobMethods |
12 | } from './job-interface' | 12 | } from './job-interface' |
13 | import { JobState } from '../../../shared/models/job.model' | 13 | import { JobCategory, JobState } from '../../../shared/models/job.model' |
14 | 14 | ||
15 | let Job: Sequelize.Model<JobInstance, JobAttributes> | 15 | let Job: Sequelize.Model<JobInstance, JobAttributes> |
16 | let listWithLimitByCategory: JobMethods.ListWithLimitByCategory | 16 | let listWithLimitByCategory: JobMethods.ListWithLimitByCategory |
@@ -38,7 +38,7 @@ export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Se | |||
38 | { | 38 | { |
39 | indexes: [ | 39 | indexes: [ |
40 | { | 40 | { |
41 | fields: [ 'state' ] | 41 | fields: [ 'state', 'category' ] |
42 | } | 42 | } |
43 | ] | 43 | ] |
44 | } | 44 | } |
@@ -52,14 +52,15 @@ export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Se | |||
52 | 52 | ||
53 | // --------------------------------------------------------------------------- | 53 | // --------------------------------------------------------------------------- |
54 | 54 | ||
55 | listWithLimitByCategory = function (limit: number, state: JobState) { | 55 | listWithLimitByCategory = function (limit: number, state: JobState, jobCategory: JobCategory) { |
56 | const query = { | 56 | const query = { |
57 | order: [ | 57 | order: [ |
58 | [ 'id', 'ASC' ] | 58 | [ 'id', 'ASC' ] |
59 | ], | 59 | ], |
60 | limit: limit, | 60 | limit: limit, |
61 | where: { | 61 | where: { |
62 | state | 62 | state, |
63 | category: jobCategory | ||
63 | } | 64 | } |
64 | } | 65 | } |
65 | 66 | ||