1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { expect } from 'chai'
4 import { wait } from '@shared/core-utils'
5 import { HttpStatusCode, UserAdminFlag, UserRole } from '@shared/models'
12 setAccessTokensToServers
13 } from '@shared/server-commands'
15 async function loginExternal (options: {
16 server: PeerTubeServer
21 expectedStatus?: HttpStatusCode
22 expectedStatusStep2?: HttpStatusCode
24 const res = await options.server.plugins.getExternalAuth({
25 npmName: options.npmName,
27 authName: options.authName,
29 expectedStatus: options.expectedStatus || HttpStatusCode.FOUND_302
32 if (res.status !== HttpStatusCode.FOUND_302) return
34 const location = res.header.location
35 const { externalAuthToken } = decodeQueryString(location)
37 const resLogin = await options.server.login.loginUsingExternalToken({
38 username: options.username,
39 externalAuthToken: externalAuthToken as string,
40 expectedStatus: options.expectedStatusStep2
46 describe('Test external auth plugins', function () {
47 let server: PeerTubeServer
49 let cyanAccessToken: string
50 let cyanRefreshToken: string
52 let kefkaAccessToken: string
53 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.id).to.equal(UserRole.USER)
160 expect(body.adminFlags).to.equal(UserAdminFlag.NONE)
161 expect(body.videoQuota).to.equal(5242880)
162 expect(body.videoQuotaDaily).to.equal(-1)
166 it('Should auto login Kefka, create the user and use the token', async function () {
168 const res = await loginExternal({
170 npmName: 'test-external-auth-one',
171 authName: 'external-auth-2',
175 kefkaAccessToken = res.access_token
176 kefkaRefreshToken = res.refresh_token
180 const body = await server.users.getMyInfo({ token: kefkaAccessToken })
181 expect(body.username).to.equal('kefka')
182 expect(body.account.displayName).to.equal('Kefka Palazzo')
183 expect(body.email).to.equal('kefka@example.com')
184 expect(body.role.id).to.equal(UserRole.ADMINISTRATOR)
185 expect(body.adminFlags).to.equal(UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST)
186 expect(body.videoQuota).to.equal(42000)
187 expect(body.videoQuotaDaily).to.equal(42100)
193 it('Should refresh Cyan token, but not Kefka token', async function () {
195 const resRefresh = await server.login.refreshToken({ refreshToken: cyanRefreshToken })
196 cyanAccessToken = resRefresh.body.access_token
197 cyanRefreshToken = resRefresh.body.refresh_token
199 const body = await server.users.getMyInfo({ token: cyanAccessToken })
200 expect(body.username).to.equal('cyan')
204 await server.login.refreshToken({ refreshToken: kefkaRefreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
208 it('Should update Cyan profile', async function () {
209 await server.users.updateMe({
210 token: cyanAccessToken,
211 displayName: 'Cyan Garamonde',
212 description: 'Retainer to the king of Doma'
215 const body = await server.users.getMyInfo({ token: cyanAccessToken })
216 expect(body.account.displayName).to.equal('Cyan Garamonde')
217 expect(body.account.description).to.equal('Retainer to the king of Doma')
220 it('Should logout Cyan', async function () {
221 await server.login.logout({ token: cyanAccessToken })
224 it('Should have logged out Cyan', async function () {
225 await server.servers.waitUntilLog('On logout cyan')
227 await server.users.getMyInfo({ token: cyanAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
230 it('Should login Cyan and keep the old existing profile', async function () {
232 const res = await loginExternal({
234 npmName: 'test-external-auth-one',
235 authName: 'external-auth-1',
242 cyanAccessToken = res.access_token
245 const body = await server.users.getMyInfo({ token: cyanAccessToken })
246 expect(body.username).to.equal('cyan')
247 expect(body.account.displayName).to.equal('Cyan Garamonde')
248 expect(body.account.description).to.equal('Retainer to the king of Doma')
249 expect(body.role.id).to.equal(UserRole.USER)
252 it('Should login Kefka and update the profile', async function () {
254 await server.users.update({ userId: kefkaId, videoQuota: 43000, videoQuotaDaily: 43100 })
255 await server.users.updateMe({ token: kefkaAccessToken, displayName: 'kefka updated' })
257 const body = await server.users.getMyInfo({ token: kefkaAccessToken })
258 expect(body.username).to.equal('kefka')
259 expect(body.account.displayName).to.equal('kefka updated')
260 expect(body.videoQuota).to.equal(43000)
261 expect(body.videoQuotaDaily).to.equal(43100)
265 const res = await loginExternal({
267 npmName: 'test-external-auth-one',
268 authName: 'external-auth-2',
272 kefkaAccessToken = res.access_token
273 kefkaRefreshToken = res.refresh_token
275 const body = await server.users.getMyInfo({ token: kefkaAccessToken })
276 expect(body.username).to.equal('kefka')
277 expect(body.account.displayName).to.equal('Kefka Palazzo')
278 expect(body.videoQuota).to.equal(42000)
279 expect(body.videoQuotaDaily).to.equal(43100)
283 it('Should not update an external auth email', async function () {
284 await server.users.updateMe({
285 token: cyanAccessToken,
286 email: 'toto@example.com',
287 currentPassword: 'toto',
288 expectedStatus: HttpStatusCode.BAD_REQUEST_400
292 it('Should reject token of Kefka by the plugin hook', async function () {
297 await server.users.getMyInfo({ token: kefkaAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
300 it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
301 await server.plugins.updateSettings({
302 npmName: 'peertube-plugin-test-external-auth-one',
303 settings: { disableKefka: true }
306 await server.login.login({ user: { username: 'kefka', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
308 await loginExternal({
310 npmName: 'test-external-auth-one',
311 authName: 'external-auth-2',
316 expectedStatus: HttpStatusCode.NOT_FOUND_404
320 it('Should have disabled this auth', async function () {
321 const config = await server.config.getConfig()
323 const auths = config.plugin.registeredExternalAuths
324 expect(auths).to.have.lengthOf(8)
326 const auth1 = auths.find(a => a.authName === 'external-auth-2')
327 expect(auth1).to.not.exist
330 it('Should uninstall the plugin one and do not login Cyan', async function () {
331 await server.plugins.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' })
333 await loginExternal({
335 npmName: 'test-external-auth-one',
336 authName: 'external-auth-1',
341 expectedStatus: HttpStatusCode.NOT_FOUND_404
344 await server.login.login({ user: { username: 'cyan', password: null }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
345 await server.login.login({ user: { username: 'cyan', password: '' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
346 await server.login.login({ user: { username: 'cyan', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
349 it('Should not login kefka with another plugin', async function () {
350 await loginExternal({
352 npmName: 'test-external-auth-two',
353 authName: 'external-auth-4',
355 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
358 await loginExternal({
360 npmName: 'test-external-auth-two',
361 authName: 'external-auth-4',
363 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
367 it('Should not login an existing user email', async function () {
368 await server.users.create({ username: 'existing_user', password: 'super_password' })
370 await loginExternal({
372 npmName: 'test-external-auth-two',
373 authName: 'external-auth-6',
374 username: 'existing_user',
375 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
379 it('Should be able to login an existing user username and channel', async function () {
380 await server.users.create({ username: 'existing_user2' })
381 await server.users.create({ username: 'existing_user2-1_channel' })
383 // Test twice to ensure we don't generate a username on every login
384 for (let i = 0; i < 2; i++) {
385 const res = await loginExternal({
387 npmName: 'test-external-auth-two',
388 authName: 'external-auth-7',
389 username: 'existing_user2'
392 const token = res.access_token
394 const myInfo = await server.users.getMyInfo({ token })
395 expect(myInfo.username).to.equal('existing_user2-1')
397 expect(myInfo.videoChannels[0].name).to.equal('existing_user2-1_channel-1')
401 it('Should display the correct configuration', async function () {
402 const config = await server.config.getConfig()
404 const auths = config.plugin.registeredExternalAuths
405 expect(auths).to.have.lengthOf(7)
407 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
408 expect(auth2).to.not.exist
411 after(async function () {
412 await cleanupTests([ server ])
415 it('Should forward the redirectUrl if the plugin returns one', async function () {
416 const resLogin = await loginExternal({
418 npmName: 'test-external-auth-three',
419 authName: 'external-auth-7',
423 const { redirectUrl } = await server.login.logout({ token: resLogin.access_token })
424 expect(redirectUrl).to.equal('https://example.com/redirectUrl')
427 it('Should call the plugin\'s onLogout method with the request', async function () {
428 const resLogin = await loginExternal({
430 npmName: 'test-external-auth-three',
431 authName: 'external-auth-8',
435 const { redirectUrl } = await server.login.logout({ token: resLogin.access_token })
436 expect(redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token)