1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import { expect } from 'chai'
11 setAccessTokensToServers,
13 } from '@shared/extra-utils'
14 import { HttpStatusCode, UserRole } from '@shared/models'
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)
62 await setAccessTokensToServers([ server ])
64 for (const suffix of [ 'one', 'two', 'three' ]) {
65 await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) })
69 it('Should display the correct configuration', async function () {
70 const config = await server.config.getConfig()
72 const auths = config.plugin.registeredExternalAuths
73 expect(auths).to.have.lengthOf(8)
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')
81 it('Should redirect for a Cyan login', async function () {
82 const res = await server.plugins.getExternalAuth({
83 npmName: 'test-external-auth-one',
85 authName: 'external-auth-1',
89 expectedStatus: HttpStatusCode.FOUND_302
92 const location = res.header.location
93 expect(location.startsWith('/login?')).to.be.true
95 const searchParams = decodeQueryString(location)
97 expect(searchParams.externalAuthToken).to.exist
98 expect(searchParams.username).to.equal('cyan')
100 externalAuthToken = searchParams.externalAuthToken as string
103 it('Should reject auto external login with a missing or invalid token', async function () {
104 const command = server.login
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 })
110 it('Should reject auto external login with a missing or invalid username', async function () {
111 const command = server.login
113 await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
114 await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
117 it('Should reject auto external login with an expired token', async function () {
122 await server.login.loginUsingExternalToken({
125 expectedStatus: HttpStatusCode.BAD_REQUEST_400
128 await server.servers.waitUntilLog('expired external auth token', 2)
131 it('Should auto login Cyan, create the user and use the token', async function () {
133 const res = await loginExternal({
135 npmName: 'test-external-auth-one',
136 authName: 'external-auth-1',
143 cyanAccessToken = res.access_token
144 cyanRefreshToken = res.refresh_token
148 const body = await server.users.getMyInfo({ token: cyanAccessToken })
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)
156 it('Should auto login Kefka, create the user and use the token', async function () {
158 const res = await loginExternal({
160 npmName: 'test-external-auth-one',
161 authName: 'external-auth-2',
165 kefkaAccessToken = res.access_token
166 kefkaRefreshToken = res.refresh_token
170 const body = await server.users.getMyInfo({ token: kefkaAccessToken })
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)
178 it('Should refresh Cyan token, but not Kefka token', async function () {
180 const resRefresh = await server.login.refreshToken({ refreshToken: cyanRefreshToken })
181 cyanAccessToken = resRefresh.body.access_token
182 cyanRefreshToken = resRefresh.body.refresh_token
184 const body = await server.users.getMyInfo({ token: cyanAccessToken })
185 expect(body.username).to.equal('cyan')
189 await server.login.refreshToken({ refreshToken: kefkaRefreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
193 it('Should update Cyan profile', async function () {
194 await server.users.updateMe({
195 token: cyanAccessToken,
196 displayName: 'Cyan Garamonde',
197 description: 'Retainer to the king of Doma'
200 const body = await server.users.getMyInfo({ token: cyanAccessToken })
201 expect(body.account.displayName).to.equal('Cyan Garamonde')
202 expect(body.account.description).to.equal('Retainer to the king of Doma')
205 it('Should logout Cyan', async function () {
206 await server.login.logout({ token: cyanAccessToken })
209 it('Should have logged out Cyan', async function () {
210 await server.servers.waitUntilLog('On logout cyan')
212 await server.users.getMyInfo({ token: cyanAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
215 it('Should login Cyan and keep the old existing profile', async function () {
217 const res = await loginExternal({
219 npmName: 'test-external-auth-one',
220 authName: 'external-auth-1',
227 cyanAccessToken = res.access_token
230 const body = await server.users.getMyInfo({ token: cyanAccessToken })
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)
237 it('Should not update an external auth email', async function () {
238 await server.users.updateMe({
239 token: cyanAccessToken,
240 email: 'toto@example.com',
241 currentPassword: 'toto',
242 expectedStatus: HttpStatusCode.BAD_REQUEST_400
246 it('Should reject token of Kefka by the plugin hook', async function () {
251 await server.users.getMyInfo({ token: kefkaAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
254 it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
255 await server.plugins.updateSettings({
256 npmName: 'peertube-plugin-test-external-auth-one',
257 settings: { disableKefka: true }
260 await server.login.login({ user: { username: 'kefka', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
262 await loginExternal({
264 npmName: 'test-external-auth-one',
265 authName: 'external-auth-2',
270 expectedStatus: HttpStatusCode.NOT_FOUND_404
274 it('Should have disabled this auth', async function () {
275 const config = await server.config.getConfig()
277 const auths = config.plugin.registeredExternalAuths
278 expect(auths).to.have.lengthOf(7)
280 const auth1 = auths.find(a => a.authName === 'external-auth-2')
281 expect(auth1).to.not.exist
284 it('Should uninstall the plugin one and do not login Cyan', async function () {
285 await server.plugins.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' })
287 await loginExternal({
289 npmName: 'test-external-auth-one',
290 authName: 'external-auth-1',
295 expectedStatus: HttpStatusCode.NOT_FOUND_404
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 })
303 it('Should not login kefka with another plugin', async function () {
304 await loginExternal({
306 npmName: 'test-external-auth-two',
307 authName: 'external-auth-4',
309 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
312 await loginExternal({
314 npmName: 'test-external-auth-two',
315 authName: 'external-auth-4',
317 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
321 it('Should not login an existing user', async function () {
322 await server.users.create({ username: 'existing_user', password: 'super_password' })
324 await loginExternal({
326 npmName: 'test-external-auth-two',
327 authName: 'external-auth-6',
328 username: 'existing_user',
329 expectedStatusStep2: HttpStatusCode.BAD_REQUEST_400
333 it('Should display the correct configuration', async function () {
334 const config = await server.config.getConfig()
336 const auths = config.plugin.registeredExternalAuths
337 expect(auths).to.have.lengthOf(6)
339 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
340 expect(auth2).to.not.exist
343 after(async function () {
344 await cleanupTests([ server ])
347 it('Should forward the redirectUrl if the plugin returns one', async function () {
348 const resLogin = await loginExternal({
350 npmName: 'test-external-auth-three',
351 authName: 'external-auth-7',
355 const { redirectUrl } = await server.login.logout({ token: resLogin.access_token })
356 expect(redirectUrl).to.equal('https://example.com/redirectUrl')
359 it('Should call the plugin\'s onLogout method with the request', async function () {
360 const resLogin = await loginExternal({
362 npmName: 'test-external-auth-three',
363 authName: 'external-auth-8',
367 const { redirectUrl } = await server.login.logout({ token: resLogin.access_token })
368 expect(redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token)