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