]>
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' | |
9107d791 | 5 | import { |
ae2abfd3 | 6 | cleanupTests, |
254d3579 | 7 | createSingleServer, |
4c7e60bc | 8 | decodeQueryString, |
254d3579 | 9 | PeerTubeServer, |
4c7e60bc | 10 | PluginsCommand, |
9107d791 | 11 | setAccessTokensToServers, |
6c5065a0 | 12 | wait |
ae2abfd3 | 13 | } from '@shared/extra-utils' |
4c7e60bc | 14 | import { HttpStatusCode, UserRole } from '@shared/models' |
9107d791 C |
15 | |
16 | async function loginExternal (options: { | |
254d3579 | 17 | server: PeerTubeServer |
9107d791 C |
18 | npmName: string |
19 | authName: string | |
20 | username: string | |
21 | query?: any | |
c0e8b12e C |
22 | expectedStatus?: HttpStatusCode |
23 | expectedStatusStep2?: HttpStatusCode | |
9107d791 | 24 | }) { |
89d241a7 | 25 | const res = await options.server.plugins.getExternalAuth({ |
9107d791 C |
26 | npmName: options.npmName, |
27 | npmVersion: '0.0.1', | |
28 | authName: options.authName, | |
29 | query: options.query, | |
c0e8b12e | 30 | expectedStatus: options.expectedStatus || HttpStatusCode.FOUND_302 |
9107d791 C |
31 | }) |
32 | ||
2d53be02 | 33 | if (res.status !== HttpStatusCode.FOUND_302) return |
9107d791 C |
34 | |
35 | const location = res.header.location | |
36 | const { externalAuthToken } = decodeQueryString(location) | |
37 | ||
89d241a7 | 38 | const resLogin = await options.server.login.loginUsingExternalToken({ |
41d1d075 C |
39 | username: options.username, |
40 | externalAuthToken: externalAuthToken as string, | |
c0e8b12e | 41 | expectedStatus: options.expectedStatusStep2 |
41d1d075 | 42 | }) |
9107d791 C |
43 | |
44 | return resLogin.body | |
45 | } | |
46 | ||
47 | describe('Test external auth plugins', function () { | |
254d3579 | 48 | let server: PeerTubeServer |
9107d791 C |
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 | ||
254d3579 | 61 | server = await createSingleServer(1) |
9107d791 C |
62 | await setAccessTokensToServers([ server ]) |
63 | ||
74fd2643 | 64 | for (const suffix of [ 'one', 'two', 'three' ]) { |
89d241a7 | 65 | await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) }) |
9107d791 C |
66 | } |
67 | }) | |
68 | ||
69 | it('Should display the correct configuration', async function () { | |
89d241a7 | 70 | const config = await server.config.getConfig() |
9107d791 C |
71 | |
72 | const auths = config.plugin.registeredExternalAuths | |
74fd2643 | 73 | expect(auths).to.have.lengthOf(8) |
9107d791 C |
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 () { | |
89d241a7 | 82 | const res = await server.plugins.getExternalAuth({ |
9107d791 C |
83 | npmName: 'test-external-auth-one', |
84 | npmVersion: '0.0.1', | |
85 | authName: 'external-auth-1', | |
86 | query: { | |
87 | username: 'cyan' | |
88 | }, | |
ae2abfd3 | 89 | expectedStatus: HttpStatusCode.FOUND_302 |
9107d791 C |
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 () { | |
89d241a7 | 104 | const command = server.login |
41d1d075 C |
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 }) | |
9107d791 C |
108 | }) |
109 | ||
110 | it('Should reject auto external login with a missing or invalid username', async function () { | |
89d241a7 | 111 | const command = server.login |
41d1d075 C |
112 | |
113 | await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | |
114 | await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | |
9107d791 C |
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 | ||
89d241a7 | 122 | await server.login.loginUsingExternalToken({ |
41d1d075 C |
123 | username: 'cyan', |
124 | externalAuthToken, | |
125 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | |
126 | }) | |
9107d791 | 127 | |
89d241a7 | 128 | await server.servers.waitUntilLog('expired external auth token', 2) |
9107d791 C |
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 | { | |
89d241a7 | 148 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) |
9107d791 C |
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 | { | |
89d241a7 | 170 | const body = await server.users.getMyInfo({ token: kefkaAccessToken }) |
9107d791 C |
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 | { | |
89d241a7 | 180 | const resRefresh = await server.login.refreshToken({ refreshToken: cyanRefreshToken }) |
9107d791 C |
181 | cyanAccessToken = resRefresh.body.access_token |
182 | cyanRefreshToken = resRefresh.body.refresh_token | |
183 | ||
89d241a7 | 184 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) |
7926c5f9 | 185 | expect(body.username).to.equal('cyan') |
9107d791 C |
186 | } |
187 | ||
188 | { | |
89d241a7 | 189 | await server.login.refreshToken({ refreshToken: kefkaRefreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) |
9107d791 C |
190 | } |
191 | }) | |
192 | ||
193 | it('Should update Cyan profile', async function () { | |
89d241a7 | 194 | await server.users.updateMe({ |
7926c5f9 | 195 | token: cyanAccessToken, |
9107d791 C |
196 | displayName: 'Cyan Garamonde', |
197 | description: 'Retainer to the king of Doma' | |
198 | }) | |
199 | ||
89d241a7 | 200 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) |
9107d791 C |
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 () { | |
89d241a7 | 206 | await server.login.logout({ token: cyanAccessToken }) |
9107d791 C |
207 | }) |
208 | ||
209 | it('Should have logged out Cyan', async function () { | |
89d241a7 | 210 | await server.servers.waitUntilLog('On logout cyan') |
9107d791 | 211 | |
89d241a7 | 212 | await server.users.getMyInfo({ token: cyanAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) |
9107d791 C |
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 | ||
89d241a7 | 230 | const body = await server.users.getMyInfo({ token: cyanAccessToken }) |
9107d791 C |
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 | ||
9a7fd960 | 237 | it('Should not update an external auth email', async function () { |
89d241a7 | 238 | await server.users.updateMe({ |
7926c5f9 | 239 | token: cyanAccessToken, |
9a7fd960 C |
240 | email: 'toto@example.com', |
241 | currentPassword: 'toto', | |
7926c5f9 | 242 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 |
9a7fd960 C |
243 | }) |
244 | }) | |
245 | ||
9107d791 C |
246 | it('Should reject token of Kefka by the plugin hook', async function () { |
247 | this.timeout(10000) | |
248 | ||
249 | await wait(5000) | |
250 | ||
89d241a7 | 251 | await server.users.getMyInfo({ token: kefkaAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) |
9107d791 C |
252 | }) |
253 | ||
a4995eb7 | 254 | it('Should unregister external-auth-2 and do not login existing Kefka', async function () { |
89d241a7 | 255 | await server.plugins.updateSettings({ |
a4995eb7 C |
256 | npmName: 'peertube-plugin-test-external-auth-one', |
257 | settings: { disableKefka: true } | |
258 | }) | |
259 | ||
89d241a7 | 260 | await server.login.login({ user: { username: 'kefka', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) |
a4995eb7 C |
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', | |
c0e8b12e | 270 | expectedStatus: HttpStatusCode.NOT_FOUND_404 |
a4995eb7 C |
271 | }) |
272 | }) | |
273 | ||
274 | it('Should have disabled this auth', async function () { | |
89d241a7 | 275 | const config = await server.config.getConfig() |
a4995eb7 C |
276 | |
277 | const auths = config.plugin.registeredExternalAuths | |
74fd2643 | 278 | expect(auths).to.have.lengthOf(7) |
a4995eb7 C |
279 | |
280 | const auth1 = auths.find(a => a.authName === 'external-auth-2') | |
281 | expect(auth1).to.not.exist | |
282 | }) | |
283 | ||
9107d791 | 284 | it('Should uninstall the plugin one and do not login Cyan', async function () { |
89d241a7 | 285 | await server.plugins.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' }) |
9107d791 C |
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', | |
c0e8b12e | 295 | expectedStatus: HttpStatusCode.NOT_FOUND_404 |
9107d791 | 296 | }) |
d253bfaa | 297 | |
89d241a7 C |
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 }) | |
d253bfaa C |
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', | |
c0e8b12e | 309 | expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400 |
d253bfaa C |
310 | }) |
311 | ||
312 | await loginExternal({ | |
313 | server, | |
314 | npmName: 'test-external-auth-two', | |
315 | authName: 'external-auth-4', | |
316 | username: 'kefka', | |
c0e8b12e | 317 | expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400 |
d253bfaa C |
318 | }) |
319 | }) | |
320 | ||
321 | it('Should not login an existing user', async function () { | |
89d241a7 | 322 | await server.users.create({ username: 'existing_user', password: 'super_password' }) |
d253bfaa C |
323 | |
324 | await loginExternal({ | |
325 | server, | |
326 | npmName: 'test-external-auth-two', | |
327 | authName: 'external-auth-6', | |
328 | username: 'existing_user', | |
c0e8b12e | 329 | expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400 |
d253bfaa | 330 | }) |
9107d791 C |
331 | }) |
332 | ||
333 | it('Should display the correct configuration', async function () { | |
89d241a7 | 334 | const config = await server.config.getConfig() |
9107d791 C |
335 | |
336 | const auths = config.plugin.registeredExternalAuths | |
74fd2643 | 337 | expect(auths).to.have.lengthOf(6) |
9107d791 C |
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 | }) | |
74fd2643 C |
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 | ||
89d241a7 | 355 | const { redirectUrl } = await server.login.logout({ token: resLogin.access_token }) |
41d1d075 | 356 | expect(redirectUrl).to.equal('https://example.com/redirectUrl') |
74fd2643 C |
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 | ||
89d241a7 | 367 | const { redirectUrl } = await server.login.logout({ token: resLogin.access_token }) |
41d1d075 | 368 | expect(redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token) |
74fd2643 | 369 | }) |
9107d791 | 370 | }) |