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