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