chore(server): migrate remaining tests to TS (#4772)
* auth tests migrated * core tests * pwdreset * authz tests
This commit is contained in:
committed by
GitHub
parent
eabfab2555
commit
d2f2d95bb5
@@ -29,5 +29,7 @@ secret:
|
||||
name: setup/keycloak/speckle-realm.json - secret for dev keycloak
|
||||
- match: 2e1b3675a4049cd39fe6db081735f747730969071528270800f00fa98720d198
|
||||
name: setup/keycloak/speckle-realm.json - algorithm name
|
||||
- match: 7aa3f40885a6914c798c95568f04d840f50164b4e2ba632da0edb9602ca5609b
|
||||
name: apps.graphql.spec.ts - fake password
|
||||
|
||||
version: 2
|
||||
|
||||
+38
-45
@@ -1,68 +1,59 @@
|
||||
/* eslint-disable camelcase */
|
||||
/* istanbul ignore file */
|
||||
const chai = require('chai')
|
||||
import chai from 'chai'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
const {
|
||||
import {
|
||||
createBareToken,
|
||||
createAppTokenFactory,
|
||||
createPersonalAccessTokenFactory
|
||||
} = require('@/modules/core/services/tokens')
|
||||
const { beforeEachContext, initializeTestServer } = require('@/test/hooks')
|
||||
const { Scopes } = require('@speckle/shared')
|
||||
const {
|
||||
} from '@/modules/core/services/tokens'
|
||||
import { beforeEachContext, initializeTestServer } from '@/test/hooks'
|
||||
import { Scopes } from '@speckle/shared'
|
||||
import {
|
||||
createAuthorizationCodeFactory,
|
||||
getAuthorizationCodeFactory,
|
||||
deleteAuthorizationCodeFactory,
|
||||
getAppFactory,
|
||||
createRefreshTokenFactory
|
||||
} = require('@/modules/auth/repositories/apps')
|
||||
const { db } = require('@/db/knex')
|
||||
const {
|
||||
createAppTokenFromAccessCodeFactory
|
||||
} = require('@/modules/auth/services/serverApps')
|
||||
const {
|
||||
} from '@/modules/auth/repositories/apps'
|
||||
import { db } from '@/db/knex'
|
||||
import { createAppTokenFromAccessCodeFactory } from '@/modules/auth/services/serverApps'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory
|
||||
} = require('@/modules/core/repositories/userEmails')
|
||||
const {
|
||||
requestNewEmailVerificationFactory
|
||||
} = require('@/modules/emails/services/verification/request')
|
||||
const {
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import {
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} = require('@/modules/core/repositories/users')
|
||||
const {
|
||||
deleteOldAndInsertNewVerificationFactory
|
||||
} = require('@/modules/emails/repositories')
|
||||
const { renderEmail } = require('@/modules/emails/services/emailRendering')
|
||||
const { sendEmail } = require('@/modules/emails/services/sending')
|
||||
const { createUserFactory } = require('@/modules/core/services/users/management')
|
||||
const {
|
||||
validateAndCreateUserEmailFactory
|
||||
} = require('@/modules/core/services/userEmails')
|
||||
const {
|
||||
finalizeInvitedServerRegistrationFactory
|
||||
} = require('@/modules/serverinvites/services/processing')
|
||||
const {
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} = require('@/modules/serverinvites/repositories/serverInvites')
|
||||
const {
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import {
|
||||
storeApiTokenFactory,
|
||||
storeTokenScopesFactory,
|
||||
storeTokenResourceAccessDefinitionsFactory,
|
||||
storeUserServerAppTokenFactory,
|
||||
storePersonalApiTokenFactory
|
||||
} = require('@/modules/core/repositories/tokens')
|
||||
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
|
||||
const { getEventBus } = require('@/modules/shared/services/eventBus')
|
||||
} from '@/modules/core/repositories/tokens'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { BasicTestUser } from '@/test/authHelper'
|
||||
|
||||
let sendRequest
|
||||
let sendRequest: Awaited<ReturnType<typeof initializeTestServer>>['sendRequest']
|
||||
|
||||
const createAppToken = createAppTokenFactory({
|
||||
storeApiToken: storeApiTokenFactory({ db }),
|
||||
@@ -120,10 +111,10 @@ const createPersonalAccessToken = createPersonalAccessTokenFactory({
|
||||
})
|
||||
|
||||
describe('GraphQL @apps-api', () => {
|
||||
let testUser
|
||||
let testUser2
|
||||
let testToken
|
||||
let testToken2
|
||||
let testUser: BasicTestUser
|
||||
let testUser2: BasicTestUser
|
||||
let testToken: string
|
||||
let testToken2: string
|
||||
|
||||
before(async () => {
|
||||
const ctx = await beforeEachContext()
|
||||
@@ -131,7 +122,8 @@ describe('GraphQL @apps-api', () => {
|
||||
testUser = {
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie@example.org',
|
||||
password: 'wtfwtfwtf'
|
||||
password: 'wtfwtfwtf',
|
||||
id: ''
|
||||
}
|
||||
|
||||
testUser.id = await createUser(testUser)
|
||||
@@ -144,7 +136,8 @@ describe('GraphQL @apps-api', () => {
|
||||
testUser2 = {
|
||||
name: 'Mr. Mac',
|
||||
email: 'steve@jobs.com',
|
||||
password: 'wtfwtfwtf'
|
||||
password: 'wtfwtfwtf',
|
||||
id: ''
|
||||
}
|
||||
|
||||
testUser2.id = await createUser(testUser2)
|
||||
@@ -155,8 +148,8 @@ describe('GraphQL @apps-api', () => {
|
||||
])}`
|
||||
})
|
||||
|
||||
let testAppId
|
||||
let testApp
|
||||
let testAppId: string
|
||||
let testApp: { secret: string }
|
||||
|
||||
it('Should create an app', async () => {
|
||||
const query =
|
||||
+86
-71
@@ -1,17 +1,17 @@
|
||||
/* istanbul ignore file */
|
||||
const expect = require('chai').expect
|
||||
import { expect } from 'chai'
|
||||
|
||||
const {
|
||||
import {
|
||||
createBareToken,
|
||||
createAppTokenFactory,
|
||||
validateTokenFactory
|
||||
} = require(`@/modules/core/services/tokens`)
|
||||
const { beforeEachContext } = require(`@/test/hooks`)
|
||||
} from '@/modules/core/services/tokens'
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
|
||||
const { Scopes } = require('@/modules/core/helpers/mainConstants')
|
||||
const { knex } = require('@/db/knex')
|
||||
const cryptoRandomString = require('crypto-random-string')
|
||||
const {
|
||||
import { Scopes } from '@/modules/core/helpers/mainConstants'
|
||||
import { knex } from '@/db/knex'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import {
|
||||
getAppFactory,
|
||||
updateDefaultAppFactory,
|
||||
getAllPublicAppsFactory,
|
||||
@@ -26,43 +26,35 @@ const {
|
||||
getRefreshTokenFactory,
|
||||
revokeRefreshTokenFactory,
|
||||
getTokenAppInfoFactory
|
||||
} = require('@/modules/auth/repositories/apps')
|
||||
const {
|
||||
} from '@/modules/auth/repositories/apps'
|
||||
import {
|
||||
createAppTokenFromAccessCodeFactory,
|
||||
refreshAppTokenFactory
|
||||
} = require('@/modules/auth/services/serverApps')
|
||||
const {
|
||||
} from '@/modules/auth/services/serverApps'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory
|
||||
} = require('@/modules/core/repositories/userEmails')
|
||||
const {
|
||||
requestNewEmailVerificationFactory
|
||||
} = require('@/modules/emails/services/verification/request')
|
||||
const {
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import {
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory,
|
||||
getUserRoleFactory
|
||||
} = require('@/modules/core/repositories/users')
|
||||
const {
|
||||
deleteOldAndInsertNewVerificationFactory
|
||||
} = require('@/modules/emails/repositories')
|
||||
const { renderEmail } = require('@/modules/emails/services/emailRendering')
|
||||
const { sendEmail } = require('@/modules/emails/services/sending')
|
||||
const { createUserFactory } = require('@/modules/core/services/users/management')
|
||||
const {
|
||||
validateAndCreateUserEmailFactory
|
||||
} = require('@/modules/core/services/userEmails')
|
||||
const {
|
||||
finalizeInvitedServerRegistrationFactory
|
||||
} = require('@/modules/serverinvites/services/processing')
|
||||
const {
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} = require('@/modules/serverinvites/repositories/serverInvites')
|
||||
const {
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import {
|
||||
storeApiTokenFactory,
|
||||
storeTokenScopesFactory,
|
||||
storeTokenResourceAccessDefinitionsFactory,
|
||||
@@ -72,9 +64,16 @@ const {
|
||||
getTokenScopesByIdFactory,
|
||||
getTokenResourceAccessDefinitionsByIdFactory,
|
||||
updateApiTokenFactory
|
||||
} = require('@/modules/core/repositories/tokens')
|
||||
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
|
||||
const { getEventBus } = require('@/modules/shared/services/eventBus')
|
||||
} from '@/modules/core/repositories/tokens'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { BasicTestUser } from '@/test/authHelper'
|
||||
import { AppScopes, ensureError } from '@speckle/shared'
|
||||
import { ValidTokenResult } from '@/modules/core/helpers/types'
|
||||
import {
|
||||
DefaultAppIds,
|
||||
DefaultAppWithUnwrappedScopes
|
||||
} from '@/modules/auth/defaultApps'
|
||||
|
||||
const db = knex
|
||||
const getApp = getAppFactory({ db: knex })
|
||||
@@ -156,10 +155,11 @@ const validateToken = validateTokenFactory({
|
||||
})
|
||||
|
||||
describe('Services @apps-services', () => {
|
||||
const actor = {
|
||||
const actor: BasicTestUser = {
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie@example.org',
|
||||
password: 'wtfwtfwtf'
|
||||
password: 'wtfwtfwtf',
|
||||
id: ''
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
@@ -173,7 +173,8 @@ describe('Services @apps-services', () => {
|
||||
name: testAppName,
|
||||
public: true,
|
||||
scopes: [Scopes.Streams.Read],
|
||||
redirectUrl: 'http://127.0.0.1:1335'
|
||||
redirectUrl: 'http://127.0.0.1:1335',
|
||||
authorId: actor.id
|
||||
})
|
||||
|
||||
expect(res).to.have.property('id')
|
||||
@@ -183,7 +184,7 @@ describe('Services @apps-services', () => {
|
||||
expect(res.secret).to.be.a('string')
|
||||
|
||||
const app = await getApp({ id: res.id })
|
||||
expect(app.id).to.equal(res.id)
|
||||
expect(app?.id).to.equal(res.id)
|
||||
})
|
||||
|
||||
it('Should get all the public apps on this server', async () => {
|
||||
@@ -193,7 +194,12 @@ describe('Services @apps-services', () => {
|
||||
})
|
||||
|
||||
it('Should fail to register an app with no scopes', async () => {
|
||||
await createApp({ name: 'test application2', redirectUrl: 'http://127.0.0.1:1335' })
|
||||
await createApp({
|
||||
name: 'test application2',
|
||||
redirectUrl: 'http://127.0.0.1:1335',
|
||||
authorId: actor.id,
|
||||
scopes: undefined as unknown as AppScopes[]
|
||||
})
|
||||
.then(() => {
|
||||
throw new Error('this should have been rejected')
|
||||
})
|
||||
@@ -207,7 +213,8 @@ describe('Services @apps-services', () => {
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
public: true,
|
||||
scopes: [Scopes.Streams.Read],
|
||||
redirectUrl: 'http://127.0.0.1:1335'
|
||||
redirectUrl: 'http://127.0.0.1:1335',
|
||||
authorId: actor.id
|
||||
})
|
||||
const res = await updateApp({
|
||||
app: {
|
||||
@@ -219,10 +226,10 @@ describe('Services @apps-services', () => {
|
||||
expect(res).to.be.a('string')
|
||||
|
||||
const app = await getApp({ id: myTestApp.id })
|
||||
expect(app.name).to.equal('updated test application')
|
||||
expect(app.scopes).to.be.an('array')
|
||||
expect(app.scopes.map((s) => s.name)).to.include(Scopes.Users.Read)
|
||||
expect(app.scopes.map((s) => s.name)).to.include(Scopes.Streams.Read)
|
||||
expect(app?.name).to.equal('updated test application')
|
||||
expect(app?.scopes).to.be.an('array')
|
||||
expect(app?.scopes.map((s) => s.name)).to.include(Scopes.Users.Read)
|
||||
expect(app?.scopes.map((s) => s.name)).to.include(Scopes.Streams.Read)
|
||||
})
|
||||
|
||||
const challenge = 'random'
|
||||
@@ -232,7 +239,8 @@ describe('Services @apps-services', () => {
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
public: true,
|
||||
scopes: [Scopes.Streams.Read],
|
||||
redirectUrl: 'http://127.0.0.1:1335'
|
||||
redirectUrl: 'http://127.0.0.1:1335',
|
||||
authorId: actor.id
|
||||
})
|
||||
const authorizationCode = await createAuthorizationCode({
|
||||
appId: myTestApp.id,
|
||||
@@ -247,7 +255,8 @@ describe('Services @apps-services', () => {
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
public: true,
|
||||
scopes: [Scopes.Streams.Read],
|
||||
redirectUrl: 'http://127.0.0.1:1335'
|
||||
redirectUrl: 'http://127.0.0.1:1335',
|
||||
authorId: actor.id
|
||||
})
|
||||
const authorizationCode = await createAuthorizationCode({
|
||||
appId: myTestApp.id,
|
||||
@@ -267,7 +276,7 @@ describe('Services @apps-services', () => {
|
||||
expect(response).to.have.property('refreshToken')
|
||||
expect(response.refreshToken).to.be.a('string')
|
||||
|
||||
const validation = await validateToken(response.token)
|
||||
const validation = (await validateToken(response.token)) as ValidTokenResult
|
||||
expect(validation.valid).to.equal(true)
|
||||
expect(validation.userId).to.equal(actor.id)
|
||||
expect(validation.scopes[0]).to.equal(Scopes.Streams.Read)
|
||||
@@ -278,7 +287,8 @@ describe('Services @apps-services', () => {
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
public: true,
|
||||
scopes: [Scopes.Streams.Read],
|
||||
redirectUrl: 'http://127.0.0.1:1335'
|
||||
redirectUrl: 'http://127.0.0.1:1335',
|
||||
authorId: actor.id
|
||||
})
|
||||
const authorizationCode = await createAuthorizationCode({
|
||||
appId: myTestApp.id,
|
||||
@@ -301,14 +311,13 @@ describe('Services @apps-services', () => {
|
||||
const res = await refreshAppToken({
|
||||
refreshToken: tokenCreateResponse.refreshToken,
|
||||
appId: myTestApp.id,
|
||||
appSecret: myTestApp.secret,
|
||||
userId: actor.id
|
||||
appSecret: myTestApp.secret
|
||||
})
|
||||
|
||||
expect(res.token).to.be.a('string')
|
||||
expect(res.refreshToken).to.be.a('string')
|
||||
|
||||
const validation = await validateToken(res.token)
|
||||
const validation = (await validateToken(res.token)) as ValidTokenResult
|
||||
expect(validation.valid).to.equal(true)
|
||||
expect(validation.userId).to.equal(actor.id)
|
||||
})
|
||||
@@ -318,7 +327,8 @@ describe('Services @apps-services', () => {
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
public: true,
|
||||
scopes: [Scopes.Streams.Read],
|
||||
redirectUrl: 'http://127.0.0.1:1335'
|
||||
redirectUrl: 'http://127.0.0.1:1335',
|
||||
authorId: actor.id
|
||||
})
|
||||
const unusedAccessCode = await createAuthorizationCode({
|
||||
appId: myTestApp.id,
|
||||
@@ -385,8 +395,8 @@ describe('Services @apps-services', () => {
|
||||
it(`Should get the default app: ${speckleAppId}`, async () => {
|
||||
const app = await getApp({ id: speckleAppId })
|
||||
expect(app).to.be.an('object')
|
||||
expect(app.redirectUrl).to.be.a('string')
|
||||
expect(app.scopes).to.be.a('array')
|
||||
expect(app?.redirectUrl).to.be.a('string')
|
||||
expect(app?.scopes).to.be.a('array')
|
||||
})
|
||||
it(`Should not invalidate tokens, refresh tokens and access codes for default app: ${speckleAppId}, if updated`, async () => {
|
||||
const [unusedAccessCode, usedAccessCode] = await Promise.all([
|
||||
@@ -418,14 +428,14 @@ describe('Services @apps-services', () => {
|
||||
await updateDefaultApp(
|
||||
{
|
||||
name: 'updated test application',
|
||||
id: speckleAppId,
|
||||
id: speckleAppId as DefaultAppIds,
|
||||
scopes: newScopes
|
||||
},
|
||||
existingApp
|
||||
} as DefaultAppWithUnwrappedScopes,
|
||||
existingApp!
|
||||
)
|
||||
const updatedApp = await getApp({ id: speckleAppId })
|
||||
|
||||
expect(updatedApp.scopes.map((s) => s.name)).to.equalInAnyOrder(newScopes)
|
||||
expect(updatedApp?.scopes.map((s) => s.name)).to.deep.equalInAnyOrder(newScopes)
|
||||
|
||||
const validationResponse = await validateToken(apiTokenResponse.token)
|
||||
expect(validationResponse.valid).to.equal(true)
|
||||
@@ -447,7 +457,7 @@ describe('Services @apps-services', () => {
|
||||
expect(appToken.token).to.exist
|
||||
expect(appToken.refreshToken).to.exist
|
||||
|
||||
const apiTokens = await knex('user_server_app_tokens')
|
||||
const apiTokens = (await knex('user_server_app_tokens')
|
||||
.join(
|
||||
'token_scopes',
|
||||
'user_server_app_tokens.tokenId',
|
||||
@@ -456,7 +466,7 @@ describe('Services @apps-services', () => {
|
||||
)
|
||||
.where({
|
||||
appId: speckleAppId
|
||||
})
|
||||
})) as { scopeName: string }[]
|
||||
|
||||
expect(newScopes).to.include.members(apiTokens.map((t) => t.scopeName))
|
||||
})
|
||||
@@ -471,19 +481,21 @@ describe('Services @apps-services', () => {
|
||||
name: 'updated test application',
|
||||
id: speckleAppId,
|
||||
scopes: ['aWeird:Scope']
|
||||
},
|
||||
existingApp
|
||||
} as unknown as DefaultAppWithUnwrappedScopes,
|
||||
existingApp!
|
||||
)
|
||||
throw new Error('This should have failed')
|
||||
} catch (err) {
|
||||
// check that the weird:Scope violates a foreign key constraint...
|
||||
// leaky abstractions i know, but no better way to test this for now
|
||||
expect(err.message).to.contain('server_apps_scopes_scopename_foreign')
|
||||
expect(ensureError(err).message).to.contain(
|
||||
'server_apps_scopes_scopename_foreign'
|
||||
)
|
||||
}
|
||||
const notUpdatedApp = await getApp({ id: speckleAppId })
|
||||
// check that no harm was done
|
||||
expect(notUpdatedApp.name).to.equal(existingApp.name)
|
||||
expect(notUpdatedApp.scopes).to.equalInAnyOrder(existingApp.scopes)
|
||||
expect(notUpdatedApp?.name).to.equal(existingApp?.name)
|
||||
expect(notUpdatedApp?.scopes).to.deep.equalInAnyOrder(existingApp?.scopes)
|
||||
})
|
||||
|
||||
it('Should revoke access for a given user', async () => {
|
||||
@@ -491,12 +503,14 @@ describe('Services @apps-services', () => {
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
public: true,
|
||||
scopes: [Scopes.Streams.Read],
|
||||
redirectUrl: 'http://127.0.0.1:1335'
|
||||
redirectUrl: 'http://127.0.0.1:1335',
|
||||
authorId: actor.id
|
||||
})
|
||||
const secondUser = {
|
||||
const secondUser: BasicTestUser = {
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie.wow@example.org',
|
||||
password: 'wtfwtfwtf'
|
||||
password: 'wtfwtfwtf',
|
||||
id: ''
|
||||
}
|
||||
|
||||
secondUser.id = await createUser(secondUser)
|
||||
@@ -557,7 +571,8 @@ describe('Services @apps-services', () => {
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
public: true,
|
||||
scopes: [Scopes.Streams.Read],
|
||||
redirectUrl: 'http://127.0.0.1:1335'
|
||||
redirectUrl: 'http://127.0.0.1:1335',
|
||||
authorId: actor.id
|
||||
})
|
||||
const res = await deleteApp({ id: myTestApp.id })
|
||||
expect(res).to.equal(1)
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
/* istanbul ignore file */
|
||||
const expect = require('chai').expect
|
||||
import { expect } from 'chai'
|
||||
|
||||
const { init } = require('@/app')
|
||||
const { knex } = require('@/db/knex')
|
||||
const { beforeEachContext } = require('@/test/hooks')
|
||||
import { init } from '@/app'
|
||||
import { knex } from '@/db/knex'
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
|
||||
// NOTE:
|
||||
// These tests check that the initialization routine of the whole server
|
||||
+31
-36
@@ -1,45 +1,38 @@
|
||||
const request = require('supertest')
|
||||
import request from 'supertest'
|
||||
|
||||
const { knex } = require('@/db/knex')
|
||||
const ResetTokens = () => knex('pwdreset_tokens')
|
||||
import { knex } from '@/db/knex'
|
||||
|
||||
const { beforeEachContext } = require('@/test/hooks')
|
||||
const { localAuthRestApi } = require('@/modules/auth/tests/helpers/registration')
|
||||
const { expectToThrow } = require('@/test/assertionHelper')
|
||||
const { expect } = require('chai')
|
||||
const {
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
import { localAuthRestApi } from '@/modules/auth/tests/helpers/registration'
|
||||
import { expectToThrow } from '@/test/assertionHelper'
|
||||
import { expect } from 'chai'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory
|
||||
} = require('@/modules/core/repositories/userEmails')
|
||||
const {
|
||||
requestNewEmailVerificationFactory
|
||||
} = require('@/modules/emails/services/verification/request')
|
||||
const {
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import {
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} = require('@/modules/core/repositories/users')
|
||||
const {
|
||||
deleteOldAndInsertNewVerificationFactory
|
||||
} = require('@/modules/emails/repositories')
|
||||
const { renderEmail } = require('@/modules/emails/services/emailRendering')
|
||||
const { sendEmail } = require('@/modules/emails/services/sending')
|
||||
const { createUserFactory } = require('@/modules/core/services/users/management')
|
||||
const {
|
||||
validateAndCreateUserEmailFactory
|
||||
} = require('@/modules/core/services/userEmails')
|
||||
const {
|
||||
finalizeInvitedServerRegistrationFactory
|
||||
} = require('@/modules/serverinvites/services/processing')
|
||||
const {
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} = require('@/modules/serverinvites/repositories/serverInvites')
|
||||
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
|
||||
const { getEventBus } = require('@/modules/shared/services/eventBus')
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { BasicTestUser } from '@/test/authHelper'
|
||||
|
||||
const ResetTokens = () => knex('pwdreset_tokens')
|
||||
const db = knex
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const findEmail = findEmailFactory({ db })
|
||||
@@ -71,17 +64,18 @@ const createUser = createUserFactory({
|
||||
})
|
||||
|
||||
describe('Password reset requests @passwordresets', () => {
|
||||
let app
|
||||
let app: Awaited<ReturnType<typeof beforeEachContext>>['app']
|
||||
|
||||
before(async () => {
|
||||
;({ app } = await beforeEachContext())
|
||||
})
|
||||
|
||||
it('Should carefully send a password request email', async () => {
|
||||
const userA = {
|
||||
const userA: BasicTestUser = {
|
||||
name: 'd1',
|
||||
email: 'd@speckle.systems',
|
||||
password: 'wowwow8charsplease'
|
||||
password: 'wowwow8charsplease',
|
||||
id: ''
|
||||
}
|
||||
userA.id = await createUser(userA)
|
||||
|
||||
@@ -108,10 +102,11 @@ describe('Password reset requests @passwordresets', () => {
|
||||
})
|
||||
|
||||
it('Should reset passwords', async () => {
|
||||
const userB = {
|
||||
const userB: BasicTestUser = {
|
||||
name: 'd2',
|
||||
email: 'd2@speckle.systems',
|
||||
password: 'w0ww0w8charsplease'
|
||||
password: 'w0ww0w8charsplease',
|
||||
id: ''
|
||||
}
|
||||
userB.id = await createUser(userB)
|
||||
|
||||
@@ -163,7 +158,7 @@ describe('Password reset requests @passwordresets', () => {
|
||||
async () =>
|
||||
await authRestApi.login({
|
||||
email: userB.email,
|
||||
password: userB.password,
|
||||
password: userB.password!,
|
||||
challenge: '123'
|
||||
})
|
||||
)
|
||||
@@ -36,12 +36,15 @@ import { ProjectRecordVisibility } from '@/modules/core/helpers/types'
|
||||
import { moduleAuthLoaders } from '@/modules/index'
|
||||
export { AuthContext, AuthParams }
|
||||
|
||||
interface AuthFailedResult extends AuthResult {
|
||||
export interface AuthFailedResult extends AuthResult {
|
||||
authorized: false
|
||||
error: BaseError | null
|
||||
fatal?: boolean
|
||||
}
|
||||
|
||||
export const isAuthFailedResult = (result: AuthResult): result is AuthFailedResult =>
|
||||
('error' in result || ('fatal' in result && !!result.fatal)) && !result.authorized
|
||||
|
||||
interface AuthFailedData extends AuthData {
|
||||
authResult: AuthFailedResult
|
||||
}
|
||||
|
||||
+221
-124
@@ -1,5 +1,5 @@
|
||||
const expect = require('chai').expect
|
||||
const {
|
||||
import { expect } from 'chai'
|
||||
import {
|
||||
authPipelineCreator,
|
||||
authFailed,
|
||||
authSuccess,
|
||||
@@ -9,167 +9,227 @@ const {
|
||||
allowForRegisteredUsersOnPublicStreamsEvenWithoutRole,
|
||||
allowForServerAdmins,
|
||||
validateResourceAccess,
|
||||
validateRequiredStreamFactory
|
||||
} = require('@/modules/shared/authz')
|
||||
const {
|
||||
ForbiddenError: SFE,
|
||||
UnauthorizedError: SUE,
|
||||
validateRequiredStreamFactory,
|
||||
AuthContext,
|
||||
isAuthFailedResult,
|
||||
AuthFailedResult
|
||||
} from '@/modules/shared/authz'
|
||||
import {
|
||||
ForbiddenError as SFE,
|
||||
UnauthorizedError as SUE,
|
||||
UnauthorizedError,
|
||||
ContextError,
|
||||
NotFoundError
|
||||
} = require('@/modules/shared/errors')
|
||||
const { Roles } = require('@speckle/shared')
|
||||
const {
|
||||
TokenResourceIdentifierType
|
||||
} = require('@/modules/core/graph/generated/graphql')
|
||||
const { ProjectRecordVisibility } = require('@/modules/core/helpers/types')
|
||||
NotFoundError,
|
||||
BaseError
|
||||
} from '@/modules/shared/errors'
|
||||
import { AvailableRoles, ensureError, Roles } from '@speckle/shared'
|
||||
import { TokenResourceIdentifierType } from '@/modules/core/graph/generated/graphql'
|
||||
import { ProjectRecordVisibility, StreamRecord } from '@/modules/core/helpers/types'
|
||||
import {
|
||||
AuthData,
|
||||
AuthPipelineFunction,
|
||||
AuthResult
|
||||
} from '@/modules/shared/domain/authz/types'
|
||||
import { UserRoleData } from '@/modules/shared/domain/rolesAndScopes/types'
|
||||
|
||||
describe('AuthZ @shared', () => {
|
||||
const buildFooAuthData = (): AuthData =>
|
||||
({
|
||||
context: { foo: 'bar' } as unknown as AuthContext
|
||||
} as AuthData)
|
||||
const buildEmptyContext = (): AuthContext => ({} as unknown as AuthContext)
|
||||
const buildEmptySuccess = () => authSuccess(buildEmptyContext())
|
||||
|
||||
describe('Auth pipeline', () => {
|
||||
it('Empty pipeline returns no authorization', async () => {
|
||||
const pipeline = authPipelineCreator([])
|
||||
const { authResult } = await pipeline({ context: { foo: 'bar' } })
|
||||
const { authResult } = await pipeline(buildFooAuthData())
|
||||
expect(authResult.authorized).to.equal(false)
|
||||
})
|
||||
it('Pipeline breaks on fatal error', async () => {
|
||||
const errorMessage = 'dummy'
|
||||
const fatalFail = async () => authFailed({}, new Error(errorMessage), true)
|
||||
const shouldRescue = async () => authSuccess()
|
||||
const fatalFail = async () =>
|
||||
authFailed(buildEmptyContext(), new BaseError(errorMessage), true)
|
||||
const shouldRescue = async () => buildEmptySuccess()
|
||||
const pipeline = authPipelineCreator([shouldRescue, fatalFail, shouldRescue])
|
||||
const { authResult } = await pipeline({ context: { foo: 'bar' } })
|
||||
const { authResult } = await pipeline(buildFooAuthData())
|
||||
|
||||
if (!isAuthFailedResult(authResult)) {
|
||||
throw new Error('AuthResult should be an auth failed result')
|
||||
}
|
||||
|
||||
expect(authResult.authorized).to.equal(false)
|
||||
expect(authResult.fatal).to.equal(true)
|
||||
expect(authResult.error.message).to.equal(errorMessage)
|
||||
expect(authResult.error?.message).to.equal(errorMessage)
|
||||
})
|
||||
it('Pipeline continues for non fatal errors', async () => {
|
||||
const nonFatalFail = async () => authFailed({}, new Error('errorMessage'), false)
|
||||
const shouldRescue = async () => authSuccess()
|
||||
const nonFatalFail = async () =>
|
||||
authFailed(buildEmptyContext(), new BaseError('errorMessage'), false)
|
||||
const shouldRescue = async () => buildEmptySuccess()
|
||||
const pipeline = authPipelineCreator([shouldRescue, nonFatalFail, shouldRescue])
|
||||
const { authResult } = await pipeline({ context: { foo: 'bar' } })
|
||||
const { authResult } = await pipeline(buildFooAuthData())
|
||||
|
||||
if (isAuthFailedResult(authResult)) {
|
||||
throw new Error('AuthResult should not be an auth failed result')
|
||||
}
|
||||
|
||||
expect(authResult.authorized).to.equal(true)
|
||||
expect(authResult.fatal).to.not.exist
|
||||
expect(authResult.error).to.not.exist
|
||||
})
|
||||
it('Pipeline throws Error if authorized but has error', async () => {
|
||||
const borkedStep = async () => ({
|
||||
const borkedStep: AuthPipelineFunction = async () => ({
|
||||
authResult: {
|
||||
authorized: true,
|
||||
error: new UnauthorizedError('Weird stuff'),
|
||||
fatal: false
|
||||
}
|
||||
},
|
||||
context: undefined as unknown as AuthContext
|
||||
})
|
||||
const pipeline = authPipelineCreator([borkedStep])
|
||||
try {
|
||||
await pipeline({ context: { foo: 'bar' } })
|
||||
await pipeline(buildFooAuthData())
|
||||
throw new Error('This should have thrown')
|
||||
} catch (err) {
|
||||
expect(err.message).to.equal('Auth failure')
|
||||
expect(ensureError(err).message).to.equal('Auth failure')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Role validation', () => {
|
||||
const rolesLookup = async () => [
|
||||
{ name: '1', weight: 1 },
|
||||
{ name: 'server:2', weight: 2 },
|
||||
{ name: '3', weight: 3 },
|
||||
{ name: 'goku', weight: 9001 },
|
||||
{ name: '42', weight: 42 }
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const rolesLookup: () => Promise<UserRoleData<any>[]> = async () => [
|
||||
{ name: '1', weight: 1, description: '', public: false },
|
||||
{ name: 'server:2', weight: 2, description: '', public: false },
|
||||
{ name: '3', weight: 3, description: '', public: false },
|
||||
{ name: 'goku', weight: 9001, description: '', public: false },
|
||||
{ name: '42', weight: 42, description: '', public: false }
|
||||
]
|
||||
|
||||
const testData = [
|
||||
{
|
||||
name: 'Having lower privileged role than required results auth failed',
|
||||
requiredRole: 'server:2',
|
||||
context: { auth: true, role: '1' },
|
||||
context: { auth: true, role: '1' } as unknown as AuthContext,
|
||||
expectedResult: authFailed(
|
||||
null,
|
||||
buildEmptyContext(),
|
||||
new SFE('You do not have the required server role')
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'Not having auth fails role validation',
|
||||
requiredRole: 'server:2',
|
||||
context: { auth: false },
|
||||
expectedResult: authFailed(null, new SUE('Must provide an auth token'))
|
||||
context: { auth: false } as unknown as AuthContext,
|
||||
expectedResult: authFailed(
|
||||
buildEmptyContext(),
|
||||
new SUE('Must provide an auth token')
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'Requiring a junk role fails auth',
|
||||
requiredRole: 'knock knock...',
|
||||
context: { auth: true, role: '1' },
|
||||
expectedResult: authFailed(null, new SFE('Invalid role requirement specified'))
|
||||
context: { auth: true, role: '1' } as unknown as AuthContext,
|
||||
expectedResult: authFailed(
|
||||
buildEmptyContext(),
|
||||
new SFE('Invalid role requirement specified')
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'Having a junk role fails auth',
|
||||
requiredRole: 'server:2',
|
||||
context: { auth: true, role: 'iddqd' },
|
||||
expectedResult: authFailed(null, new SFE('Your role is not valid'))
|
||||
context: { auth: true, role: 'iddqd' } as unknown as AuthContext,
|
||||
expectedResult: authFailed(
|
||||
buildEmptyContext(),
|
||||
new SFE('Your role is not valid')
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'Not having the required level fails',
|
||||
requiredRole: 'goku',
|
||||
context: { auth: true, role: '3' },
|
||||
context: { auth: true, role: '3' } as unknown as AuthContext,
|
||||
expectedResult: authFailed(
|
||||
null,
|
||||
buildEmptyContext(),
|
||||
new SFE('You do not have the required goku role')
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'Having the god mode role defeats even higher privilege requirement',
|
||||
requiredRole: 'goku',
|
||||
context: { auth: true, role: '42' },
|
||||
expectedResult: authSuccess()
|
||||
context: { auth: true, role: '42' } as unknown as AuthContext,
|
||||
expectedResult: buildEmptySuccess()
|
||||
},
|
||||
{
|
||||
name: 'Having equal role weight to required succeeds',
|
||||
requiredRole: '3',
|
||||
context: { auth: true, role: '3' },
|
||||
expectedResult: authSuccess()
|
||||
context: { auth: true, role: '3' } as unknown as AuthContext,
|
||||
expectedResult: buildEmptySuccess()
|
||||
},
|
||||
{
|
||||
name: 'Having bigger role weight than required succeeds',
|
||||
requiredRole: '3',
|
||||
context: { auth: true, role: 'goku' },
|
||||
expectedResult: authSuccess()
|
||||
context: { auth: true, role: 'goku' } as unknown as AuthContext,
|
||||
expectedResult: buildEmptySuccess()
|
||||
}
|
||||
]
|
||||
|
||||
testData.forEach((testCase) =>
|
||||
it(`${testCase.name}`, async () => {
|
||||
const step = validateRole({
|
||||
requiredRole: testCase.requiredRole,
|
||||
requiredRole: testCase.requiredRole as unknown as AvailableRoles,
|
||||
rolesLookup,
|
||||
iddqd: '42',
|
||||
roleGetter: (context) => context.role
|
||||
iddqd: '42' as AvailableRoles,
|
||||
roleGetter: (context) => context.role || null
|
||||
})
|
||||
const { authResult, context } = await step({
|
||||
context: testCase.context,
|
||||
authResult: authFailed()
|
||||
authResult: { authorized: false }
|
||||
})
|
||||
expect(authResult.authorized).to.exist
|
||||
expect(authResult.authorized).to.equal(
|
||||
testCase.expectedResult.authResult.authorized
|
||||
)
|
||||
// this also needs to check for the error type... is this how do you do that in JS????
|
||||
expect(authResult.error?.name).to.equal(
|
||||
testCase.expectedResult.authResult.error?.name
|
||||
)
|
||||
expect(authResult.error?.message).to.equal(
|
||||
testCase.expectedResult.authResult.error?.message
|
||||
)
|
||||
|
||||
if (
|
||||
isAuthFailedResult(authResult) ||
|
||||
isAuthFailedResult(testCase.expectedResult.authResult)
|
||||
) {
|
||||
if (
|
||||
!isAuthFailedResult(authResult) ||
|
||||
!isAuthFailedResult(testCase.expectedResult.authResult)
|
||||
) {
|
||||
throw new Error('AuthResult should be an auth failed result')
|
||||
}
|
||||
|
||||
// this also needs to check for the error type... is this how do you do that in JS????
|
||||
expect(authResult.error?.name).to.equal(
|
||||
testCase.expectedResult.authResult.error?.name
|
||||
)
|
||||
expect(authResult.error?.message).to.equal(
|
||||
testCase.expectedResult.authResult.error?.message
|
||||
)
|
||||
}
|
||||
|
||||
expect(context).to.deep.equal(testCase.context)
|
||||
})
|
||||
)
|
||||
it('Role validation fails if input authResult is already in an error state', async () => {
|
||||
const step = validateRole({ requiredRole: 'goku', rolesLookup, iddqd: '42' })
|
||||
const step = validateRole({
|
||||
requiredRole: 'goku' as AvailableRoles,
|
||||
rolesLookup,
|
||||
iddqd: '42' as AvailableRoles,
|
||||
roleGetter: (context) => context.role || null
|
||||
})
|
||||
const error = new SFE('This will be echoed back')
|
||||
const { authResult } = await step({
|
||||
context: {},
|
||||
authResult: { authorized: false, error }
|
||||
context: buildEmptyContext(),
|
||||
authResult: { authorized: false, error } as AuthFailedResult
|
||||
})
|
||||
|
||||
if (!isAuthFailedResult(authResult)) {
|
||||
throw new Error('AuthResult should be an auth failed result')
|
||||
}
|
||||
|
||||
expect(authResult.authorized).to.be.false
|
||||
expect(authResult.error.message).to.equal(error.message)
|
||||
expect(authResult.error.name).to.equal(error.name)
|
||||
expect(authResult.error?.message).to.equal(error.message)
|
||||
expect(authResult.error?.name).to.equal(error.name)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -178,53 +238,77 @@ describe('AuthZ @shared', () => {
|
||||
const step = validateScope({ requiredScope: 'play mahjong' })
|
||||
const expectedError = new SFE("Scope validation doesn't rescue the auth pipeline")
|
||||
const { authResult } = await step({
|
||||
context: {},
|
||||
authResult: { authorized: false, error: expectedError }
|
||||
context: buildEmptyContext(),
|
||||
authResult: { authorized: false, error: expectedError } as AuthFailedResult
|
||||
})
|
||||
|
||||
if (!isAuthFailedResult(authResult)) {
|
||||
throw new Error('AuthResult should be an auth failed result')
|
||||
}
|
||||
|
||||
expect(authResult.authorized).to.be.false
|
||||
expect(authResult.error.message).to.equal(expectedError.message)
|
||||
expect(authResult.error.name).to.equal(expectedError.name)
|
||||
expect(authResult.error?.message).to.equal(expectedError.message)
|
||||
expect(authResult.error?.name).to.equal(expectedError.name)
|
||||
})
|
||||
it('Without having any scopes on the context cannot validate scopes', async () => {
|
||||
const step = validateScope({ requiredScope: 'play mahjong' })
|
||||
const { authResult } = await step({ context: {}, authResult: {} })
|
||||
const { authResult } = await step({
|
||||
context: buildEmptyContext(),
|
||||
authResult: { authorized: false }
|
||||
})
|
||||
|
||||
if (!isAuthFailedResult(authResult)) {
|
||||
throw new Error('AuthResult should be an auth failed result')
|
||||
}
|
||||
|
||||
expect(authResult.authorized).to.equal(false)
|
||||
const expectedError = new SFE(
|
||||
'Your auth token does not have the required scope: play mahjong.'
|
||||
)
|
||||
expect(authResult.error.message).to.equal(expectedError.message)
|
||||
expect(authResult.error.name).to.equal(expectedError.name)
|
||||
expect(authResult.error?.message).to.equal(expectedError.message)
|
||||
expect(authResult.error?.name).to.equal(expectedError.name)
|
||||
})
|
||||
it('Not having the right scopes results auth failed', async () => {
|
||||
const step = validateScope({ requiredScope: 'play mahjong' })
|
||||
const { authResult } = await step({
|
||||
context: { scopes: ['sit around and wait', 'try to be cool'] },
|
||||
authResult: {}
|
||||
context: { scopes: ['sit around and wait', 'try to be cool'] } as AuthContext,
|
||||
authResult: { authorized: false }
|
||||
})
|
||||
|
||||
if (!isAuthFailedResult(authResult)) {
|
||||
throw new Error('AuthResult should be an auth failed result')
|
||||
}
|
||||
|
||||
expect(authResult.authorized).to.equal(false)
|
||||
const expectedError = new SFE(
|
||||
'Your auth token does not have the required scope: play mahjong.'
|
||||
)
|
||||
|
||||
expect(authResult.error.message).to.equal(expectedError.message)
|
||||
expect(authResult.error.name).to.equal(expectedError.name)
|
||||
expect(authResult.error?.message).to.equal(expectedError.message)
|
||||
expect(authResult.error?.name).to.equal(expectedError.name)
|
||||
})
|
||||
it('Having the right scopes results auth success', async () => {
|
||||
const step = validateScope({ requiredScope: 'play mahjong' })
|
||||
const { authResult } = await step({
|
||||
context: { scopes: ['sit around and wait', 'try to be cool', 'play mahjong'] },
|
||||
authResult: {}
|
||||
context: {
|
||||
scopes: ['sit around and wait', 'try to be cool', 'play mahjong']
|
||||
} as AuthContext,
|
||||
authResult: { authorized: false }
|
||||
})
|
||||
|
||||
if (isAuthFailedResult(authResult)) {
|
||||
throw new Error('AuthResult should not be an auth failed result')
|
||||
}
|
||||
|
||||
expect(authResult.authorized).to.equal(true)
|
||||
expect(authResult.error).to.not.exist
|
||||
})
|
||||
})
|
||||
|
||||
describe('Validate resource access', () => {
|
||||
it('Succeeds when no resource access rules present', async () => {
|
||||
const res = await validateResourceAccess({
|
||||
context: {},
|
||||
authResult: {}
|
||||
context: buildEmptyContext(),
|
||||
authResult: { authorized: false }
|
||||
})
|
||||
|
||||
expect(res.authResult.authorized).to.be.true
|
||||
@@ -236,8 +320,8 @@ describe('AuthZ @shared', () => {
|
||||
resourceAccessRules: [
|
||||
{ id: 'foo', type: TokenResourceIdentifierType.Project }
|
||||
]
|
||||
},
|
||||
authResult: {}
|
||||
} as AuthContext,
|
||||
authResult: { authorized: false }
|
||||
})
|
||||
|
||||
expect(res.authResult.authorized).to.be.true
|
||||
@@ -249,8 +333,8 @@ describe('AuthZ @shared', () => {
|
||||
resourceAccessRules: [
|
||||
{ id: 'foo', type: TokenResourceIdentifierType.Project }
|
||||
]
|
||||
},
|
||||
authResult: { authorized: false, error: new Error('dummy') }
|
||||
} as AuthContext,
|
||||
authResult: { authorized: false, error: new Error('dummy') } as AuthFailedResult
|
||||
})
|
||||
|
||||
expect(res.authResult.authorized).to.be.false
|
||||
@@ -263,12 +347,16 @@ describe('AuthZ @shared', () => {
|
||||
{ id: 'foo', type: TokenResourceIdentifierType.Project }
|
||||
],
|
||||
stream: { id: 'bar' }
|
||||
},
|
||||
authResult: {}
|
||||
} as AuthContext,
|
||||
authResult: { authorized: false }
|
||||
})
|
||||
|
||||
if (!isAuthFailedResult(res.authResult)) {
|
||||
throw new Error('AuthResult should be an auth failed result')
|
||||
}
|
||||
|
||||
expect(res.authResult.authorized).to.be.false
|
||||
expect(res.authResult.error.message).to.equal(
|
||||
expect(res.authResult.error?.message).to.equal(
|
||||
'You are not authorized to access this resource.'
|
||||
)
|
||||
})
|
||||
@@ -281,8 +369,8 @@ describe('AuthZ @shared', () => {
|
||||
{ id: 'bar', type: TokenResourceIdentifierType.Project }
|
||||
],
|
||||
stream: { id: 'bar' }
|
||||
},
|
||||
authResult: {}
|
||||
} as AuthContext,
|
||||
authResult: { authorized: false }
|
||||
})
|
||||
|
||||
expect(res.authResult.authorized).to.be.true
|
||||
@@ -295,8 +383,8 @@ describe('AuthZ @shared', () => {
|
||||
{ id: 'foo', type: 'fake' },
|
||||
{ id: 'bar', type: 'fake' }
|
||||
]
|
||||
},
|
||||
authResult: {}
|
||||
} as unknown as AuthContext,
|
||||
authResult: { authorized: false }
|
||||
})
|
||||
|
||||
expect(res.authResult.authorized).to.be.true
|
||||
@@ -304,18 +392,21 @@ describe('AuthZ @shared', () => {
|
||||
})
|
||||
|
||||
describe('Context requires stream', () => {
|
||||
const expectAuthError = (expectedError, authResult) => {
|
||||
const expectAuthError = (expectedError: Error, authResult: AuthResult) => {
|
||||
if (!isAuthFailedResult(authResult)) {
|
||||
throw new Error('AuthResult should be an auth failed result')
|
||||
}
|
||||
|
||||
expect(authResult.authorized).to.be.false
|
||||
expect(authResult.error).to.exist
|
||||
expect(authResult.error.message).to.equal(expectedError.message)
|
||||
expect(authResult.error.name).to.equal(expectedError.name)
|
||||
expect(authResult.error?.message).to.equal(expectedError.message)
|
||||
expect(authResult.error?.name).to.equal(expectedError.name)
|
||||
}
|
||||
it('Without streamId in the params it raises context error', async () => {
|
||||
const step = validateRequiredStreamFactory({
|
||||
getStream: async () => ({ ur: 'bamboozled' }),
|
||||
getAutomationProject: async () => null
|
||||
getStream: async () => ({ ur: 'bamboozled' } as unknown as StreamRecord)
|
||||
})
|
||||
const { authResult } = await step({ params: {} })
|
||||
const { authResult } = await step({ params: {} } as AuthData)
|
||||
expectAuthError(
|
||||
new ContextError("The context doesn't have a streamId"),
|
||||
authResult
|
||||
@@ -323,10 +414,9 @@ describe('AuthZ @shared', () => {
|
||||
})
|
||||
it('If params is not defined it raises context error', async () => {
|
||||
const step = validateRequiredStreamFactory({
|
||||
getStream: async () => ({ ur: 'bamboozled' }),
|
||||
getAutomationProject: async () => null
|
||||
getStream: async () => ({ ur: 'bamboozled' } as unknown as StreamRecord)
|
||||
})
|
||||
const { authResult } = await step({})
|
||||
const { authResult } = await step({} as AuthData)
|
||||
expectAuthError(
|
||||
new ContextError("The context doesn't have a streamId"),
|
||||
authResult
|
||||
@@ -336,23 +426,24 @@ describe('AuthZ @shared', () => {
|
||||
const demoStream = {
|
||||
id: 'foo',
|
||||
name: 'bar'
|
||||
}
|
||||
} as StreamRecord
|
||||
|
||||
const step = validateRequiredStreamFactory({
|
||||
getStream: async () => demoStream,
|
||||
getAutomationProject: async () => null
|
||||
getStream: async () => demoStream
|
||||
})
|
||||
const { context } = await step({
|
||||
context: {},
|
||||
context: buildEmptyContext(),
|
||||
params: { streamId: 'this is fake and its fine' }
|
||||
})
|
||||
} as AuthData)
|
||||
expect(context.stream).to.deep.equal(demoStream)
|
||||
})
|
||||
it('If context is not defined return auth failure', async () => {
|
||||
const step = validateRequiredStreamFactory({
|
||||
getStream: async () => {},
|
||||
getAutomationProject: async () => null
|
||||
getStream: async () => undefined
|
||||
})
|
||||
const { authResult } = await step({ params: { streamId: 'the need for stream' } })
|
||||
const { authResult } = await step({
|
||||
params: { streamId: 'the need for stream' }
|
||||
} as AuthData)
|
||||
|
||||
expectAuthError(new ContextError('The context is not defined'), authResult)
|
||||
})
|
||||
@@ -361,25 +452,23 @@ describe('AuthZ @shared', () => {
|
||||
const step = validateRequiredStreamFactory({
|
||||
getStream: async () => {
|
||||
throw new Error(errorMessage)
|
||||
},
|
||||
getAutomationProject: async () => null
|
||||
}
|
||||
})
|
||||
const { authResult } = await step({
|
||||
context: {},
|
||||
params: { streamId: 'the need for stream' }
|
||||
})
|
||||
} as AuthData)
|
||||
|
||||
expectAuthError(new ContextError(errorMessage), authResult)
|
||||
})
|
||||
it("If stream getter doesn't find a stream it returns fatal auth failure", async () => {
|
||||
const step = validateRequiredStreamFactory({
|
||||
getStream: async () => {},
|
||||
getAutomationProject: async () => null
|
||||
getStream: async () => undefined
|
||||
})
|
||||
const { authResult } = await step({
|
||||
params: { streamId: 'the need for stream' },
|
||||
context: {}
|
||||
})
|
||||
} as AuthData)
|
||||
|
||||
expectAuthError(
|
||||
new NotFoundError(
|
||||
@@ -393,19 +482,25 @@ describe('AuthZ @shared', () => {
|
||||
describe('Escape hatches', () => {
|
||||
describe('Admin override', () => {
|
||||
it('server:admins get authSuccess', async () => {
|
||||
const input = { context: { role: Roles.Server.Admin }, authResult: 'fake' }
|
||||
const input = {
|
||||
context: { role: Roles.Server.Admin },
|
||||
authResult: 'fake'
|
||||
} as unknown as AuthData
|
||||
const result = await allowForServerAdmins(input)
|
||||
expect(result).to.deep.equal(authSuccess(input.context))
|
||||
})
|
||||
it('server:users get the previous authResult', async () => {
|
||||
const input = { context: { role: Roles.Server.User }, authResult: 'fake' }
|
||||
const input = {
|
||||
context: { role: Roles.Server.User },
|
||||
authResult: 'fake'
|
||||
} as unknown as AuthData
|
||||
const result = await allowForServerAdmins(input)
|
||||
expect(result).to.deep.equal(input)
|
||||
})
|
||||
})
|
||||
describe('Allow for public stream no role', () => {
|
||||
it('not public stream, no auth returns same context ', async () => {
|
||||
const input = { context: 'dummy', authResult: 'fake' }
|
||||
const input = { context: 'dummy', authResult: 'fake' } as unknown as AuthData
|
||||
const result = await allowForRegisteredUsersOnPublicStreamsEvenWithoutRole(
|
||||
input
|
||||
)
|
||||
@@ -415,7 +510,7 @@ describe('AuthZ @shared', () => {
|
||||
const input = {
|
||||
context: { stream: { visibility: ProjectRecordVisibility.Public } },
|
||||
authResult: 'fake'
|
||||
}
|
||||
} as unknown as AuthData
|
||||
const result = await allowForRegisteredUsersOnPublicStreamsEvenWithoutRole(
|
||||
input
|
||||
)
|
||||
@@ -428,7 +523,7 @@ describe('AuthZ @shared', () => {
|
||||
stream: { visibility: ProjectRecordVisibility.Private }
|
||||
},
|
||||
authResult: 'fake'
|
||||
}
|
||||
} as unknown as AuthData
|
||||
const result = await allowForRegisteredUsersOnPublicStreamsEvenWithoutRole(
|
||||
input
|
||||
)
|
||||
@@ -441,7 +536,7 @@ describe('AuthZ @shared', () => {
|
||||
stream: { visibility: ProjectRecordVisibility.Public }
|
||||
},
|
||||
authResult: 'fake'
|
||||
}
|
||||
} as unknown as AuthData
|
||||
const result = await allowForRegisteredUsersOnPublicStreamsEvenWithoutRole(
|
||||
input
|
||||
)
|
||||
@@ -560,7 +655,9 @@ describe('AuthZ @shared', () => {
|
||||
sameContextTestData.map(([caseName, context]) =>
|
||||
it(`${caseName} returns same context`, async () => {
|
||||
const result =
|
||||
await allowForAllRegisteredUsersOnPublicStreamsWithPublicComments(context)
|
||||
await allowForAllRegisteredUsersOnPublicStreamsWithPublicComments(
|
||||
context as unknown as AuthData
|
||||
)
|
||||
expect(result).to.deep.equal(context)
|
||||
})
|
||||
)
|
||||
@@ -574,7 +671,7 @@ describe('AuthZ @shared', () => {
|
||||
}
|
||||
},
|
||||
authResult: 'fake'
|
||||
}
|
||||
} as unknown as AuthData
|
||||
const result =
|
||||
await allowForAllRegisteredUsersOnPublicStreamsWithPublicComments(input)
|
||||
expect(result).to.deep.equal(authSuccess(input.context))
|
||||
Reference in New Issue
Block a user