aboutsummaryrefslogblamecommitdiffhomepage
path: root/server/tests/plugins/external-auth.ts
blob: 3125615388f099e68a8d5d0940d774d3198775f8 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

















                                                                                              


                      
















































































































































































































































                                                                                                                      

































                                                                                             


































                                                                                 
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */

import 'mocha'
import { expect } from 'chai'
import { ServerConfig, User, UserRole } from '@shared/models'
import {
  decodeQueryString,
  getConfig,
  getExternalAuth,
  getMyUserInformation,
  getPluginTestPath,
  installPlugin,
  loginUsingExternalToken,
  logout,
  refreshToken,
  setAccessTokensToServers,
  uninstallPlugin,
  updateMyUser,
  wait,
  userLogin,
  updatePluginSettings
} from '../../../shared/extra-utils'
import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers'

async function loginExternal (options: {
  server: ServerInfo
  npmName: string
  authName: string
  username: string
  query?: any
  statusCodeExpected?: number
}) {
  const res = await getExternalAuth({
    url: options.server.url,
    npmName: options.npmName,
    npmVersion: '0.0.1',
    authName: options.authName,
    query: options.query,
    statusCodeExpected: options.statusCodeExpected || 302
  })

  if (res.status !== 302) return

  const location = res.header.location
  const { externalAuthToken } = decodeQueryString(location)

  const resLogin = await loginUsingExternalToken(
    options.server,
    options.username,
    externalAuthToken as string
  )

  return resLogin.body
}

