diff options
-rw-r--r-- | config/default.yaml | 2 | ||||
-rw-r--r-- | config/production.yaml.example | 2 | ||||
-rw-r--r-- | server/controllers/api/config.ts | 3 | ||||
-rw-r--r-- | server/controllers/api/server/follows.ts | 38 | ||||
-rw-r--r-- | server/initializers/checker-before-init.ts | 2 | ||||
-rw-r--r-- | server/initializers/constants.ts | 3 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-follow.ts | 8 | ||||
-rw-r--r-- | server/middlewares/validators/config.ts | 3 | ||||
-rw-r--r-- | server/middlewares/validators/follows.ts | 20 | ||||
-rw-r--r-- | server/tests/api/check-params/config.ts | 3 | ||||
-rw-r--r-- | server/tests/api/check-params/follows.ts | 80 | ||||
-rw-r--r-- | server/tests/api/server/config.ts | 5 | ||||
-rw-r--r-- | server/tests/api/server/follows-moderation.ts | 83 | ||||
-rw-r--r-- | shared/models/server/custom-config.model.ts | 3 | ||||
-rw-r--r-- | shared/utils/server/config.ts | 3 | ||||
-rw-r--r-- | shared/utils/server/follows.ts | 33 |
16 files changed, 261 insertions, 30 deletions
diff --git a/config/default.yaml b/config/default.yaml index 51f3ad833..0d6e34d86 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -205,3 +205,5 @@ followers: | |||
205 | instance: | 205 | instance: |
206 | # Allow or not other instances to follow yours | 206 | # Allow or not other instances to follow yours |
207 | enabled: true | 207 | enabled: true |
208 | # Whether or not an administrator must manually validate a new follower | ||
209 | manual_approval: false | ||
diff --git a/config/production.yaml.example b/config/production.yaml.example index a2811abd6..5029cc25b 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -222,3 +222,5 @@ followers: | |||
222 | instance: | 222 | instance: |
223 | # Allow or not other instances to follow yours | 223 | # Allow or not other instances to follow yours |
224 | enabled: true | 224 | enabled: true |
225 | # Whether or not an administrator must manually validate a new follower | ||
226 | manual_approval: false | ||
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index f9bb0b947..5c4f455ee 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -282,7 +282,8 @@ function customConfig (): CustomConfig { | |||
282 | }, | 282 | }, |
283 | followers: { | 283 | followers: { |
284 | instance: { | 284 | instance: { |
285 | enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED | 285 | enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED, |
286 | manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL | ||
286 | } | 287 | } |
287 | } | 288 | } |
288 | } | 289 | } |
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts index 87cf091cb..207a09a4c 100644 --- a/server/controllers/api/server/follows.ts +++ b/server/controllers/api/server/follows.ts | |||
@@ -3,7 +3,7 @@ import { UserRight } from '../../../../shared/models/users' | |||
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' | 4 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' |
5 | import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../../../initializers' | 5 | import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../../../initializers' |
6 | import { sendReject, sendUndoFollow } from '../../../lib/activitypub/send' | 6 | import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send' |
7 | import { | 7 | import { |
8 | asyncMiddleware, | 8 | asyncMiddleware, |
9 | authenticate, | 9 | authenticate, |
@@ -14,10 +14,11 @@ import { | |||
14 | setDefaultSort | 14 | setDefaultSort |
15 | } from '../../../middlewares' | 15 | } from '../../../middlewares' |
16 | import { | 16 | import { |
17 | acceptOrRejectFollowerValidator, | ||
17 | followersSortValidator, | 18 | followersSortValidator, |
18 | followingSortValidator, | 19 | followingSortValidator, |
19 | followValidator, | 20 | followValidator, |
20 | removeFollowerValidator, | 21 | getFollowerValidator, |
21 | removeFollowingValidator | 22 | removeFollowingValidator |
22 | } from '../../../middlewares/validators' | 23 | } from '../../../middlewares/validators' |
23 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 24 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
@@ -59,8 +60,24 @@ serverFollowsRouter.get('/followers', | |||
59 | serverFollowsRouter.delete('/followers/:nameWithHost', | 60 | serverFollowsRouter.delete('/followers/:nameWithHost', |
60 | authenticate, | 61 | authenticate, |
61 | ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW), | 62 | ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW), |
62 | asyncMiddleware(removeFollowerValidator), | 63 | asyncMiddleware(getFollowerValidator), |
63 | asyncMiddleware(removeFollower) | 64 | asyncMiddleware(removeOrRejectFollower) |
65 | ) | ||
66 | |||
67 | serverFollowsRouter.post('/followers/:nameWithHost/reject', | ||
68 | authenticate, | ||
69 | ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW), | ||
70 | asyncMiddleware(getFollowerValidator), | ||
71 | acceptOrRejectFollowerValidator, | ||
72 | asyncMiddleware(removeOrRejectFollower) | ||
73 | ) | ||
74 | |||
75 | serverFollowsRouter.post('/followers/:nameWithHost/accept', | ||
76 | authenticate, | ||
77 | ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW), | ||
78 | asyncMiddleware(getFollowerValidator), | ||
79 | acceptOrRejectFollowerValidator, | ||
80 | asyncMiddleware(acceptFollower) | ||
64 | ) | 81 | ) |
65 | 82 | ||
66 | // --------------------------------------------------------------------------- | 83 | // --------------------------------------------------------------------------- |
@@ -136,7 +153,7 @@ async function removeFollowing (req: express.Request, res: express.Response) { | |||
136 | return res.status(204).end() | 153 | return res.status(204).end() |
137 | } | 154 | } |
138 | 155 | ||
139 | async function removeFollower (req: express.Request, res: express.Response) { | 156 | async function removeOrRejectFollower (req: express.Request, res: express.Response) { |
140 | const follow = res.locals.follow | 157 | const follow = res.locals.follow |
141 | 158 | ||
142 | await sendReject(follow.ActorFollower, follow.ActorFollowing) | 159 | await sendReject(follow.ActorFollower, follow.ActorFollowing) |
@@ -145,3 +162,14 @@ async function removeFollower (req: express.Request, res: express.Response) { | |||
145 | 162 | ||
146 | return res.status(204).end() | 163 | return res.status(204).end() |
147 | } | 164 | } |
165 | |||
166 | async function acceptFollower (req: express.Request, res: express.Response) { | ||
167 | const follow = res.locals.follow | ||
168 | |||
169 | await sendAccept(follow) | ||
170 | |||
171 | follow.state = 'accepted' | ||
172 | await follow.save() | ||
173 | |||
174 | return res.status(204).end() | ||
175 | } | ||
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index a9896907d..3095913a3 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -25,7 +25,7 @@ function checkMissedConfig () { | |||
25 | 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', | 25 | 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', |
26 | 'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt', | 26 | 'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt', |
27 | 'services.twitter.username', 'services.twitter.whitelisted', | 27 | 'services.twitter.username', 'services.twitter.whitelisted', |
28 | 'followers.instance.enabled' | 28 | 'followers.instance.enabled', 'followers.instance.manual_approval' |
29 | ] | 29 | ] |
30 | const requiredAlternatives = [ | 30 | const requiredAlternatives = [ |
31 | [ // set | 31 | [ // set |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 43c5ec54c..7d9ffc668 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -327,7 +327,8 @@ const CONFIG = { | |||
327 | }, | 327 | }, |
328 | FOLLOWERS: { | 328 | FOLLOWERS: { |
329 | INSTANCE: { | 329 | INSTANCE: { |
330 | get ENABLED () { return config.get<boolean>('followers.instance.enabled') } | 330 | get ENABLED () { return config.get<boolean>('followers.instance.enabled') }, |
331 | get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') } | ||
331 | } | 332 | } |
332 | } | 333 | } |
333 | } | 334 | } |
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts index cecf09b47..140bbe9f1 100644 --- a/server/lib/activitypub/process/process-follow.ts +++ b/server/lib/activitypub/process/process-follow.ts | |||
@@ -32,6 +32,8 @@ async function processFollow (actor: ActorModel, targetActorURL: string) { | |||
32 | 32 | ||
33 | const serverActor = await getServerActor() | 33 | const serverActor = await getServerActor() |
34 | if (targetActor.id === serverActor.id && CONFIG.FOLLOWERS.INSTANCE.ENABLED === false) { | 34 | if (targetActor.id === serverActor.id && CONFIG.FOLLOWERS.INSTANCE.ENABLED === false) { |
35 | logger.info('Rejecting %s because instance followers are disabled.', targetActor.url) | ||
36 | |||
35 | return sendReject(actor, targetActor) | 37 | return sendReject(actor, targetActor) |
36 | } | 38 | } |
37 | 39 | ||
@@ -43,7 +45,7 @@ async function processFollow (actor: ActorModel, targetActorURL: string) { | |||
43 | defaults: { | 45 | defaults: { |
44 | actorId: actor.id, | 46 | actorId: actor.id, |
45 | targetActorId: targetActor.id, | 47 | targetActorId: targetActor.id, |
46 | state: 'accepted' | 48 | state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted' |
47 | }, | 49 | }, |
48 | transaction: t | 50 | transaction: t |
49 | }) | 51 | }) |
@@ -51,7 +53,7 @@ async function processFollow (actor: ActorModel, targetActorURL: string) { | |||
51 | actorFollow.ActorFollower = actor | 53 | actorFollow.ActorFollower = actor |
52 | actorFollow.ActorFollowing = targetActor | 54 | actorFollow.ActorFollowing = targetActor |
53 | 55 | ||
54 | if (actorFollow.state !== 'accepted') { | 56 | if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) { |
55 | actorFollow.state = 'accepted' | 57 | actorFollow.state = 'accepted' |
56 | await actorFollow.save({ transaction: t }) | 58 | await actorFollow.save({ transaction: t }) |
57 | } | 59 | } |
@@ -60,7 +62,7 @@ async function processFollow (actor: ActorModel, targetActorURL: string) { | |||
60 | actorFollow.ActorFollowing = targetActor | 62 | actorFollow.ActorFollowing = targetActor |
61 | 63 | ||
62 | // Target sends to actor he accepted the follow request | 64 | // Target sends to actor he accepted the follow request |
63 | await sendAccept(actorFollow) | 65 | if (actorFollow.state === 'accepted') await sendAccept(actorFollow) |
64 | 66 | ||
65 | return { actorFollow, created } | 67 | return { actorFollow, created } |
66 | }) | 68 | }) |
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index 270ce66f6..d015fa6fe 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts | |||
@@ -44,6 +44,9 @@ const customConfigUpdateValidator = [ | |||
44 | body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'), | 44 | body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'), |
45 | body('import.videos.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'), | 45 | body('import.videos.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'), |
46 | 46 | ||
47 | body('followers.instance.enabled').isBoolean().withMessage('Should have a valid followers of instance boolean'), | ||
48 | body('followers.instance.manualApproval').isBoolean().withMessage('Should have a valid manual approval boolean'), | ||
49 | |||
47 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 50 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
48 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) | 51 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) |
49 | 52 | ||
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts index 38df39fda..b360cf95e 100644 --- a/server/middlewares/validators/follows.ts +++ b/server/middlewares/validators/follows.ts | |||
@@ -57,11 +57,11 @@ const removeFollowingValidator = [ | |||
57 | } | 57 | } |
58 | ] | 58 | ] |
59 | 59 | ||
60 | const removeFollowerValidator = [ | 60 | const getFollowerValidator = [ |
61 | param('nameWithHost').custom(isValidActorHandle).withMessage('Should have a valid nameWithHost'), | 61 | param('nameWithHost').custom(isValidActorHandle).withMessage('Should have a valid nameWithHost'), |
62 | 62 | ||
63 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 63 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
64 | logger.debug('Checking remove follower parameters', { parameters: req.params }) | 64 | logger.debug('Checking get follower parameters', { parameters: req.params }) |
65 | 65 | ||
66 | if (areValidationErrors(req, res)) return | 66 | if (areValidationErrors(req, res)) return |
67 | 67 | ||
@@ -90,10 +90,24 @@ const removeFollowerValidator = [ | |||
90 | } | 90 | } |
91 | ] | 91 | ] |
92 | 92 | ||
93 | const acceptOrRejectFollowerValidator = [ | ||
94 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
95 | logger.debug('Checking accept/reject follower parameters', { parameters: req.params }) | ||
96 | |||
97 | const follow = res.locals.follow | ||
98 | if (follow.state !== 'pending') { | ||
99 | return res.status(400).json({ error: 'Follow is not in pending state.' }).end() | ||
100 | } | ||
101 | |||
102 | return next() | ||
103 | } | ||
104 | ] | ||
105 | |||
93 | // --------------------------------------------------------------------------- | 106 | // --------------------------------------------------------------------------- |
94 | 107 | ||
95 | export { | 108 | export { |
96 | followValidator, | 109 | followValidator, |
97 | removeFollowingValidator, | 110 | removeFollowingValidator, |
98 | removeFollowerValidator | 111 | getFollowerValidator, |
112 | acceptOrRejectFollowerValidator | ||
99 | } | 113 | } |
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index d117f26e6..01ab84584 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -90,7 +90,8 @@ describe('Test config API validators', function () { | |||
90 | }, | 90 | }, |
91 | followers: { | 91 | followers: { |
92 | instance: { | 92 | instance: { |
93 | enabled: false | 93 | enabled: false, |
94 | manualApproval: true | ||
94 | } | 95 | } |
95 | } | 96 | } |
96 | } | 97 | } |
diff --git a/server/tests/api/check-params/follows.ts b/server/tests/api/check-params/follows.ts index 67fa43778..ed1d2db59 100644 --- a/server/tests/api/check-params/follows.ts +++ b/server/tests/api/check-params/follows.ts | |||
@@ -184,6 +184,86 @@ describe('Test server follows API validators', function () { | |||
184 | }) | 184 | }) |
185 | }) | 185 | }) |
186 | 186 | ||
187 | describe('When accepting a follower', function () { | ||
188 | const path = '/api/v1/server/followers' | ||
189 | |||
190 | it('Should fail with an invalid token', async function () { | ||
191 | await makePostBodyRequest({ | ||
192 | url: server.url, | ||
193 | path: path + '/toto@localhost:9002/accept', | ||
194 | token: 'fake_token', | ||
195 | statusCodeExpected: 401 | ||
196 | }) | ||
197 | }) | ||
198 | |||
199 | it('Should fail if the user is not an administrator', async function () { | ||
200 | await makePostBodyRequest({ | ||
201 | url: server.url, | ||
202 | path: path + '/toto@localhost:9002/accept', | ||
203 | token: userAccessToken, | ||
204 | statusCodeExpected: 403 | ||
205 | }) | ||
206 | }) | ||
207 | |||
208 | it('Should fail with an invalid follower', async function () { | ||
209 | await makePostBodyRequest({ | ||
210 | url: server.url, | ||
211 | path: path + '/toto/accept', | ||
212 | token: server.accessToken, | ||
213 | statusCodeExpected: 400 | ||
214 | }) | ||
215 | }) | ||
216 | |||
217 | it('Should fail with an unknown follower', async function () { | ||
218 | await makePostBodyRequest({ | ||
219 | url: server.url, | ||
220 | path: path + '/toto@localhost:9003/accept', | ||
221 | token: server.accessToken, | ||
222 | statusCodeExpected: 404 | ||
223 | }) | ||
224 | }) | ||
225 | }) | ||
226 | |||
227 | describe('When rejecting a follower', function () { | ||
228 | const path = '/api/v1/server/followers' | ||
229 | |||
230 | it('Should fail with an invalid token', async function () { | ||
231 | await makePostBodyRequest({ | ||
232 | url: server.url, | ||
233 | path: path + '/toto@localhost:9002/reject', | ||
234 | token: 'fake_token', | ||
235 | statusCodeExpected: 401 | ||
236 | }) | ||
237 | }) | ||
238 | |||
239 | it('Should fail if the user is not an administrator', async function () { | ||
240 | await makePostBodyRequest({ | ||
241 | url: server.url, | ||
242 | path: path + '/toto@localhost:9002/reject', | ||
243 | token: userAccessToken, | ||
244 | statusCodeExpected: 403 | ||
245 | }) | ||
246 | }) | ||
247 | |||
248 | it('Should fail with an invalid follower', async function () { | ||
249 | await makePostBodyRequest({ | ||
250 | url: server.url, | ||
251 | path: path + '/toto/reject', | ||
252 | token: server.accessToken, | ||
253 | statusCodeExpected: 400 | ||
254 | }) | ||
255 | }) | ||
256 | |||
257 | it('Should fail with an unknown follower', async function () { | ||
258 | await makePostBodyRequest({ | ||
259 | url: server.url, | ||
260 | path: path + '/toto@localhost:9003/reject', | ||
261 | token: server.accessToken, | ||
262 | statusCodeExpected: 404 | ||
263 | }) | ||
264 | }) | ||
265 | }) | ||
266 | |||
187 | describe('When removing following', function () { | 267 | describe('When removing following', function () { |
188 | const path = '/api/v1/server/following' | 268 | const path = '/api/v1/server/following' |
189 | 269 | ||
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index cb2700f29..5373d02f2 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -65,6 +65,7 @@ function checkInitialConfig (data: CustomConfig) { | |||
65 | expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.false | 65 | expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.false |
66 | 66 | ||
67 | expect(data.followers.instance.enabled).to.be.true | 67 | expect(data.followers.instance.enabled).to.be.true |
68 | expect(data.followers.instance.manualApproval).to.be.false | ||
68 | } | 69 | } |
69 | 70 | ||
70 | function checkUpdatedConfig (data: CustomConfig) { | 71 | function checkUpdatedConfig (data: CustomConfig) { |
@@ -109,6 +110,7 @@ function checkUpdatedConfig (data: CustomConfig) { | |||
109 | expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.true | 110 | expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.true |
110 | 111 | ||
111 | expect(data.followers.instance.enabled).to.be.false | 112 | expect(data.followers.instance.enabled).to.be.false |
113 | expect(data.followers.instance.manualApproval).to.be.true | ||
112 | } | 114 | } |
113 | 115 | ||
114 | describe('Test config', function () { | 116 | describe('Test config', function () { |
@@ -241,7 +243,8 @@ describe('Test config', function () { | |||
241 | }, | 243 | }, |
242 | followers: { | 244 | followers: { |
243 | instance: { | 245 | instance: { |
244 | enabled: false | 246 | enabled: false, |
247 | manualApproval: true | ||
245 | } | 248 | } |
246 | } | 249 | } |
247 | } | 250 | } |
diff --git a/server/tests/api/server/follows-moderation.ts b/server/tests/api/server/follows-moderation.ts index a360706f2..0bb3aa866 100644 --- a/server/tests/api/server/follows-moderation.ts +++ b/server/tests/api/server/follows-moderation.ts | |||
@@ -3,6 +3,7 @@ | |||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | acceptFollower, | ||
6 | flushAndRunMultipleServers, | 7 | flushAndRunMultipleServers, |
7 | killallServers, | 8 | killallServers, |
8 | ServerInfo, | 9 | ServerInfo, |
@@ -13,19 +14,21 @@ import { | |||
13 | follow, | 14 | follow, |
14 | getFollowersListPaginationAndSort, | 15 | getFollowersListPaginationAndSort, |
15 | getFollowingListPaginationAndSort, | 16 | getFollowingListPaginationAndSort, |
16 | removeFollower | 17 | removeFollower, |
18 | rejectFollower | ||
17 | } from '../../../../shared/utils/server/follows' | 19 | } from '../../../../shared/utils/server/follows' |
18 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 20 | import { waitJobs } from '../../../../shared/utils/server/jobs' |
19 | import { ActorFollow } from '../../../../shared/models/actors' | 21 | import { ActorFollow } from '../../../../shared/models/actors' |
20 | 22 | ||
21 | const expect = chai.expect | 23 | const expect = chai.expect |
22 | 24 | ||
23 | async function checkHasFollowers (servers: ServerInfo[]) { | 25 | async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'accepted') { |
24 | { | 26 | { |
25 | const res = await getFollowingListPaginationAndSort(servers[0].url, 0, 5, 'createdAt') | 27 | const res = await getFollowingListPaginationAndSort(servers[0].url, 0, 5, 'createdAt') |
26 | expect(res.body.total).to.equal(1) | 28 | expect(res.body.total).to.equal(1) |
27 | 29 | ||
28 | const follow = res.body.data[0] as ActorFollow | 30 | const follow = res.body.data[0] as ActorFollow |
31 | expect(follow.state).to.equal(state) | ||
29 | expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube') | 32 | expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube') |
30 | expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube') | 33 | expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube') |
31 | } | 34 | } |
@@ -35,6 +38,7 @@ async function checkHasFollowers (servers: ServerInfo[]) { | |||
35 | expect(res.body.total).to.equal(1) | 38 | expect(res.body.total).to.equal(1) |
36 | 39 | ||
37 | const follow = res.body.data[0] as ActorFollow | 40 | const follow = res.body.data[0] as ActorFollow |
41 | expect(follow.state).to.equal(state) | ||
38 | expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube') | 42 | expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube') |
39 | expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube') | 43 | expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube') |
40 | } | 44 | } |
@@ -58,7 +62,7 @@ describe('Test follows moderation', function () { | |||
58 | before(async function () { | 62 | before(async function () { |
59 | this.timeout(30000) | 63 | this.timeout(30000) |
60 | 64 | ||
61 | servers = await flushAndRunMultipleServers(2) | 65 | servers = await flushAndRunMultipleServers(3) |
62 | 66 | ||
63 | // Get the access tokens | 67 | // Get the access tokens |
64 | await setAccessTokensToServers(servers) | 68 | await setAccessTokensToServers(servers) |
@@ -73,7 +77,7 @@ describe('Test follows moderation', function () { | |||
73 | }) | 77 | }) |
74 | 78 | ||
75 | it('Should have correct follows', async function () { | 79 | it('Should have correct follows', async function () { |
76 | await checkHasFollowers(servers) | 80 | await checkServer1And2HasFollowers(servers) |
77 | }) | 81 | }) |
78 | 82 | ||
79 | it('Should remove follower on server 2', async function () { | 83 | it('Should remove follower on server 2', async function () { |
@@ -90,7 +94,8 @@ describe('Test follows moderation', function () { | |||
90 | const subConfig = { | 94 | const subConfig = { |
91 | followers: { | 95 | followers: { |
92 | instance: { | 96 | instance: { |
93 | enabled: false | 97 | enabled: false, |
98 | manualApproval: false | ||
94 | } | 99 | } |
95 | } | 100 | } |
96 | } | 101 | } |
@@ -107,7 +112,8 @@ describe('Test follows moderation', function () { | |||
107 | const subConfig = { | 112 | const subConfig = { |
108 | followers: { | 113 | followers: { |
109 | instance: { | 114 | instance: { |
110 | enabled: true | 115 | enabled: true, |
116 | manualApproval: false | ||
111 | } | 117 | } |
112 | } | 118 | } |
113 | } | 119 | } |
@@ -117,7 +123,70 @@ describe('Test follows moderation', function () { | |||
117 | await follow(servers[0].url, [ servers[1].url ], servers[0].accessToken) | 123 | await follow(servers[0].url, [ servers[1].url ], servers[0].accessToken) |
118 | await waitJobs(servers) | 124 | await waitJobs(servers) |
119 | 125 | ||
120 | await checkHasFollowers(servers) | 126 | await checkServer1And2HasFollowers(servers) |
127 | }) | ||
128 | |||
129 | it('Should manually approve followers', async function () { | ||
130 | this.timeout(20000) | ||
131 | |||
132 | await removeFollower(servers[1].url, servers[1].accessToken, servers[0]) | ||
133 | await waitJobs(servers) | ||
134 | |||
135 | const subConfig = { | ||
136 | followers: { | ||
137 | instance: { | ||
138 | enabled: true, | ||
139 | manualApproval: true | ||
140 | } | ||
141 | } | ||
142 | } | ||
143 | |||
144 | await updateCustomSubConfig(servers[1].url, servers[1].accessToken, subConfig) | ||
145 | await updateCustomSubConfig(servers[2].url, servers[2].accessToken, subConfig) | ||
146 | |||
147 | await follow(servers[0].url, [ servers[1].url ], servers[0].accessToken) | ||
148 | await waitJobs(servers) | ||
149 | |||
150 | await checkServer1And2HasFollowers(servers, 'pending') | ||
151 | }) | ||
152 | |||
153 | it('Should accept a follower', async function () { | ||
154 | await acceptFollower(servers[1].url, servers[1].accessToken, 'peertube@localhost:9001') | ||
155 | await waitJobs(servers) | ||
156 | |||
157 | await checkServer1And2HasFollowers(servers) | ||
158 | }) | ||
159 | |||
160 | it('Should reject another follower', async function () { | ||
161 | this.timeout(20000) | ||
162 | |||
163 | await follow(servers[0].url, [ servers[2].url ], servers[0].accessToken) | ||
164 | await waitJobs(servers) | ||
165 | |||
166 | { | ||
167 | const res = await getFollowingListPaginationAndSort(servers[0].url, 0, 5, 'createdAt') | ||
168 | expect(res.body.total).to.equal(2) | ||
169 | } | ||
170 | |||
171 | { | ||
172 | const res = await getFollowersListPaginationAndSort(servers[1].url, 0, 5, 'createdAt') | ||
173 | expect(res.body.total).to.equal(1) | ||
174 | } | ||
175 | |||
176 | { | ||
177 | const res = await getFollowersListPaginationAndSort(servers[2].url, 0, 5, 'createdAt') | ||
178 | expect(res.body.total).to.equal(1) | ||
179 | } | ||
180 | |||
181 | await rejectFollower(servers[2].url, servers[2].accessToken, 'peertube@localhost:9001') | ||
182 | await waitJobs(servers) | ||
183 | |||
184 | await checkServer1And2HasFollowers(servers) | ||
185 | |||
186 | { | ||
187 | const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt') | ||
188 | expect(res.body.total).to.equal(0) | ||
189 | } | ||
121 | }) | 190 | }) |
122 | 191 | ||
123 | after(async function () { | 192 | after(async function () { |
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts index 642ffea39..ca52eff4b 100644 --- a/shared/models/server/custom-config.model.ts +++ b/shared/models/server/custom-config.model.ts | |||
@@ -88,7 +88,8 @@ export interface CustomConfig { | |||
88 | 88 | ||
89 | followers: { | 89 | followers: { |
90 | instance: { | 90 | instance: { |
91 | enabled: boolean | 91 | enabled: boolean, |
92 | manualApproval: boolean | ||
92 | } | 93 | } |
93 | } | 94 | } |
94 | 95 | ||
diff --git a/shared/utils/server/config.ts b/shared/utils/server/config.ts index 21c689714..deb77e9c0 100644 --- a/shared/utils/server/config.ts +++ b/shared/utils/server/config.ts | |||
@@ -122,7 +122,8 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) { | |||
122 | }, | 122 | }, |
123 | followers: { | 123 | followers: { |
124 | instance: { | 124 | instance: { |
125 | enabled: true | 125 | enabled: true, |
126 | manualApproval: false | ||
126 | } | 127 | } |
127 | } | 128 | } |
128 | } | 129 | } |
diff --git a/shared/utils/server/follows.ts b/shared/utils/server/follows.ts index 949fd8109..1505804de 100644 --- a/shared/utils/server/follows.ts +++ b/shared/utils/server/follows.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { ServerInfo } from './servers' | 2 | import { ServerInfo } from './servers' |
3 | import { waitJobs } from './jobs' | 3 | import { waitJobs } from './jobs' |
4 | import { makeGetRequest, makePostBodyRequest } from '..' | ||
4 | 5 | ||
5 | function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { | 6 | function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { |
6 | const path = '/api/v1/server/followers' | 7 | const path = '/api/v1/server/followers' |
@@ -16,6 +17,28 @@ function getFollowersListPaginationAndSort (url: string, start: number, count: n | |||
16 | .expect('Content-Type', /json/) | 17 | .expect('Content-Type', /json/) |
17 | } | 18 | } |
18 | 19 | ||
20 | function acceptFollower (url: string, token: string, follower: string, statusCodeExpected = 204) { | ||
21 | const path = '/api/v1/server/followers/' + follower + '/accept' | ||
22 | |||
23 | return makePostBodyRequest({ | ||
24 | url, | ||
25 | token, | ||
26 | path, | ||
27 | statusCodeExpected | ||
28 | }) | ||
29 | } | ||
30 | |||
31 | function rejectFollower (url: string, token: string, follower: string, statusCodeExpected = 204) { | ||
32 | const path = '/api/v1/server/followers/' + follower + '/reject' | ||
33 | |||
34 | return makePostBodyRequest({ | ||
35 | url, | ||
36 | token, | ||
37 | path, | ||
38 | statusCodeExpected | ||
39 | }) | ||
40 | } | ||
41 | |||
19 | function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { | 42 | function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { |
20 | const path = '/api/v1/server/following' | 43 | const path = '/api/v1/server/following' |
21 | 44 | ||
@@ -30,18 +53,16 @@ function getFollowingListPaginationAndSort (url: string, start: number, count: n | |||
30 | .expect('Content-Type', /json/) | 53 | .expect('Content-Type', /json/) |
31 | } | 54 | } |
32 | 55 | ||
33 | async function follow (follower: string, following: string[], accessToken: string, expectedStatus = 204) { | 56 | function follow (follower: string, following: string[], accessToken: string, expectedStatus = 204) { |
34 | const path = '/api/v1/server/following' | 57 | const path = '/api/v1/server/following' |
35 | 58 | ||
36 | const followingHosts = following.map(f => f.replace(/^http:\/\//, '')) | 59 | const followingHosts = following.map(f => f.replace(/^http:\/\//, '')) |
37 | const res = await request(follower) | 60 | return request(follower) |
38 | .post(path) | 61 | .post(path) |
39 | .set('Accept', 'application/json') | 62 | .set('Accept', 'application/json') |
40 | .set('Authorization', 'Bearer ' + accessToken) | 63 | .set('Authorization', 'Bearer ' + accessToken) |
41 | .send({ 'hosts': followingHosts }) | 64 | .send({ 'hosts': followingHosts }) |
42 | .expect(expectedStatus) | 65 | .expect(expectedStatus) |
43 | |||
44 | return res | ||
45 | } | 66 | } |
46 | 67 | ||
47 | async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) { | 68 | async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) { |
@@ -84,5 +105,7 @@ export { | |||
84 | unfollow, | 105 | unfollow, |
85 | removeFollower, | 106 | removeFollower, |
86 | follow, | 107 | follow, |
87 | doubleFollow | 108 | doubleFollow, |
109 | acceptFollower, | ||
110 | rejectFollower | ||
88 | } | 111 | } |