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 loginUsingExternalToken,
17 setAccessTokensToServers,
22 } from '@shared/extra-utils'
23 import { User, UserRole } from '@shared/models'
25 async function loginExternal (options: {
31 statusCodeExpected?: HttpStatusCode
32 statusCodeExpectedStep2?: HttpStatusCode
34 const res = await options.server.pluginsCommand.getExternalAuth({
35 npmName: options.npmName,
37 authName: options.authName,
39 expectedStatus: options.statusCodeExpected || HttpStatusCode.FOUND_302
42 if (res.status !== HttpStatusCode.FOUND_302) return
44 const location = res.header.location
45 const { externalAuthToken } = decodeQueryString(location)
47 const resLogin = await loginUsingExternalToken(
50 externalAuthToken as string,
51 options.statusCodeExpectedStep2
57 describe('Test external auth plugins', function () {
58 let server: ServerInfo
60 let cyanAccessToken: string
61 let cyanRefreshToken: string
63 let kefkaAccessToken: string
64 let kefkaRefreshToken: string
66 let externalAuthToken: string
68 before(async function () {
71 server = await flushAndRunServer(1)
72 await setAccessTokensToServers([ server ])
74 for (const suffix of [ 'one', 'two', 'three' ]) {
75 await server.pluginsCommand.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) })
79 it('Should display the correct configuration', async function () {
80 const config = await server.configCommand.getConfig()
82 const auths = config.plugin.registeredExternalAuths
83 expect(auths).to.have.lengthOf(8)
85 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
86 expect(auth2).to.exist
87 expect(auth2.authDisplayName).to.equal('External Auth 2')
88 expect(auth2.npmName).to.equal('peertube-plugin-test-external-auth-one')
91 it('Should redirect for a Cyan login', async function () {
92 const res = await server.pluginsCommand.getExternalAuth({
93 npmName: 'test-external-auth-one',
95 authName: 'external-auth-1',
99 expectedStatus: HttpStatusCode.FOUND_302
102 const location = res.header.location
103 expect(location.startsWith('/login?')).to.be.true
105 const searchParams = decodeQueryString(location)
107 expect(searchParams.externalAuthToken).to.exist
108 expect(searchParams.username).to.equal('cyan')
110 externalAuthToken = searchParams.externalAuthToken as string
113 it('Should reject auto external login with a missing or invalid token', async function () {
114 await loginUsingExternalToken(server, 'cyan', '', HttpStatusCode.BAD_REQUEST_400)
115 await loginUsingExternalToken(server, 'cyan', 'blabla', HttpStatusCode.BAD_REQUEST_400)
118 it('Should reject auto external login with a missing or invalid username', async function () {
119 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
120 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
123 it('Should reject auto external login with an expired token', async function () {
128 await loginUsingExternalToken(server, 'cyan', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
130 await waitUntilLog(server, 'expired external auth token', 2)
133 it('Should auto login Cyan, create the user and use the token', async function () {
135 const res = await loginExternal({
137 npmName: 'test-external-auth-one',
138 authName: 'external-auth-1',
145 cyanAccessToken = res.access_token
146 cyanRefreshToken = res.refresh_token
150 const res = await getMyUserInformation(server.url, cyanAccessToken)
152 const body: User = res.body
153 expect(body.username).to.equal('cyan')
154 expect(body.account.displayName).to.equal('cyan')
155 expect(body.email).to.equal('cyan@example.com')
156 expect(body.role).to.equal(UserRole.USER)
160 it('Should auto login Kefka, create the user and use the token', async function () {
162 const res = await loginExternal({
164 npmName: 'test-external-auth-one',
165 authName: 'external-auth-2',
169 kefkaAccessToken = res.access_token
170 kefkaRefreshToken = res.refresh_token
174 const res = await getMyUserInformation(server.url, kefkaAccessToken)
176 const body: User = res.body
177 expect(body.username).to.equal('kefka')
178 expect(body.account.displayName).to.equal('Kefka Palazzo')
179 expect(body.email).to.equal('kefka@example.com')
180 expect(body.role).to.equal(UserRole.ADMINISTRATOR)
184 it('Should refresh Cyan token, but not Kefka token', async function () {
186 const resRefresh = await refreshToken(server, cyanRefreshToken)
187 cyanAccessToken = resRefresh.body.access_token
188 cyanRefreshToken = resRefresh.body.refresh_token
190 const res = await getMyUserInformation(server.url, cyanAccessToken)
191 const user: User = res.body
192 expect(user.username).to.equal('cyan')
196 await refreshToken(server, kefkaRefreshToken, HttpStatusCode.BAD_REQUEST_400)
200 it('Should update Cyan profile', async function () {
203 accessToken: cyanAccessToken,
204 displayName: 'Cyan Garamonde',
205 description: 'Retainer to the king of Doma'
208 const res = await getMyUserInformation(server.url, cyanAccessToken)
210 const body: User = res.body
211 expect(body.account.displayName).to.equal('Cyan Garamonde')
212 expect(body.account.description).to.equal('Retainer to the king of Doma')
215 it('Should logout Cyan', async function () {
216 await logout(server.url, cyanAccessToken)
219 it('Should have logged out Cyan', async function () {
220 await waitUntilLog(server, 'On logout cyan')
222 await getMyUserInformation(server.url, cyanAccessToken, HttpStatusCode.UNAUTHORIZED_401)
225 it('Should login Cyan and keep the old existing profile', async function () {
227 const res = await loginExternal({
229 npmName: 'test-external-auth-one',
230 authName: 'external-auth-1',
237 cyanAccessToken = res.access_token
240 const res = await getMyUserInformation(server.url, cyanAccessToken)
242 const body: User = res.body
243 expect(body.username).to.equal('cyan')
244 expect(body.account.displayName).to.equal('Cyan Garamonde')
245 expect(body.account.description).to.equal('Retainer to the king of Doma')
246 expect(body.role).to.equal(UserRole.USER)
249 it('Should not update an external auth email', async function () {
252 accessToken: cyanAccessToken,
253 email: 'toto@example.com',
254 currentPassword: 'toto',
255 statusCodeExpected: HttpStatusCode.BAD_REQUEST_400
259 it('Should reject token of Kefka by the plugin hook', async function () {
264 await getMyUserInformation(server.url, kefkaAccessToken, HttpStatusCode.UNAUTHORIZED_401)
267 it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
268 await server.pluginsCommand.updateSettings({
269 npmName: 'peertube-plugin-test-external-auth-one',
270 settings: { disableKefka: true }
273 await userLogin(server, { username: 'kefka', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400)
275 await loginExternal({
277 npmName: 'test-external-auth-one',
278 authName: 'external-auth-2',
283 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
287 it('Should have disabled this auth', async function () {
288 const config = await server.configCommand.getConfig()
290 const auths = config.plugin.registeredExternalAuths
291 expect(auths).to.have.lengthOf(7)
293 const auth1 = auths.find(a => a.authName === 'external-auth-2')
294 expect(auth1).to.not.exist
297 it('Should uninstall the plugin one and do not login Cyan', async function () {
298 await server.pluginsCommand.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' })
300 await loginExternal({
302 npmName: 'test-external-auth-one',
303 authName: 'external-auth-1',
308 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
311 await userLogin(server, { username: 'cyan', password: null }, HttpStatusCode.BAD_REQUEST_400)
312 await userLogin(server, { username: 'cyan', password: '' }, HttpStatusCode.BAD_REQUEST_400)
313 await userLogin(server, { username: 'cyan', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400)
316 it('Should not login kefka with another plugin', async function () {
317 await loginExternal({
319 npmName: 'test-external-auth-two',
320 authName: 'external-auth-4',
322 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
325 await loginExternal({
327 npmName: 'test-external-auth-two',
328 authName: 'external-auth-4',
330 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
334 it('Should not login an existing user', async function () {
337 accessToken: server.accessToken,
338 username: 'existing_user',
339 password: 'super_password'
342 await loginExternal({
344 npmName: 'test-external-auth-two',
345 authName: 'external-auth-6',
346 username: 'existing_user',
347 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
351 it('Should display the correct configuration', async function () {
352 const config = await server.configCommand.getConfig()
354 const auths = config.plugin.registeredExternalAuths
355 expect(auths).to.have.lengthOf(6)
357 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
358 expect(auth2).to.not.exist
361 after(async function () {
362 await cleanupTests([ server ])
365 it('Should forward the redirectUrl if the plugin returns one', async function () {
366 const resLogin = await loginExternal({
368 npmName: 'test-external-auth-three',
369 authName: 'external-auth-7',
373 const resLogout = await logout(server.url, resLogin.access_token)
375 expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl')
378 it('Should call the plugin\'s onLogout method with the request', async function () {
379 const resLogin = await loginExternal({
381 npmName: 'test-external-auth-three',
382 authName: 'external-auth-8',
386 const resLogout = await logout(server.url, resLogin.access_token)
388 expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token)