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