]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/plugins/external-auth.ts
Introduce plugins command
[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'
ae2abfd3 5import { HttpStatusCode } from '@shared/core-utils'
9107d791 6import {
ae2abfd3
C
7 cleanupTests,
8 createUser,
9107d791 9 decodeQueryString,
ae2abfd3 10 flushAndRunServer,
9107d791 11 getConfig,
9107d791 12 getMyUserInformation,
9107d791
C
13 loginUsingExternalToken,
14 logout,
ae2abfd3 15 PluginsCommand,
9107d791 16 refreshToken,
ae2abfd3 17 ServerInfo,
9107d791 18 setAccessTokensToServers,
9107d791 19 updateMyUser,
a4995eb7 20 userLogin,
ae2abfd3
C
21 wait,
22 waitUntilLog
23} from '@shared/extra-utils'
24import { ServerConfig, User, UserRole } from '@shared/models'
9107d791
C
25
26async function loginExternal (options: {
27 server: ServerInfo
28 npmName: string
29 authName: string
30 username: string
31 query?: any
2d53be02
RK
32 statusCodeExpected?: HttpStatusCode
33 statusCodeExpectedStep2?: HttpStatusCode
9107d791 34}) {
ae2abfd3 35 const res = await options.server.pluginsCommand.getExternalAuth({
9107d791
C
36 npmName: options.npmName,
37 npmVersion: '0.0.1',
38 authName: options.authName,
39 query: options.query,
ae2abfd3 40 expectedStatus: options.statusCodeExpected || HttpStatusCode.FOUND_302
9107d791
C
41 })
42
2d53be02 43 if (res.status !== HttpStatusCode.FOUND_302) return
9107d791
C
44
45 const location = res.header.location
46 const { externalAuthToken } = decodeQueryString(location)
47
48 const resLogin = await loginUsingExternalToken(
49 options.server,
50 options.username,
d253bfaa
C
51 externalAuthToken as string,
52 options.statusCodeExpectedStep2
9107d791
C
53 )
54
55 return resLogin.body
56}
57
58describe('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
74fd2643 75 for (const suffix of [ 'one', 'two', 'three' ]) {
ae2abfd3 76 await server.pluginsCommand.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) })
9107d791
C
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
74fd2643 86 expect(auths).to.have.lengthOf(8)
9107d791
C
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 () {
ae2abfd3 95 const res = await server.pluginsCommand.getExternalAuth({
9107d791
C
96 npmName: 'test-external-auth-one',
97 npmVersion: '0.0.1',
98 authName: 'external-auth-1',
99 query: {
100 username: 'cyan'
101 },
ae2abfd3 102 expectedStatus: HttpStatusCode.FOUND_302
9107d791
C
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 () {
2d53be02
RK
117 await loginUsingExternalToken(server, 'cyan', '', HttpStatusCode.BAD_REQUEST_400)
118 await loginUsingExternalToken(server, 'cyan', 'blabla', HttpStatusCode.BAD_REQUEST_400)
9107d791
C
119 })
120
121 it('Should reject auto external login with a missing or invalid username', async function () {
2d53be02
RK
122 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
123 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
9107d791
C
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
2d53be02 131 await loginUsingExternalToken(server, 'cyan', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
9107d791 132
f43db2f4 133 await waitUntilLog(server, 'expired external auth token', 2)
9107d791
C
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 {
2d53be02 199 await refreshToken(server, kefkaRefreshToken, HttpStatusCode.BAD_REQUEST_400)
9107d791
C
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
2d53be02 225 await getMyUserInformation(server.url, cyanAccessToken, HttpStatusCode.UNAUTHORIZED_401)
9107d791
C
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
9a7fd960
C
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',
2d53be02 258 statusCodeExpected: HttpStatusCode.BAD_REQUEST_400
9a7fd960
C
259 })
260 })
261
9107d791
C
262 it('Should reject token of Kefka by the plugin hook', async function () {
263 this.timeout(10000)
264
265 await wait(5000)
266
2d53be02 267 await getMyUserInformation(server.url, kefkaAccessToken, HttpStatusCode.UNAUTHORIZED_401)
9107d791
C
268 })
269
a4995eb7 270 it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
ae2abfd3 271 await server.pluginsCommand.updateSettings({
a4995eb7
C
272 npmName: 'peertube-plugin-test-external-auth-one',
273 settings: { disableKefka: true }
274 })
275
2d53be02 276 await userLogin(server, { username: 'kefka', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400)
a4995eb7
C
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',
2d53be02 286 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
a4995eb7
C
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
74fd2643 296 expect(auths).to.have.lengthOf(7)
a4995eb7
C
297
298 const auth1 = auths.find(a => a.authName === 'external-auth-2')
299 expect(auth1).to.not.exist
300 })
301
9107d791 302 it('Should uninstall the plugin one and do not login Cyan', async function () {
ae2abfd3 303 await server.pluginsCommand.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' })
9107d791
C
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',
2d53be02 313 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
9107d791 314 })
d253bfaa 315
2d53be02
RK
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)
d253bfaa
C
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',
2d53be02 327 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
d253bfaa
C
328 })
329
330 await loginExternal({
331 server,
332 npmName: 'test-external-auth-two',
333 authName: 'external-auth-4',
334 username: 'kefka',
2d53be02 335 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
d253bfaa
C
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',
2d53be02 352 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
d253bfaa 353 })
9107d791
C
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
74fd2643 362 expect(auths).to.have.lengthOf(6)
9107d791
C
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 })
74fd2643
C
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 })
9107d791 397})