]>
Commit | Line | Data |
---|---|---|
9107d791 C |
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | ||
9107d791 | 3 | import { expect } from 'chai' |
c55e3d72 | 4 | import { wait } from '@shared/core-utils' |
7e0c2606 | 5 | import { HttpStatusCode, UserAdminFlag, UserRole } from '@shared/models' |
9107d791 | 6 | import { |
ae2abfd3 | 7 | cleanupTests, |
254d3579 | 8 | createSingleServer, |
4c7e60bc | 9 | decodeQueryString, |
254d3579 | 10 | PeerTubeServer, |
4c7e60bc | 11 | PluginsCommand, |
c55e3d72 | 12 | setAccessTokensToServers |
bf54587a | 13 | } from '@shared/server-commands' |
9107d791 C |
14 | |
15 | async function loginExternal (options: { | |
254d3579 | 16 | server: PeerTubeServer |
9107d791 C |
17 | npmName: string |
18 | authName: string | |
19 | username: string | |
20 | query?: any | |
c0e8b12e C |
21 | expectedStatus?: HttpStatusCode |
22 | expectedStatusStep2?: HttpStatusCode | |
9107d791 | 23 | }) { |
89d241a7 | 24 | const res = await options.server.plugins.getExternalAuth({ |
9107d791 C |
25 | npmName: options.npmName, |
26 | npmVersion: '0.0.1', | |
27 | authName: options.authName, | |
28 | query: options.query, | |
c0e8b12e | 29 | expectedStatus: options.expectedStatus || HttpStatusCode.FOUND_302 |
9107d791 C |
30 | }) |
31 | ||
2d53be02 | 32 | if (res.status !== HttpStatusCode.FOUND_302) return |
9107d791 C |
33 | |
34 | const location = res.header.location | |
35 | const { externalAuthToken } = decodeQueryString(location) | |
36 | ||
89d241a7 | 37 | const resLogin = await options.server.login.loginUsingExternalToken({ |
41d1d075 C |
38 | username: options.username, |
39 | externalAuthToken: externalAuthToken as string, | |
c0e8b12e | 40 | expectedStatus: options.expectedStatusStep2 |
41d1d075 | 41 | }) |
9107d791 C |
42 | |
43 | return resLogin.body | |
44 | } | |
45 | ||
46 | describe('Test external auth plugins', function () { | |
254d3579 | 47 | let server: PeerTubeServer |
9107d791 C |
48 | |
49 | let cyanAccessToken: string | |
50 | let cyanRefreshToken: string | |
51 | ||
52 | let kefkaAccessToken: string | |
53 | let kefkaRefreshToken: string | |
54 | ||
55 | let externalAuthToken: string | |
56 | ||
57 | before(async function () { | |
58 | this.timeout(30000) | |
59 | ||
0b6f5316 C |
60 | server = await createSingleServer(1, { |
61 | rates_limit: { | |
62 | login: { | |
63 | max: 30 | |
64 | } | |
65 | } | |
66 | }) | |
67 | ||
9107d791 C |
68 | await setAccessTokensToServers([ server ]) |
69 | ||
74fd2643 | 70 | for (const suffix of [ 'one', 'two', 'three' ]) { |
89d241a7 | 71 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) }) |
9107d791 C |
72 | } |
73 | }) | |
74 | ||
75 | it('Should display the correct configuration', async function () { | |
89d241a7 | 76 | const config = await server.config.getConfig() |
9107d791 C |
77 | |
78 | const auths = config.plugin.registeredExternalAuths | |
0b6f5316 | 79 | expect(auths).to.have.lengthOf(9) |
9107d791 C |
80 | |
81 | const auth2 = auths.find((a) => a.authName === 'external-auth-2') | |
82 | expect(auth2).to.exist | |
83 | expect(auth2.authDisplayName).to.equal('External Auth 2') | |
84 | expect(auth2.npmName).to.equal('peertube-plugin-test-external-auth-one') | |
85 | }) | |
86 | ||
87 | it('Should redirect for a Cyan login', async function () { | |
89d241a7 | 88 | const res = await server.plugins.getExternalAuth({ |
9107d791 C |
89 | npmName: 'test-external-auth-one', |
90 | npmVersion: '0.0.1', | |
91 | authName: 'external-auth-1', | |
92 | query: { | |
93 | username: 'cyan' | |
94 | }, | |
ae2abfd3 | 95 | expectedStatus: HttpStatusCode.FOUND_302 |
9107d791 C |
96 | }) |
97 | ||
98 | const location = res.header.location | |
99 | expect(location.startsWith('/login?')).to.be.true | |
100 | ||
101 | const searchParams = decodeQueryString(location) | |
102 | ||
103 | expect(searchParams.externalAuthToken).to.exist | |
104 | expect(searchParams.username).to.equal('cyan') | |
105 | ||
106 | externalAuthToken = searchParams.externalAuthToken as string | |
107 | }) | |
108 | ||
109 | it('Should reject auto external login with a missing or invalid token', async function () { | |
89d241a7 | 110 | const command = server.login |
41d1d075 C |
111 | |
112 | await command.loginUsingExternalToken({ username: 'cyan', externalAuthToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | |
113 | await command.loginUsingExternalToken({ username: 'cyan', externalAuthToken: 'blabla', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | |
9107d791 C |
114 | }) |
115 | ||
116 | it('Should reject auto external login with a missing or invalid username', async function () { | |
89d241a7 | 117 | const command = server.login |
41d1d075 C |
118 | |
119 | await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | |
120 | await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | |
9107d791 C |
121 | }) |
122 | ||
123 | it('Should reject auto external login with an expired token', async function () { | |
124 | this.timeout(15000) | |
125 | ||
126 | await wait(5000) | |
127 | ||
89d241a7 | 128 | await server.login.loginUsingExternalToken({ |
41d1d075 C |
129 | username: 'cyan', |
130 | externalAuthToken, | |
131 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | |
132 | }) | |
9107d791 | 133 | |
65058050 | 134 | await server.servers.waitUntilLog('expired external auth token', 4) |
9107d791 C |
135 | }) |
136 | ||
137 | it('Should auto login Cyan, create the user and use the token', async function () { | |
138 | { | |
139 | const res = await loginExternal({ | |
140 | server, | |
141 | npmName: 'test-external-auth-one', | |
142 | authName: 'external-auth-1', | |
143 | query: { | |
144 | username: 'cyan' | |
145 | }, | |
146 | username: 'cyan' | |
147 | }) | |
148 | ||
149 | cyanAccessToken = res.access_token | |
150 | cyanRefreshToken = res.refresh_token | |
151 | } | |
152 | ||
153 | { | |
89d241a7 | 154 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) |
9107d791 C |
155 | expect(body.username).to.equal('cyan') |
156 | expect(body.account.displayName).to.equal('cyan') | |
157 | expect(body.email).to.equal('cyan@example.com') | |
9e5cf66b | 158 | expect(body.role.id).to.equal(UserRole.USER) |
7e0c2606 C |
159 | expect(body.adminFlags).to.equal(UserAdminFlag.NONE) |
160 | expect(body.videoQuota).to.equal(5242880) | |
161 | expect(body.videoQuotaDaily).to.equal(-1) | |
9107d791 C |
162 | } |
163 | }) | |
164 | ||
165 | it('Should auto login Kefka, create the user and use the token', async function () { | |
166 | { | |
167 | const res = await loginExternal({ | |
168 | server, | |
169 | npmName: 'test-external-auth-one', | |
170 | authName: 'external-auth-2', | |
171 | username: 'kefka' | |
172 | }) | |
173 | ||
174 | kefkaAccessToken = res.access_token | |
175 | kefkaRefreshToken = res.refresh_token | |
176 | } | |
177 | ||
178 | { | |
89d241a7 | 179 | const body = await server.users.getMyInfo({ token: kefkaAccessToken }) |
9107d791 C |
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') | |
9e5cf66b | 183 | expect(body.role.id).to.equal(UserRole.ADMINISTRATOR) |
7e0c2606 C |
184 | expect(body.adminFlags).to.equal(UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST) |
185 | expect(body.videoQuota).to.equal(42000) | |
186 | expect(body.videoQuotaDaily).to.equal(42100) | |
9107d791 C |
187 | } |
188 | }) | |
189 | ||
190 | it('Should refresh Cyan token, but not Kefka token', async function () { | |
191 | { | |
89d241a7 | 192 | const resRefresh = await server.login.refreshToken({ refreshToken: cyanRefreshToken }) |
9107d791 C |
193 | cyanAccessToken = resRefresh.body.access_token |
194 | cyanRefreshToken = resRefresh.body.refresh_token | |
195 | ||
89d241a7 | 196 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) |
7926c5f9 | 197 | expect(body.username).to.equal('cyan') |
9107d791 C |
198 | } |
199 | ||
200 | { | |
89d241a7 | 201 | await server.login.refreshToken({ refreshToken: kefkaRefreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) |
9107d791 C |
202 | } |
203 | }) | |
204 | ||
205 | it('Should update Cyan profile', async function () { | |
89d241a7 | 206 | await server.users.updateMe({ |
7926c5f9 | 207 | token: cyanAccessToken, |
9107d791 C |
208 | displayName: 'Cyan Garamonde', |
209 | description: 'Retainer to the king of Doma' | |
210 | }) | |
211 | ||
89d241a7 | 212 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) |
9107d791 C |
213 | expect(body.account.displayName).to.equal('Cyan Garamonde') |
214 | expect(body.account.description).to.equal('Retainer to the king of Doma') | |
215 | }) | |
216 | ||
217 | it('Should logout Cyan', async function () { | |
89d241a7 | 218 | await server.login.logout({ token: cyanAccessToken }) |
9107d791 C |
219 | }) |
220 | ||
221 | it('Should have logged out Cyan', async function () { | |
89d241a7 | 222 | await server.servers.waitUntilLog('On logout cyan') |
9107d791 | 223 | |
89d241a7 | 224 | await server.users.getMyInfo({ token: cyanAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) |
9107d791 C |
225 | }) |
226 | ||
227 | it('Should login Cyan and keep the old existing profile', async function () { | |
228 | { | |
229 | const res = await loginExternal({ | |
230 | server, | |
231 | npmName: 'test-external-auth-one', | |
232 | authName: 'external-auth-1', | |
233 | query: { | |
234 | username: 'cyan' | |
235 | }, | |
236 | username: 'cyan' | |
237 | }) | |
238 | ||
239 | cyanAccessToken = res.access_token | |
240 | } | |
241 | ||
89d241a7 | 242 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) |
9107d791 C |
243 | expect(body.username).to.equal('cyan') |
244 | expect(body.account.displayName).to.equal('Cyan Garamonde') | |
245 | expect(body.account.description).to.equal('Retainer to the king of Doma') | |
9e5cf66b | 246 | expect(body.role.id).to.equal(UserRole.USER) |
9107d791 C |
247 | }) |
248 | ||
9a7fd960 | 249 | it('Should not update an external auth email', async function () { |
89d241a7 | 250 | await server.users.updateMe({ |
7926c5f9 | 251 | token: cyanAccessToken, |
9a7fd960 C |
252 | email: 'toto@example.com', |
253 | currentPassword: 'toto', | |
7926c5f9 | 254 | expectedStatus: 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 | ||
89d241a7 | 263 | await server.users.getMyInfo({ token: kefkaAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) |
9107d791 C |
264 | }) |
265 | ||
a4995eb7 | 266 | it('Should unregister external-auth-2 and do not login existing Kefka', async function () { |
89d241a7 | 267 | await server.plugins.updateSettings({ |
a4995eb7 C |
268 | npmName: 'peertube-plugin-test-external-auth-one', |
269 | settings: { disableKefka: true } | |
270 | }) | |
271 | ||
89d241a7 | 272 | await server.login.login({ user: { username: 'kefka', password: 'fake' }, expectedStatus: 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', | |
c0e8b12e | 282 | expectedStatus: HttpStatusCode.NOT_FOUND_404 |
a4995eb7 C |
283 | }) |
284 | }) | |
285 | ||
286 | it('Should have disabled this auth', async function () { | |
89d241a7 | 287 | const config = await server.config.getConfig() |
a4995eb7 C |
288 | |
289 | const auths = config.plugin.registeredExternalAuths | |
0b6f5316 | 290 | expect(auths).to.have.lengthOf(8) |
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 () { |
89d241a7 | 297 | await server.plugins.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', | |
c0e8b12e | 307 | expectedStatus: HttpStatusCode.NOT_FOUND_404 |
9107d791 | 308 | }) |
d253bfaa | 309 | |
89d241a7 C |
310 | await server.login.login({ user: { username: 'cyan', password: null }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) |
311 | await server.login.login({ user: { username: 'cyan', password: '' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | |
312 | await server.login.login({ user: { username: 'cyan', password: 'fake' }, expectedStatus: 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', | |
c0e8b12e | 321 | expectedStatusStep2: 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', | |
c0e8b12e | 329 | expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400 |
d253bfaa C |
330 | }) |
331 | }) | |
332 | ||
0b6f5316 | 333 | it('Should not login an existing user email', async function () { |
89d241a7 | 334 | await server.users.create({ username: 'existing_user', password: 'super_password' }) |
d253bfaa C |
335 | |
336 | await loginExternal({ | |
337 | server, | |
338 | npmName: 'test-external-auth-two', | |
339 | authName: 'external-auth-6', | |
340 | username: 'existing_user', | |
c0e8b12e | 341 | expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400 |
d253bfaa | 342 | }) |
9107d791 C |
343 | }) |
344 | ||
0b6f5316 C |
345 | it('Should be able to login an existing user username and channel', async function () { |
346 | await server.users.create({ username: 'existing_user2' }) | |
347 | await server.users.create({ username: 'existing_user2-1_channel' }) | |
348 | ||
349 | // Test twice to ensure we don't generate a username on every login | |
350 | for (let i = 0; i < 2; i++) { | |
351 | const res = await loginExternal({ | |
352 | server, | |
353 | npmName: 'test-external-auth-two', | |
354 | authName: 'external-auth-7', | |
355 | username: 'existing_user2' | |
356 | }) | |
357 | ||
358 | const token = res.access_token | |
359 | ||
360 | const myInfo = await server.users.getMyInfo({ token }) | |
361 | expect(myInfo.username).to.equal('existing_user2-1') | |
362 | ||
363 | expect(myInfo.videoChannels[0].name).to.equal('existing_user2-1_channel-1') | |
364 | } | |
365 | }) | |
366 | ||
9107d791 | 367 | it('Should display the correct configuration', async function () { |
89d241a7 | 368 | const config = await server.config.getConfig() |
9107d791 C |
369 | |
370 | const auths = config.plugin.registeredExternalAuths | |
0b6f5316 | 371 | expect(auths).to.have.lengthOf(7) |
9107d791 C |
372 | |
373 | const auth2 = auths.find((a) => a.authName === 'external-auth-2') | |
374 | expect(auth2).to.not.exist | |
375 | }) | |
376 | ||
377 | after(async function () { | |
378 | await cleanupTests([ server ]) | |
379 | }) | |
74fd2643 C |
380 | |
381 | it('Should forward the redirectUrl if the plugin returns one', async function () { | |
382 | const resLogin = await loginExternal({ | |
383 | server, | |
384 | npmName: 'test-external-auth-three', | |
385 | authName: 'external-auth-7', | |
386 | username: 'cid' | |
387 | }) | |
388 | ||
89d241a7 | 389 | const { redirectUrl } = await server.login.logout({ token: resLogin.access_token }) |
41d1d075 | 390 | expect(redirectUrl).to.equal('https://example.com/redirectUrl') |
74fd2643 C |
391 | }) |
392 | ||
393 | it('Should call the plugin\'s onLogout method with the request', async function () { | |
394 | const resLogin = await loginExternal({ | |
395 | server, | |
396 | npmName: 'test-external-auth-three', | |
397 | authName: 'external-auth-8', | |
398 | username: 'cid' | |
399 | }) | |
400 | ||
89d241a7 | 401 | const { redirectUrl } = await server.login.logout({ token: resLogin.access_token }) |
41d1d075 | 402 | expect(redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token) |
74fd2643 | 403 | }) |
9107d791 | 404 | }) |