1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import { expect } from 'chai'
5 import { wait } from '@shared/core-utils'
6 import { HttpStatusCode, UserRole } from '@shared/models'
13 setAccessTokensToServers
14 } from '@shared/server-commands'
16 async function loginExternal (options: {
17 server: PeerTubeServer
22 expectedStatus?: HttpStatusCode
23 expectedStatusStep2?: HttpStatusCode
25 const res = await options.server.plugins.getExternalAuth({
26 npmName: options.npmName,
28 authName: options.authName,
30 expectedStatus: options.expectedStatus || HttpStatusCode.FOUND_302
33 if (res.status !== HttpStatusCode.FOUND_302) return
35 const location = res.header.location
36 const { externalAuthToken } = decodeQueryString(location)
38 const resLogin = await options.server.login.loginUsingExternalToken({
39 username: options.username,
40 externalAuthToken: externalAuthToken as string,
41 expectedStatus: options.expectedStatusStep2
47 describe('Test external auth plugins', function () {
48 let server: PeerTubeServer
50 let cyanAccessToken: string
51 let cyanRefreshToken: string
53 let kefkaAccessToken: string
54 let kefkaRefreshToken: string
56 let externalAuthToken: string
58 before(async function () {
61 server = await createSingleServer(1, {
69 await setAccessTokensToServers([ server ])
71 for (const suffix of [ 'one', 'two', 'three' ]) {
72 await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) })
76 it('Should display the correct configuration', async function () {
77 const config = await server.config.getConfig()
79 const auths = config.plugin.registeredExternalAuths
80 expect(auths).to.have.lengthOf(9)
82 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
83 expect(auth2).to.exist
84 expect(auth2.authDisplayName).to.equal('External Auth 2')
85 expect(auth2.npmName).to.equal('peertube-plugin-test-external-auth-one')
88 it('Should redirect for a Cyan login', async function () {
89 const res = await server.plugins.getExternalAuth({
90 npmName: 'test-external-auth-one',
92 authName: 'external-auth-1',
96 expectedStatus: HttpStatusCode.FOUND_302
99 const location = res.header.location
100 expect(location.startsWith('/login?')).to.be.true
102 const searchParams = decodeQueryString(location)
104 expect(searchParams.externalAuthToken).to.exist
105 expect(searchParams.username).to.equal('cyan')
107 externalAuthToken = searchParams.externalAuthToken as string
110 it('Should reject auto external login with a missing or invalid token', async function () {
111 const command = server.login
113 await command.loginUsingExternalToken({ username: 'cyan', externalAuthToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
114 await command.loginUsingExternalToken({ username: 'cyan', externalAuthToken: 'blabla', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
117 it('Should reject auto external login with a missing or invalid username', async function () {
118 const command = server.login
120 await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
121 await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
124 it('Should reject auto external login with an expired token', async function () {
129 await server.login.loginUsingExternalToken({
132 expectedStatus: HttpStatusCode.BAD_REQUEST_400
135 await server.servers.waitUntilLog('expired external auth token', 4)
138 it('Should auto login Cyan, create the user and use the token', async function () {
140 const res = await loginExternal({
142 npmName: 'test-external-auth-one',
143 authName: 'external-auth-1',
150 cyanAccessToken = res.access_token
151 cyanRefreshToken = res.refresh_token
155 const body = await server.users.getMyInfo({ token: cyanAccessToken })
156 expect(body.username).to.equal('cyan')
157 expect(body.account.displayName).to.equal('cyan')
158 expect(body.email).to.equal('cyan@example.com')
159 expect(body.role).to.equal(UserRole.USER)
163 it('Should auto login Kefka, create the user and use the token', async function () {
165 const res = await loginExternal({
167 npmName: 'test-external-auth-one',
168 authName: 'external-auth-2',
172 kefkaAccessToken = res.access_token
173 kefkaRefreshToken = res.refresh_token
177 const body = await server.users.getMyInfo({ token: kefkaAccessToken })
178 expect(body.username).to.equal('kefka')
179 expect(body.account.displayName).to.equal('Kefka Palazzo')
180 expect(body.email).to.equal('kefka@example.com')
181 expect(body.role).to.equal(UserRole.ADMINISTRATOR)
185 it('Should refresh Cyan token, but not Kefka token', async function () {
187 const resRefresh = await server.login.refreshToken({ refreshToken: cyanRefreshToken })
188 cyanAccessToken = resRefresh.body.access_token
189 cyanRefreshToken = resRefresh.body.refresh_token
191 const body = await server.users.getMyInfo({ token: cyanAccessToken })
192 expect(body.username).to.equal('cyan')
196 await server.login.refreshToken({ refreshToken: kefkaRefreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
200 it('Should update Cyan profile', async function () {
201 await server.users.updateMe({
202 token: cyanAccessToken,
203 displayName: 'Cyan Garamonde',
204 description: 'Retainer to the king of Doma'
207 const body = await server.users.getMyInfo({ token: cyanAccessToken })
208 expect(body.account.displayName).to.equal('Cyan Garamonde')
209 expect(body.account.description).to.equal('Retainer to the king of Doma')
212 it('Should logout Cyan', async function () {
213 await server.login.logout({ token: cyanAccessToken })
216 it('Should have logged out Cyan', async function () {
217 await server.servers.waitUntilLog('On logout cyan')
219 await server.users.getMyInfo({ token: cyanAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
222 it('Should login Cyan and keep the old existing profile', async function () {
224 const res = await loginExternal({
226 npmName: 'test-external-auth-one',
227 authName: 'external-auth-1',
234 cyanAccessToken = res.access_token
237 const body = await server.users.getMyInfo({ token: cyanAccessToken })
238 expect(body.username).to.equal('cyan')
239 expect(body.account.displayName).to.equal('Cyan Garamonde')
240 expect(body.account.description).to.equal('Retainer to the king of Doma')
241 expect(body.role).to.equal(UserRole.USER)
244 it('Should not update an external auth email', async function () {
245 await server.users.updateMe({
246 token: cyanAccessToken,
247 email: 'toto@example.com',
248 currentPassword: 'toto',
249 expectedStatus: HttpStatusCode.BAD_REQUEST_400
253 it('Should reject token of Kefka by the plugin hook', async function () {
258 await server.users.getMyInfo({ token: kefkaAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
261 it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
262 await server.plugins.updateSettings({
263 npmName: 'peertube-plugin-test-external-auth-one',
264 settings: { disableKefka: true }
267 await server.login.login({ user: { username: 'kefka', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
269 await loginExternal({
271 npmName: 'test-external-auth-one',
272 authName: 'external-auth-2',
277 expectedStatus: HttpStatusCode.NOT_FOUND_404
281 it('Should have disabled this auth', async function () {
282 const config = await server.config.getConfig()
284 const auths = config.plugin.registeredExternalAuths
285 expect(auths).to.have.lengthOf(8)
287 const auth1 = auths.find(a => a.authName === 'external-auth-2')
288 expect(auth1).to.not.exist
291 it('Should uninstall the plugin one and do not login Cyan', async function () {
292 await server.plugins.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' })
294 await loginExternal({
296 npmName: 'test-external-auth-one',
297 authName: 'external-auth-1',
302 expectedStatus: HttpStatusCode.NOT_FOUND_404
305 await server.login.login({ user: { username: 'cyan', password: null }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
306 await server.login.login({ user: { username: 'cyan', password: '' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
307 await server.login.login({ user: { username: 'cyan', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
310 it('Should not login kefka with another plugin', async function () {
311 await loginExternal({
313 npmName: 'test-external-auth-two',
314 authName: 'external-auth-4',
316 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
319 await loginExternal({
321 npmName: 'test-external-auth-two',
322 authName: 'external-auth-4',
324 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
328 it('Should not login an existing user email', async function () {
329 await server.users.create({ username: 'existing_user', password: 'super_password' })
331 await loginExternal({
333 npmName: 'test-external-auth-two',
334 authName: 'external-auth-6',
335 username: 'existing_user',
336 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
340 it('Should be able to login an existing user username and channel', async function () {
341 await server.users.create({ username: 'existing_user2' })
342 await server.users.create({ username: 'existing_user2-1_channel' })
344 // Test twice to ensure we don't generate a username on every login
345 for (let i = 0; i < 2; i++) {
346 const res = await loginExternal({
348 npmName: 'test-external-auth-two',
349 authName: 'external-auth-7',
350 username: 'existing_user2'
353 const token = res.access_token
355 const myInfo = await server.users.getMyInfo({ token })
356 expect(myInfo.username).to.equal('existing_user2-1')
358 expect(myInfo.videoChannels[0].name).to.equal('existing_user2-1_channel-1')
362 it('Should display the correct configuration', async function () {
363 const config = await server.config.getConfig()
365 const auths = config.plugin.registeredExternalAuths
366 expect(auths).to.have.lengthOf(7)
368 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
369 expect(auth2).to.not.exist
372 after(async function () {
373 await cleanupTests([ server ])
376 it('Should forward the redirectUrl if the plugin returns one', async function () {
377 const resLogin = await loginExternal({
379 npmName: 'test-external-auth-three',
380 authName: 'external-auth-7',
384 const { redirectUrl } = await server.login.logout({ token: resLogin.access_token })
385 expect(redirectUrl).to.equal('https://example.com/redirectUrl')
388 it('Should call the plugin\'s onLogout method with the request', async function () {
389 const resLogin = await loginExternal({
391 npmName: 'test-external-auth-three',
392 authName: 'external-auth-8',
396 const { redirectUrl } = await server.login.logout({ token: resLogin.access_token })
397 expect(redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token)