]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/plugins/external-auth.ts
e600f958f89bb68657713cadbf64f17387d2abc0
[github/Chocobozzz/PeerTube.git] / server / tests / plugins / external-auth.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3 import { expect } from 'chai'
4 import { wait } from '@shared/core-utils'
5 import { HttpStatusCode, UserAdminFlag, UserRole } from '@shared/models'
6 import {
7 cleanupTests,
8 createSingleServer,
9 decodeQueryString,
10 PeerTubeServer,
11 PluginsCommand,
12 setAccessTokensToServers
13 } from '@shared/server-commands'
14
15 async function loginExternal (options: {
16 server: PeerTubeServer
17 npmName: string
18 authName: string
19 username: string
20 query?: any
21 expectedStatus?: HttpStatusCode
22 expectedStatusStep2?: HttpStatusCode
23 }) {
24 const res = await options.server.plugins.getExternalAuth({
25 npmName: options.npmName,
26 npmVersion: '0.0.1',
27 authName: options.authName,
28 query: options.query,
29 expectedStatus: options.expectedStatus || HttpStatusCode.FOUND_302
30 })
31
32 if (res.status !== HttpStatusCode.FOUND_302) return
33
34 const location = res.header.location
35 const { externalAuthToken } = decodeQueryString(location)
36
37 const resLogin = await options.server.login.loginUsingExternalToken({
38 username: options.username,
39 externalAuthToken: externalAuthToken as string,
40 expectedStatus: options.expectedStatusStep2
41 })
42
43 return resLogin.body
44 }
45
46 describe('Test external auth plugins', function () {
47 let server: PeerTubeServer
48
49 let cyanAccessToken: string
50 let cyanRefreshToken: string
51
52 let kefkaAccessToken: string
53 let kefkaRefreshToken: string
54 let kefkaId: number
55
56 let externalAuthToken: string
57
58 before(async function () {
59 this.timeout(30000)
60
61 server = await createSingleServer(1, {
62 rates_limit: {
63 login: {
64 max: 30
65 }
66 }
67 })
68
69 await setAccessTokensToServers([ server ])
70
71 for (const suffix of [ 'one', 'two', 'three' ]) {
72 await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) })
73 }
74 })
75
76 it('Should display the correct configuration', async function () {
77 const config = await server.config.getConfig()
78
79 const auths = config.plugin.registeredExternalAuths
80 expect(auths).to.have.lengthOf(9)
81
82 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
83 expect(auth2).to.exist
84 expect(auth2.authDisplayName).to.equal('External Auth 2')
85 expect(auth2.npmName).to.equal('peertube-plugin-test-external-auth-one')
86 })
87
88 it('Should redirect for a Cyan login', async function () {
89 const res = await server.plugins.getExternalAuth({
90 npmName: 'test-external-auth-one',
91 npmVersion: '0.0.1',
92 authName: 'external-auth-1',
93 query: {
94 username: 'cyan'
95 },
96 expectedStatus: HttpStatusCode.FOUND_302
97 })
98
99 const location = res.header.location
100 expect(location.startsWith('/login?')).to.be.true
101
102 const searchParams = decodeQueryString(location)
103
104 expect(searchParams.externalAuthToken).to.exist
105 expect(searchParams.username).to.equal('cyan')
106
107 externalAuthToken = searchParams.externalAuthToken as string
108 })
109
110 it('Should reject auto external login with a missing or invalid token', async function () {
111 const command = server.login
112
113 await command.loginUsingExternalToken({ username: 'cyan', externalAuthToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
114 await command.loginUsingExternalToken({ username: 'cyan', externalAuthToken: 'blabla', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
115 })
116
117 it('Should reject auto external login with a missing or invalid username', async function () {
118 const command = server.login
119
120 await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
121 await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
122 })
123
124 it('Should reject auto external login with an expired token', async function () {
125 this.timeout(15000)
126
127 await wait(5000)
128
129 await server.login.loginUsingExternalToken({
130 username: 'cyan',
131 externalAuthToken,
132 expectedStatus: HttpStatusCode.BAD_REQUEST_400
133 })
134
135 await server.servers.waitUntilLog('expired external auth token', 4)
136 })
137
138 it('Should auto login Cyan, create the user and use the token', async function () {
139 {
140 const res = await loginExternal({
141 server,
142 npmName: 'test-external-auth-one',
143 authName: 'external-auth-1',
144 query: {
145 username: 'cyan'
146 },
147 username: 'cyan'
148 })
149
150 cyanAccessToken = res.access_token
151 cyanRefreshToken = res.refresh_token
152 }
153
154 {
155 const body = await server.users.getMyInfo({ token: cyanAccessToken })
156 expect(body.username).to.equal('cyan')
157 expect(body.account.displayName).to.equal('cyan')
158 expect(body.email).to.equal('cyan@example.com')
159 expect(body.role.id).to.equal(UserRole.USER)
160 expect(body.adminFlags).to.equal(UserAdminFlag.NONE)
161 expect(body.videoQuota).to.equal(5242880)
162 expect(body.videoQuotaDaily).to.equal(-1)
163 }
164 })
165
166 it('Should auto login Kefka, create the user and use the token', async function () {
167 {
168 const res = await loginExternal({
169 server,
170 npmName: 'test-external-auth-one',
171 authName: 'external-auth-2',
172 username: 'kefka'
173 })
174
175 kefkaAccessToken = res.access_token
176 kefkaRefreshToken = res.refresh_token
177 }
178
179 {
180 const body = await server.users.getMyInfo({ token: kefkaAccessToken })
181 expect(body.username).to.equal('kefka')
182 expect(body.account.displayName).to.equal('Kefka Palazzo')
183 expect(body.email).to.equal('kefka@example.com')
184 expect(body.role.id).to.equal(UserRole.ADMINISTRATOR)
185 expect(body.adminFlags).to.equal(UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST)
186 expect(body.videoQuota).to.equal(42000)
187 expect(body.videoQuotaDaily).to.equal(42100)
188
189 kefkaId = body.id
190 }
191 })
192
193 it('Should refresh Cyan token, but not Kefka token', async function () {
194 {
195 const resRefresh = await server.login.refreshToken({ refreshToken: cyanRefreshToken })
196 cyanAccessToken = resRefresh.body.access_token
197 cyanRefreshToken = resRefresh.body.refresh_token
198
199 const body = await server.users.getMyInfo({ token: cyanAccessToken })
200 expect(body.username).to.equal('cyan')
201 }
202
203 {
204 await server.login.refreshToken({ refreshToken: kefkaRefreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
205 }
206 })
207
208 it('Should update Cyan profile', async function () {
209 await server.users.updateMe({
210 token: cyanAccessToken,
211 displayName: 'Cyan Garamonde',
212 description: 'Retainer to the king of Doma'
213 })
214
215 const body = await server.users.getMyInfo({ token: cyanAccessToken })
216 expect(body.account.displayName).to.equal('Cyan Garamonde')
217 expect(body.account.description).to.equal('Retainer to the king of Doma')
218 })
219
220 it('Should logout Cyan', async function () {
221 await server.login.logout({ token: cyanAccessToken })
222 })
223
224 it('Should have logged out Cyan', async function () {
225 await server.servers.waitUntilLog('On logout cyan')
226
227 await server.users.getMyInfo({ token: cyanAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
228 })
229
230 it('Should login Cyan and keep the old existing profile', async function () {
231 {
232 const res = await loginExternal({
233 server,
234 npmName: 'test-external-auth-one',
235 authName: 'external-auth-1',
236 query: {
237 username: 'cyan'
238 },
239 username: 'cyan'
240 })
241
242 cyanAccessToken = res.access_token
243 }
244
245 const body = await server.users.getMyInfo({ token: cyanAccessToken })
246 expect(body.username).to.equal('cyan')
247 expect(body.account.displayName).to.equal('Cyan Garamonde')
248 expect(body.account.description).to.equal('Retainer to the king of Doma')
249 expect(body.role.id).to.equal(UserRole.USER)
250 })
251
252 it('Should login Kefka and update the profile', async function () {
253 {
254 await server.users.update({ userId: kefkaId, videoQuota: 43000, videoQuotaDaily: 43100 })
255 await server.users.updateMe({ token: kefkaAccessToken, displayName: 'kefka updated' })
256
257 const body = await server.users.getMyInfo({ token: kefkaAccessToken })
258 expect(body.username).to.equal('kefka')
259 expect(body.account.displayName).to.equal('kefka updated')
260 expect(body.videoQuota).to.equal(43000)
261 expect(body.videoQuotaDaily).to.equal(43100)
262 }
263
264 {
265 const res = await loginExternal({
266 server,
267 npmName: 'test-external-auth-one',
268 authName: 'external-auth-2',
269 username: 'kefka'
270 })
271
272 kefkaAccessToken = res.access_token
273 kefkaRefreshToken = res.refresh_token
274
275 const body = await server.users.getMyInfo({ token: kefkaAccessToken })
276 expect(body.username).to.equal('kefka')
277 expect(body.account.displayName).to.equal('Kefka Palazzo')
278 expect(body.videoQuota).to.equal(42000)
279 expect(body.videoQuotaDaily).to.equal(43100)
280 }
281 })
282
283 it('Should not update an external auth email', async function () {
284 await server.users.updateMe({
285 token: cyanAccessToken,
286 email: 'toto@example.com',
287 currentPassword: 'toto',
288 expectedStatus: HttpStatusCode.BAD_REQUEST_400
289 })
290 })
291
292 it('Should reject token of Kefka by the plugin hook', async function () {
293 this.timeout(10000)
294
295 await wait(5000)
296
297 await server.users.getMyInfo({ token: kefkaAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
298 })
299
300 it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
301 await server.plugins.updateSettings({
302 npmName: 'peertube-plugin-test-external-auth-one',
303 settings: { disableKefka: true }
304 })
305
306 await server.login.login({ user: { username: 'kefka', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
307
308 await loginExternal({
309 server,
310 npmName: 'test-external-auth-one',
311 authName: 'external-auth-2',
312 query: {
313 username: 'kefka'
314 },
315 username: 'kefka',
316 expectedStatus: HttpStatusCode.NOT_FOUND_404
317 })
318 })
319
320 it('Should have disabled this auth', async function () {
321 const config = await server.config.getConfig()
322
323 const auths = config.plugin.registeredExternalAuths
324 expect(auths).to.have.lengthOf(8)
325
326 const auth1 = auths.find(a => a.authName === 'external-auth-2')
327 expect(auth1).to.not.exist
328 })
329
330 it('Should uninstall the plugin one and do not login Cyan', async function () {
331 await server.plugins.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' })
332
333 await loginExternal({
334 server,
335 npmName: 'test-external-auth-one',
336 authName: 'external-auth-1',
337 query: {
338 username: 'cyan'
339 },
340 username: 'cyan',
341 expectedStatus: HttpStatusCode.NOT_FOUND_404
342 })
343
344 await server.login.login({ user: { username: 'cyan', password: null }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
345 await server.login.login({ user: { username: 'cyan', password: '' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
346 await server.login.login({ user: { username: 'cyan', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
347 })
348
349 it('Should not login kefka with another plugin', async function () {
350 await loginExternal({
351 server,
352 npmName: 'test-external-auth-two',
353 authName: 'external-auth-4',
354 username: 'kefka2',
355 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
356 })
357
358 await loginExternal({
359 server,
360 npmName: 'test-external-auth-two',
361 authName: 'external-auth-4',
362 username: 'kefka',
363 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
364 })
365 })
366
367 it('Should not login an existing user email', async function () {
368 await server.users.create({ username: 'existing_user', password: 'super_password' })
369
370 await loginExternal({
371 server,
372 npmName: 'test-external-auth-two',
373 authName: 'external-auth-6',
374 username: 'existing_user',
375 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
376 })
377 })
378
379 it('Should be able to login an existing user username and channel', async function () {
380 await server.users.create({ username: 'existing_user2' })
381 await server.users.create({ username: 'existing_user2-1_channel' })
382
383 // Test twice to ensure we don't generate a username on every login
384 for (let i = 0; i < 2; i++) {
385 const res = await loginExternal({
386 server,
387 npmName: 'test-external-auth-two',
388 authName: 'external-auth-7',
389 username: 'existing_user2'
390 })
391
392 const token = res.access_token
393
394 const myInfo = await server.users.getMyInfo({ token })
395 expect(myInfo.username).to.equal('existing_user2-1')
396
397 expect(myInfo.videoChannels[0].name).to.equal('existing_user2-1_channel-1')
398 }
399 })
400
401 it('Should display the correct configuration', async function () {
402 const config = await server.config.getConfig()
403
404 const auths = config.plugin.registeredExternalAuths
405 expect(auths).to.have.lengthOf(7)
406
407 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
408 expect(auth2).to.not.exist
409 })
410
411 after(async function () {
412 await cleanupTests([ server ])
413 })
414
415 it('Should forward the redirectUrl if the plugin returns one', async function () {
416 const resLogin = await loginExternal({
417 server,
418 npmName: 'test-external-auth-three',
419 authName: 'external-auth-7',
420 username: 'cid'
421 })
422
423 const { redirectUrl } = await server.login.logout({ token: resLogin.access_token })
424 expect(redirectUrl).to.equal('https://example.com/redirectUrl')
425 })
426
427 it('Should call the plugin\'s onLogout method with the request', async function () {
428 const resLogin = await loginExternal({
429 server,
430 npmName: 'test-external-auth-three',
431 authName: 'external-auth-8',
432 username: 'cid'
433 })
434
435 const { redirectUrl } = await server.login.logout({ token: resLogin.access_token })
436 expect(redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token)
437 })
438 })