/* istanbul ignore file */ import { expect } from 'chai' import { beforeEachContext } from '@/test/hooks' import { validateScopes, authorizeResolver } from '@/modules/shared' import { buildContext } from '@/modules/shared/middleware' import type { AvailableRoles, ServerRoles } from '@speckle/shared' import { Roles, Scopes } from '@speckle/shared' import { throwForNotHavingServerRole } from '@/modules/shared/authz' import { ForbiddenError } from '@/modules/shared/errors' import { mockAdminOverride } from '@/test/mocks/global' import type { Request } from 'express' import type { BasicTestUser } from '@/test/authHelper' import { createTestUser } from '@/test/authHelper' import type { BasicTestStream } from '@/test/speckle-helpers/streamHelper' import { createTestStream } from '@/test/speckle-helpers/streamHelper' const adminOverrideMock = mockAdminOverride() describe('Generic AuthN & AuthZ controller tests', () => { before(async () => { await beforeEachContext() }) it('Validate scopes', async () => { await validateScopes(undefined, undefined as unknown as string) .then(() => { throw new Error('This should have been rejected') }) .catch((err) => expect('Your auth token does not have the required scope.').to.equal( err.message ) ) await validateScopes(['a'], 'b') .then(() => { throw new Error('This should have been rejected') }) .catch((err) => expect('Your auth token does not have the required scope: b.').to.equal( err.message ) ) await validateScopes(['a', 'b'], 'b') // should pass }) ;([ ['BS header', { req: { headers: { authorization: 'Bearer BS' } } as Request }], [ 'Null header', { req: { headers: { authorization: null as string | null } } as Request } ], ['Undefined header', { req: { headers: { authorization: undefined } } as Request }], ['BS token', { token: 'Bearer BS' }], ['Null token', { token: null }], ['Undefined token', { token: undefined }] ]).map(([caseName, contextInput]) => it(`Should create proper context ${caseName}`, async () => { const res = await buildContext(contextInput) expect(res.auth).to.equal(false) }) ) it('Should validate server role', async () => { await throwForNotHavingServerRole( { auth: true, role: Roles.Server.User }, Roles.Server.Admin ) .then(() => { throw new Error('This should have been rejected') }) .catch((err) => expect('You do not have the required server role').to.equal(err.message) ) await throwForNotHavingServerRole( { auth: true, role: 'HACZOR' as ServerRoles }, '133TCR3w' as ServerRoles ) .then(() => { throw new Error('This should have been rejected') }) .catch((err) => expect('Invalid role requirement specified').to.equal(err.message) ) await throwForNotHavingServerRole( { auth: true, role: Roles.Server.Admin }, '133TCR3w' as ServerRoles ) .then(() => { throw new Error('This should have been rejected') }) .catch((err) => expect('Invalid role requirement specified').to.equal(err.message) ) const test = await throwForNotHavingServerRole( { auth: true, role: Roles.Server.Admin }, Roles.Server.User ) expect(test).to.equal(true) }) it('Resolver Authorization Should fail nicely when roles & resources are wanky', async () => { await authorizeResolver(null, 'foo', 'bar' as AvailableRoles, null) .then(() => { throw new Error('This should have been rejected') }) .catch((err) => expect('Unknown role: bar').to.equal(err.message)) // this caught me out, but streams:read is not a valid role for now await authorizeResolver( 'foo', 'bar' as AvailableRoles, Scopes.Streams.Read as AvailableRoles, null ) .then(() => { throw new Error('This should have been rejected') }) .catch((err) => expect('Unknown role: streams:read').to.equal(err.message)) }) describe('Authorize resolver ', () => { let myStream: BasicTestStream let notMyStream: BasicTestStream let serverOwner: BasicTestUser let otherGuy: BasicTestUser before(async function () { // Seeding serverOwner = await createTestUser({ name: 'Itsa Me', email: 'me@example.org', password: 'sn3aky-1337-b1m' }) otherGuy = await createTestUser({ name: 'Some Other DUde', email: 'otherguy@example.org', password: 'sn3aky-1337-b1m' }) myStream = await createTestStream( { name: 'My Stream 2', isPublic: true }, serverOwner ) notMyStream = await createTestStream( { name: 'Not My Stream 1', isPublic: false }, otherGuy ) }) afterEach(() => { adminOverrideMock.disable() }) after(() => { adminOverrideMock.disable() }) it('should allow stream:owners to be stream:owners', async () => { await authorizeResolver( serverOwner.id, myStream.id, Roles.Stream.Contributor, null ) }) it('should get the passed in role for server:admins if override enabled', async () => { adminOverrideMock.enable(true) await authorizeResolver( serverOwner.id, myStream.id, Roles.Stream.Contributor, null ) }) it('should not allow server:admins to be anything if adminOverride is disabled', async () => { try { await authorizeResolver( serverOwner.id, notMyStream.id, Roles.Stream.Contributor, null ) throw new Error('This should have thrown') } catch (e) { expect(e instanceof ForbiddenError) } }) it('should allow server:admins to be anything if adminOverride is enabled', async () => { adminOverrideMock.enable(true) await authorizeResolver( serverOwner.id, notMyStream.id, Roles.Stream.Contributor, null ) }) it('should not allow server:users to be anything if adminOverride is disabled', async () => { try { await authorizeResolver( otherGuy.id, myStream.id, Roles.Stream.Contributor, null ) throw new Error('This should have thrown') } catch (e) { expect(e instanceof ForbiddenError) } }) it('should not allow server:users to be anything if adminOverride is enabled', async () => { adminOverrideMock.enable(true) try { await authorizeResolver( otherGuy.id, myStream.id, Roles.Stream.Contributor, null ) throw new Error('This should have thrown') } catch (e) { expect(e instanceof ForbiddenError) } }) }) })