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