1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import { expect } from 'chai'
5 import { HttpStatusCode } from '@shared/core-utils'
12 setAccessTokensToServers,
14 } from '@shared/extra-utils'
15 import { UserRole } from '@shared/models'
17 async function loginExternal (options: {
18 server: PeerTubeServer
23 statusCodeExpected?: HttpStatusCode
24 statusCodeExpectedStep2?: HttpStatusCode
26 const res = await options.server.plugins.getExternalAuth({
27 npmName: options.npmName,
29 authName: options.authName,
31 expectedStatus: options.statusCodeExpected || HttpStatusCode.FOUND_302
34 if (res.status !== HttpStatusCode.FOUND_302) return
36 const location = res.header.location
37 const { externalAuthToken } = decodeQueryString(location)
39 const resLogin = await options.server.login.loginUsingExternalToken({
40 username: options.username,
41 externalAuthToken: externalAuthToken as string,
42 expectedStatus: options.statusCodeExpectedStep2
48 describe('Test external auth plugins', function () {
49 let server: PeerTubeServer
51 let cyanAccessToken: string
52 let cyanRefreshToken: string
54 let kefkaAccessToken: string
55 let kefkaRefreshToken: string
57 let externalAuthToken: string
59 before(async function () {
62 server = await createSingleServer(1)
63 await setAccessTokensToServers([ server ])
65 for (const suffix of [ 'one', 'two', 'three' ]) {
66 await server.plugins.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) })
70 it('Should display the correct configuration', async function () {
71 const config = await server.config.getConfig()
73 const auths = config.plugin.registeredExternalAuths
74 expect(auths).to.have.lengthOf(8)
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')
82 it('Should redirect for a Cyan login', async function () {
83 const res = await server.plugins.getExternalAuth({
84 npmName: 'test-external-auth-one',
86 authName: 'external-auth-1',
90 expectedStatus: HttpStatusCode.FOUND_302
93 const location = res.header.location
94 expect(location.startsWith('/login?')).to.be.true
96 const searchParams = decodeQueryString(location)
98 expect(searchParams.externalAuthToken).to.exist
99 expect(searchParams.username).to.equal('cyan')
101 externalAuthToken = searchParams.externalAuthToken as string
104 it('Should reject auto external login with a missing or invalid token', async function () {
105 const command = server.login
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 })
111 it('Should reject auto external login with a missing or invalid username', async function () {
112 const command = server.login
114 await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
115 await command.loginUsingExternalToken({ username: '', externalAuthToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
118 it('Should reject auto external login with an expired token', async function () {
123 await server.login.loginUsingExternalToken({
126 expectedStatus: HttpStatusCode.BAD_REQUEST_400
129 await server.servers.waitUntilLog('expired external auth token', 2)
132 it('Should auto login Cyan, create the user and use the token', async function () {
134 const res = await loginExternal({
136 npmName: 'test-external-auth-one',
137 authName: 'external-auth-1',
144 cyanAccessToken = res.access_token
145 cyanRefreshToken = res.refresh_token
149 const body = await server.users.getMyInfo({ token: cyanAccessToken })
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)
157 it('Should auto login Kefka, create the user and use the token', async function () {
159 const res = await loginExternal({
161 npmName: 'test-external-auth-one',
162 authName: 'external-auth-2',
166 kefkaAccessToken = res.access_token
167 kefkaRefreshToken = res.refresh_token
171 const body = await server.users.getMyInfo({ token: kefkaAccessToken })
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)
179 it('Should refresh Cyan token, but not Kefka token', async function () {
181 const resRefresh = await server.login.refreshToken({ refreshToken: cyanRefreshToken })
182 cyanAccessToken = resRefresh.body.access_token
183 cyanRefreshToken = resRefresh.body.refresh_token
185 const body = await server.users.getMyInfo({ token: cyanAccessToken })
186 expect(body.username).to.equal('cyan')
190 await server.login.refreshToken({ refreshToken: kefkaRefreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
194 it('Should update Cyan profile', async function () {
195 await server.users.updateMe({
196 token: cyanAccessToken,
197 displayName: 'Cyan Garamonde',
198 description: 'Retainer to the king of Doma'
201 const body = await server.users.getMyInfo({ token: cyanAccessToken })
202 expect(body.account.displayName).to.equal('Cyan Garamonde')
203 expect(body.account.description).to.equal('Retainer to the king of Doma')
206 it('Should logout Cyan', async function () {
207 await server.login.logout({ token: cyanAccessToken })
210 it('Should have logged out Cyan', async function () {
211 await server.servers.waitUntilLog('On logout cyan')
213 await server.users.getMyInfo({ token: cyanAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
216 it('Should login Cyan and keep the old existing profile', async function () {
218 const res = await loginExternal({
220 npmName: 'test-external-auth-one',
221 authName: 'external-auth-1',
228 cyanAccessToken = res.access_token
231 const body = await server.users.getMyInfo({ token: cyanAccessToken })
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)
238 it('Should not update an external auth email', async function () {
239 await server.users.updateMe({
240 token: cyanAccessToken,
241 email: 'toto@example.com',
242 currentPassword: 'toto',
243 expectedStatus: HttpStatusCode.BAD_REQUEST_400
247 it('Should reject token of Kefka by the plugin hook', async function () {
252 await server.users.getMyInfo({ token: kefkaAccessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
255 it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
256 await server.plugins.updateSettings({
257 npmName: 'peertube-plugin-test-external-auth-one',
258 settings: { disableKefka: true }
261 await server.login.login({ user: { username: 'kefka', password: 'fake' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
263 await loginExternal({
265 npmName: 'test-external-auth-one',
266 authName: 'external-auth-2',
271 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
275 it('Should have disabled this auth', async function () {
276 const config = await server.config.getConfig()
278 const auths = config.plugin.registeredExternalAuths
279 expect(auths).to.have.lengthOf(7)
281 const auth1 = auths.find(a => a.authName === 'external-auth-2')
282 expect(auth1).to.not.exist
285 it('Should uninstall the plugin one and do not login Cyan', async function () {
286 await server.plugins.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' })
288 await loginExternal({
290 npmName: 'test-external-auth-one',
291 authName: 'external-auth-1',
296 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
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 })
304 it('Should not login kefka with another plugin', async function () {
305 await loginExternal({
307 npmName: 'test-external-auth-two',
308 authName: 'external-auth-4',
310 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
313 await loginExternal({
315 npmName: 'test-external-auth-two',
316 authName: 'external-auth-4',
318 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
322 it('Should not login an existing user', async function () {
323 await server.users.create({ username: 'existing_user', password: 'super_password' })
325 await loginExternal({
327 npmName: 'test-external-auth-two',
328 authName: 'external-auth-6',
329 username: 'existing_user',
330 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
334 it('Should display the correct configuration', async function () {
335 const config = await server.config.getConfig()
337 const auths = config.plugin.registeredExternalAuths
338 expect(auths).to.have.lengthOf(6)
340 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
341 expect(auth2).to.not.exist
344 after(async function () {
345 await cleanupTests([ server ])
348 it('Should forward the redirectUrl if the plugin returns one', async function () {
349 const resLogin = await loginExternal({
351 npmName: 'test-external-auth-three',
352 authName: 'external-auth-7',
356 const { redirectUrl } = await server.login.logout({ token: resLogin.access_token })
357 expect(redirectUrl).to.equal('https://example.com/redirectUrl')
360 it('Should call the plugin\'s onLogout method with the request', async function () {
361 const resLogin = await loginExternal({
363 npmName: 'test-external-auth-three',
364 authName: 'external-auth-8',
368 const { redirectUrl } = await server.login.logout({ token: resLogin.access_token })
369 expect(redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token)