1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import { expect } from 'chai'
5 import { HttpStatusCode } from '@shared/core-utils'
13 loginUsingExternalToken,
18 setAccessTokensToServers,
23 } from '@shared/extra-utils'
24 import { ServerConfig, User, UserRole } from '@shared/models'
26 async function loginExternal (options: {
32 statusCodeExpected?: HttpStatusCode
33 statusCodeExpectedStep2?: HttpStatusCode
35 const res = await options.server.pluginsCommand.getExternalAuth({
36 npmName: options.npmName,
38 authName: options.authName,
40 expectedStatus: options.statusCodeExpected || HttpStatusCode.FOUND_302
43 if (res.status !== HttpStatusCode.FOUND_302) return
45 const location = res.header.location
46 const { externalAuthToken } = decodeQueryString(location)
48 const resLogin = await loginUsingExternalToken(
51 externalAuthToken as string,
52 options.statusCodeExpectedStep2
58 describe('Test external auth plugins', function () {
59 let server: ServerInfo
61 let cyanAccessToken: string
62 let cyanRefreshToken: string
64 let kefkaAccessToken: string
65 let kefkaRefreshToken: string
67 let externalAuthToken: string
69 before(async function () {
72 server = await flushAndRunServer(1)
73 await setAccessTokensToServers([ server ])
75 for (const suffix of [ 'one', 'two', 'three' ]) {
76 await server.pluginsCommand.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) })
80 it('Should display the correct configuration', async function () {
81 const res = await getConfig(server.url)
83 const config: ServerConfig = res.body
85 const auths = config.plugin.registeredExternalAuths
86 expect(auths).to.have.lengthOf(8)
88 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
89 expect(auth2).to.exist
90 expect(auth2.authDisplayName).to.equal('External Auth 2')
91 expect(auth2.npmName).to.equal('peertube-plugin-test-external-auth-one')
94 it('Should redirect for a Cyan login', async function () {
95 const res = await server.pluginsCommand.getExternalAuth({
96 npmName: 'test-external-auth-one',
98 authName: 'external-auth-1',
102 expectedStatus: HttpStatusCode.FOUND_302
105 const location = res.header.location
106 expect(location.startsWith('/login?')).to.be.true
108 const searchParams = decodeQueryString(location)
110 expect(searchParams.externalAuthToken).to.exist
111 expect(searchParams.username).to.equal('cyan')
113 externalAuthToken = searchParams.externalAuthToken as string
116 it('Should reject auto external login with a missing or invalid token', async function () {
117 await loginUsingExternalToken(server, 'cyan', '', HttpStatusCode.BAD_REQUEST_400)
118 await loginUsingExternalToken(server, 'cyan', 'blabla', HttpStatusCode.BAD_REQUEST_400)
121 it('Should reject auto external login with a missing or invalid username', async function () {
122 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
123 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
126 it('Should reject auto external login with an expired token', async function () {
131 await loginUsingExternalToken(server, 'cyan', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
133 await waitUntilLog(server, 'expired external auth token', 2)
136 it('Should auto login Cyan, create the user and use the token', async function () {
138 const res = await loginExternal({
140 npmName: 'test-external-auth-one',
141 authName: 'external-auth-1',
148 cyanAccessToken = res.access_token
149 cyanRefreshToken = res.refresh_token
153 const res = await getMyUserInformation(server.url, cyanAccessToken)
155 const body: User = res.body
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 res = await getMyUserInformation(server.url, kefkaAccessToken)
179 const body: User = res.body
180 expect(body.username).to.equal('kefka')
181 expect(body.account.displayName).to.equal('Kefka Palazzo')
182 expect(body.email).to.equal('kefka@example.com')
183 expect(body.role).to.equal(UserRole.ADMINISTRATOR)
187 it('Should refresh Cyan token, but not Kefka token', async function () {
189 const resRefresh = await refreshToken(server, cyanRefreshToken)
190 cyanAccessToken = resRefresh.body.access_token
191 cyanRefreshToken = resRefresh.body.refresh_token
193 const res = await getMyUserInformation(server.url, cyanAccessToken)
194 const user: User = res.body
195 expect(user.username).to.equal('cyan')
199 await refreshToken(server, kefkaRefreshToken, HttpStatusCode.BAD_REQUEST_400)
203 it('Should update Cyan profile', async function () {
206 accessToken: cyanAccessToken,
207 displayName: 'Cyan Garamonde',
208 description: 'Retainer to the king of Doma'
211 const res = await getMyUserInformation(server.url, cyanAccessToken)
213 const body: User = res.body
214 expect(body.account.displayName).to.equal('Cyan Garamonde')
215 expect(body.account.description).to.equal('Retainer to the king of Doma')
218 it('Should logout Cyan', async function () {
219 await logout(server.url, cyanAccessToken)
222 it('Should have logged out Cyan', async function () {
223 await waitUntilLog(server, 'On logout cyan')
225 await getMyUserInformation(server.url, cyanAccessToken, HttpStatusCode.UNAUTHORIZED_401)
228 it('Should login Cyan and keep the old existing profile', async function () {
230 const res = await loginExternal({
232 npmName: 'test-external-auth-one',
233 authName: 'external-auth-1',
240 cyanAccessToken = res.access_token
243 const res = await getMyUserInformation(server.url, cyanAccessToken)
245 const body: User = res.body
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).to.equal(UserRole.USER)
252 it('Should not update an external auth email', async function () {
255 accessToken: cyanAccessToken,
256 email: 'toto@example.com',
257 currentPassword: 'toto',
258 statusCodeExpected: HttpStatusCode.BAD_REQUEST_400
262 it('Should reject token of Kefka by the plugin hook', async function () {
267 await getMyUserInformation(server.url, kefkaAccessToken, HttpStatusCode.UNAUTHORIZED_401)
270 it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
271 await server.pluginsCommand.updateSettings({
272 npmName: 'peertube-plugin-test-external-auth-one',
273 settings: { disableKefka: true }
276 await userLogin(server, { username: 'kefka', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400)
278 await loginExternal({
280 npmName: 'test-external-auth-one',
281 authName: 'external-auth-2',
286 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
290 it('Should have disabled this auth', async function () {
291 const res = await getConfig(server.url)
293 const config: ServerConfig = res.body
295 const auths = config.plugin.registeredExternalAuths
296 expect(auths).to.have.lengthOf(7)
298 const auth1 = auths.find(a => a.authName === 'external-auth-2')
299 expect(auth1).to.not.exist
302 it('Should uninstall the plugin one and do not login Cyan', async function () {
303 await server.pluginsCommand.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' })
305 await loginExternal({
307 npmName: 'test-external-auth-one',
308 authName: 'external-auth-1',
313 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
316 await userLogin(server, { username: 'cyan', password: null }, HttpStatusCode.BAD_REQUEST_400)
317 await userLogin(server, { username: 'cyan', password: '' }, HttpStatusCode.BAD_REQUEST_400)
318 await userLogin(server, { username: 'cyan', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400)
321 it('Should not login kefka with another plugin', async function () {
322 await loginExternal({
324 npmName: 'test-external-auth-two',
325 authName: 'external-auth-4',
327 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
330 await loginExternal({
332 npmName: 'test-external-auth-two',
333 authName: 'external-auth-4',
335 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
339 it('Should not login an existing user', async function () {
342 accessToken: server.accessToken,
343 username: 'existing_user',
344 password: 'super_password'
347 await loginExternal({
349 npmName: 'test-external-auth-two',
350 authName: 'external-auth-6',
351 username: 'existing_user',
352 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
356 it('Should display the correct configuration', async function () {
357 const res = await getConfig(server.url)
359 const config: ServerConfig = res.body
361 const auths = config.plugin.registeredExternalAuths
362 expect(auths).to.have.lengthOf(6)
364 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
365 expect(auth2).to.not.exist
368 after(async function () {
369 await cleanupTests([ server ])
372 it('Should forward the redirectUrl if the plugin returns one', async function () {
373 const resLogin = await loginExternal({
375 npmName: 'test-external-auth-three',
376 authName: 'external-auth-7',
380 const resLogout = await logout(server.url, resLogin.access_token)
382 expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl')
385 it('Should call the plugin\'s onLogout method with the request', async function () {
386 const resLogin = await loginExternal({
388 npmName: 'test-external-auth-three',
389 authName: 'external-auth-8',
393 const resLogout = await logout(server.url, resLogin.access_token)
395 expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token)