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