]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/plugins/external-auth.ts
Introduce server commands
[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 getMyUserInformation,
9107d791
C
12 loginUsingExternalToken,
13 logout,
ae2abfd3 14 PluginsCommand,
9107d791 15 refreshToken,
ae2abfd3 16 ServerInfo,
9107d791 17 setAccessTokensToServers,
9107d791 18 updateMyUser,
a4995eb7 19 userLogin,
6c5065a0 20 wait
ae2abfd3 21} from '@shared/extra-utils'
65e6e260 22import { User, UserRole } from '@shared/models'
9107d791
C
23
24async function loginExternal (options: {
25 server: ServerInfo
26 npmName: string
27 authName: string
28 username: string
29 query?: any
2d53be02
RK
30 statusCodeExpected?: HttpStatusCode
31 statusCodeExpectedStep2?: HttpStatusCode
9107d791 32}) {
ae2abfd3 33 const res = await options.server.pluginsCommand.getExternalAuth({
9107d791
C
34 npmName: options.npmName,
35 npmVersion: '0.0.1',
36 authName: options.authName,
37 query: options.query,
ae2abfd3 38 expectedStatus: options.statusCodeExpected || HttpStatusCode.FOUND_302
9107d791
C
39 })
40
2d53be02 41 if (res.status !== HttpStatusCode.FOUND_302) return
9107d791
C
42
43 const location = res.header.location
44 const { externalAuthToken } = decodeQueryString(location)
45
46 const resLogin = await loginUsingExternalToken(
47 options.server,
48 options.username,
d253bfaa
C
49 externalAuthToken as string,
50 options.statusCodeExpectedStep2
9107d791
C
51 )
52
53 return resLogin.body
54}
55
56describe('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
74fd2643 73 for (const suffix of [ 'one', 'two', 'three' ]) {
ae2abfd3 74 await server.pluginsCommand.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) })
9107d791
C
75 }
76 })
77
78 it('Should display the correct configuration', async function () {
65e6e260 79 const config = await server.configCommand.getConfig()
9107d791
C
80
81 const auths = config.plugin.registeredExternalAuths
74fd2643 82 expect(auths).to.have.lengthOf(8)
9107d791
C
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 () {
ae2abfd3 91 const res = await server.pluginsCommand.getExternalAuth({
9107d791
C
92 npmName: 'test-external-auth-one',
93 npmVersion: '0.0.1',
94 authName: 'external-auth-1',
95 query: {
96 username: 'cyan'
97 },
ae2abfd3 98 expectedStatus: HttpStatusCode.FOUND_302
9107d791
C
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 () {
2d53be02
RK
113 await loginUsingExternalToken(server, 'cyan', '', HttpStatusCode.BAD_REQUEST_400)
114 await loginUsingExternalToken(server, 'cyan', 'blabla', HttpStatusCode.BAD_REQUEST_400)
9107d791
C
115 })
116
117 it('Should reject auto external login with a missing or invalid username', async function () {
2d53be02
RK
118 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
119 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
9107d791
C
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
2d53be02 127 await loginUsingExternalToken(server, 'cyan', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
9107d791 128
6c5065a0 129 await server.serversCommand.waitUntilLog('expired external auth token', 2)
9107d791
C
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 {
2d53be02 195 await refreshToken(server, kefkaRefreshToken, HttpStatusCode.BAD_REQUEST_400)
9107d791
C
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 () {
6c5065a0 219 await server.serversCommand.waitUntilLog('On logout cyan')
9107d791 220
2d53be02 221 await getMyUserInformation(server.url, cyanAccessToken, HttpStatusCode.UNAUTHORIZED_401)
9107d791
C
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
9a7fd960
C
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',
2d53be02 254 statusCodeExpected: HttpStatusCode.BAD_REQUEST_400
9a7fd960
C
255 })
256 })
257
9107d791
C
258 it('Should reject token of Kefka by the plugin hook', async function () {
259 this.timeout(10000)
260
261 await wait(5000)
262
2d53be02 263 await getMyUserInformation(server.url, kefkaAccessToken, HttpStatusCode.UNAUTHORIZED_401)
9107d791
C
264 })
265
a4995eb7 266 it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
ae2abfd3 267 await server.pluginsCommand.updateSettings({
a4995eb7
C
268 npmName: 'peertube-plugin-test-external-auth-one',
269 settings: { disableKefka: true }
270 })
271
2d53be02 272 await userLogin(server, { username: 'kefka', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400)
a4995eb7
C
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',
2d53be02 282 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
a4995eb7
C
283 })
284 })
285
286 it('Should have disabled this auth', async function () {
65e6e260 287 const config = await server.configCommand.getConfig()
a4995eb7
C
288
289 const auths = config.plugin.registeredExternalAuths
74fd2643 290 expect(auths).to.have.lengthOf(7)
a4995eb7
C
291
292 const auth1 = auths.find(a => a.authName === 'external-auth-2')
293 expect(auth1).to.not.exist
294 })
295
9107d791 296 it('Should uninstall the plugin one and do not login Cyan', async function () {
ae2abfd3 297 await server.pluginsCommand.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' })
9107d791
C
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',
2d53be02 307 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
9107d791 308 })
d253bfaa 309
2d53be02
RK
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)
d253bfaa
C
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',
2d53be02 321 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
d253bfaa
C
322 })
323
324 await loginExternal({
325 server,
326 npmName: 'test-external-auth-two',
327 authName: 'external-auth-4',
328 username: 'kefka',
2d53be02 329 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
d253bfaa
C
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',
2d53be02 346 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
d253bfaa 347 })
9107d791
C
348 })
349
350 it('Should display the correct configuration', async function () {
65e6e260 351 const config = await server.configCommand.getConfig()
9107d791
C
352
353 const auths = config.plugin.registeredExternalAuths
74fd2643 354 expect(auths).to.have.lengthOf(6)
9107d791
C
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 })
74fd2643
C
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 })
9107d791 389})