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,
21 } from '@shared/extra-utils'
22 import { User, UserRole } from '@shared/models'
24 async function loginExternal (options: {
30 statusCodeExpected?: HttpStatusCode
31 statusCodeExpectedStep2?: HttpStatusCode
33 const res = await options.server.pluginsCommand.getExternalAuth({
34 npmName: options.npmName,
36 authName: options.authName,
38 expectedStatus: options.statusCodeExpected || HttpStatusCode.FOUND_302
41 if (res.status !== HttpStatusCode.FOUND_302) return
43 const location = res.header.location
44 const { externalAuthToken } = decodeQueryString(location)
46 const resLogin = await loginUsingExternalToken(
49 externalAuthToken as string,
50 options.statusCodeExpectedStep2
56 describe('Test external auth plugins', function () {
57 let server: ServerInfo
59 let cyanAccessToken: string
60 let cyanRefreshToken: string
62 let kefkaAccessToken: string
63 let kefkaRefreshToken: string
65 let externalAuthToken: string
67 before(async function () {
70 server = await flushAndRunServer(1)
71 await setAccessTokensToServers([ server ])
73 for (const suffix of [ 'one', 'two', 'three' ]) {
74 await server.pluginsCommand.install({ path: PluginsCommand.getPluginTestPath('-external-auth-' + suffix) })
78 it('Should display the correct configuration', async function () {
79 const config = await server.configCommand.getConfig()
81 const auths = config.plugin.registeredExternalAuths
82 expect(auths).to.have.lengthOf(8)
84 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
85 expect(auth2).to.exist
86 expect(auth2.authDisplayName).to.equal('External Auth 2')
87 expect(auth2.npmName).to.equal('peertube-plugin-test-external-auth-one')
90 it('Should redirect for a Cyan login', async function () {
91 const res = await server.pluginsCommand.getExternalAuth({
92 npmName: 'test-external-auth-one',
94 authName: 'external-auth-1',
98 expectedStatus: HttpStatusCode.FOUND_302
101 const location = res.header.location
102 expect(location.startsWith('/login?')).to.be.true
104 const searchParams = decodeQueryString(location)
106 expect(searchParams.externalAuthToken).to.exist
107 expect(searchParams.username).to.equal('cyan')
109 externalAuthToken = searchParams.externalAuthToken as string
112 it('Should reject auto external login with a missing or invalid token', async function () {
113 await loginUsingExternalToken(server, 'cyan', '', HttpStatusCode.BAD_REQUEST_400)
114 await loginUsingExternalToken(server, 'cyan', 'blabla', HttpStatusCode.BAD_REQUEST_400)
117 it('Should reject auto external login with a missing or invalid username', async function () {
118 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
119 await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
122 it('Should reject auto external login with an expired token', async function () {
127 await loginUsingExternalToken(server, 'cyan', externalAuthToken, HttpStatusCode.BAD_REQUEST_400)
129 await server.serversCommand.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 res = await getMyUserInformation(server.url, cyanAccessToken)
151 const body: User = res.body
152 expect(body.username).to.equal('cyan')
153 expect(body.account.displayName).to.equal('cyan')
154 expect(body.email).to.equal('cyan@example.com')
155 expect(body.role).to.equal(UserRole.USER)
159 it('Should auto login Kefka, create the user and use the token', async function () {
161 const res = await loginExternal({
163 npmName: 'test-external-auth-one',
164 authName: 'external-auth-2',
168 kefkaAccessToken = res.access_token
169 kefkaRefreshToken = res.refresh_token
173 const res = await getMyUserInformation(server.url, kefkaAccessToken)
175 const body: User = res.body
176 expect(body.username).to.equal('kefka')
177 expect(body.account.displayName).to.equal('Kefka Palazzo')
178 expect(body.email).to.equal('kefka@example.com')
179 expect(body.role).to.equal(UserRole.ADMINISTRATOR)
183 it('Should refresh Cyan token, but not Kefka token', async function () {
185 const resRefresh = await refreshToken(server, cyanRefreshToken)
186 cyanAccessToken = resRefresh.body.access_token
187 cyanRefreshToken = resRefresh.body.refresh_token
189 const res = await getMyUserInformation(server.url, cyanAccessToken)
190 const user: User = res.body
191 expect(user.username).to.equal('cyan')
195 await refreshToken(server, kefkaRefreshToken, HttpStatusCode.BAD_REQUEST_400)
199 it('Should update Cyan profile', async function () {
202 accessToken: cyanAccessToken,
203 displayName: 'Cyan Garamonde',
204 description: 'Retainer to the king of Doma'
207 const res = await getMyUserInformation(server.url, cyanAccessToken)
209 const body: User = res.body
210 expect(body.account.displayName).to.equal('Cyan Garamonde')
211 expect(body.account.description).to.equal('Retainer to the king of Doma')
214 it('Should logout Cyan', async function () {
215 await logout(server.url, cyanAccessToken)
218 it('Should have logged out Cyan', async function () {
219 await server.serversCommand.waitUntilLog('On logout cyan')
221 await getMyUserInformation(server.url, cyanAccessToken, HttpStatusCode.UNAUTHORIZED_401)
224 it('Should login Cyan and keep the old existing profile', async function () {
226 const res = await loginExternal({
228 npmName: 'test-external-auth-one',
229 authName: 'external-auth-1',
236 cyanAccessToken = res.access_token
239 const res = await getMyUserInformation(server.url, cyanAccessToken)
241 const body: User = res.body
242 expect(body.username).to.equal('cyan')
243 expect(body.account.displayName).to.equal('Cyan Garamonde')
244 expect(body.account.description).to.equal('Retainer to the king of Doma')
245 expect(body.role).to.equal(UserRole.USER)
248 it('Should not update an external auth email', async function () {
251 accessToken: cyanAccessToken,
252 email: 'toto@example.com',
253 currentPassword: 'toto',
254 statusCodeExpected: HttpStatusCode.BAD_REQUEST_400
258 it('Should reject token of Kefka by the plugin hook', async function () {
263 await getMyUserInformation(server.url, kefkaAccessToken, HttpStatusCode.UNAUTHORIZED_401)
266 it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
267 await server.pluginsCommand.updateSettings({
268 npmName: 'peertube-plugin-test-external-auth-one',
269 settings: { disableKefka: true }
272 await userLogin(server, { username: 'kefka', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400)
274 await loginExternal({
276 npmName: 'test-external-auth-one',
277 authName: 'external-auth-2',
282 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
286 it('Should have disabled this auth', async function () {
287 const config = await server.configCommand.getConfig()
289 const auths = config.plugin.registeredExternalAuths
290 expect(auths).to.have.lengthOf(7)
292 const auth1 = auths.find(a => a.authName === 'external-auth-2')
293 expect(auth1).to.not.exist
296 it('Should uninstall the plugin one and do not login Cyan', async function () {
297 await server.pluginsCommand.uninstall({ npmName: 'peertube-plugin-test-external-auth-one' })
299 await loginExternal({
301 npmName: 'test-external-auth-one',
302 authName: 'external-auth-1',
307 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
310 await userLogin(server, { username: 'cyan', password: null }, HttpStatusCode.BAD_REQUEST_400)
311 await userLogin(server, { username: 'cyan', password: '' }, HttpStatusCode.BAD_REQUEST_400)
312 await userLogin(server, { username: 'cyan', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400)
315 it('Should not login kefka with another plugin', async function () {
316 await loginExternal({
318 npmName: 'test-external-auth-two',
319 authName: 'external-auth-4',
321 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
324 await loginExternal({
326 npmName: 'test-external-auth-two',
327 authName: 'external-auth-4',
329 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
333 it('Should not login an existing user', async function () {
336 accessToken: server.accessToken,
337 username: 'existing_user',
338 password: 'super_password'
341 await loginExternal({
343 npmName: 'test-external-auth-two',
344 authName: 'external-auth-6',
345 username: 'existing_user',
346 statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400
350 it('Should display the correct configuration', async function () {
351 const config = await server.configCommand.getConfig()
353 const auths = config.plugin.registeredExternalAuths
354 expect(auths).to.have.lengthOf(6)
356 const auth2 = auths.find((a) => a.authName === 'external-auth-2')
357 expect(auth2).to.not.exist
360 after(async function () {
361 await cleanupTests([ server ])
364 it('Should forward the redirectUrl if the plugin returns one', async function () {
365 const resLogin = await loginExternal({
367 npmName: 'test-external-auth-three',
368 authName: 'external-auth-7',
372 const resLogout = await logout(server.url, resLogin.access_token)
374 expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl')
377 it('Should call the plugin\'s onLogout method with the request', async function () {
378 const resLogin = await loginExternal({
380 npmName: 'test-external-auth-three',
381 authName: 'external-auth-8',
385 const resLogout = await logout(server.url, resLogin.access_token)
387 expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token)