1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import { expect } from 'chai'
5 import { ServerConfig, User, UserRole } from '@shared/models'
13 loginUsingExternalToken,
16 setAccessTokensToServers,
23 } from '../../../shared/extra-utils'
24 import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers'
25 import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
27 async function loginExternal (options: {
33 statusCodeExpected?: HttpStatusCode
34 statusCodeExpectedStep2?: HttpStatusCode
36 const res = await getExternalAuth({
37 url: options.server.url,
38 npmName: options.npmName,
40 authName: options.authName,
42 statusCodeExpected: options.statusCodeExpected || HttpStatusCode.FOUND_302
45 if (res.status !== HttpStatusCode.FOUND_302) return
47 const location = res.header.location
48 const { externalAuthToken } = decodeQueryString(location)
50 const resLogin = await loginUsingExternalToken(
53 externalAuthToken as string,
54 options.statusCodeExpectedStep2
60 describe('Test external auth plugins', function () {
61 let server: ServerInfo
63 let cyanAccessToken: string
64 let cyanRefreshToken: string
66 let kefkaAccessToken: string
67 let kefkaRefreshToken: string
69 let externalAuthToken: string
71 before(async function () {
74 server = await flushAndRunServer(1)
75 await setAccessTokensToServers([ server ])
77 for (const suffix of [ 'one', 'two', 'three' ]) {
80 accessToken: server.accessToken,
81 path: getPluginTestPath('-external-auth-' + suffix)
86 it('Should display the correct configuration', async function () {
87 const res = await getConfig(server.url)
89 const config: ServerConfig = res.body
91 const auths = config.plugin.registeredExternalAuths
92 expect(auths).to.have.lengthOf(8)
94 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
95 expect(auth2).to.exist
96 expect(auth2.authDisplayName).to.equal('External Auth 2')
97 expect(auth2.npmName).to.equal('peertube-plugin-test-external-auth-one')
100 it('Should redirect for a Cyan login', async function () {
101 const res = await getExternalAuth({
103 npmName: 'test-external-auth-one',
105 authName: 'external-auth-1',
109 statusCodeExpected: HttpStatusCode.FOUND_302
112 const location = res.header.location
113 expect(location.startsWith('/login?')).to.be.true
115 const searchParams = decodeQueryString(location)
117 expect(searchParams.externalAuthToken).to.exist
118 expect(searchParams.username).to.equal('cyan')
120 externalAuthToken = searchParams.externalAuthToken as string
123 it('Should reject auto external login with a missing or invalid token', async function () {
124 await loginUsingExternalToken(server, 'cyan', '', HttpStatusCode.BAD_REQUEST_400)
125 await loginUsingExternalToken(server, 'cyan', 'blabla', HttpStatusCode.BAD_REQUEST_400)
128 it('Should reject auto external login with a missing or invalid username', async function () {
129 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
130 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
133 it('Should reject auto external login with an expired token', async function () {
138 await loginUsingExternalToken(server, 'cyan', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
140 await waitUntilLog(server, 'expired external auth token')
143 it('Should auto login Cyan, create the user and use the token', async function () {
145 const res = await loginExternal({
147 npmName: 'test-external-auth-one',
148 authName: 'external-auth-1',
155 cyanAccessToken = res.access_token
156 cyanRefreshToken = res.refresh_token
160 const res = await getMyUserInformation(server.url, cyanAccessToken)
162 const body: User = res.body
163 expect(body.username).to.equal('cyan')
164 expect(body.account.displayName).to.equal('cyan')
165 expect(body.email).to.equal('cyan@example.com')
166 expect(body.role).to.equal(UserRole.USER)
170 it('Should auto login Kefka, create the user and use the token', async function () {
172 const res = await loginExternal({
174 npmName: 'test-external-auth-one',
175 authName: 'external-auth-2',
179 kefkaAccessToken = res.access_token
180 kefkaRefreshToken = res.refresh_token
184 const res = await getMyUserInformation(server.url, kefkaAccessToken)
186 const body: User = res.body
187 expect(body.username).to.equal('kefka')
188 expect(body.account.displayName).to.equal('Kefka Palazzo')
189 expect(body.email).to.equal('kefka@example.com')
190 expect(body.role).to.equal(UserRole.ADMINISTRATOR)
194 it('Should refresh Cyan token, but not Kefka token', async function () {
196 const resRefresh = await refreshToken(server, cyanRefreshToken)
197 cyanAccessToken = resRefresh.body.access_token
198 cyanRefreshToken = resRefresh.body.refresh_token
200 const res = await getMyUserInformation(server.url, cyanAccessToken)
201 const user: User = res.body
202 expect(user.username).to.equal('cyan')
206 await refreshToken(server, kefkaRefreshToken, HttpStatusCode.BAD_REQUEST_400)
210 it('Should update Cyan profile', async function () {
213 accessToken: cyanAccessToken,
214 displayName: 'Cyan Garamonde',
215 description: 'Retainer to the king of Doma'
218 const res = await getMyUserInformation(server.url, cyanAccessToken)
220 const body: User = res.body
221 expect(body.account.displayName).to.equal('Cyan Garamonde')
222 expect(body.account.description).to.equal('Retainer to the king of Doma')
225 it('Should logout Cyan', async function () {
226 await logout(server.url, cyanAccessToken)
229 it('Should have logged out Cyan', async function () {
230 await waitUntilLog(server, 'On logout cyan')
232 await getMyUserInformation(server.url, cyanAccessToken, HttpStatusCode.UNAUTHORIZED_401)
235 it('Should login Cyan and keep the old existing profile', async function () {
237 const res = await loginExternal({
239 npmName: 'test-external-auth-one',
240 authName: 'external-auth-1',
247 cyanAccessToken = res.access_token
250 const res = await getMyUserInformation(server.url, cyanAccessToken)
252 const body: User = res.body
253 expect(body.username).to.equal('cyan')
254 expect(body.account.displayName).to.equal('Cyan Garamonde')
255 expect(body.account.description).to.equal('Retainer to the king of Doma')
256 expect(body.role).to.equal(UserRole.USER)
259 it('Should not update an external auth email', async function () {
262 accessToken: cyanAccessToken,
263 email: 'toto@example.com',
264 currentPassword: 'toto',
265 statusCodeExpected: HttpStatusCode.BAD_REQUEST_400
269 it('Should reject token of Kefka by the plugin hook', async function () {
274 await getMyUserInformation(server.url, kefkaAccessToken, HttpStatusCode.UNAUTHORIZED_401)
277 it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
278 await updatePluginSettings({
280 accessToken: server.accessToken,
281 npmName: 'peertube-plugin-test-external-auth-one',
282 settings: { disableKefka: true }
285 await userLogin(server, { username: 'kefka', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400)
287 await loginExternal({
289 npmName: 'test-external-auth-one',
290 authName: 'external-auth-2',
295 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
299 it('Should have disabled this auth', async function () {
300 const res = await getConfig(server.url)
302 const config: ServerConfig = res.body
304 const auths = config.plugin.registeredExternalAuths
305 expect(auths).to.have.lengthOf(7)
307 const auth1 = auths.find(a => a.authName === 'external-auth-2')
308 expect(auth1).to.not.exist
311 it('Should uninstall the plugin one and do not login Cyan', async function () {
312 await uninstallPlugin({
314 accessToken: server.accessToken,
315 npmName: 'peertube-plugin-test-external-auth-one'
318 await loginExternal({
320 npmName: 'test-external-auth-one',
321 authName: 'external-auth-1',
326 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
329 await userLogin(server, { username: 'cyan', password: null }, HttpStatusCode.BAD_REQUEST_400)
330 await userLogin(server, { username: 'cyan', password: '' }, HttpStatusCode.BAD_REQUEST_400)
331 await userLogin(server, { username: 'cyan', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400)
334 it('Should not login kefka with another plugin', async function () {
335 await loginExternal({
337 npmName: 'test-external-auth-two',
338 authName: 'external-auth-4',
340 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
343 await loginExternal({
345 npmName: 'test-external-auth-two',
346 authName: 'external-auth-4',
348 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
352 it('Should not login an existing user', async function () {
355 accessToken: server.accessToken,
356 username: 'existing_user',
357 password: 'super_password'
360 await loginExternal({
362 npmName: 'test-external-auth-two',
363 authName: 'external-auth-6',
364 username: 'existing_user',
365 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
369 it('Should display the correct configuration', async function () {
370 const res = await getConfig(server.url)
372 const config: ServerConfig = res.body
374 const auths = config.plugin.registeredExternalAuths
375 expect(auths).to.have.lengthOf(6)
377 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
378 expect(auth2).to.not.exist
381 after(async function () {
382 await cleanupTests([ server ])
385 it('Should forward the redirectUrl if the plugin returns one', async function () {
386 const resLogin = await loginExternal({
388 npmName: 'test-external-auth-three',
389 authName: 'external-auth-7',
393 const resLogout = await logout(server.url, resLogin.access_token)
395 expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl')
398 it('Should call the plugin\'s onLogout method with the request', async function () {
399 const resLogin = await loginExternal({
401 npmName: 'test-external-auth-three',
402 authName: 'external-auth-8',
406 const resLogout = await logout(server.url, resLogin.access_token)
408 expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token)