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