describe('Test external auth plugins', function () {
  let server: ServerInfo

  let cyanAccessToken: string
  let cyanRefreshToken: string

  let kefkaAccessToken: string
  let kefkaRefreshToken: string

  let externalAuthToken: string

  before(async function () {
    this.timeout(30000)

    server = await flushAndRunServer(1)
    await setAccessTokensToServers([ server ])

    for (const suffix of [ 'one', 'two' ]) {
      await installPlugin({
        url: server.url,
        accessToken: server.accessToken,
        path: getPluginTestPath('-external-auth-' + suffix)
      })
    }
  })

  it('Should display the correct configuration', async function () {
    const res = await getConfig(server.url)

    const config: ServerConfig = res.body

    const auths = config.plugin.registeredExternalAuths
    expect(auths).to.have.lengthOf(3)

    const auth2 = auths.find((a) => a.authName === 'external-auth-2')
    expect(auth2).to.exist
    expect(auth2.authDisplayName).to.equal('External Auth 2')
    expect(auth2.npmName).to.equal('peertube-plugin-test-external-auth-one')
  })

  it('Should redirect for a Cyan login', async function () {
    const res = await getExternalAuth({
      url: server.url,
      npmName: 'test-external-auth-one',
      npmVersion: '0.0.1',
      authName: 'external-auth-1',
      query: {
        username: 'cyan'
      },
      statusCodeExpected: 302
    })

    const location = res.header.location
    expect(location.startsWith('/login?')).to.be.true

    const searchParams = decodeQueryString(location)

    expect(searchParams.externalAuthToken).to.exist
    expect(searchParams.username).to.equal('cyan')

    externalAuthToken = searchParams.externalAuthToken as string
  })

  it('Should reject auto external login with a missing or invalid token', async function () {
    await loginUsingExternalToken(server, 'cyan', '', 400)
    await loginUsingExternalToken(server, 'cyan', 'blabla', 400)
  })

  it('Should reject auto external login with a missing or invalid username', async function () {
    await loginUsingExternalToken(server, '', externalAuthToken, 400)
    await loginUsingExternalToken(server, '', externalAuthToken, 400)
  })

  it('Should reject auto external login with an expired token', async function () {
    this.timeout(15000)

    await wait(5000)

    await loginUsingExternalToken(server, 'cyan', externalAuthToken, 400)

    await waitUntilLog(server, 'expired external auth token')
  })

  it('Should auto login Cyan, create the user and use the token', async function () {
    {
      const res = await loginExternal({
        server,
        npmName: 'test-external-auth-one',
        authName: 'external-auth-1',
        query: {
          username: 'cyan'
        },
        username: 'cyan'
      })

      cyanAccessToken = res.access_token
      cyanRefreshToken = res.refresh_token
    }

    {
      const res = await getMyUserInformation(server.url, cyanAccessToken)

      const body: User = res.body
      expect(body.username).to.equal('cyan')
      expect(body.account.displayName).to.equal('cyan')
      expect(body.email).to.equal('cyan@example.com')
      expect(body.role).to.equal(UserRole.USER)
    }
  })

  it('Should auto login Kefka, create the user and use the token', async function () {
    {
      const res = await loginExternal({
        server,
        npmName: 'test-external-auth-one',
        authName: 'external-auth-2',
        username: 'kefka'
      })

      kefkaAccessToken = res.access_token
      kefkaRefreshToken = res.refresh_token
    }

    {
      const res = await getMyUserInformation(server.url, kefkaAccessToken)

      const body: User = res.body
      expect(body.username).to.equal('kefka')
      expect(body.account.displayName).to.equal('Kefka Palazzo')
      expect(body.email).to.equal('kefka@example.com')
      expect(body.role).to.equal(UserRole.ADMINISTRATOR)
    }
  })

  it('Should refresh Cyan token, but not Kefka token', async function () {
    {
      const resRefresh = await refreshToken(server, cyanRefreshToken)
      cyanAccessToken = resRefresh.body.access_token
      cyanRefreshToken = resRefresh.body.refresh_token

      const res = await getMyUserInformation(server.url, cyanAccessToken)
      const user: User = res.body
      expect(user.username).to.equal('cyan')
    }

    {
      await refreshToken(server, kefkaRefreshToken, 400)
    }
  })

  it('Should update Cyan profile', async function () {
    await updateMyUser({
      url: server.url,
      accessToken: cyanAccessToken,
      displayName: 'Cyan Garamonde',
      description: 'Retainer to the king of Doma'
    })

    const res = await getMyUserInformation(server.url, cyanAccessToken)

    const body: User = res.body
    expect(body.account.displayName).to.equal('Cyan Garamonde')
    expect(body.account.description).to.equal('Retainer to the king of Doma')
  })

  it('Should logout Cyan', async function () {
    await logout(server.url, cyanAccessToken)
  })

  it('Should have logged out Cyan', async function () {
    await waitUntilLog(server, 'On logout cyan')

    await getMyUserInformation(server.url, cyanAccessToken, 401)
  })

  it('Should login Cyan and keep the old existing profile', async function () {
    {
      const res = await loginExternal({
        server,
        npmName: 'test-external-auth-one',
        authName: 'external-auth-1',
        query: {
          username: 'cyan'
        },
        username: 'cyan'
      })

      cyanAccessToken = res.access_token
    }

    const res = await getMyUserInformation(server.url, cyanAccessToken)

    const body: User = res.body
    expect(body.username).to.equal('cyan')
    expect(body.account.displayName).to.equal('Cyan Garamonde')
    expect(body.account.description).to.equal('Retainer to the king of Doma')
    expect(body.role).to.equal(UserRole.USER)
  })

  it('Should reject token of Kefka by the plugin hook', async function () {
    this.timeout(10000)

    await wait(5000)

    await getMyUserInformation(server.url, kefkaAccessToken, 401)
  })

  it('Should unregister external-auth-2 and do not login existing Kefka', async function () {
    await updatePluginSettings({
      url: server.url,
      accessToken: server.accessToken,
      npmName: 'peertube-plugin-test-external-auth-one',
      settings: { disableKefka: true }
    })

    await userLogin(server, { username: 'kefka', password: 'fake' }, 400)

    await loginExternal({
      server,
      npmName: 'test-external-auth-one',
      authName: 'external-auth-2',
      query: {
        username: 'kefka'
      },
      username: 'kefka',
      statusCodeExpected: 404
    })
  })

  it('Should have disabled this auth', async function () {
    const res = await getConfig(server.url)

    const config: ServerConfig = res.body

    const auths = config.plugin.registeredExternalAuths
    expect(auths).to.have.lengthOf(2)

    const auth1 = auths.find(a => a.authName === 'external-auth-2')
    expect(auth1).to.not.exist
  })

  it('Should uninstall the plugin one and do not login Cyan', async function () {
    await uninstallPlugin({
      url: server.url,
      accessToken: server.accessToken,
      npmName: 'peertube-plugin-test-external-auth-one'
    })

    await loginExternal({
      server,
      npmName: 'test-external-auth-one',
      authName: 'external-auth-1',
      query: {
        username: 'cyan'
      },
      username: 'cyan',
      statusCodeExpected: 404
    })
  })

  it('Should display the correct configuration', async function () {
    const res = await getConfig(server.url)

    const config: ServerConfig = res.body

    const auths = config.plugin.registeredExternalAuths
    expect(auths).to.have.lengthOf(1)

    const auth2 = auths.find((a) => a.authName === 'external-auth-2')
    expect(auth2).to.not.exist
  })

  after(async function () {
    await cleanupTests([ server ])
  })
})