feat(multiregion): replace user replication (#5253)
This commit is contained in:
@@ -16,28 +16,7 @@ import {
|
||||
getStreamRolesFactory,
|
||||
grantStreamPermissionsFactory
|
||||
} from '@/modules/core/repositories/streams'
|
||||
import {
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
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
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { getUserFactory } from '@/modules/core/repositories/users'
|
||||
import { createPersonalAccessTokenFactory } from '@/modules/core/services/tokens'
|
||||
import {
|
||||
storePersonalApiTokenFactory,
|
||||
@@ -45,7 +24,6 @@ import {
|
||||
storeTokenScopesFactory,
|
||||
storeTokenResourceAccessDefinitionsFactory
|
||||
} from '@/modules/core/repositories/tokens'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { createObjectFactory } from '@/modules/core/services/objects/management'
|
||||
import { storeSingleObjectIfNotFoundFactory } from '@/modules/core/repositories/objects'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
@@ -55,6 +33,8 @@ import { createTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import type { BasicTestBranch } from '@/test/speckle-helpers/branchHelper'
|
||||
import { createTestBranch } from '@/test/speckle-helpers/branchHelper'
|
||||
import { getActivitiesFactory } from '@/modules/activitystream/repositories/index'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
|
||||
const getUser = getUserFactory({ db })
|
||||
const getUserActivity = getUserActivityFactory({ db })
|
||||
@@ -67,34 +47,6 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const createPersonalAccessToken = createPersonalAccessTokenFactory({
|
||||
storeApiToken: storeApiTokenFactory({ db }),
|
||||
storeTokenScopes: storeTokenScopesFactory({ db }),
|
||||
@@ -113,29 +65,13 @@ let server: http.Server
|
||||
let sendRequest: Awaited<ReturnType<typeof initializeTestServer>>['sendRequest']
|
||||
|
||||
describe('Activity @activity', () => {
|
||||
const userIz = {
|
||||
name: 'Izzy Lyseggen',
|
||||
email: 'izzybizzi@speckle.systems',
|
||||
password: 'sp0ckle sucks 9001',
|
||||
id: '',
|
||||
token: ''
|
||||
}
|
||||
let userIz: BasicTestUser
|
||||
let userCr: BasicTestUser
|
||||
let userX: BasicTestUser
|
||||
|
||||
const userCr = {
|
||||
name: 'Cristi Balas',
|
||||
email: 'cristib@speckle.systems',
|
||||
password: 'hack3r man 666',
|
||||
id: '',
|
||||
token: ''
|
||||
}
|
||||
|
||||
const userX = {
|
||||
name: 'Mystery User',
|
||||
email: 'mysteriousDude@speckle.systems',
|
||||
password: 'super $ecret pw0rd',
|
||||
id: '',
|
||||
token: ''
|
||||
}
|
||||
let userIzToken: string
|
||||
let userCrToken: string
|
||||
let userXToken: string
|
||||
|
||||
const streamPublic: BasicTestStream = {
|
||||
name: 'a fun stream for sharing',
|
||||
@@ -197,30 +133,42 @@ describe('Activity @activity', () => {
|
||||
]
|
||||
|
||||
// create users
|
||||
await Promise.all([
|
||||
createUser(userIz).then((id) => (userIz.id = id)),
|
||||
createUser(userCr).then((id) => (userCr.id = id)),
|
||||
createUser(userX).then((id) => (userX.id = id))
|
||||
])
|
||||
userIz = await createTestUser({
|
||||
name: 'Izzy Lyseggen',
|
||||
email: 'izzybizzi@speckle.systems',
|
||||
password: 'sp0ckle sucks 9001'
|
||||
})
|
||||
userCr = await createTestUser({
|
||||
name: 'Cristi Balas',
|
||||
email: 'cristib@speckle.systems',
|
||||
password: 'hack3r man 666'
|
||||
})
|
||||
userX = await createTestUser({
|
||||
name: 'Mystery User',
|
||||
email: 'mysteriousDude@speckle.systems',
|
||||
password: 'super $ecret pw0rd'
|
||||
})
|
||||
|
||||
// create tokens and streams
|
||||
await Promise.all([
|
||||
// tokens
|
||||
createPersonalAccessToken(userIz.id, 'izz test token', normalScopesList).then(
|
||||
(token) => (userIz.token = `Bearer ${token}`)
|
||||
),
|
||||
createPersonalAccessToken(userCr.id, 'cristi test token', normalScopesList).then(
|
||||
(token) => (userCr.token = `Bearer ${token}`)
|
||||
),
|
||||
createPersonalAccessToken(userX.id, 'no users:read test token', [
|
||||
Scopes.Streams.Read,
|
||||
Scopes.Streams.Write
|
||||
]).then((token) => (userX.token = `Bearer ${token}`))
|
||||
// streams
|
||||
// createStream({ ...collaboratorTestStream, ownerId: userIz.id }).then(
|
||||
// (id) => (collaboratorTestStream.id = id)
|
||||
// )
|
||||
])
|
||||
userIzToken = `Bearer ${await createPersonalAccessToken(
|
||||
userIz.id,
|
||||
'izz test token',
|
||||
normalScopesList
|
||||
)}`
|
||||
userCrToken = `Bearer ${await createPersonalAccessToken(
|
||||
userCr.id,
|
||||
'cristi test token',
|
||||
normalScopesList
|
||||
)}`
|
||||
userXToken = `Bearer ${createPersonalAccessToken(
|
||||
userX.id,
|
||||
'no users:read test token',
|
||||
[Scopes.Streams.Read, Scopes.Streams.Write]
|
||||
)}`
|
||||
|
||||
// streams
|
||||
// createStream({ ...collaboratorTestStream, ownerId: userIz.id }).then(
|
||||
// (id) => (collaboratorTestStream.id = id)
|
||||
// )
|
||||
|
||||
// It's definitely not great that there's a full on test case in the before() hook, but that's because
|
||||
// these tests were originally written incorrectly - they depend on each other. So this is a temporary fix that
|
||||
@@ -232,7 +180,7 @@ describe('Activity @activity', () => {
|
||||
|
||||
// create commit (cr2)
|
||||
testObj2.id = await createObject({ streamId: streamSecret.id, object: testObj2 })
|
||||
const resCommit1 = await sendRequest(userCr.token, {
|
||||
const resCommit1 = await sendRequest(userCrToken, {
|
||||
query: `mutation { commitCreate(commit: {streamId: "${streamSecret.id}", branchName: "main", objectId: "${testObj2.id}", message: "first commit"})}`
|
||||
})
|
||||
expect(noErrors(resCommit1))
|
||||
@@ -249,7 +197,7 @@ describe('Activity @activity', () => {
|
||||
|
||||
// create commit #2 (iz3)
|
||||
testObj.id = await createObject({ streamId: streamPublic.id, object: testObj })
|
||||
const resCommit2 = await sendRequest(userIz.token, {
|
||||
const resCommit2 = await sendRequest(userIzToken, {
|
||||
query: `mutation { commitCreate(commit: { streamId: "${streamPublic.id}", branchName: "${branchPublic.name}", objectId: "${testObj.id}", message: "first commit" })}`
|
||||
})
|
||||
expect(noErrors(resCommit2))
|
||||
@@ -263,7 +211,7 @@ describe('Activity @activity', () => {
|
||||
)
|
||||
|
||||
// update collaborator (iz4)
|
||||
const resCollab = await sendRequest(userIz.token, {
|
||||
const resCollab = await sendRequest(userIzToken, {
|
||||
query: `mutation { streamUpdatePermission( permissionParams: { streamId: "${streamPublic.id}", userId: "${userCr.id}", role: "stream:contributor" } ) }`
|
||||
})
|
||||
expect(noErrors(resCollab))
|
||||
@@ -312,7 +260,7 @@ describe('Activity @activity', () => {
|
||||
})
|
||||
|
||||
it("Should get a user's own activity", async () => {
|
||||
const res = await sendRequest(userIz.token, {
|
||||
const res = await sendRequest(userIzToken, {
|
||||
query: `query {activeUser { name activity { totalCount items {id streamId resourceType resourceId actionType userId message time}}} }`
|
||||
})
|
||||
expect(noErrors(res))
|
||||
@@ -325,7 +273,7 @@ describe('Activity @activity', () => {
|
||||
})
|
||||
|
||||
it("Should get another user's activity", async () => {
|
||||
const res = await sendRequest(userIz.token, {
|
||||
const res = await sendRequest(userIzToken, {
|
||||
query: `query {otherUser(id:"${userCr.id}") { name activity { totalCount items {id streamId resourceType resourceId actionType userId message time}}} }`
|
||||
})
|
||||
expect(noErrors(res))
|
||||
@@ -334,7 +282,7 @@ describe('Activity @activity', () => {
|
||||
})
|
||||
|
||||
it("Should get a user's timeline", async () => {
|
||||
const res = await sendRequest(userIz.token, {
|
||||
const res = await sendRequest(userIzToken, {
|
||||
query: `query {otherUser(id:"${userCr.id}") { name timeline { totalCount items {id streamId resourceType resourceId actionType userId message time}}} }`
|
||||
})
|
||||
expect(noErrors(res))
|
||||
@@ -343,7 +291,7 @@ describe('Activity @activity', () => {
|
||||
})
|
||||
|
||||
it("Should get a stream's activity", async () => {
|
||||
const res = await sendRequest(userCr.token, {
|
||||
const res = await sendRequest(userCrToken, {
|
||||
query: `query { stream(id: "${streamPublic.id}") { activity { totalCount items {id streamId resourceId actionType message} } } }`
|
||||
})
|
||||
expect(noErrors(res))
|
||||
@@ -354,7 +302,7 @@ describe('Activity @activity', () => {
|
||||
})
|
||||
|
||||
it("Should get a branch's activity", async () => {
|
||||
const res = await sendRequest(userCr.token, {
|
||||
const res = await sendRequest(userCrToken, {
|
||||
query: `query { stream(id: "${streamPublic.id}") { branch(name: "${branchPublic.name}") { activity { totalCount items {id streamId resourceId actionType message} } } } }`
|
||||
})
|
||||
expect(noErrors(res))
|
||||
@@ -365,7 +313,7 @@ describe('Activity @activity', () => {
|
||||
})
|
||||
|
||||
it("Should *not* get a stream's activity if you don't have access to it", async () => {
|
||||
const res = await sendRequest(userIz.token, {
|
||||
const res = await sendRequest(userIzToken, {
|
||||
query: `query {stream(id:"${streamSecret.id}") {name activity {items {id streamId resourceType resourceId actionType userId message time}}} }`
|
||||
})
|
||||
expect(res.body.errors?.length).to.equal(1)
|
||||
@@ -379,16 +327,18 @@ describe('Activity @activity', () => {
|
||||
})
|
||||
|
||||
it("Should *not* get a user's activity without the `users:read` scope", async () => {
|
||||
const res = await sendRequest(userX.token, {
|
||||
const res = await sendRequest(userXToken, {
|
||||
query: `query {otherUser(id:"${userCr.id}") { name activity {items {id streamId resourceType resourceId actionType userId message time}}} }`
|
||||
})
|
||||
expect(res.body.errors?.length).to.equal(1)
|
||||
|
||||
expect(res.body.error).to.exist
|
||||
})
|
||||
|
||||
it("Should *not* get a user's timeline without the `users:read` scope", async () => {
|
||||
const res = await sendRequest(userX.token, {
|
||||
const res = await sendRequest(userXToken, {
|
||||
query: `query {otherUser(id:"${userCr.id}") { name timeline {items {id streamId resourceType resourceId actionType userId message time}}} }`
|
||||
})
|
||||
expect(res.body.errors?.length).to.equal(1)
|
||||
|
||||
expect(res.body.error).to.exist
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SpeckleModule } from '@/modules/shared/helpers/typeHelper'
|
||||
|
||||
import { registerOrUpdateScopeFactory } from '@/modules/shared/repositories/scopes'
|
||||
import { moduleLogger } from '@/observability/logging'
|
||||
import { logger, moduleLogger } from '@/observability/logging'
|
||||
import db from '@/db/knex'
|
||||
import { initializeDefaultAppsFactory } from '@/modules/auth/services/serverApps'
|
||||
import {
|
||||
@@ -60,42 +60,9 @@ import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { isRateLimiterEnabled } from '@/modules/shared/helpers/envHelper'
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
|
||||
db
|
||||
}),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const findOrCreateUser = findOrCreateUserFactory({
|
||||
createUser,
|
||||
findPrimaryEmailForUser: findPrimaryEmailForUserFactory({ db })
|
||||
})
|
||||
import { getAllRegisteredDbs } from '@/modules/multiregion/utils/dbSelector'
|
||||
import type { CreateValidatedUser } from '@/modules/core/domain/users/operations'
|
||||
import { asMultiregionalOperation } from '@/modules/shared/command'
|
||||
|
||||
const initializeDefaultApps = initializeDefaultAppsFactory({
|
||||
getAllScopes: getAllScopesFactory({ db }),
|
||||
@@ -113,10 +80,65 @@ const finalizeInvitedServerRegistration = finalizeInvitedServerRegistrationFacto
|
||||
})
|
||||
const resolveAuthRedirectPath = resolveAuthRedirectPathFactory()
|
||||
|
||||
const createUser: CreateValidatedUser = async (...input) =>
|
||||
asMultiregionalOperation(
|
||||
async ({ mainDb, allDbs, emit }) => {
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo: getServerInfoFactory({ db: mainDb }),
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
storeUser: async (...params) => {
|
||||
const [user] = await Promise.all(
|
||||
allDbs.map((db) => storeUserFactory({ db })(...params))
|
||||
)
|
||||
|
||||
return user
|
||||
},
|
||||
countAdminUsers: countAdminUsersFactory({ db: mainDb }),
|
||||
storeUserAcl: storeUserAclFactory({ db: mainDb }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db: mainDb }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({
|
||||
db: mainDb
|
||||
}),
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db: mainDb }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db: mainDb })
|
||||
}),
|
||||
requestNewEmailVerification: requestNewEmailVerificationFactory({
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
getUser: getUserFactory({ db: mainDb }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory(
|
||||
{
|
||||
db: mainDb
|
||||
}
|
||||
),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
}),
|
||||
emitEvent: emit
|
||||
})
|
||||
|
||||
return createUser(...input)
|
||||
},
|
||||
{
|
||||
dbs: await getAllRegisteredDbs(),
|
||||
name: 'create user',
|
||||
logger
|
||||
}
|
||||
)
|
||||
|
||||
const commonBuilderDeps = {
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
getUserByEmail: legacyGetUserByEmailFactory({ db }),
|
||||
findOrCreateUser,
|
||||
buildFindOrCreateUser: async () => {
|
||||
return findOrCreateUserFactory({
|
||||
createUser,
|
||||
findPrimaryEmailForUser: findPrimaryEmailForUserFactory({ db })
|
||||
})
|
||||
},
|
||||
validateServerInvite,
|
||||
finalizeInvitedServerRegistration,
|
||||
resolveAuthRedirectPath,
|
||||
|
||||
@@ -40,7 +40,7 @@ const azureAdStrategyBuilderFactory =
|
||||
(deps: {
|
||||
getServerInfo: GetServerInfo
|
||||
getUserByEmail: LegacyGetUserByEmail
|
||||
findOrCreateUser: FindOrCreateValidatedUser
|
||||
buildFindOrCreateUser: () => Promise<FindOrCreateValidatedUser>
|
||||
validateServerInvite: ValidateServerInvite
|
||||
finalizeInvitedServerRegistration: FinalizeInvitedServerRegistration
|
||||
resolveAuthRedirectPath: ResolveAuthRedirectPath
|
||||
@@ -102,6 +102,8 @@ const azureAdStrategyBuilderFactory =
|
||||
serverVersion: serverInfo.version
|
||||
})
|
||||
|
||||
const findOrCreateUser = await deps.buildFindOrCreateUser()
|
||||
|
||||
try {
|
||||
// This is the only strategy that does its own type for req.user - easier to force type cast for now
|
||||
// than to refactor everything
|
||||
@@ -130,7 +132,7 @@ const azureAdStrategyBuilderFactory =
|
||||
// if there is an existing user, go ahead and log them in (regardless of
|
||||
// whether the server is invite only or not).
|
||||
if (existingUser) {
|
||||
const myUser = await deps.findOrCreateUser({
|
||||
const myUser = await findOrCreateUser({
|
||||
user
|
||||
})
|
||||
// ID is used later for verifying access token
|
||||
@@ -156,7 +158,7 @@ const azureAdStrategyBuilderFactory =
|
||||
}
|
||||
|
||||
// create the user
|
||||
const myUser = await deps.findOrCreateUser({
|
||||
const myUser = await findOrCreateUser({
|
||||
user: {
|
||||
...user,
|
||||
role: invite
|
||||
|
||||
@@ -44,7 +44,7 @@ const githubStrategyBuilderFactory =
|
||||
(deps: {
|
||||
getServerInfo: GetServerInfo
|
||||
getUserByEmail: LegacyGetUserByEmail
|
||||
findOrCreateUser: FindOrCreateValidatedUser
|
||||
buildFindOrCreateUser: () => Promise<FindOrCreateValidatedUser>
|
||||
validateServerInvite: ValidateServerInvite
|
||||
finalizeInvitedServerRegistration: FinalizeInvitedServerRegistration
|
||||
resolveAuthRedirectPath: ResolveAuthRedirectPath
|
||||
@@ -91,6 +91,8 @@ const githubStrategyBuilderFactory =
|
||||
serverVersion: serverInfo.version
|
||||
})
|
||||
|
||||
const findOrCreateUser = await deps.buildFindOrCreateUser()
|
||||
|
||||
try {
|
||||
const email = profile.emails?.[0].value
|
||||
if (!email) {
|
||||
@@ -115,7 +117,7 @@ const githubStrategyBuilderFactory =
|
||||
// if there is an existing user, go ahead and log them in (regardless of
|
||||
// whether the server is invite only or not).
|
||||
if (existingUser) {
|
||||
const myUser = await deps.findOrCreateUser({ user })
|
||||
const myUser = await findOrCreateUser({ user })
|
||||
return done(null, myUser)
|
||||
}
|
||||
|
||||
@@ -133,7 +135,7 @@ const githubStrategyBuilderFactory =
|
||||
}
|
||||
|
||||
// create the user
|
||||
const myUser = await deps.findOrCreateUser({
|
||||
const myUser = await findOrCreateUser({
|
||||
user: {
|
||||
...user,
|
||||
role: invite
|
||||
|
||||
@@ -39,7 +39,7 @@ const googleStrategyBuilderFactory =
|
||||
(deps: {
|
||||
getServerInfo: GetServerInfo
|
||||
getUserByEmail: LegacyGetUserByEmail
|
||||
findOrCreateUser: FindOrCreateValidatedUser
|
||||
buildFindOrCreateUser: () => Promise<FindOrCreateValidatedUser>
|
||||
validateServerInvite: ValidateServerInvite
|
||||
finalizeInvitedServerRegistration: FinalizeInvitedServerRegistration
|
||||
resolveAuthRedirectPath: ResolveAuthRedirectPath
|
||||
@@ -75,6 +75,7 @@ const googleStrategyBuilderFactory =
|
||||
profileId: profile.id,
|
||||
serverVersion: serverInfo.version
|
||||
})
|
||||
const findOrCreateUser = await deps.buildFindOrCreateUser()
|
||||
|
||||
try {
|
||||
// seems very weird that the Google strategy is not parsing 'error' query params
|
||||
@@ -117,7 +118,7 @@ const googleStrategyBuilderFactory =
|
||||
// if there is an existing user, go ahead and log them in (regardless of
|
||||
// whether the server is invite only or not).
|
||||
if (existingUser) {
|
||||
const myUser = await deps.findOrCreateUser({ user })
|
||||
const myUser = await findOrCreateUser({ user })
|
||||
return done(null, myUser)
|
||||
}
|
||||
|
||||
@@ -135,7 +136,7 @@ const googleStrategyBuilderFactory =
|
||||
}
|
||||
|
||||
// create the user
|
||||
const myUser = await deps.findOrCreateUser({
|
||||
const myUser = await findOrCreateUser({
|
||||
user: {
|
||||
...user,
|
||||
role: invite
|
||||
|
||||
@@ -38,7 +38,7 @@ const oidcStrategyBuilderFactory =
|
||||
(deps: {
|
||||
getServerInfo: GetServerInfo
|
||||
getUserByEmail: LegacyGetUserByEmail
|
||||
findOrCreateUser: FindOrCreateValidatedUser
|
||||
buildFindOrCreateUser: () => Promise<FindOrCreateValidatedUser>
|
||||
validateServerInvite: ValidateServerInvite
|
||||
finalizeInvitedServerRegistration: FinalizeInvitedServerRegistration
|
||||
resolveAuthRedirectPath: ResolveAuthRedirectPath
|
||||
@@ -78,6 +78,8 @@ const oidcStrategyBuilderFactory =
|
||||
serverVersion: serverInfo.version
|
||||
})
|
||||
|
||||
const findOrCreateUser = await deps.buildFindOrCreateUser()
|
||||
|
||||
// TODO: req.session.inviteId doesn't appear to exist, but i'm not removing it to not break things
|
||||
const token: Optional<string> =
|
||||
get(req.session, 'inviteId') || req.session.token
|
||||
@@ -107,7 +109,7 @@ const oidcStrategyBuilderFactory =
|
||||
// if there is an existing user, go ahead and log them in (regardless of
|
||||
// whether the server is invite only or not).
|
||||
if (existingUser) {
|
||||
const myUser = await deps.findOrCreateUser({
|
||||
const myUser = await findOrCreateUser({
|
||||
user
|
||||
})
|
||||
|
||||
@@ -128,7 +130,7 @@ const oidcStrategyBuilderFactory =
|
||||
}
|
||||
|
||||
// create the user
|
||||
const myUser = await deps.findOrCreateUser({
|
||||
const myUser = await findOrCreateUser({
|
||||
user: {
|
||||
...user,
|
||||
role: invite
|
||||
|
||||
@@ -20,28 +20,6 @@ import {
|
||||
} from '@/modules/auth/repositories/apps'
|
||||
import { db } from '@/db/knex'
|
||||
import { createAppTokenFromAccessCodeFactory } from '@/modules/auth/services/serverApps'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import {
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} 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
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import {
|
||||
storeApiTokenFactory,
|
||||
storeTokenScopesFactory,
|
||||
@@ -49,9 +27,7 @@ import {
|
||||
storeUserServerAppTokenFactory,
|
||||
storePersonalApiTokenFactory
|
||||
} from '@/modules/core/repositories/tokens'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser, type BasicTestUser } from '@/test/authHelper'
|
||||
|
||||
let sendRequest: Awaited<ReturnType<typeof initializeTestServer>>['sendRequest']
|
||||
|
||||
@@ -73,34 +49,6 @@ const createAppTokenFromAccessCode = createAppTokenFromAccessCodeFactory({
|
||||
createBareToken
|
||||
})
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const createPersonalAccessToken = createPersonalAccessTokenFactory({
|
||||
storeApiToken: storeApiTokenFactory({ db }),
|
||||
storeTokenScopes: storeTokenScopesFactory({ db }),
|
||||
@@ -119,28 +67,25 @@ describe('GraphQL @apps-api', () => {
|
||||
before(async () => {
|
||||
const ctx = await beforeEachContext()
|
||||
;({ sendRequest } = await initializeTestServer(ctx))
|
||||
testUser = {
|
||||
|
||||
testUser = await createTestUser({
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie@example.org',
|
||||
password: 'wtfwtfwtf',
|
||||
id: ''
|
||||
}
|
||||
|
||||
testUser.id = await createUser(testUser)
|
||||
})
|
||||
testToken = `Bearer ${await createPersonalAccessToken(testUser.id, 'test token', [
|
||||
Scopes.Profile.Read,
|
||||
Scopes.Apps.Read,
|
||||
Scopes.Apps.Write
|
||||
])}`
|
||||
|
||||
testUser2 = {
|
||||
testUser2 = await createTestUser({
|
||||
name: 'Mr. Mac',
|
||||
email: 'steve@jobs.com',
|
||||
password: 'wtfwtfwtf',
|
||||
id: ''
|
||||
}
|
||||
|
||||
testUser2.id = await createUser(testUser2)
|
||||
})
|
||||
testToken2 = `Bearer ${await createPersonalAccessToken(testUser2.id, 'test token', [
|
||||
Scopes.Profile.Read,
|
||||
Scopes.Apps.Read,
|
||||
|
||||
@@ -31,29 +31,7 @@ import {
|
||||
createAppTokenFromAccessCodeFactory,
|
||||
refreshAppTokenFactory
|
||||
} from '@/modules/auth/services/serverApps'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import {
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory,
|
||||
getUserRoleFactory
|
||||
} 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
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { getUserRoleFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
storeApiTokenFactory,
|
||||
storeTokenScopesFactory,
|
||||
@@ -65,9 +43,7 @@ import {
|
||||
getTokenResourceAccessDefinitionsByIdFactory,
|
||||
updateApiTokenFactory
|
||||
} from '@/modules/core/repositories/tokens'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser, type BasicTestUser } from '@/test/authHelper'
|
||||
import type { AppScopes } from '@speckle/shared'
|
||||
import { ensureError } from '@speckle/shared'
|
||||
import type { ValidTokenResult } from '@/modules/core/helpers/types'
|
||||
@@ -115,34 +91,6 @@ const refreshAppToken = refreshAppTokenFactory({
|
||||
createBareToken
|
||||
})
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const validateToken = validateTokenFactory({
|
||||
revokeUserTokenById: revokeUserTokenByIdFactory({ db }),
|
||||
getApiTokenById: getApiTokenByIdFactory({ db }),
|
||||
@@ -156,16 +104,16 @@ const validateToken = validateTokenFactory({
|
||||
})
|
||||
|
||||
describe('Services @apps-services', () => {
|
||||
const actor: BasicTestUser = {
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie@example.org',
|
||||
password: 'wtfwtfwtf',
|
||||
id: ''
|
||||
}
|
||||
let actor: BasicTestUser
|
||||
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
actor.id = await createUser(actor)
|
||||
actor = await createTestUser({
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie@example.org',
|
||||
password: 'wtfwtfwtf',
|
||||
id: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('Should register an app', async () => {
|
||||
@@ -507,14 +455,12 @@ describe('Services @apps-services', () => {
|
||||
redirectUrl: 'http://127.0.0.1:1335',
|
||||
authorId: actor.id
|
||||
})
|
||||
const secondUser: BasicTestUser = {
|
||||
const secondUser = await createTestUser({
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie.wow@example.org',
|
||||
password: 'wtfwtfwtf',
|
||||
id: ''
|
||||
}
|
||||
|
||||
secondUser.id = await createUser(secondUser)
|
||||
})
|
||||
const accessCode = await createAuthorizationCode({
|
||||
appId: myTestApp.id,
|
||||
userId: secondUser.id,
|
||||
|
||||
@@ -31,9 +31,6 @@ import { createBranchFactory } from '@/modules/core/repositories/branches'
|
||||
import {
|
||||
getUsersFactory,
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory,
|
||||
legacyGetUserByEmailFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import {
|
||||
@@ -45,7 +42,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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,
|
||||
@@ -76,6 +72,8 @@ import { UserInputError } from '@/modules/core/errors/userinput'
|
||||
import { createRandomEmail } from '@/modules/core/helpers/testHelpers'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { getFrontendOrigin } from '@/modules/shared/helpers/envHelper'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
@@ -161,33 +159,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
})
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const getUserByEmail = legacyGetUserByEmailFactory({ db })
|
||||
const updateServerInfo = updateServerInfoFactory({ db })
|
||||
const logger = extendLoggerComponent(baseLogger, 'auth-tests')
|
||||
@@ -201,20 +172,7 @@ describe('Auth @auth', () => {
|
||||
describe('Local authN & authZ (token endpoints)', () => {
|
||||
const registeredUserEmail = 'registered@speckle.systems'
|
||||
|
||||
const me: {
|
||||
name: string
|
||||
company: string
|
||||
email: string
|
||||
password: string
|
||||
id?: string
|
||||
} = {
|
||||
name: 'dimitrie stefanescu',
|
||||
company: 'speckle',
|
||||
email: registeredUserEmail,
|
||||
password: 'roll saving throws',
|
||||
id: undefined
|
||||
}
|
||||
|
||||
let me: BasicTestUser
|
||||
const myPrivateStream: {
|
||||
name: string
|
||||
isPublic: boolean
|
||||
@@ -231,8 +189,13 @@ describe('Auth @auth', () => {
|
||||
;({ sendRequest } = await initializeTestServer(ctx))
|
||||
|
||||
// Register a user for testing login flows
|
||||
const meId = await createUser(me)
|
||||
me.id = meId
|
||||
me = await createTestUser({
|
||||
name: 'dimitrie stefanescu',
|
||||
company: 'speckle',
|
||||
email: registeredUserEmail,
|
||||
password: 'roll saving throws',
|
||||
id: undefined
|
||||
})
|
||||
|
||||
// Create a test stream for testing stream invites
|
||||
const myPrivateStreamId = await createStream({
|
||||
|
||||
@@ -31,13 +31,7 @@ import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/se
|
||||
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { createBranchFactory } from '@/modules/core/repositories/branches'
|
||||
import {
|
||||
getUsersFactory,
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUsersFactory, getUserFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
@@ -47,7 +41,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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,
|
||||
@@ -63,6 +56,8 @@ import {
|
||||
validateStreamAccessFactory
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
@@ -146,47 +141,18 @@ const createStream = legacyCreateStreamFactory({
|
||||
})
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
describe('Blobs graphql @blobstorage', () => {
|
||||
let graphqlServer: ServerAndContext
|
||||
|
||||
const user = {
|
||||
name: 'Baron Von Blubba',
|
||||
email: 'zebarron@bubble.bobble',
|
||||
password: 'bubblesAreMyBlobs',
|
||||
id: ''
|
||||
}
|
||||
let user: BasicTestUser
|
||||
|
||||
before(async () => {
|
||||
await truncateTables(['blob_storage', Users.name, Streams.name])
|
||||
user.id = await createUser(user)
|
||||
user = await createTestUser({
|
||||
name: 'Baron Von Blubba',
|
||||
email: 'zebarron@bubble.bobble',
|
||||
password: 'bubblesAreMyBlobs',
|
||||
id: ''
|
||||
})
|
||||
graphqlServer = {
|
||||
apollo: await buildApolloServer(),
|
||||
context: await createAuthedTestContext(user.id)
|
||||
|
||||
@@ -4,87 +4,29 @@ import { expect } from 'chai'
|
||||
import { beforeEachContext, getMainTestRegionKeyIfMultiRegion } from '@/test/hooks'
|
||||
import { Scopes } from '@/modules/core/helpers/mainConstants'
|
||||
import { db } from '@/db/knex'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
|
||||
import {
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
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 { createTokenFactory } from '@/modules/core/services/tokens'
|
||||
import {
|
||||
storeApiTokenFactory,
|
||||
storeTokenScopesFactory,
|
||||
storeTokenResourceAccessDefinitionsFactory
|
||||
} from '@/modules/core/repositories/tokens'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import type { BasicTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import { createTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import { waitForRegionUser } from '@/test/speckle-helpers/regions'
|
||||
import { createTestWorkspace } from '@/modules/workspaces/tests/helpers/creation'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser, type BasicTestUser } from '@/test/authHelper'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import type { BlobStorageItem } from '@/modules/blobstorage/domain/types'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const createRandomUser = async (): Promise<BasicTestUser> => {
|
||||
const userDetails = {
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
email: `${cryptoRandomString({ length: 10, type: 'url-safe' })}@example.org`,
|
||||
password: cryptoRandomString({ length: 12 })
|
||||
}
|
||||
return {
|
||||
...userDetails,
|
||||
id: await createUser(userDetails)
|
||||
}
|
||||
return createTestUser(userDetails)
|
||||
}
|
||||
const createToken = createTokenFactory({
|
||||
storeApiToken: storeApiTokenFactory({ db }),
|
||||
|
||||
@@ -53,30 +53,6 @@ import {
|
||||
getStreamObjectsFactory
|
||||
} from '@/modules/core/repositories/objects'
|
||||
import { legacyUpdateStreamFactory } from '@/modules/core/services/streams/management'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import {
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
createUserEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
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 { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { createObjectFactory } from '@/modules/core/services/objects/management'
|
||||
import {
|
||||
getViewerResourcesFromLegacyIdentifiersFactory,
|
||||
@@ -84,8 +60,10 @@ import {
|
||||
} from '@/modules/core/services/commit/viewerResources'
|
||||
import type { SetNonNullable } from 'type-fest'
|
||||
import { createProject } from '@/test/projectHelper'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const markCommitStreamUpdated = markCommitStreamUpdatedFactory({ db })
|
||||
const streamResourceCheck = streamResourceCheckFactory({
|
||||
checkStreamResourceAccess: checkStreamResourceAccessFactory({ db })
|
||||
@@ -139,33 +117,6 @@ const updateStream = legacyUpdateStreamFactory({
|
||||
})
|
||||
const grantPermissionsStream = grantStreamPermissionsFactory({ db })
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const createObject = createObjectFactory({
|
||||
storeSingleObjectIfNotFoundFactory: storeSingleObjectIfNotFoundFactory({ db })
|
||||
})
|
||||
@@ -718,7 +669,7 @@ describe('Graphql @comments', () => {
|
||||
// this user will be admin by default
|
||||
// it will be used to create all resources, that the other actors can
|
||||
// be tested against
|
||||
const myTestActor = {
|
||||
let myTestActor: BasicTestUser = {
|
||||
name: 'Gergo Jedlicska',
|
||||
email: 'gergo@jedlicska.com',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
@@ -1011,11 +962,11 @@ describe('Graphql @comments', () => {
|
||||
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
myTestActor.id = await createUser(myTestActor)
|
||||
myTestActor = await createTestUser(myTestActor)
|
||||
await Promise.all(
|
||||
[chadTheEngineer, archived].map((user) =>
|
||||
createUser({ name: user.name, email: user.email, password: user.password })
|
||||
.then((id) => (user.id = id))
|
||||
createTestUser({ name: user.name, email: user.email, password: user.password })
|
||||
.then(({ id }) => (user.id = id))
|
||||
.catch((err) => {
|
||||
throw err
|
||||
})
|
||||
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
import { get, range } from 'lodash-es'
|
||||
import { buildApolloServer } from '@/app'
|
||||
import { AllScopes } from '@/modules/core/helpers/mainConstants'
|
||||
import { createAuthTokenForUser } from '@/test/authHelper'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createAuthTokenForUser, createTestUser } from '@/test/authHelper'
|
||||
import type { UploadedBlob } from '@/test/blobHelper'
|
||||
import { uploadBlob } from '@/test/blobHelper'
|
||||
import { Comments } from '@/modules/core/dbSchema'
|
||||
@@ -95,13 +96,7 @@ import {
|
||||
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
|
||||
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import {
|
||||
getUsersFactory,
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUsersFactory, getUserFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
@@ -111,7 +106,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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,
|
||||
@@ -313,33 +307,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
createStreamReturnRecord
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const createObject = createObjectFactory({
|
||||
storeSingleObjectIfNotFoundFactory: storeSingleObjectIfNotFoundFactory({ db })
|
||||
})
|
||||
@@ -360,19 +327,8 @@ describe('Comments @comments', () => {
|
||||
|
||||
let notificationsState: NotificationsStateManager
|
||||
|
||||
const user = {
|
||||
name: 'The comment wizard',
|
||||
email: 'comment@wizard.ry',
|
||||
password: 'i did not like Rivendel wine :(',
|
||||
id: ''
|
||||
}
|
||||
|
||||
const otherUser = {
|
||||
name: 'Fondalf The Brey',
|
||||
email: 'totalnotfakegandalf87@mordor.com',
|
||||
password: 'what gandalf puts in his pipe stays in his pipe',
|
||||
id: ''
|
||||
}
|
||||
let user: BasicTestUser
|
||||
let otherUser: BasicTestUser
|
||||
|
||||
const stream = {
|
||||
name: 'Commented stream',
|
||||
@@ -400,8 +356,18 @@ describe('Comments @comments', () => {
|
||||
const { app: express } = await beforeEachContext()
|
||||
app = express
|
||||
|
||||
user.id = await createUser(user)
|
||||
otherUser.id = await createUser(otherUser)
|
||||
user = await createTestUser({
|
||||
name: 'The comment wizard',
|
||||
email: 'comment@wizard.ry',
|
||||
password: 'i did not like Rivendel wine :(',
|
||||
id: ''
|
||||
})
|
||||
otherUser = await createTestUser({
|
||||
name: 'Fondalf The Brey',
|
||||
email: 'totalnotfakegandalf87@mordor.com',
|
||||
password: 'what gandalf puts in his pipe stays in his pipe',
|
||||
id: ''
|
||||
})
|
||||
|
||||
stream.id = await createStream({ ...stream, ownerId: user.id })
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ import { dbLogger } from '@/observability/logging'
|
||||
import { getAdminUsersListCollectionFactory } from '@/modules/core/services/users/legacyAdminUsersList'
|
||||
import type { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import {
|
||||
getMailchimpStatus,
|
||||
getMailchimpOnboardingIds
|
||||
@@ -48,36 +47,19 @@ import {
|
||||
import { updateMailchimpMemberTags } from '@/modules/auth/services/mailchimp'
|
||||
import { withOperationLogging } from '@/observability/domain/businessLogging'
|
||||
import { metaHelpers } from '@/modules/core/helpers/meta'
|
||||
import { asOperation } from '@/modules/shared/command'
|
||||
import { asMultiregionalOperation, asOperation } from '@/modules/shared/command'
|
||||
import { setUserOnboardingChoicesFactory } from '@/modules/core/services/users/tracking'
|
||||
import { getMixpanelClient } from '@/modules/shared/utils/mixpanel'
|
||||
import { throwIfAuthNotOk } from '@/modules/shared/helpers/errorHelper'
|
||||
import { getUserWorkspaceSeatsFactory } from '@/modules/workspacesCore/repositories/workspaces'
|
||||
import { queryAllProjectsFactory } from '@/modules/core/services/projects'
|
||||
import { getAllRegisteredDbs } from '@/modules/multiregion/utils/dbSelector'
|
||||
|
||||
const getUser = legacyGetUserFactory({ db })
|
||||
const getUserByEmail = legacyGetUserByEmailFactory({ db })
|
||||
|
||||
const updateUserAndNotify = updateUserAndNotifyFactory({
|
||||
getUser: getUserFactory({ db }),
|
||||
updateUser: updateUserFactory({ db }),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const deleteUser = deleteUserFactory({
|
||||
deleteStream: deleteStreamFactory({ db }),
|
||||
logger: dbLogger,
|
||||
isLastAdminUser: isLastAdminUserFactory({ db }),
|
||||
getUserDeletableStreams: getUserDeletableStreamsFactory({ db }),
|
||||
queryAllProjects: queryAllProjectsFactory({
|
||||
getExplicitProjects: getExplicitProjects({ db })
|
||||
}),
|
||||
getUserWorkspaceSeats: getUserWorkspaceSeatsFactory({ db }),
|
||||
deleteAllUserInvites: deleteAllUserInvitesFactory({ db }),
|
||||
deleteUserRecord: deleteUserRecordFactory({ db }),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const getUserRole = getUserRoleFactory({ db })
|
||||
const changeUserRole = changeUserRoleFactory({
|
||||
getServerInfo,
|
||||
@@ -261,14 +243,31 @@ export default {
|
||||
const logger = context.log.child({
|
||||
userIdToOperateOn: context.userId
|
||||
})
|
||||
await withOperationLogging(
|
||||
async () => await updateUserAndNotify(context.userId!, args.user),
|
||||
|
||||
await asMultiregionalOperation(
|
||||
async ({ mainDb, allDbs, emit }) => {
|
||||
const updateUserAndNotify = updateUserAndNotifyFactory({
|
||||
getUser: getUserFactory({ db: mainDb }),
|
||||
updateUser: async (...params) => {
|
||||
const [res] = await Promise.all(
|
||||
allDbs.map((db) => updateUserFactory({ db })(...params))
|
||||
)
|
||||
|
||||
return res
|
||||
},
|
||||
emitEvent: emit
|
||||
})
|
||||
|
||||
return await updateUserAndNotify(context.userId!, args.user)
|
||||
},
|
||||
{
|
||||
dbs: await getAllRegisteredDbs(),
|
||||
logger,
|
||||
operationName: 'updateUser',
|
||||
operationDescription: `Update user`
|
||||
name: 'updateUser',
|
||||
description: `Update user`
|
||||
}
|
||||
)
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
@@ -299,14 +298,39 @@ export default {
|
||||
const logger = context.log.child({
|
||||
userIdToOperateOn: user.id
|
||||
})
|
||||
await withOperationLogging(
|
||||
async () => await deleteUser(user.id, context.userId),
|
||||
|
||||
await asMultiregionalOperation(
|
||||
({ mainDb, allDbs, emit }) => {
|
||||
const deleteUser = deleteUserFactory({
|
||||
deleteStream: deleteStreamFactory({ db: mainDb }),
|
||||
logger: dbLogger,
|
||||
isLastAdminUser: isLastAdminUserFactory({ db: mainDb }),
|
||||
getUserDeletableStreams: getUserDeletableStreamsFactory({ db: mainDb }),
|
||||
queryAllProjects: queryAllProjectsFactory({
|
||||
getExplicitProjects: getExplicitProjects({ db: mainDb })
|
||||
}),
|
||||
getUserWorkspaceSeats: getUserWorkspaceSeatsFactory({ db: mainDb }),
|
||||
deleteAllUserInvites: deleteAllUserInvitesFactory({ db: mainDb }),
|
||||
deleteUserRecord: async (params) => {
|
||||
const [res] = await Promise.all(
|
||||
allDbs.map((db) => deleteUserRecordFactory({ db })(params))
|
||||
)
|
||||
|
||||
return res
|
||||
},
|
||||
emitEvent: emit
|
||||
})
|
||||
|
||||
return deleteUser(user.id, context.userId)
|
||||
},
|
||||
{
|
||||
logger,
|
||||
operationName: 'adminDeleteUser',
|
||||
operationDescription: `Admin deletion of an user`
|
||||
name: 'adminDeleteUser',
|
||||
description: 'Admin deletion of an user',
|
||||
dbs: await getAllRegisteredDbs()
|
||||
}
|
||||
)
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
@@ -325,19 +349,40 @@ export default {
|
||||
// Since I am paranoid, I'll leave them here too.
|
||||
await throwForNotHavingServerRole(context, Roles.Server.Guest)
|
||||
await validateScopes(context.scopes, Scopes.Profile.Delete)
|
||||
await asMultiregionalOperation(
|
||||
({ mainDb, allDbs, emit }) => {
|
||||
const deleteUser = deleteUserFactory({
|
||||
deleteStream: deleteStreamFactory({ db: mainDb }),
|
||||
logger: dbLogger,
|
||||
isLastAdminUser: isLastAdminUserFactory({ db: mainDb }),
|
||||
getUserDeletableStreams: getUserDeletableStreamsFactory({ db: mainDb }),
|
||||
queryAllProjects: queryAllProjectsFactory({
|
||||
getExplicitProjects: getExplicitProjects({ db: mainDb })
|
||||
}),
|
||||
getUserWorkspaceSeats: getUserWorkspaceSeatsFactory({ db: mainDb }),
|
||||
deleteAllUserInvites: deleteAllUserInvitesFactory({ db: mainDb }),
|
||||
deleteUserRecord: async (params) => {
|
||||
const [res] = await Promise.all(
|
||||
allDbs.map((db) => deleteUserRecordFactory({ db })(params))
|
||||
)
|
||||
|
||||
await withOperationLogging(
|
||||
async () => await deleteUser(context.userId!, context.userId!),
|
||||
return res
|
||||
},
|
||||
emitEvent: emit
|
||||
})
|
||||
|
||||
return deleteUser(user.id, context.userId)
|
||||
},
|
||||
{
|
||||
logger,
|
||||
operationName: 'deleteUser',
|
||||
operationDescription: `Delete user`
|
||||
name: 'deleteUser',
|
||||
description: 'Delete user',
|
||||
dbs: await getAllRegisteredDbs()
|
||||
}
|
||||
)
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
activeUserMutations: () => ({})
|
||||
},
|
||||
ActiveUserMutations: {
|
||||
@@ -394,14 +439,31 @@ export default {
|
||||
},
|
||||
async update(_parent, args, context) {
|
||||
const logger = context.log
|
||||
const newUser = await withOperationLogging(
|
||||
async () => await updateUserAndNotify(context.userId!, args.user),
|
||||
|
||||
const newUser = await asMultiregionalOperation(
|
||||
async ({ mainDb, allDbs, emit }) => {
|
||||
const updateUserAndNotify = updateUserAndNotifyFactory({
|
||||
getUser: getUserFactory({ db: mainDb }),
|
||||
updateUser: async (...params) => {
|
||||
const [res] = await Promise.all(
|
||||
allDbs.map((db) => updateUserFactory({ db })(...params))
|
||||
)
|
||||
|
||||
return res
|
||||
},
|
||||
emitEvent: emit
|
||||
})
|
||||
|
||||
return await updateUserAndNotify(context.userId!, args.user)
|
||||
},
|
||||
{
|
||||
dbs: await getAllRegisteredDbs(),
|
||||
logger,
|
||||
operationName: 'updateUser',
|
||||
operationDescription: 'Update user'
|
||||
name: 'updateUser',
|
||||
description: `Update user`
|
||||
}
|
||||
)
|
||||
|
||||
return newUser
|
||||
},
|
||||
meta: () => ({})
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { Knex } from 'knex'
|
||||
|
||||
const tableName = 'users'
|
||||
const colUuid = 'suuid'
|
||||
const colCreatedAt = 'createdAt'
|
||||
const colVerified = 'verified'
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.raw(`
|
||||
ALTER TABLE "${tableName}"
|
||||
ALTER COLUMN "${colUuid}" DROP DEFAULT,
|
||||
ALTER COLUMN "${colCreatedAt}" DROP DEFAULT,
|
||||
ALTER COLUMN "${colVerified}" DROP DEFAULT;
|
||||
`)
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable(tableName, (table) => {
|
||||
table.string(colUuid).defaultTo(knex.raw('gen_random_uuid()')).alter()
|
||||
table.timestamp(colCreatedAt).defaultTo(knex.fn.now()).alter()
|
||||
table.boolean(colVerified).defaultTo(false).alter()
|
||||
})
|
||||
}
|
||||
@@ -21,8 +21,6 @@ import { UserValidationError } from '@/modules/core/errors/user'
|
||||
import type { Knex } from 'knex'
|
||||
import type { ServerRoles } from '@speckle/shared'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { updateUserEmailFactory } from '@/modules/core/repositories/userEmails'
|
||||
import { markUserEmailAsVerifiedFactory } from '@/modules/core/services/users/emailVerification'
|
||||
import type { UserWithOptionalRole } from '@/modules/core/domain/users/types'
|
||||
import type {
|
||||
BulkLookupUsers,
|
||||
@@ -228,11 +226,7 @@ export const markUserAsVerifiedFactory =
|
||||
[UserCols.verified]: true
|
||||
})
|
||||
|
||||
const userEmailsUpdate = await markUserEmailAsVerifiedFactory({
|
||||
updateUserEmail: updateUserEmailFactory({ db: deps.db })
|
||||
})({ email: email.toLowerCase().trim() })
|
||||
|
||||
return !!(usersUpdate || userEmailsUpdate)
|
||||
return !!usersUpdate
|
||||
}
|
||||
|
||||
export const markOnboardingCompleteFactory =
|
||||
@@ -285,13 +279,6 @@ export const updateUserFactory =
|
||||
.where(Users.col.id, userId)
|
||||
.update(update, '*')
|
||||
|
||||
if (update.email) {
|
||||
await updateUserEmailFactory(deps)({
|
||||
query: { userId, primary: true },
|
||||
update: { email: update.email }
|
||||
})
|
||||
}
|
||||
|
||||
return newUser as Nullable<UserRecord>
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
||||
import { ProjectEvents } from '@/modules/core/domain/projects/events'
|
||||
import type { QueryAllProjects } from '@/modules/core/domain/projects/operations'
|
||||
import type { StreamWithOptionalRole } from '@/modules/core/repositories/streams'
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
const { FF_NO_PERSONAL_EMAILS_ENABLED } = getFeatureFlags()
|
||||
|
||||
@@ -169,11 +170,12 @@ export const createUserFactory =
|
||||
|
||||
const signUpCtx = user.signUpContext
|
||||
|
||||
let finalUser: typeof user &
|
||||
Omit<NullableKeysToOptional<UserRecord>, 'suuid' | 'createdAt'> = {
|
||||
let finalUser: typeof user & NullableKeysToOptional<UserRecord> = {
|
||||
...user,
|
||||
id: crs({ length: 10 }),
|
||||
verified: user.verified || false
|
||||
verified: user.verified || false,
|
||||
createdAt: new Date(),
|
||||
suuid: v4()
|
||||
}
|
||||
delete finalUser.signUpContext
|
||||
|
||||
@@ -207,7 +209,10 @@ export const createUserFactory =
|
||||
'name',
|
||||
'company',
|
||||
'verified',
|
||||
'avatar'
|
||||
'avatar',
|
||||
'verified',
|
||||
'createdAt',
|
||||
'suuid'
|
||||
]) as typeof finalUser)
|
||||
|
||||
finalUser.email = finalUser.email.toLowerCase()
|
||||
|
||||
@@ -61,13 +61,7 @@ import {
|
||||
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
|
||||
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import {
|
||||
getUsersFactory,
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUsersFactory, getUserFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
@@ -77,7 +71,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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,
|
||||
@@ -97,6 +90,8 @@ import {
|
||||
validateStreamAccessFactory
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const db = knex
|
||||
@@ -217,34 +212,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const getBranchesByStreamId = getPaginatedStreamBranchesFactory({
|
||||
getPaginatedStreamBranchesPage: getPaginatedStreamBranchesPageFactory({ db }),
|
||||
getStreamBranchCount: getStreamBranchCountFactory({ db })
|
||||
@@ -254,13 +221,7 @@ const createObject = createObjectFactory({
|
||||
})
|
||||
|
||||
describe('Branches @core-branches', () => {
|
||||
const user = {
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie4342@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
}
|
||||
|
||||
let user: BasicTestUser
|
||||
const stream = {
|
||||
name: 'Test Stream References',
|
||||
description: 'Whatever goes in here usually...',
|
||||
@@ -278,7 +239,12 @@ describe('Branches @core-branches', () => {
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
|
||||
user.id = await createUser(user)
|
||||
user = await createTestUser({
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie4342@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
})
|
||||
stream.id = await createStream({ ...stream, ownerId: user.id })
|
||||
testObject.id = await createObject({ streamId: stream.id, object: testObject })
|
||||
})
|
||||
|
||||
@@ -62,13 +62,7 @@ import {
|
||||
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
|
||||
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import {
|
||||
getUsersFactory,
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUsersFactory, getUserFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
@@ -78,7 +72,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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,
|
||||
@@ -101,6 +94,8 @@ import {
|
||||
validateStreamAccessFactory
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
@@ -232,33 +227,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
})
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const getCommitsByUserId = legacyGetPaginatedUserCommitsPage({ db })
|
||||
const getCommitsByStreamId = legacyGetPaginatedStreamCommitsPageFactory({ db })
|
||||
const getCommitsTotalCountByBranchName = getBranchCommitsTotalCountByNameFactory({
|
||||
@@ -274,13 +242,7 @@ const createObject = createObjectFactory({
|
||||
})
|
||||
|
||||
describe('Commits @core-commits', () => {
|
||||
const user = {
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie4342@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
}
|
||||
|
||||
let user: BasicTestUser
|
||||
const stream = {
|
||||
name: 'Test Stream References',
|
||||
description: 'Whatever goes in here usually...',
|
||||
@@ -314,7 +276,12 @@ describe('Commits @core-commits', () => {
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
|
||||
user.id = await createUser(user)
|
||||
user = await createTestUser({
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie4342@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
})
|
||||
stream.id = await createStream({ ...stream, ownerId: user.id })
|
||||
|
||||
const testObjectId = await createObject({ streamId: stream.id, object: testObject })
|
||||
|
||||
@@ -37,13 +37,7 @@ import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/se
|
||||
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { createBranchFactory } from '@/modules/core/repositories/branches'
|
||||
import {
|
||||
getUsersFactory,
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUsersFactory, getUserFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
@@ -53,7 +47,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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,
|
||||
@@ -69,6 +62,7 @@ import {
|
||||
validateStreamAccessFactory
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import { createTestUser, type BasicTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
@@ -152,34 +146,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
})
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
/**
|
||||
* Cleaning up relevant tables
|
||||
*/
|
||||
@@ -274,27 +240,25 @@ describe('Favorite streams', () => {
|
||||
isPublic: true,
|
||||
id: ''
|
||||
}
|
||||
const me = {
|
||||
name: 'Itsa Me',
|
||||
email: 'me@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
}
|
||||
const otherGuy = {
|
||||
name: 'Some Other DUde',
|
||||
email: 'otherguy@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
}
|
||||
let me: BasicTestUser
|
||||
let otherGuy: BasicTestUser
|
||||
|
||||
before(async function () {
|
||||
await cleanup()
|
||||
|
||||
// Seeding
|
||||
await Promise.all([
|
||||
createUser(me).then((id) => (me.id = id)),
|
||||
createUser(otherGuy).then((id) => (otherGuy.id = id))
|
||||
])
|
||||
me = await createTestUser({
|
||||
name: 'Itsa Me',
|
||||
email: 'me@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
})
|
||||
|
||||
otherGuy = await createTestUser({
|
||||
name: 'Some Other DUde',
|
||||
email: 'otherguy@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
})
|
||||
|
||||
await Promise.all([
|
||||
createStream({ ...myPubStream, ownerId: me.id }).then(
|
||||
|
||||
@@ -34,13 +34,7 @@ import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/se
|
||||
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { createBranchFactory } from '@/modules/core/repositories/branches'
|
||||
import {
|
||||
getUsersFactory,
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUsersFactory, getUserFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
@@ -50,7 +44,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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,
|
||||
@@ -67,6 +60,8 @@ import {
|
||||
validateStreamAccessFactory
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import type { Request } from 'express'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const buildFinalizeProjectInvite = () =>
|
||||
@@ -149,33 +144,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
})
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const adminOverrideMock = mockAdminOverride()
|
||||
|
||||
describe('Generic AuthN & AuthZ controller tests', () => {
|
||||
@@ -295,23 +263,23 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
||||
isPublic: false,
|
||||
id: ''
|
||||
}
|
||||
const serverOwner = {
|
||||
name: 'Itsa Me',
|
||||
email: 'me@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
}
|
||||
const otherGuy = {
|
||||
name: 'Some Other DUde',
|
||||
email: 'otherguy@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
}
|
||||
let serverOwner: BasicTestUser
|
||||
let otherGuy: BasicTestUser
|
||||
|
||||
before(async function () {
|
||||
// Seeding
|
||||
serverOwner.id = await createUser(serverOwner)
|
||||
otherGuy.id = await createUser(otherGuy)
|
||||
serverOwner = await createTestUser({
|
||||
name: 'Itsa Me',
|
||||
email: 'me@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
})
|
||||
otherGuy = await createTestUser({
|
||||
name: 'Some Other DUde',
|
||||
email: 'otherguy@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
})
|
||||
|
||||
await Promise.all([
|
||||
createStream({ ...myStream, ownerId: serverOwner.id }).then(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,72 +4,17 @@ import {
|
||||
createRandomEmail,
|
||||
createRandomPassword
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
|
||||
import { db } from '@/db/knex'
|
||||
import { testApolloServer } from '@/test/graphqlHelper'
|
||||
import {
|
||||
CreateWorkspaceDocument,
|
||||
CreateWorkspaceProjectDocument
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
legacyGetUserFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { WorkspaceReadOnlyError } from '@/modules/gatekeeper/errors/billing'
|
||||
import gql from 'graphql-tag'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUser = legacyGetUserFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db }),
|
||||
getUser,
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
|
||||
const createUserEmail = validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail: findEmailFactory({ db }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: createUserEmail,
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
|
||||
const { FF_BILLING_INTEGRATION_ENABLED } = getFeatureFlags()
|
||||
|
||||
@@ -87,7 +32,7 @@ describe('Commits graphql @core', () => {
|
||||
;(FF_BILLING_INTEGRATION_ENABLED ? it : it.skip)(
|
||||
'should return error if project is read-only',
|
||||
async () => {
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'emails user',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory,
|
||||
updateUserEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
@@ -11,60 +9,13 @@ import {
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import { markUserEmailAsVerifiedFactory } from '@/modules/core/services/users/emailVerification'
|
||||
import { expect } from 'chai'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
getUserFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} 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
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
|
||||
describe('Verification @user-emails', () => {
|
||||
it('should mark user email as verified', async () => {
|
||||
const email = createRandomEmail()
|
||||
|
||||
await createUser({
|
||||
await createTestUser({
|
||||
name: 'John',
|
||||
email,
|
||||
password: createRandomPassword()
|
||||
|
||||
@@ -2,72 +2,22 @@ import {
|
||||
createRandomEmail,
|
||||
createRandomPassword
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory,
|
||||
updateUserEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { updateUserEmailFactory } from '@/modules/core/repositories/userEmails'
|
||||
import { db } from '@/db/knex'
|
||||
import { expect } from 'chai'
|
||||
import {
|
||||
bulkLookupUsersFactory,
|
||||
countAdminUsersFactory,
|
||||
getUserByEmailFactory,
|
||||
getUserFactory,
|
||||
getUsersFactory,
|
||||
listUsersFactory,
|
||||
lookupUsersFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
lookupUsersFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
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
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import type { BasicTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import { createTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { buildBasicTestUser, createTestUser } from '@/test/authHelper'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUsers = getUsersFactory({ db })
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const getUserByEmail = getUserByEmailFactory({ db })
|
||||
const listUsers = listUsersFactory({ db })
|
||||
const lookupUsers = lookupUsersFactory({ db })
|
||||
@@ -79,7 +29,7 @@ describe('Find users @core', () => {
|
||||
const email = createRandomEmail()
|
||||
const password = createRandomPassword()
|
||||
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'John Doe',
|
||||
password,
|
||||
email
|
||||
@@ -103,7 +53,7 @@ describe('Find users @core', () => {
|
||||
const email = createRandomEmail()
|
||||
const password = createRandomPassword()
|
||||
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'John Doe',
|
||||
password,
|
||||
email
|
||||
@@ -129,7 +79,7 @@ describe('Find users @core', () => {
|
||||
const email = createRandomEmail()
|
||||
const password = createRandomPassword()
|
||||
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'John Doe',
|
||||
password,
|
||||
email
|
||||
@@ -155,11 +105,13 @@ describe('Find users @core', () => {
|
||||
describe('getUserByEmail', () => {
|
||||
it('should ignore email casing', async () => {
|
||||
const email = 'TeST@ExamPLE.oRg'
|
||||
await createUser({
|
||||
name: 'John Doe',
|
||||
password: createRandomPassword(),
|
||||
email
|
||||
})
|
||||
await createTestUser(
|
||||
buildBasicTestUser({
|
||||
name: 'John Doe',
|
||||
password: createRandomPassword(),
|
||||
email
|
||||
})
|
||||
)
|
||||
const user = await getUserByEmail(email)
|
||||
expect(user!.email).to.equal(email.toLowerCase())
|
||||
})
|
||||
@@ -168,41 +120,49 @@ describe('Find users @core', () => {
|
||||
describe('lookupUsers', () => {
|
||||
it('should find matches by name', async () => {
|
||||
const email = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
email,
|
||||
name: 'John Spackle',
|
||||
password: createRandomPassword()
|
||||
})
|
||||
const { id: userId } = await createTestUser(
|
||||
buildBasicTestUser({
|
||||
email,
|
||||
name: 'John Spackle',
|
||||
password: createRandomPassword()
|
||||
})
|
||||
)
|
||||
const { users } = await lookupUsers({ query: 'Spack' })
|
||||
expect(users.some((user) => user.id === userId)).to.equal(true)
|
||||
})
|
||||
it('should not find matches by name if filtered to emails only', async () => {
|
||||
const email = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
email,
|
||||
name: 'John Spackle',
|
||||
password: createRandomPassword()
|
||||
})
|
||||
const { id: userId } = await createTestUser(
|
||||
buildBasicTestUser({
|
||||
email,
|
||||
name: 'John Spackle',
|
||||
password: createRandomPassword()
|
||||
})
|
||||
)
|
||||
const { users } = await lookupUsers({ query: 'Spack', emailOnly: true })
|
||||
expect(users.some((user) => user.id === userId)).to.equal(false)
|
||||
})
|
||||
it('should find matches by email', async () => {
|
||||
const email = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
email,
|
||||
name: 'John Spackle',
|
||||
password: createRandomPassword()
|
||||
})
|
||||
const { id: userId } = await createTestUser(
|
||||
buildBasicTestUser({
|
||||
email,
|
||||
name: 'John Spackle',
|
||||
password: createRandomPassword()
|
||||
})
|
||||
)
|
||||
const { users } = await lookupUsers({ query: email })
|
||||
expect(users.some((user) => user.id === userId)).to.equal(true)
|
||||
})
|
||||
it('should find matches by email, case insensitive', async () => {
|
||||
const email = 'fooBAR@example.org'
|
||||
const userId = await createUser({
|
||||
email,
|
||||
name: 'John Spackle',
|
||||
password: createRandomPassword()
|
||||
})
|
||||
const { id: userId } = await createTestUser(
|
||||
buildBasicTestUser({
|
||||
email,
|
||||
name: 'John Spackle',
|
||||
password: createRandomPassword()
|
||||
})
|
||||
)
|
||||
const { users } = await lookupUsers({ query: 'FoObAr@example.org' })
|
||||
expect(users.some((user) => user.id === userId)).to.equal(true)
|
||||
})
|
||||
|
||||
@@ -4,11 +4,6 @@ import {
|
||||
createRandomEmail,
|
||||
createRandomPassword
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { db } from '@/db/knex'
|
||||
import { testApolloServer } from '@/test/graphqlHelper'
|
||||
import {
|
||||
@@ -16,61 +11,10 @@ import {
|
||||
CreateWorkspaceDocument,
|
||||
CreateWorkspaceProjectDocument
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
legacyGetUserFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { WorkspaceReadOnlyError } from '@/modules/gatekeeper/errors/billing'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { PaidWorkspacePlanStatuses } from '@speckle/shared'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUser = legacyGetUserFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db }),
|
||||
getUser,
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
|
||||
const createUserEmail = validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail: findEmailFactory({ db }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: createUserEmail,
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
|
||||
const { FF_BILLING_INTEGRATION_ENABLED } = getFeatureFlags()
|
||||
|
||||
@@ -83,7 +27,7 @@ describe('Objects graphql @core', () => {
|
||||
;(FF_BILLING_INTEGRATION_ENABLED ? it : it.skip)(
|
||||
'should return error if project is read-only',
|
||||
async () => {
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'emails user',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
|
||||
@@ -3,29 +3,7 @@ import {
|
||||
createRandomEmail,
|
||||
createRandomPassword
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
legacyGetUserFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import { legacyGetUserFactory } from '@/modules/core/repositories/users'
|
||||
import { createTestWorkspace } from '@/modules/workspaces/tests/helpers/creation'
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
import type { BasicTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
@@ -42,41 +20,9 @@ import {
|
||||
import { PaidWorkspacePlans, Scopes } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUser = legacyGetUserFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db }),
|
||||
getUser,
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
|
||||
const createUserEmail = validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail: findEmailFactory({ db }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: createUserEmail,
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const createPersonalAccessToken = createPersonalAccessTokenFactory({
|
||||
storeApiToken: storeApiTokenFactory({ db }),
|
||||
storeTokenScopes: storeTokenScopesFactory({ db }),
|
||||
@@ -96,7 +42,7 @@ describe('Objects REST @core', () => {
|
||||
;(FF_BILLING_INTEGRATION_ENABLED ? it : it.skip)(
|
||||
'should return an error if the project is read-only',
|
||||
async () => {
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'emails user',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
|
||||
@@ -6,29 +6,7 @@ import {
|
||||
createRandomEmail,
|
||||
createRandomPassword
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
legacyGetUserFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import { legacyGetUserFactory } from '@/modules/core/repositories/users'
|
||||
import { beforeEachContext, initializeTestServer } from '@/test/hooks'
|
||||
import type { BasicTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import { createTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
@@ -40,7 +18,6 @@ import {
|
||||
storeTokenScopesFactory
|
||||
} from '@/modules/core/repositories/tokens'
|
||||
import { Scopes } from '@speckle/shared'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { generateManyObjects } from '@/test/helpers'
|
||||
import type { RawSpeckleObject } from '@/modules/core/domain/objects/types'
|
||||
import { storeObjectsIfNotFoundFactory } from '@/modules/core/repositories/objects'
|
||||
@@ -49,41 +26,11 @@ import type { Parser } from 'csv-parse'
|
||||
import { parse } from 'csv-parse'
|
||||
import { createReadStream } from 'fs'
|
||||
import { createObjectsBatchedAndNoClosuresFactory } from '@/modules/core/services/objects/management'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
|
||||
const IS_NODE_22_OR_ABOVE = process.versions.node.split('.').map(Number)[0] >= 22
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUser = legacyGetUserFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db }),
|
||||
getUser,
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
|
||||
const createUserEmail = validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail: findEmailFactory({ db }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: createUserEmail,
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const createPersonalAccessToken = createPersonalAccessTokenFactory({
|
||||
storeApiToken: storeApiTokenFactory({ db }),
|
||||
@@ -106,7 +53,7 @@ describe('Objects streaming REST @core', () => {
|
||||
;(IS_NODE_22_OR_ABOVE ? it : it.skip)(
|
||||
'should close database connections if client connection is prematurely closed',
|
||||
async () => {
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'emails user',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
@@ -160,7 +107,7 @@ describe('Objects streaming REST @core', () => {
|
||||
;(IS_NODE_22_OR_ABOVE ? it : it.skip)(
|
||||
'should stream model with some failing feature',
|
||||
async () => {
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'emails user',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
|
||||
@@ -5,66 +5,11 @@ import {
|
||||
createRandomEmail,
|
||||
createRandomPassword
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import { UserEmails } from '@/modules/core/dbSchema'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
getUserFactory,
|
||||
legacyGetUserFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory,
|
||||
updateUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { updateUserFactory } from '@/modules/core/repositories/users'
|
||||
import { expectToThrow } from '@/test/assertionHelper'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
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
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
|
||||
const userEmailsDB = db(UserEmails.name)
|
||||
import { buildBasicTestUser, createTestUser } from '@/test/authHelper'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUser = legacyGetUserFactory({ db })
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const updateUser = updateUserFactory({ db })
|
||||
|
||||
describe('Users @core-users', () => {
|
||||
@@ -85,23 +30,20 @@ describe('Users @core-users', () => {
|
||||
expect(err.message).eq('User update payload empty')
|
||||
})
|
||||
|
||||
it('Should update user email if skipClean is true', async () => {
|
||||
const email = createRandomEmail()
|
||||
const newUser = {
|
||||
name: 'John Doe',
|
||||
email,
|
||||
password: createRandomPassword()
|
||||
}
|
||||
|
||||
const userId = await createUser(newUser)
|
||||
// this will never actually happen
|
||||
it('updates the user email', async () => {
|
||||
const { id: userId } = await createTestUser(
|
||||
buildBasicTestUser({
|
||||
name: 'John Doe',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
})
|
||||
)
|
||||
|
||||
const newEmail = createRandomEmail()
|
||||
await updateUser(userId, { email: newEmail }, { skipClean: true })
|
||||
|
||||
const updated = await getUser(userId)
|
||||
const updatedUserEmail = await userEmailsDB.where({ userId, primary: true }).first()
|
||||
|
||||
const updated = await db('users').where({ id: userId }).first()
|
||||
expect(updated.email.toLowerCase()).eq(newEmail.toLowerCase())
|
||||
expect(updatedUserEmail.email.toLowerCase()).eq(newEmail.toLowerCase())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,15 +28,8 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
legacyGetUserFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { legacyGetUserFactory } from '@/modules/core/repositories/users'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { createTestUser, login } from '@/test/authHelper'
|
||||
import { EmailVerificationFinalizationError } from '@/modules/emails/errors'
|
||||
import { Roles } from '@speckle/shared'
|
||||
@@ -63,17 +56,6 @@ const createUserEmail = validateAndCreateUserEmailFactory({
|
||||
requestNewEmailVerification
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: createUserEmail,
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
describe('User emails graphql @core', () => {
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
@@ -85,7 +67,7 @@ describe('User emails graphql @core', () => {
|
||||
describe('createUserEmail mutation', () => {
|
||||
it('should create new email for user', async () => {
|
||||
const firstEmail = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'emails user',
|
||||
email: firstEmail,
|
||||
password: createRandomPassword()
|
||||
@@ -117,7 +99,7 @@ describe('User emails graphql @core', () => {
|
||||
describe('deleteUserEmail mutation', () => {
|
||||
it('should delete email for user', async () => {
|
||||
const firstEmail = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'emails user',
|
||||
email: firstEmail,
|
||||
password: createRandomPassword()
|
||||
@@ -147,7 +129,7 @@ describe('User emails graphql @core', () => {
|
||||
|
||||
describe('setPrimaryUserEmail mutation', () => {
|
||||
it('should set primary email for user', async () => {
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'emails user',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
import { expect } from 'chai'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
getUserByEmailFactory,
|
||||
getUserFactory,
|
||||
legacyGetPaginatedUsersCountFactory,
|
||||
legacyGetPaginatedUsersFactory,
|
||||
legacyGetUserByEmailFactory,
|
||||
listUsersFactory,
|
||||
markUserAsVerifiedFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
listUsersFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { db } from '@/db/knex'
|
||||
import {
|
||||
@@ -30,7 +26,7 @@ import {
|
||||
import { expectToThrow } from '@/test/assertionHelper'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUsers } from '@/test/authHelper'
|
||||
import { createTestUser, createTestUsers } from '@/test/authHelper'
|
||||
import { UserEmails, Users } from '@/modules/core/dbSchema'
|
||||
import { UserEmailPrimaryUnverifiedError } from '@/modules/core/errors/userEmails'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
@@ -43,9 +39,8 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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 { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { markUserEmailAsVerifiedFactory } from '@/modules/core/services/users/emailVerification'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUsers = legacyGetPaginatedUsersFactory({ db })
|
||||
@@ -72,20 +67,12 @@ const createUserEmail = validateAndCreateUserEmailFactory({
|
||||
requestNewEmailVerification
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: createUserEmail,
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const getUserByEmail = getUserByEmailFactory({ db })
|
||||
const legacyGetUserByEmail = legacyGetUserByEmailFactory({ db })
|
||||
const listUsers = listUsersFactory({ db })
|
||||
const markUserAsVerified = markUserAsVerifiedFactory({ db })
|
||||
const markUserEmailAsVerified = markUserEmailAsVerifiedFactory({
|
||||
updateUserEmail: updateUserEmailFactory({ db })
|
||||
})
|
||||
|
||||
describe('Core @user-emails', () => {
|
||||
before(async () => {
|
||||
@@ -100,13 +87,13 @@ describe('Core @user-emails', () => {
|
||||
describe('markUserEmailAsVerified', () => {
|
||||
it('should mark user email as verified', async () => {
|
||||
const email = createRandomEmail()
|
||||
await createUser({
|
||||
await createTestUser({
|
||||
name: 'John Doe',
|
||||
email,
|
||||
password: createRandomPassword()
|
||||
})
|
||||
|
||||
await markUserAsVerified(email)
|
||||
await markUserEmailAsVerified({ email })
|
||||
|
||||
const userEmail = await findEmailFactory({ db })({ email })
|
||||
expect(userEmail?.verified).to.be.true
|
||||
@@ -116,7 +103,7 @@ describe('Core @user-emails', () => {
|
||||
describe('deleteUserEmail', () => {
|
||||
it('should throw and error when trying to delete last email', async () => {
|
||||
const email = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'John Doe',
|
||||
email,
|
||||
password: createRandomPassword()
|
||||
@@ -133,7 +120,7 @@ describe('Core @user-emails', () => {
|
||||
|
||||
it('should throw and error when trying to delete primary email', async () => {
|
||||
const email = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'John Doe',
|
||||
email,
|
||||
password: createRandomPassword()
|
||||
@@ -157,7 +144,7 @@ describe('Core @user-emails', () => {
|
||||
|
||||
it('should delete email', async () => {
|
||||
const email = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'John Doe',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
@@ -198,7 +185,7 @@ describe('Core @user-emails', () => {
|
||||
it('should throw an error if trying to set non verified email as primary', async () => {
|
||||
const email1 = createRandomEmail()
|
||||
const email2 = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'John Doe',
|
||||
email: email1,
|
||||
password: createRandomPassword()
|
||||
@@ -239,7 +226,7 @@ describe('Core @user-emails', () => {
|
||||
})
|
||||
it('should set primary email', async () => {
|
||||
const email = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'John Doe',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
@@ -284,7 +271,7 @@ describe('Core @user-emails', () => {
|
||||
describe('validateAndCreateUserEmailFactory', () => {
|
||||
it('should throw an error when trying to create a primary email for a user and there is already one for that user', async () => {
|
||||
const email = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'John Doe',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
@@ -304,12 +291,12 @@ describe('Core @user-emails', () => {
|
||||
})
|
||||
it('should throw an error when trying to create an email for a user and the same email is already on the server', async () => {
|
||||
const email = createRandomEmail()
|
||||
const userId1 = await createUser({
|
||||
const { id: userId1 } = await createTestUser({
|
||||
name: 'John Doe 2',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
})
|
||||
const userId2 = await createUser({
|
||||
const { id: userId2 } = await createTestUser({
|
||||
name: 'John Doe',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
@@ -341,7 +328,7 @@ describe('Core @user-emails', () => {
|
||||
describe('updateUserEmail', () => {
|
||||
it('should throw an error when trying to mark an email as primary and there is already one for the user', async () => {
|
||||
const email = createRandomEmail()
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'John Doe',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
@@ -486,7 +473,9 @@ describe('Core @user-emails', () => {
|
||||
})
|
||||
|
||||
it('with markUserAsVerified()', async () => {
|
||||
const res = await markUserAsVerified(randomizeCase(randomCaseGuy.email))
|
||||
const res = await markUserEmailAsVerified({
|
||||
email: randomizeCase(randomCaseGuy.email)
|
||||
})
|
||||
expect(res).to.be.ok
|
||||
|
||||
const user = await getUserByEmail(randomCaseGuy.email)
|
||||
|
||||
@@ -5,11 +5,6 @@ import {
|
||||
createRandomPassword,
|
||||
createRandomString
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { db } from '@/db/knex'
|
||||
import { testApolloServer } from '@/test/graphqlHelper'
|
||||
import {
|
||||
@@ -20,28 +15,10 @@ import {
|
||||
GetProjectWithModelVersionsDocument,
|
||||
GetProjectWithVersionsDocument
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
legacyGetUserFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { WorkspaceReadOnlyError } from '@/modules/gatekeeper/errors/billing'
|
||||
import type { CreateVersionInput } from '@/modules/core/graph/generated/graphql'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { buildBasicTestUser, createTestUser, login } from '@/test/authHelper'
|
||||
import type { BasicTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import { createTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
@@ -62,39 +39,6 @@ import {
|
||||
} from '@/modules/core/tests/helpers/creation'
|
||||
import type { Optional } from '@speckle/shared'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUser = legacyGetUserFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db }),
|
||||
getUser,
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
|
||||
const createUserEmail = validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail: findEmailFactory({ db }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: createUserEmail,
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const { FF_BILLING_INTEGRATION_ENABLED, FF_PERSONAL_PROJECTS_LIMITS_ENABLED } =
|
||||
getFeatureFlags()
|
||||
|
||||
@@ -107,7 +51,7 @@ describe('Versions graphql @core', () => {
|
||||
;(FF_BILLING_INTEGRATION_ENABLED ? it : it.skip)(
|
||||
'should return error if project is read-only',
|
||||
async () => {
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'emails user',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
@@ -156,7 +100,7 @@ describe('Versions graphql @core', () => {
|
||||
createdAt: Date // Make the project read-only
|
||||
) => await db('commits').update({ createdAt }).where({ id })
|
||||
|
||||
const user = buildBasicTestUser()
|
||||
let user: BasicTestUser
|
||||
const workspace = buildBasicTestWorkspace()
|
||||
const model1 = buildBasicTestModel()
|
||||
const model2 = buildBasicTestModel()
|
||||
@@ -169,7 +113,7 @@ describe('Versions graphql @core', () => {
|
||||
let objectId3: Optional<string> = undefined
|
||||
|
||||
before(async () => {
|
||||
user.id = await createUser(user)
|
||||
user = await createTestUser(buildBasicTestUser())
|
||||
await createTestWorkspace(workspace, user, {
|
||||
addPlan: { name: 'free', status: 'valid' }
|
||||
})
|
||||
|
||||
@@ -32,13 +32,7 @@ import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/se
|
||||
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { createBranchFactory } from '@/modules/core/repositories/branches'
|
||||
import {
|
||||
getUsersFactory,
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUsersFactory, getUserFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
@@ -48,7 +42,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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,
|
||||
@@ -79,6 +72,7 @@ import {
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import type { ObjectRecord } from '@/modules/core/helpers/types'
|
||||
import { createTestUser, type BasicTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const sampleCommit = JSON.parse(`{
|
||||
@@ -184,34 +178,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const createObject = createObjectFactory({
|
||||
storeSingleObjectIfNotFoundFactory: storeSingleObjectIfNotFoundFactory({ db })
|
||||
})
|
||||
@@ -228,13 +194,7 @@ const getObjectChildrenQuery = getObjectChildrenQueryFactory({ db })
|
||||
const getObjects = getStreamObjectsFactory({ db })
|
||||
|
||||
describe('Objects @core-objects', () => {
|
||||
const userOne = {
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie43@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
}
|
||||
|
||||
let userOne: BasicTestUser
|
||||
const stream = {
|
||||
name: 'Test Streams',
|
||||
description: 'Whatever goes in here usually...',
|
||||
@@ -244,7 +204,12 @@ describe('Objects @core-objects', () => {
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
|
||||
userOne.id = await createUser(userOne)
|
||||
userOne = await createTestUser({
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie43@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: ''
|
||||
})
|
||||
stream.id = await createStream({ ...stream, isPublic: false, ownerId: userOne.id })
|
||||
})
|
||||
|
||||
|
||||
@@ -34,13 +34,7 @@ import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/se
|
||||
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { createBranchFactory } from '@/modules/core/repositories/branches'
|
||||
import {
|
||||
getUsersFactory,
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUsersFactory, getUserFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
@@ -50,7 +44,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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,
|
||||
@@ -75,6 +68,7 @@ import {
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import type Express from 'express'
|
||||
import { createTestUser, type BasicTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
@@ -158,33 +152,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
})
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const createPersonalAccessToken = createPersonalAccessTokenFactory({
|
||||
storeApiToken: storeApiTokenFactory({ db }),
|
||||
storeTokenScopes: storeTokenScopesFactory({ db }),
|
||||
@@ -195,20 +162,10 @@ const createPersonalAccessToken = createPersonalAccessTokenFactory({
|
||||
})
|
||||
|
||||
describe('Upload/Download Routes @api-rest', () => {
|
||||
const userA = {
|
||||
name: 'd1',
|
||||
email: 'd.1@speckle.systems',
|
||||
password: 'wowwow8charsplease',
|
||||
id: '',
|
||||
token: ''
|
||||
}
|
||||
const userB = {
|
||||
name: 'd2',
|
||||
email: 'd.2@speckle.systems',
|
||||
password: 'wowwow8charsplease',
|
||||
id: '',
|
||||
token: ''
|
||||
}
|
||||
let userA: BasicTestUser
|
||||
let tokenUserA: string
|
||||
let userB: BasicTestUser
|
||||
let tokenUserB: string
|
||||
|
||||
const testStream = {
|
||||
name: 'Test Stream 01',
|
||||
@@ -228,8 +185,13 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
before(async () => {
|
||||
;({ app } = await beforeEachContext())
|
||||
|
||||
userA.id = await createUser(userA)
|
||||
userA.token = `Bearer ${await createPersonalAccessToken(
|
||||
userA = await createTestUser({
|
||||
name: 'd1',
|
||||
email: 'd.1@speckle.systems',
|
||||
password: 'wowwow8charsplease',
|
||||
id: ''
|
||||
})
|
||||
tokenUserA = `Bearer ${await createPersonalAccessToken(
|
||||
userA.id,
|
||||
'test token user A',
|
||||
[
|
||||
@@ -244,8 +206,13 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
]
|
||||
)}`
|
||||
|
||||
userB.id = await createUser(userB)
|
||||
userB.token = `Bearer ${await createPersonalAccessToken(
|
||||
userB = await createTestUser({
|
||||
name: 'd2',
|
||||
email: 'd.2@speckle.systems',
|
||||
password: 'wowwow8charsplease',
|
||||
id: ''
|
||||
})
|
||||
tokenUserB = `Bearer ${await createPersonalAccessToken(
|
||||
userB.id,
|
||||
'test token user B',
|
||||
[
|
||||
@@ -289,7 +256,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
// invalid streamId
|
||||
res = await request(app)
|
||||
.get(`/objects/${'thisDoesNotExist'}/null`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
expect(res).to.have.status(404)
|
||||
|
||||
// create some objects
|
||||
@@ -297,7 +264,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
|
||||
await request(app)
|
||||
.post(`/objects/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.set('Content-type', 'multipart/form-data')
|
||||
.attach('batch1', Buffer.from(JSON.stringify(objBatches[0]), 'utf8'))
|
||||
.attach('batch2', Buffer.from(JSON.stringify(objBatches[1]), 'utf8'))
|
||||
@@ -318,14 +285,14 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
// should not allow user b to access user a's private stream
|
||||
res = await request(app)
|
||||
.get(`/objects/${privateTestStream.id}/${objBatches[0][0].id}`)
|
||||
.set('Authorization', userB.token)
|
||||
.set('Authorization', tokenUserB)
|
||||
expect(res).to.have.status(401)
|
||||
})
|
||||
|
||||
it('should not allow a non-multipart/form-data request without a boundary', async () => {
|
||||
const res = await request(app)
|
||||
.post(`/objects/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.set('Content-type', 'multipart/form-data')
|
||||
.send(Buffer.from(JSON.stringify(objBatches[0]), 'utf8')) //sent, not attached, so no boundary will be added to Content-type header.
|
||||
expect(res).to.have.status(400)
|
||||
@@ -337,7 +304,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
it('should not allow a non-multipart/form-data request, even if it has a valid header', async () => {
|
||||
const res = await request(app)
|
||||
.post(`/objects/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.set('Content-type', 'application/json')
|
||||
.attach(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -354,7 +321,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
it('should not allow non-buffered requests', async () => {
|
||||
const res = await request(app)
|
||||
.post(`/objects/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.set('Content-type', 'multipart/form-data')
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.attach(JSON.stringify(objBatches[0]) as any, undefined as any)
|
||||
@@ -369,20 +336,20 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
|
||||
await request(app)
|
||||
.post(`/objects/${privateTestStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.set('Content-type', 'multipart/form-data')
|
||||
.attach('batch1', Buffer.from(JSON.stringify(objBatch), 'utf8'))
|
||||
|
||||
// should allow userA to access privateTestStream object
|
||||
let res = await request(app)
|
||||
.get(`/objects/${privateTestStream.id}/${objBatch[0].id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
expect(res).to.have.status(200)
|
||||
|
||||
// should not allow userB to access privateTestStream object by pretending it's in public stream
|
||||
res = await request(app)
|
||||
.get(`/objects/${testStream.id}/${objBatch[0].id}`)
|
||||
.set('Authorization', userB.token)
|
||||
.set('Authorization', tokenUserB)
|
||||
expect(res).to.have.status(404)
|
||||
})
|
||||
|
||||
@@ -402,7 +369,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
// invalid streamId
|
||||
res = await request(app)
|
||||
.post(`/objects/${'thisDoesNotExist'}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
expect(res).to.have.status(401)
|
||||
})
|
||||
|
||||
@@ -420,7 +387,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
|
||||
const res = await request(app)
|
||||
.post(`/objects/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.set('Content-type', 'multipart/form-data')
|
||||
.attach('batch1', Buffer.from(JSON.stringify(objectToPost), 'utf8'))
|
||||
|
||||
@@ -430,7 +397,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
it('Should not allow upload with invalid body (invalid json)', async () => {
|
||||
const res = await request(app)
|
||||
.post(`/objects/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.set('Content-type', 'multipart/form-data')
|
||||
.attach('batch1', Buffer.from(JSON.stringify('this is not json'), 'utf8'))
|
||||
|
||||
@@ -445,7 +412,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
|
||||
// const res = await request(app)
|
||||
// .post(`/objects/${testStream.id}`)
|
||||
// .set('Authorization', userA.token)
|
||||
// .set('Authorization', tokenUserA)
|
||||
// .set('Content-type', 'multipart/form-data')
|
||||
// .attach('batch1', Buffer.from(JSON.stringify([objectToPost]), 'utf8'))
|
||||
|
||||
@@ -466,7 +433,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
|
||||
const res = await request(app)
|
||||
.post(`/objects/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.set('Content-type', 'multipart/form-data')
|
||||
.attach('batch1', Buffer.from(JSON.stringify(objBatches[0]), 'utf8'))
|
||||
.attach('batch2', Buffer.from(JSON.stringify(objBatches[1]), 'utf8'))
|
||||
@@ -486,7 +453,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
void request(app)
|
||||
.get(`/objects/${testStream.id}/${parentId}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.buffer()
|
||||
.parse((res, cb) => {
|
||||
const resTyped = res as typeof res & { data: string }
|
||||
@@ -518,7 +485,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
void request(app)
|
||||
.get(`/objects/${testStream.id}/${parentId}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.set('Accept', 'text/plain')
|
||||
.buffer()
|
||||
.parse((res, cb) => {
|
||||
@@ -557,7 +524,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
void request(app)
|
||||
.post(`/api/getobjects/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.set('Accept', 'text/plain')
|
||||
.send({ objects: JSON.stringify(objectIds) })
|
||||
.buffer()
|
||||
@@ -594,7 +561,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
|
||||
const res = await request(app)
|
||||
.post(`/api/getobjects/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.set('Accept', 'text/plain')
|
||||
.send({ objects: JSON.stringify(objectIds) })
|
||||
.buffer()
|
||||
@@ -605,7 +572,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
it('Should return status code 400 when getting the list of objects and if it is not parseable', async () => {
|
||||
const response = await request(app)
|
||||
.post(`/api/getobjects/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.send({ objects: ['lolz', 'thisIsBroken', 'shouldHaveBeenJSONStringified'] })
|
||||
|
||||
expect(response).to.have.status(400)
|
||||
@@ -629,7 +596,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
void request(app)
|
||||
.post(`/api/diff/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.send({ objects: JSON.stringify(objectIds) })
|
||||
.buffer()
|
||||
.parse((res, cb) => {
|
||||
@@ -674,7 +641,7 @@ describe('Upload/Download Routes @api-rest', () => {
|
||||
it('Should return status code 400 if the list of objects is not parseable', async () => {
|
||||
const response = await request(app)
|
||||
.post(`/api/diff/${testStream.id}`)
|
||||
.set('Authorization', userA.token)
|
||||
.set('Authorization', tokenUserA)
|
||||
.send({ objects: ['lolz', 'thisIsBroken', 'shouldHaveBeenJSONStringified'] })
|
||||
|
||||
expect(response).to.have.status(400)
|
||||
|
||||
@@ -132,6 +132,14 @@ import {
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import { getUserWorkspaceSeatsFactory } from '@/modules/workspacesCore/repositories/workspaces'
|
||||
import { queryAllProjectsFactory } from '@/modules/core/services/projects'
|
||||
import { getTestRegionClients } from '@/modules/multiregion/tests/helpers'
|
||||
import { asMultiregionalOperation } from '@/modules/shared/command'
|
||||
import type {
|
||||
ChangeUserPassword,
|
||||
CreateValidatedUser,
|
||||
DeleteUser,
|
||||
UpdateUserAndNotify
|
||||
} from '@/modules/core/domain/users/operations'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
@@ -237,63 +245,144 @@ const createStream = legacyCreateStreamFactory({
|
||||
})
|
||||
const grantPermissionsStream = grantStreamPermissionsFactory({ db })
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const createUser: CreateValidatedUser = async (...input) =>
|
||||
asMultiregionalOperation(
|
||||
async ({ mainDb, allDbs, emit }) => {
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
storeUser: async (...params) => {
|
||||
const [user] = await Promise.all(
|
||||
allDbs.map((db) => storeUserFactory({ db })(...params))
|
||||
)
|
||||
|
||||
return user
|
||||
},
|
||||
countAdminUsers: countAdminUsersFactory({ db: mainDb }),
|
||||
storeUserAcl: storeUserAclFactory({ db: mainDb }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db: mainDb }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({
|
||||
db: mainDb
|
||||
}),
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db: mainDb }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db: mainDb })
|
||||
}),
|
||||
requestNewEmailVerification: requestNewEmailVerificationFactory({
|
||||
getServerInfo,
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
getUser: getUserFactory({ db: mainDb }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory(
|
||||
{
|
||||
db: mainDb
|
||||
}
|
||||
),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
}),
|
||||
emitEvent: emit
|
||||
})
|
||||
|
||||
return createUser(...input)
|
||||
},
|
||||
{
|
||||
dbs: await getTestRegionClients(),
|
||||
name: 'create user spec',
|
||||
logger: dbLogger
|
||||
}
|
||||
)
|
||||
|
||||
const findOrCreateUser = findOrCreateUserFactory({
|
||||
createUser,
|
||||
findPrimaryEmailForUser: findPrimaryEmailForUserFactory({ db })
|
||||
})
|
||||
const getUserByEmail = legacyGetUserByEmailFactory({ db })
|
||||
const updateUser = updateUserAndNotifyFactory({
|
||||
getUser: getUserFactory({ db }),
|
||||
updateUser: updateUserFactory({ db }),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const updateUserPassword = changePasswordFactory({
|
||||
getUser: getUserFactory({ db }),
|
||||
updateUser: updateUserFactory({ db })
|
||||
})
|
||||
const updateUser: UpdateUserAndNotify = async (...input) =>
|
||||
asMultiregionalOperation(
|
||||
({ mainDb, allDbs, emit }) => {
|
||||
const updateUserAndNotify = updateUserAndNotifyFactory({
|
||||
getUser: getUserFactory({ db: mainDb }),
|
||||
updateUser: async (...params) => {
|
||||
const [res] = await Promise.all(
|
||||
allDbs.map((db) => updateUserFactory({ db })(...params))
|
||||
)
|
||||
|
||||
return res
|
||||
},
|
||||
emitEvent: emit
|
||||
})
|
||||
|
||||
return updateUserAndNotify(...input)
|
||||
},
|
||||
{
|
||||
logger: dbLogger,
|
||||
name: 'update user and notify spec',
|
||||
dbs: await getTestRegionClients()
|
||||
}
|
||||
)
|
||||
|
||||
const updateUserPassword: ChangeUserPassword = async (...input) =>
|
||||
asMultiregionalOperation(
|
||||
({ mainDb, allDbs }) => {
|
||||
const updateUserPassword = changePasswordFactory({
|
||||
getUser: getUserFactory({ db: mainDb }),
|
||||
updateUser: async (...params) => {
|
||||
const [res] = await Promise.all(
|
||||
allDbs.map((db) => updateUserFactory({ db })(...params))
|
||||
)
|
||||
|
||||
return res
|
||||
}
|
||||
})
|
||||
|
||||
return updateUserPassword(...input)
|
||||
},
|
||||
{
|
||||
logger: dbLogger,
|
||||
name: 'update user password spec',
|
||||
dbs: await getTestRegionClients()
|
||||
}
|
||||
)
|
||||
|
||||
const validateUserPassword = validateUserPasswordFactory({
|
||||
getUserByEmail: getUserByEmailFactory({ db })
|
||||
})
|
||||
const deleteUser = deleteUserFactory({
|
||||
deleteStream: deleteStreamFactory({ db }),
|
||||
logger: dbLogger,
|
||||
isLastAdminUser: isLastAdminUserFactory({ db }),
|
||||
getUserDeletableStreams: getUserDeletableStreamsFactory({ db }),
|
||||
queryAllProjects: queryAllProjectsFactory({
|
||||
getExplicitProjects: getExplicitProjects({ db })
|
||||
}),
|
||||
getUserWorkspaceSeats: getUserWorkspaceSeatsFactory({ db }),
|
||||
deleteAllUserInvites: deleteAllUserInvitesFactory({ db }),
|
||||
deleteUserRecord: deleteUserRecordFactory({ db }),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const deleteUser: DeleteUser = async (...input) =>
|
||||
asMultiregionalOperation(
|
||||
({ mainDb, allDbs, emit }) => {
|
||||
const deleteUser = deleteUserFactory({
|
||||
deleteStream: deleteStreamFactory({ db: mainDb }),
|
||||
logger: dbLogger,
|
||||
isLastAdminUser: isLastAdminUserFactory({ db: mainDb }),
|
||||
getUserDeletableStreams: getUserDeletableStreamsFactory({ db: mainDb }),
|
||||
queryAllProjects: queryAllProjectsFactory({
|
||||
getExplicitProjects: getExplicitProjects({ db: mainDb })
|
||||
}),
|
||||
getUserWorkspaceSeats: getUserWorkspaceSeatsFactory({ db: mainDb }),
|
||||
deleteAllUserInvites: deleteAllUserInvitesFactory({ db: mainDb }),
|
||||
deleteUserRecord: async (params) => {
|
||||
const [res] = await Promise.all(
|
||||
allDbs.map((db) => deleteUserRecordFactory({ db })(params))
|
||||
)
|
||||
|
||||
return res
|
||||
},
|
||||
emitEvent: emit
|
||||
})
|
||||
|
||||
return deleteUser(...input)
|
||||
},
|
||||
{
|
||||
logger: dbLogger,
|
||||
name: 'delete user spec',
|
||||
dbs: await getTestRegionClients()
|
||||
}
|
||||
)
|
||||
|
||||
const changeUserRole = changeUserRoleFactory({
|
||||
getServerInfo,
|
||||
isLastAdminUser: isLastAdminUserFactory({ db }),
|
||||
@@ -334,7 +423,7 @@ const createObject = createObjectFactory({
|
||||
storeSingleObjectIfNotFoundFactory: storeSingleObjectIfNotFoundFactory({ db })
|
||||
})
|
||||
|
||||
describe('Actors & Tokens @user-services', () => {
|
||||
describe('Actors & Tokens @user-services @multiregion', () => {
|
||||
const myTestActor = {
|
||||
name: 'Dimitrie Stefanescu',
|
||||
email: 'didimitrie@example.org',
|
||||
@@ -394,7 +483,7 @@ describe('Actors & Tokens @user-services', () => {
|
||||
})
|
||||
|
||||
// Note: deletion is more complicated.
|
||||
it('Should delete a user', async () => {
|
||||
it('Should delete a user @multiregion', async () => {
|
||||
const soloOwnerStream = {
|
||||
name: 'Test Stream 01',
|
||||
description: 'wonderful test stream',
|
||||
|
||||
@@ -47,6 +47,8 @@ import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { expect } from 'chai'
|
||||
import { getUserWorkspaceSeatsFactory } from '@/modules/workspacesCore/repositories/workspaces'
|
||||
import { queryAllProjectsFactory } from '@/modules/core/services/projects'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
|
||||
const getUsers = legacyGetPaginatedUsersFactory({ db })
|
||||
const countUsers = legacyGetPaginatedUsersCountFactory({ db })
|
||||
@@ -61,6 +63,7 @@ const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
// this does not uses createTestUser as 250 parallel transactions for user creation can timeout some of them
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
@@ -112,8 +115,8 @@ describe('User admin @user-services', () => {
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
|
||||
const actorId = await createUser(myTestActor)
|
||||
myTestActor.id = actorId
|
||||
const actor = await createTestUser(myTestActor)
|
||||
myTestActor.id = actor.id
|
||||
})
|
||||
|
||||
it('First created user should be admin', async () => {
|
||||
@@ -133,16 +136,16 @@ describe('User admin @user-services', () => {
|
||||
newUser.email = 'bill@gates.com'
|
||||
newUser.password = 'testthebest'
|
||||
|
||||
const actorId = await createUser(newUser)
|
||||
const actor = await createTestUser(newUser)
|
||||
|
||||
expect(await countUsers()).to.equal(2)
|
||||
|
||||
await deleteUser(actorId)
|
||||
await deleteUser(actor.id)
|
||||
expect(await countUsers()).to.equal(1)
|
||||
})
|
||||
|
||||
it('Get users query limit is sanitized to upper limit', async () => {
|
||||
const userInputs = Array(250)
|
||||
const userInputs: BasicTestUser[] = Array(250)
|
||||
.fill(undefined)
|
||||
.map((v, i) => createNewDroid(i))
|
||||
|
||||
@@ -191,9 +194,10 @@ describe('User admin @user-services', () => {
|
||||
}
|
||||
})
|
||||
it('modifies role', async () => {
|
||||
const userId = await createUser(
|
||||
const user = await createTestUser(
|
||||
createNewDroid(cryptoRandomString({ length: 13 }))
|
||||
)
|
||||
const userId = user.id
|
||||
|
||||
const oldRole = await getUserRole(userId)
|
||||
expect(oldRole).to.equal(Roles.Server.User)
|
||||
@@ -228,6 +232,7 @@ describe('User admin @user-services', () => {
|
||||
|
||||
const createNewDroid = (number: string | number) => {
|
||||
return {
|
||||
id: `${number}`,
|
||||
name: `${number}`,
|
||||
email: `${number}@droidarmy.com`,
|
||||
password: 'sn3aky-1337-b1m'
|
||||
|
||||
@@ -35,13 +35,7 @@ import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/se
|
||||
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { createBranchFactory } from '@/modules/core/repositories/branches'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
getUserFactory,
|
||||
getUsersFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUserFactory, getUsersFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
@@ -51,7 +45,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import {
|
||||
finalizeInvitedServerRegistrationFactory,
|
||||
@@ -67,6 +60,8 @@ import {
|
||||
validateStreamAccessFactory
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
// To ensure that the invites are created in the correct order, we need to wait a bit between each creation
|
||||
@@ -154,34 +149,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
})
|
||||
const createInviteDirectly = createStreamInviteDirectly
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
function randomEl<T>(array: T[]): T {
|
||||
return array[Math.floor(Math.random() * array.length)]
|
||||
}
|
||||
@@ -204,13 +171,7 @@ async function getOrderedUserIds() {
|
||||
}
|
||||
|
||||
describe('[Admin users list]', () => {
|
||||
const me = {
|
||||
name: 'Mr Server Admin Dude',
|
||||
email: 'adminuserguy@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: undefined as Optional<string>,
|
||||
verified: false
|
||||
}
|
||||
let me: BasicTestUser
|
||||
|
||||
const USER_COUNT = 15
|
||||
const SERVER_INVITE_COUNT = 5
|
||||
@@ -251,7 +212,13 @@ describe('[Admin users list]', () => {
|
||||
|
||||
await cleanup()
|
||||
|
||||
await createUser(me).then((id) => (me.id = id))
|
||||
me = await createTestUser({
|
||||
name: 'Mr Server Admin Dude',
|
||||
email: 'adminuserguy@example.org',
|
||||
password: 'sn3aky-1337-b1m',
|
||||
id: undefined as Optional<string>,
|
||||
verified: false
|
||||
})
|
||||
|
||||
const userIds: string[] = []
|
||||
let remainingSearchQueryUserCount = SEARCH_QUERY_RESULT_COUNT
|
||||
@@ -260,7 +227,7 @@ describe('[Admin users list]', () => {
|
||||
// Create Users
|
||||
// count - 1, cause `me` also exists
|
||||
for (let i = 0; i < USER_COUNT - 1; i++) {
|
||||
const id = await createUser({
|
||||
const { id } = await createTestUser({
|
||||
name: `User #${i} - ${
|
||||
remainingSearchQueryUserCount-- >= 1 ? SEARCH_QUERY : ''
|
||||
}`,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Users } from '@/modules/core/dbSchema'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUsers } from '@/test/authHelper'
|
||||
import { createTestUser, createTestUsers } from '@/test/authHelper'
|
||||
import { getActiveUser, getOtherUser } from '@/test/graphql/users'
|
||||
import { beforeEachContext, truncateTables } from '@/test/hooks'
|
||||
import { expect } from 'chai'
|
||||
@@ -32,15 +32,8 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
getUserFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { getUserFactory } from '@/modules/core/repositories/users'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUser = getUserFactory({ db })
|
||||
@@ -64,17 +57,6 @@ const createUserEmail = validateAndCreateUserEmailFactory({
|
||||
requestNewEmailVerification
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: createUserEmail,
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
describe('Users (GraphQL)', () => {
|
||||
const me: BasicTestUser = {
|
||||
id: '',
|
||||
@@ -162,7 +144,7 @@ describe('Users (GraphQL)', () => {
|
||||
})
|
||||
|
||||
it('should return emails for user', async () => {
|
||||
const userId = await createUser({
|
||||
const { id: userId } = await createTestUser({
|
||||
name: 'emails user',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword(),
|
||||
|
||||
@@ -7,26 +7,39 @@ import {
|
||||
deleteVerificationsFactory,
|
||||
getPendingTokenFactory
|
||||
} from '@/modules/emails/repositories'
|
||||
import { db } from '@/db/knex'
|
||||
import { markUserAsVerifiedFactory } from '@/modules/core/repositories/users'
|
||||
import { withOperationLogging } from '@/observability/domain/businessLogging'
|
||||
import { markUserEmailAsVerifiedFactory } from '@/modules/core/services/users/emailVerification'
|
||||
import { updateUserEmailFactory } from '@/modules/core/repositories/userEmails'
|
||||
import { getAllRegisteredDbs } from '@/modules/multiregion/utils/dbSelector'
|
||||
import { asMultiregionalOperation } from '@/modules/shared/command'
|
||||
|
||||
export default (app: Express) => {
|
||||
app.get('/auth/verifyemail', async (req, res) => {
|
||||
const logger = req.log
|
||||
try {
|
||||
const finalizeEmailVerification = finalizeEmailVerificationFactory({
|
||||
getPendingToken: getPendingTokenFactory({ db }),
|
||||
markUserAsVerified: markUserAsVerifiedFactory({ db }),
|
||||
deleteVerifications: deleteVerificationsFactory({ db })
|
||||
})
|
||||
await asMultiregionalOperation(
|
||||
async ({ mainDb, allDbs }) => {
|
||||
const finalizeEmailVerification = finalizeEmailVerificationFactory({
|
||||
getPendingToken: getPendingTokenFactory({ db: mainDb }),
|
||||
markUserAsVerified: async (params) => {
|
||||
const [res] = await Promise.all(
|
||||
allDbs.map((db) => markUserAsVerifiedFactory({ db })(params))
|
||||
)
|
||||
return res
|
||||
},
|
||||
deleteVerifications: deleteVerificationsFactory({ db: mainDb }),
|
||||
markUserEmailAsVerified: markUserEmailAsVerifiedFactory({
|
||||
updateUserEmail: updateUserEmailFactory({ db: mainDb })
|
||||
})
|
||||
})
|
||||
|
||||
await withOperationLogging(
|
||||
async () => await finalizeEmailVerification(req.query.t as Optional<string>),
|
||||
return await finalizeEmailVerification(req.query.t as Optional<string>)
|
||||
},
|
||||
{
|
||||
logger,
|
||||
operationName: 'finalizeEmailVerification',
|
||||
operationDescription: 'Finalize email verification'
|
||||
dbs: await getAllRegisteredDbs(),
|
||||
name: 'finalizeEmailVerification',
|
||||
description: 'Finalize email verification'
|
||||
}
|
||||
)
|
||||
return res.redirect(
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
GetPendingToken
|
||||
} from '@/modules/emails/domain/operations'
|
||||
import type { MarkUserAsVerified } from '@/modules/core/domain/users/operations'
|
||||
import type { MarkUserEmailAsVerified } from '@/modules/core/domain/userEmails/operations'
|
||||
|
||||
type InitializeStateDeps = {
|
||||
getPendingToken: GetPendingToken
|
||||
@@ -28,6 +29,7 @@ type FinalizationState = Awaited<ReturnType<ReturnType<typeof initializeState>>>
|
||||
|
||||
type FinalizeVerificationDeps = {
|
||||
markUserAsVerified: MarkUserAsVerified
|
||||
markUserEmailAsVerified: MarkUserEmailAsVerified
|
||||
deleteVerifications: DeleteVerifications
|
||||
}
|
||||
|
||||
@@ -36,7 +38,11 @@ const finalizeVerification =
|
||||
const { token } = state
|
||||
const { email } = token
|
||||
|
||||
await Promise.all([deps.markUserAsVerified(email), deps.deleteVerifications(email)])
|
||||
await Promise.all([
|
||||
deps.markUserEmailAsVerified({ email: email.toLowerCase().trim() }),
|
||||
deps.markUserAsVerified(email),
|
||||
deps.deleteVerifications(email)
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,13 +18,7 @@ import {
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
getUserFactory,
|
||||
getUsersFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUserFactory, getUsersFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
addOrUpdateStreamCollaboratorFactory,
|
||||
validateStreamAccessFactory
|
||||
@@ -35,7 +29,6 @@ import {
|
||||
} from '@/modules/core/services/streams/management'
|
||||
import { createTokenFactory } from '@/modules/core/services/tokens'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
@@ -81,25 +74,6 @@ export const initUploadTestEnvironment = () => {
|
||||
sendEmail
|
||||
})
|
||||
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const createToken = createTokenFactory({
|
||||
storeApiToken: storeApiTokenFactory({ db }),
|
||||
storeTokenScopes: storeTokenScopesFactory({ db }),
|
||||
@@ -197,7 +171,6 @@ export const initUploadTestEnvironment = () => {
|
||||
return {
|
||||
findEmail,
|
||||
requestNewEmailVerification,
|
||||
createUser,
|
||||
createToken,
|
||||
createStream,
|
||||
getUser,
|
||||
|
||||
@@ -9,8 +9,9 @@ import { noErrors } from '@/test/helpers'
|
||||
import { TIME_MS } from '@speckle/shared'
|
||||
import { initUploadTestEnvironment } from '@/modules/fileuploads/tests/helpers/init'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
|
||||
const { createStream, createUser, createToken } = initUploadTestEnvironment()
|
||||
const { createStream, createToken } = initUploadTestEnvironment()
|
||||
const gqlQueryToListFileUploads = `query ($streamId: String!) {
|
||||
stream(id: $streamId) {
|
||||
id
|
||||
@@ -54,7 +55,8 @@ describe('FileUploads @fileuploads integration', () => {
|
||||
process.env['CANONICAL_URL'] = serverAddress
|
||||
process.env['PORT'] = serverPort
|
||||
|
||||
userOneId = await createUser(userOne)
|
||||
const user = await createTestUser(userOne)
|
||||
userOneId = user.id
|
||||
})
|
||||
beforeEach(async () => {
|
||||
createdStreamId = await createStream({ ownerId: userOneId })
|
||||
@@ -267,7 +269,7 @@ describe('FileUploads @fileuploads integration', () => {
|
||||
email: 'user2@example.org',
|
||||
password: 'jdsadjsadasfdsa'
|
||||
}
|
||||
const userTwoId = await createUser(userTwo)
|
||||
const { id: userTwoId } = await createTestUser(userTwo)
|
||||
const streamTwoId = await createStream({ ownerId: userTwoId })
|
||||
|
||||
const response = await request(app)
|
||||
|
||||
@@ -15,8 +15,9 @@ import cryptoRandomString from 'crypto-random-string'
|
||||
import type { Server } from 'http'
|
||||
import { initUploadTestEnvironment } from '@/modules/fileuploads/tests/helpers/init'
|
||||
import { createFileUploadJob } from '@/modules/fileuploads/tests/helpers/creation'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
|
||||
const { createUser, createStream, createToken } = initUploadTestEnvironment()
|
||||
const { createStream, createToken } = initUploadTestEnvironment()
|
||||
|
||||
const { FF_NEXT_GEN_FILE_IMPORTER_ENABLED } = getFeatureFlags()
|
||||
|
||||
@@ -52,7 +53,8 @@ const { FF_NEXT_GEN_FILE_IMPORTER_ENABLED } = getFeatureFlags()
|
||||
process.env['CANONICAL_URL'] = serverAddress
|
||||
process.env['PORT'] = serverPort
|
||||
|
||||
userOneId = await createUser(userOne)
|
||||
const user = await createTestUser(userOne)
|
||||
userOneId = user.id
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -200,7 +202,7 @@ const { FF_NEXT_GEN_FILE_IMPORTER_ENABLED } = getFeatureFlags()
|
||||
})
|
||||
|
||||
it('should 403 if the token is for a different user', async () => {
|
||||
const userTwoId = await createUser({
|
||||
const { id: userTwoId } = await createTestUser({
|
||||
name: createRandomString(),
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
|
||||
@@ -24,9 +24,9 @@ import type { JobPayload } from '@speckle/shared/workers/fileimport'
|
||||
import type { EventBusEmit } from '@/modules/shared/services/eventBus'
|
||||
import { FileuploadEvents } from '@/modules/fileuploads/domain/events'
|
||||
import type { BranchRecord } from '@/modules/core/helpers/types'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
|
||||
const { createStream, createBranch, createUser, garbageCollector } =
|
||||
initUploadTestEnvironment()
|
||||
const { createStream, createBranch, garbageCollector } = initUploadTestEnvironment()
|
||||
|
||||
const { FF_NEXT_GEN_FILE_IMPORTER_ENABLED } = getFeatureFlags()
|
||||
|
||||
@@ -42,7 +42,8 @@ describe('FileUploads @fileuploads', () => {
|
||||
let createdBranch: BranchRecord
|
||||
|
||||
before(async () => {
|
||||
userOneId = await createUser(userOne)
|
||||
const user = await createTestUser(userOne)
|
||||
userOneId = user.id
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -28,6 +28,7 @@ import type { MultiRegionConfig } from '@speckle/shared/environment/db'
|
||||
import { getConnectionSettings } from '@speckle/shared/environment/db'
|
||||
import { expect } from 'chai'
|
||||
import { merge } from 'lodash-es'
|
||||
import { resetRegisteredRegions } from '@/modules/multiregion/utils/dbSelector'
|
||||
|
||||
const isEnabled = isMultiRegionEnabled()
|
||||
|
||||
@@ -110,6 +111,7 @@ isEnabled
|
||||
after(async () => {
|
||||
setMultiRegionConfig(originalConfig)
|
||||
await truncateRegionsSafely()
|
||||
resetRegisteredRegions()
|
||||
})
|
||||
|
||||
describe('server config', () => {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { db } from '@/db/knex'
|
||||
import { getRegisteredRegionClients } from '@/modules/multiregion/utils/dbSelector'
|
||||
import { isMultiRegionTestMode } from '@/test/speckle-helpers/regions'
|
||||
import type { Knex } from 'knex'
|
||||
|
||||
export async function getTestRegionClients(): Promise<[Knex, ...Knex[]]> {
|
||||
if (!isMultiRegionTestMode()) return [db]
|
||||
|
||||
const regionClients = await getRegisteredRegionClients()
|
||||
const regionDbs = Object.values(regionClients)
|
||||
return [db, ...regionDbs]
|
||||
}
|
||||
+13
-4
@@ -7,23 +7,32 @@ import {
|
||||
} from '@/modules/shared/helpers/dbHelper'
|
||||
import { wait } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
|
||||
describe('prepared transaction repository functions', () => {
|
||||
describe('getStalePreparedTransactionsFactory returns a function, that', () => {
|
||||
let trx: Knex
|
||||
let trx: Knex.Transaction
|
||||
let transactionId: string = ''
|
||||
|
||||
beforeEach(async () => {
|
||||
trx = await db.transaction()
|
||||
transactionId = await prepareTransaction(trx)
|
||||
transactionId = cryptoRandomString({ length: 10 })
|
||||
|
||||
await prepareTransaction(trx, transactionId)
|
||||
try {
|
||||
await trx.commit()
|
||||
} catch {}
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await rollbackPreparedTransaction(trx, transactionId)
|
||||
await rollbackPreparedTransaction(db, transactionId)
|
||||
try {
|
||||
await trx.rollback()
|
||||
} catch {}
|
||||
})
|
||||
|
||||
it('returns prepared transactions older than a given time interval', async () => {
|
||||
await wait(5000)
|
||||
await wait(2000)
|
||||
const result = await getStalePreparedTransactionsFactory({ db })({
|
||||
interval: '1 second'
|
||||
})
|
||||
|
||||
@@ -9,7 +9,8 @@ import { getRegionFactory } from '@/modules/multiregion/repositories'
|
||||
import {
|
||||
DatabaseError,
|
||||
LogicError,
|
||||
MisconfiguredEnvironmentError
|
||||
MisconfiguredEnvironmentError,
|
||||
TestOnlyLogicError
|
||||
} from '@/modules/shared/errors'
|
||||
import { configureClient } from '@/knexfile'
|
||||
import type { InitializeRegion } from '@/modules/multiregion/domain/operations'
|
||||
@@ -19,8 +20,8 @@ import {
|
||||
getMainRegionConfig
|
||||
} from '@/modules/multiregion/regionConfig'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import { ensureError } from '@speckle/shared'
|
||||
import { isDevOrTestEnv, isTestEnv } from '@/modules/shared/helpers/envHelper'
|
||||
import { ensureError, TIME_MS, wait } from '@speckle/shared'
|
||||
import { isTestEnv } from '@/modules/shared/helpers/envHelper'
|
||||
import { migrateDbToLatest } from '@/db/migrations'
|
||||
import {
|
||||
getProjectRegionKey,
|
||||
@@ -28,6 +29,7 @@ import {
|
||||
} from '@/modules/multiregion/utils/regionSelector'
|
||||
import { get, mapValues } from 'lodash-es'
|
||||
import { isMultiRegionEnabled } from '@/modules/multiregion/helpers'
|
||||
import { logger } from '@/observability/logging'
|
||||
|
||||
let getter: GetProjectDb | undefined = undefined
|
||||
|
||||
@@ -114,13 +116,10 @@ export const initializeRegisteredRegionClients = async (): Promise<RegionClients
|
||||
Object.entries(ret).map(([region, db]) => migrateDbToLatest({ db, region }))
|
||||
)
|
||||
|
||||
// (re-)set up pub-sub, if needed
|
||||
// (disabled in prod cause there's too many DBs and connections and the load is too hard to handle)
|
||||
if (isDevOrTestEnv()) {
|
||||
await Promise.all(
|
||||
Object.keys(ret).map((regionKey) => initializeRegion({ regionKey }))
|
||||
)
|
||||
}
|
||||
// initialize regions
|
||||
await Promise.all(
|
||||
Object.keys(ret).map((regionKey) => initializeRegion({ regionKey }))
|
||||
)
|
||||
|
||||
registeredRegionClients = ret
|
||||
return ret
|
||||
@@ -129,6 +128,7 @@ export const initializeRegisteredRegionClients = async (): Promise<RegionClients
|
||||
export const getRegisteredRegionClients = async (): Promise<RegionClients> => {
|
||||
if (!registeredRegionClients)
|
||||
registeredRegionClients = await initializeRegisteredRegionClients()
|
||||
|
||||
return registeredRegionClients
|
||||
}
|
||||
|
||||
@@ -156,6 +156,15 @@ export const getAllRegisteredDbClients = async (): Promise<
|
||||
]
|
||||
}
|
||||
|
||||
export const getAllRegisteredDbs = async (): Promise<[Knex, ...Knex[]]> => {
|
||||
const mainDb = db
|
||||
const regionDbs: RegionClients = isMultiRegionEnabled()
|
||||
? await getRegisteredRegionClients()
|
||||
: {}
|
||||
|
||||
return [mainDb, ...Object.entries(regionDbs).map(([, client]) => client)]
|
||||
}
|
||||
|
||||
/**
|
||||
* Idempotently initialize region db
|
||||
*/
|
||||
@@ -181,7 +190,7 @@ export const initializeRegion: InitializeRegion = async ({ regionKey }) => {
|
||||
? 'require'
|
||||
: 'disable'
|
||||
|
||||
await setUpUserReplication({
|
||||
await dropUserReplicationIfExists({
|
||||
from: mainDb,
|
||||
to: regionDb,
|
||||
regionName: regionKey,
|
||||
@@ -210,104 +219,63 @@ interface ReplicationArgs {
|
||||
regionName: string
|
||||
}
|
||||
|
||||
const setUpUserReplication = async ({
|
||||
const dropUserReplicationIfExists = async ({
|
||||
from,
|
||||
to,
|
||||
sslmode,
|
||||
regionName
|
||||
}: ReplicationArgs): Promise<void> => {
|
||||
const subName = createPubSubName(`userssub_${regionName}`)
|
||||
const pubName = createPubSubName('userspub')
|
||||
|
||||
try {
|
||||
await from.public.raw(`CREATE PUBLICATION ${pubName} FOR TABLE users;`)
|
||||
} catch (err) {
|
||||
if (!(err instanceof Error)) {
|
||||
throw new DatabaseError(
|
||||
'Could not create publication {pubName} when setting up user replication for region {regionName}',
|
||||
from.public,
|
||||
{
|
||||
cause: ensureError(
|
||||
sanitizeError(err),
|
||||
'Unknown database error when creating publication'
|
||||
),
|
||||
info: { pubName, regionName }
|
||||
}
|
||||
)
|
||||
const { rows: pubExist } = await from.public.raw(
|
||||
`SELECT pubname FROM pg_publication WHERE pubname = '${pubName}';`
|
||||
)
|
||||
|
||||
if (pubExist.length > 0) {
|
||||
await from.public.raw(`DROP PUBLICATION ${pubName};`)
|
||||
logger.info({ regionName, pubName }, 'dropped publication')
|
||||
}
|
||||
|
||||
const errorMessage = err.message
|
||||
|
||||
if (
|
||||
!['already exists', 'violates unique constraint'].some((message) =>
|
||||
errorMessage.includes(message)
|
||||
)
|
||||
)
|
||||
throw new DatabaseError(
|
||||
'Unknown error while creating publication {pubName} when setting up user replication for region {regionName}',
|
||||
from.public,
|
||||
{
|
||||
cause: ensureError(
|
||||
sanitizeError(err),
|
||||
'Unknown database error when creating publication'
|
||||
),
|
||||
info: { pubName, regionName }
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
logger.warn({ error }, 'while dropping publication')
|
||||
// silent error as
|
||||
// dropping pub can have race conditions (n subs - 1 pub)
|
||||
// and action DROP PUBLICATION does not support if exist for current postgres version
|
||||
}
|
||||
|
||||
const fromUrl = new URL(
|
||||
from.private
|
||||
? from.private.client.config.connection.connectionString
|
||||
: from.public.client.config.connection.connectionString
|
||||
)
|
||||
const port = fromUrl.port ? fromUrl.port : '5432'
|
||||
const fromDbName = fromUrl.pathname.replace('/', '')
|
||||
const rawSqeel = `SELECT * FROM aiven_extras.pg_create_subscription(
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
TRUE,
|
||||
TRUE
|
||||
);`
|
||||
try {
|
||||
await to.public.raw('CREATE EXTENSION IF NOT EXISTS "aiven_extras"')
|
||||
await to.public.raw(rawSqeel, [
|
||||
subName,
|
||||
`dbname=${fromDbName} host=${fromUrl.hostname} port=${port} sslmode=${sslmode} user=${fromUrl.username} password=${fromUrl.password}`,
|
||||
pubName,
|
||||
subName
|
||||
])
|
||||
} catch (err) {
|
||||
if (!(err instanceof Error))
|
||||
throw new DatabaseError(
|
||||
'Could not create subscription {subName} to {pubName} when setting up user replication for region {regionName}',
|
||||
to.public,
|
||||
{
|
||||
cause: ensureError(
|
||||
sanitizeError(err),
|
||||
'Unknown database error when creating subscription'
|
||||
),
|
||||
info: { subName, pubName, regionName }
|
||||
}
|
||||
)
|
||||
if (
|
||||
!err.message.includes('already exists') &&
|
||||
!err.message.includes('duplicate key value violates unique constraint')
|
||||
const { rows: aivenExists } = await to.public.raw(
|
||||
"SELECT * FROM pg_extension WHERE extname = 'aiven_extras';"
|
||||
)
|
||||
throw new DatabaseError(
|
||||
'Unknown error while creating subscription {subName} to {pubName} when setting up user replication for region {regionName}',
|
||||
to.public,
|
||||
{
|
||||
cause: ensureError(
|
||||
sanitizeError(err),
|
||||
'Unknown database error when creating subscription'
|
||||
),
|
||||
info: { subName, pubName, regionName }
|
||||
}
|
||||
)
|
||||
|
||||
if (!aivenExists) return
|
||||
|
||||
const {
|
||||
rows: [sub]
|
||||
} = await to.public.raw<{ rows: { subconninfo: string; subslotname: string }[] }>(
|
||||
`SELECT subconninfo, subslotname FROM aiven_extras.pg_list_all_subscriptions() WHERE subname = '${subName}';`
|
||||
)
|
||||
|
||||
if (!sub) return
|
||||
|
||||
await to.public.raw(
|
||||
`SELECT * FROM aiven_extras.pg_alter_subscription_disable('${subName}');`
|
||||
)
|
||||
await wait(TIME_MS.second)
|
||||
await to.public.raw(
|
||||
`SELECT * FROM aiven_extras.pg_drop_subscription('${subName}');`
|
||||
)
|
||||
await wait(TIME_MS.second)
|
||||
await to.public.raw(
|
||||
`SELECT * FROM aiven_extras.dblink_slot_create_or_drop('${sub.subconninfo}', '${sub.subslotname}', 'drop');`
|
||||
)
|
||||
logger.info({ regionName, subName }, 'dropped subscription')
|
||||
} catch (error) {
|
||||
logger.error({ error }, 'Failed to drop subscription')
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const setUpProjectReplication = async ({
|
||||
@@ -410,3 +378,11 @@ const sanitizeError = (err: unknown): unknown => {
|
||||
if ((get(err, 'where') as unknown as string).includes('password='))
|
||||
return { ...err, where: '[REDACTED AS IT CONTAINS CONNECTION STRING]' }
|
||||
}
|
||||
|
||||
export const resetRegisteredRegions = () => {
|
||||
if (!isTestEnv()) {
|
||||
throw new TestOnlyLogicError()
|
||||
}
|
||||
|
||||
registeredRegionClients = undefined
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { changePasswordFactory } from '@/modules/core/services/users/management'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { getAllRegisteredDbs } from '@/modules/multiregion/utils/dbSelector'
|
||||
import {
|
||||
createTokenFactory,
|
||||
deleteTokensFactory,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
} from '@/modules/pwdreset/repositories'
|
||||
import { finalizePasswordResetFactory } from '@/modules/pwdreset/services/finalize'
|
||||
import { requestPasswordRecoveryFactory } from '@/modules/pwdreset/services/request'
|
||||
import { asMultiregionalOperation } from '@/modules/shared/command'
|
||||
import { BadRequestError } from '@/modules/shared/errors'
|
||||
import { withOperationLogging } from '@/observability/domain/businessLogging'
|
||||
import { ensureError } from '@speckle/shared'
|
||||
@@ -55,25 +57,34 @@ export default function (app: Express) {
|
||||
app.post('/auth/pwdreset/finalize', async (req, res) => {
|
||||
const logger = req.log
|
||||
try {
|
||||
const finalizePasswordReset = finalizePasswordResetFactory({
|
||||
getUserByEmail,
|
||||
getPendingToken: getPendingTokenFactory({ db }),
|
||||
deleteTokens: deleteTokensFactory({ db }),
|
||||
updateUserPassword: changePasswordFactory({
|
||||
getUser: getUserFactory({ db }),
|
||||
updateUser: updateUserFactory({ db })
|
||||
}),
|
||||
deleteExistingAuthTokens: deleteExistingAuthTokensFactory({ db })
|
||||
})
|
||||
|
||||
if (!req.body.tokenId || !req.body.password)
|
||||
throw new BadRequestError('Invalid request.')
|
||||
await withOperationLogging(
|
||||
async () => await finalizePasswordReset(req.body.tokenId, req.body.password),
|
||||
await asMultiregionalOperation(
|
||||
async ({ mainDb, allDbs }) => {
|
||||
const finalizePasswordReset = finalizePasswordResetFactory({
|
||||
getUserByEmail,
|
||||
getPendingToken: getPendingTokenFactory({ db: mainDb }),
|
||||
deleteTokens: deleteTokensFactory({ db: mainDb }),
|
||||
updateUserPassword: changePasswordFactory({
|
||||
getUser: getUserFactory({ db: mainDb }),
|
||||
updateUser: async (...params) => {
|
||||
const [res] = await Promise.all(
|
||||
allDbs.map((db) => updateUserFactory({ db })(...params))
|
||||
)
|
||||
|
||||
return res
|
||||
}
|
||||
}),
|
||||
deleteExistingAuthTokens: deleteExistingAuthTokensFactory({ db: mainDb })
|
||||
})
|
||||
|
||||
return await finalizePasswordReset(req.body.tokenId, req.body.password)
|
||||
},
|
||||
{
|
||||
logger,
|
||||
operationName: 'finalizePasswordReset',
|
||||
operationDescription: `Finalizing password reset`
|
||||
dbs: await getAllRegisteredDbs(),
|
||||
name: 'finalizePasswordReset',
|
||||
description: `Finalizing password reset`
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -6,62 +6,9 @@ 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
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import {
|
||||
getUserFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} 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
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
|
||||
const ResetTokens = () => knex('pwdreset_tokens')
|
||||
const db = knex
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
describe('Password reset requests @passwordresets', () => {
|
||||
let app: Awaited<ReturnType<typeof beforeEachContext>>['app']
|
||||
@@ -71,13 +18,12 @@ describe('Password reset requests @passwordresets', () => {
|
||||
})
|
||||
|
||||
it('Should carefully send a password request email', async () => {
|
||||
const userA: BasicTestUser = {
|
||||
const userA = await createTestUser({
|
||||
name: 'd1',
|
||||
email: 'd@speckle.systems',
|
||||
password: 'wowwow8charsplease',
|
||||
id: ''
|
||||
}
|
||||
userA.id = await createUser(userA)
|
||||
})
|
||||
|
||||
// invalid request
|
||||
await request(app).post('/auth/pwdreset/request').expect(400)
|
||||
@@ -102,13 +48,12 @@ describe('Password reset requests @passwordresets', () => {
|
||||
})
|
||||
|
||||
it('Should reset passwords', async () => {
|
||||
const userB: BasicTestUser = {
|
||||
const userB = await createTestUser({
|
||||
name: 'd2',
|
||||
email: 'd2@speckle.systems',
|
||||
password: 'w0ww0w8charsplease',
|
||||
id: ''
|
||||
}
|
||||
userB.id = await createUser(userB)
|
||||
})
|
||||
|
||||
const authRestApi = localAuthRestApi({ express: app })
|
||||
const newPassword = '12345678'
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { mainDb } from '@/db/knex'
|
||||
import { withTransaction } from '@/modules/shared/helpers/dbHelper'
|
||||
import {
|
||||
commitPreparedTransaction as commitPrepared,
|
||||
prepareTransaction,
|
||||
rollbackPreparedTransaction as rollbackPrepared,
|
||||
withTransaction
|
||||
} from '@/modules/shared/helpers/dbHelper'
|
||||
import type {
|
||||
EmitArg,
|
||||
EventBus,
|
||||
@@ -8,9 +13,12 @@ import type {
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { withOperationLogging } from '@/observability/domain/businessLogging'
|
||||
import type { MaybeAsync } from '@speckle/shared'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import type { Knex } from 'knex'
|
||||
import { isBoolean } from 'lodash-es'
|
||||
import type { Logger } from 'pino'
|
||||
import { wasRejected } from '@/modules/shared/domain/constants'
|
||||
import { RegionalTransactionFatalError } from '@/modules/shared/errors'
|
||||
|
||||
/**
|
||||
* @deprecated asOperation does this and more. Also many usages of commandFactory are broken
|
||||
@@ -120,3 +128,142 @@ export const asOperation = async <T>(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to execute a command across multiple regions
|
||||
* works similarly to asOperation, but provides references to every db instance in the dbs array provided
|
||||
* It opens a transaction for each db, and uses 2PC to ensure consistency at commit moment
|
||||
* txs represents all the transactions
|
||||
* dbTx represents the main transaction (Knex)
|
||||
* regionTxs represents the transactions that were given as regions (Knex[])
|
||||
*/
|
||||
export const asMultiregionalOperation = async <T, K extends [Knex, ...Knex[]]>(
|
||||
operation: (args: {
|
||||
/**
|
||||
* @description reference to all dbs involved in the operation
|
||||
*/
|
||||
allDbs: Knex[]
|
||||
/**
|
||||
* @description reference to the main db (first one passed in the array)
|
||||
*/
|
||||
mainDb: Knex
|
||||
/**
|
||||
* @description reference for second db (first one not main)
|
||||
*/
|
||||
regionDb: Knex
|
||||
/**
|
||||
* @description reference for all regions (all dbs except the main one)
|
||||
*/
|
||||
regionDbs: Knex[]
|
||||
emit: EventBusEmit
|
||||
}) => MaybeAsync<T>,
|
||||
params: {
|
||||
name: string
|
||||
logger: Logger
|
||||
description?: string
|
||||
/**
|
||||
* @description Dbs to open transactions for the operation
|
||||
*/
|
||||
dbs: K
|
||||
/**
|
||||
* @description Defaults to main event bus
|
||||
*/
|
||||
eventBus?: EventBus
|
||||
}
|
||||
): Promise<T> => {
|
||||
const {
|
||||
eventBus = getEventBus(),
|
||||
logger,
|
||||
name,
|
||||
description,
|
||||
dbs: [mainDb, ...regionDbs]
|
||||
} = params
|
||||
|
||||
return await withOperationLogging(
|
||||
async () => {
|
||||
const events: EmitArg[] = []
|
||||
const emit: EventBusEmit = async ({ eventName, payload }) => {
|
||||
events.push({ eventName, payload })
|
||||
}
|
||||
|
||||
const gid = cryptoRandomString({ length: 10 })
|
||||
const trxs: Knex.Transaction[] = []
|
||||
|
||||
const rollback = async () => {
|
||||
await Promise.allSettled(trxs.map((trx) => rollbackPrepared(trx, gid)))
|
||||
await Promise.allSettled(trxs.map((trx) => trx.rollback()))
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
const mainDbTx = await mainDb.transaction()
|
||||
trxs.push(mainDbTx)
|
||||
|
||||
const regionDbsTx: Knex.Transaction[] = []
|
||||
for (const regionDb of regionDbs) {
|
||||
const regionTx = await regionDb.transaction()
|
||||
trxs.push(regionTx)
|
||||
regionDbsTx.push(regionTx)
|
||||
}
|
||||
|
||||
result = await operation({
|
||||
mainDb: mainDbTx,
|
||||
allDbs: trxs,
|
||||
regionDb: regionDbsTx[0],
|
||||
regionDbs: regionDbsTx,
|
||||
emit
|
||||
})
|
||||
|
||||
// Every transaction is prepared
|
||||
// - important to do prepare sequentially
|
||||
// - if a query won't complete, every preparedTransaction is rollbacked (from prepared or unprepared)
|
||||
// - this applies a lock on the rows to be updated to assure that the commit will succeed.
|
||||
// - the transactions once prepared, gets written to disk db and is no longer scoped to the connection.
|
||||
// - this last part knex does not handle well, so no matter what, we need to rollback/commit
|
||||
// the transaction (the prepared one and the connection transaction) that's why it's wrapped in a transaction block
|
||||
for (const tx of trxs) await prepareTransaction(tx, gid)
|
||||
} catch (e) {
|
||||
await rollback()
|
||||
throw e
|
||||
}
|
||||
|
||||
const commits = await Promise.allSettled(
|
||||
trxs.map(async (trx) => {
|
||||
await commitPrepared(trx, gid)
|
||||
try {
|
||||
await trx.commit()
|
||||
} catch {
|
||||
// forcing knex to release connection
|
||||
// for the db this tx is gone already as its in a prepared state unbinded from the connection
|
||||
// but knex does not know this, and it won't release the connection until a commit/rollback happen
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (commits.some(wasRejected)) {
|
||||
// we never should reach this point
|
||||
// as once a transaction is prepared successfully
|
||||
// it will commit
|
||||
|
||||
logger.error(
|
||||
{ commits, gid },
|
||||
`Failed to commit transactions in 2PC operation.`
|
||||
)
|
||||
|
||||
throw new RegionalTransactionFatalError(
|
||||
'Failed some or all transactions in 2PC operation.',
|
||||
{ clients: trxs, gid }
|
||||
)
|
||||
}
|
||||
|
||||
for (const event of events) await eventBus.emit(event)
|
||||
|
||||
return result
|
||||
},
|
||||
{
|
||||
logger,
|
||||
operationName: name,
|
||||
operationDescription: description
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import { StringEnum } from '@speckle/shared'
|
||||
|
||||
export const PromiseAllSettledResultStatus = StringEnum(['rejected', 'fulfilled'])
|
||||
|
||||
export const wasRejected = <T>(
|
||||
result: PromiseSettledResult<T>
|
||||
): result is PromiseRejectedResult =>
|
||||
result.status === PromiseAllSettledResultStatus.rejected
|
||||
|
||||
@@ -163,33 +163,6 @@ export class TestOnlyLogicError extends BaseError {
|
||||
static statusCode = 500
|
||||
}
|
||||
|
||||
const getErrorInfoFromTransactions = (
|
||||
preparedTransactions: { knex: Knex; preparedId: string }[]
|
||||
) => {
|
||||
return preparedTransactions.map(({ knex, preparedId }) => ({
|
||||
db: retrieveMetadataFromDatabaseClient(knex),
|
||||
gid: preparedId
|
||||
}))
|
||||
}
|
||||
|
||||
// 2PC failed but we successfully rolled back all prepared transactions.
|
||||
export class RegionalTransactionError extends BaseError {
|
||||
static code = 'REGIONAL_TRANSACTION_ERROR'
|
||||
static defaultMessage = 'Failed to complete 2PC operation'
|
||||
static statusCode = 500
|
||||
|
||||
constructor(
|
||||
message?: string | null,
|
||||
preparedTransactions: { knex: Knex; preparedId: string }[] = []
|
||||
) {
|
||||
super(message, {
|
||||
info: {
|
||||
clients: getErrorInfoFromTransactions(preparedTransactions)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 2PC failed and we failed to rollback. A prepared transaction may have been left behind.
|
||||
export class RegionalTransactionFatalError extends BaseError {
|
||||
static code = 'REGIONAL_TRANSACTION_FATAL_ERROR'
|
||||
@@ -197,12 +170,13 @@ export class RegionalTransactionFatalError extends BaseError {
|
||||
static statusCode = 500
|
||||
|
||||
constructor(
|
||||
message?: string | null,
|
||||
preparedTransactions: { knex: Knex; preparedId: string }[] = []
|
||||
message: string | null,
|
||||
{ gid, clients }: { gid: string; clients: Knex[] }
|
||||
) {
|
||||
super(message, {
|
||||
info: {
|
||||
clients: getErrorInfoFromTransactions(preparedTransactions)
|
||||
gid,
|
||||
dbs: clients.map(retrieveMetadataFromDatabaseClient)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { Knex } from 'knex'
|
||||
import { postgresMaxConnections } from '@/modules/shared/helpers/envHelper'
|
||||
import {
|
||||
EnvironmentResourceError,
|
||||
LogicError,
|
||||
RegionalTransactionFatalError,
|
||||
RegionalTransactionError
|
||||
} from '@/modules/shared/errors'
|
||||
import { EnvironmentResourceError } from '@/modules/shared/errors'
|
||||
import type { MaybeAsync } from '@speckle/shared'
|
||||
import { isNonNullable } from '@speckle/shared'
|
||||
import { base64Decode, base64Encode } from '@/modules/shared/helpers/cryptoHelper'
|
||||
@@ -15,10 +10,6 @@ import dayjs from 'dayjs'
|
||||
import type { MaybeNullOrUndefined, Nullable } from '@speckle/shared'
|
||||
import type { SchemaConfig } from '@/modules/core/dbSchema'
|
||||
import { has, isObjectLike, isString, mapValues, pick, times } from 'lodash-es'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { logger } from '@/observability/logging'
|
||||
import { isEqual } from 'lodash-es'
|
||||
import { PromiseAllSettledResultStatus } from '@/modules/shared/domain/constants'
|
||||
|
||||
export type Collection<T> = {
|
||||
cursor: string | null
|
||||
@@ -312,16 +303,8 @@ export const compositeCursorTools = <
|
||||
}
|
||||
}
|
||||
|
||||
export const prepareTransaction = async (db: Knex): Promise<string> => {
|
||||
if (!db.isTransaction) {
|
||||
throw new LogicError('Cannot PREPARE postgres operation outside of a transaction')
|
||||
}
|
||||
|
||||
const preparedId = cryptoRandomString({ length: 10 })
|
||||
|
||||
await db.raw(`PREPARE TRANSACTION '${preparedId}';`)
|
||||
|
||||
return preparedId
|
||||
export const prepareTransaction = async (db: Knex, gid: string): Promise<void> => {
|
||||
await db.raw(`PREPARE TRANSACTION '${gid}';`)
|
||||
}
|
||||
|
||||
export const commitPreparedTransaction = async (
|
||||
@@ -337,102 +320,3 @@ export const rollbackPreparedTransaction = async (
|
||||
): Promise<void> => {
|
||||
await db.raw(`ROLLBACK PREPARED '${gid}';`)
|
||||
}
|
||||
|
||||
export const replicateQuery = <T, U>(
|
||||
dbs: Knex[],
|
||||
factory: ({ db }: { db: Knex }) => (params: T) => Promise<U>
|
||||
) => {
|
||||
return async (params: T) => {
|
||||
const preparedTransactions: {
|
||||
knex: Knex
|
||||
preparedId: string
|
||||
}[] = []
|
||||
|
||||
const returnValues: U[] = []
|
||||
|
||||
try {
|
||||
// Phase 1: Prepare transaction across all specified db instances
|
||||
for (const db of dbs) {
|
||||
const trx = await db.transaction()
|
||||
const returnValue = await factory({ db: trx })(params)
|
||||
returnValues.push(returnValue)
|
||||
const preparedId = await prepareTransaction(trx)
|
||||
preparedTransactions.push({ knex: db, preparedId })
|
||||
}
|
||||
|
||||
// Phase 2: Attempt commit of all prepared transactions
|
||||
const results = await Promise.allSettled(
|
||||
preparedTransactions.map(({ knex, preparedId }) => {
|
||||
return commitPreparedTransaction(knex, preparedId)
|
||||
})
|
||||
)
|
||||
|
||||
const errors = results.filter((result): result is PromiseRejectedResult => {
|
||||
return result.status === PromiseAllSettledResultStatus.rejected
|
||||
})
|
||||
|
||||
if (errors.length > 0) {
|
||||
logger.error(
|
||||
{
|
||||
params,
|
||||
errors,
|
||||
errorCount: errors.length,
|
||||
resultCount: results.length
|
||||
},
|
||||
`Failed {errorCount} of {resultCount} transactions in 2PC operation.`
|
||||
)
|
||||
throw new RegionalTransactionError(
|
||||
'Failed some or all transactions in 2PC operation.',
|
||||
preparedTransactions
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Do we need this validation?
|
||||
if (!returnValues.every((value) => isEqual(value, returnValues[0]))) {
|
||||
throw new RegionalTransactionError(
|
||||
'Return values of 2PC transactions do not match',
|
||||
preparedTransactions
|
||||
)
|
||||
}
|
||||
|
||||
return returnValues[0]
|
||||
} catch {
|
||||
const rollbacks = preparedTransactions.map(async ({ knex, preparedId }) => {
|
||||
try {
|
||||
await rollbackPreparedTransaction(knex, preparedId)
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
{ preparedId },
|
||||
'Failed to rollback prepared transaction {preparedId}'
|
||||
)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
|
||||
logger.warn(
|
||||
{
|
||||
preparedTransactions: preparedTransactions.map(({ preparedId }) => preparedId)
|
||||
},
|
||||
'Error during 2PC operation. Rolling back all transactions.'
|
||||
)
|
||||
|
||||
const results = await Promise.allSettled(rollbacks)
|
||||
|
||||
if (
|
||||
results.some(
|
||||
(result) => result.status === PromiseAllSettledResultStatus.rejected
|
||||
)
|
||||
) {
|
||||
throw new RegionalTransactionFatalError(
|
||||
'Failed to rollback all transactions.',
|
||||
preparedTransactions
|
||||
)
|
||||
}
|
||||
|
||||
throw new RegionalTransactionError(
|
||||
'Failed to complete 2PC operation but successfully recovered.',
|
||||
preparedTransactions
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,18 @@ import { getDb } from '@/modules/multiregion/utils/dbSelector'
|
||||
import { Scopes } from '@/modules/core/dbSchema'
|
||||
import { expect } from 'chai'
|
||||
import type { Knex } from 'knex'
|
||||
import { replicateQuery } from '@/modules/shared/helpers/dbHelper'
|
||||
import { isMultiRegionTestMode } from '@/test/speckle-helpers/regions'
|
||||
import { db } from '@/db/knex'
|
||||
import { sleep } from '@/test/helpers'
|
||||
import { asMultiregionalOperation } from '@/modules/shared/command'
|
||||
import { logger } from '@/observability/logging'
|
||||
|
||||
isMultiRegionTestMode()
|
||||
? describe('Prepared transaction utils (2PC) @multiregion', async () => {
|
||||
let main: Knex
|
||||
let region1: Knex
|
||||
let region2: Knex
|
||||
let ALL_DBS: Knex[] = []
|
||||
let ALL_DBS: [Knex, ...Knex[]] = [db]
|
||||
|
||||
const testOperationFactory =
|
||||
({ db }: { db: Knex }) =>
|
||||
@@ -23,12 +26,16 @@ isMultiRegionTestMode()
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
main = await getDb({ regionKey: null })
|
||||
main = db
|
||||
region1 = await getDb({ regionKey: 'region1' })
|
||||
region2 = await getDb({ regionKey: 'region2' })
|
||||
ALL_DBS = [main, region1, region2]
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await db('users').del()
|
||||
})
|
||||
|
||||
it('successfully replicates operation across all specified db instances', async () => {
|
||||
const testOperationParams = {
|
||||
name: 'test:scope:a',
|
||||
@@ -36,7 +43,17 @@ isMultiRegionTestMode()
|
||||
public: false
|
||||
}
|
||||
|
||||
await replicateQuery(ALL_DBS, testOperationFactory)(testOperationParams)
|
||||
await asMultiregionalOperation(
|
||||
({ allDbs }) =>
|
||||
Promise.all(
|
||||
allDbs.map((db) => testOperationFactory({ db })(testOperationParams))
|
||||
),
|
||||
{
|
||||
dbs: ALL_DBS,
|
||||
name: 'testing regional success',
|
||||
logger
|
||||
}
|
||||
)
|
||||
|
||||
const scopeMain = await main
|
||||
.table(Scopes.name)
|
||||
@@ -66,11 +83,17 @@ isMultiRegionTestMode()
|
||||
|
||||
await testOperationFactory({ db: region2 })(testOperationParams)
|
||||
|
||||
const promise = replicateQuery(
|
||||
ALL_DBS,
|
||||
testOperationFactory
|
||||
)(testOperationParams)
|
||||
|
||||
const promise = asMultiregionalOperation(
|
||||
({ allDbs }) =>
|
||||
Promise.all(
|
||||
allDbs.map((db) => testOperationFactory({ db })(testOperationParams))
|
||||
),
|
||||
{
|
||||
dbs: ALL_DBS,
|
||||
name: 'testing regional failure',
|
||||
logger
|
||||
}
|
||||
)
|
||||
await expect(promise).eventually.to.be.rejected
|
||||
|
||||
const scopeMain = await main
|
||||
@@ -99,16 +122,20 @@ isMultiRegionTestMode()
|
||||
}
|
||||
|
||||
const dbThatFails = {
|
||||
transaction: () =>
|
||||
Promise.resolve(() => ({
|
||||
insert: () => Promise.resolve()
|
||||
})) // will fail on raw call
|
||||
transaction: async () => Promise.reject(new Error('Transaction failed'))
|
||||
} as unknown as Knex
|
||||
|
||||
const promise = replicateQuery(
|
||||
[...ALL_DBS, dbThatFails],
|
||||
testOperationFactory
|
||||
)(testOperationParams)
|
||||
const promise = asMultiregionalOperation(
|
||||
({ allDbs }) =>
|
||||
Promise.all(
|
||||
allDbs.map((db) => testOperationFactory({ db })(testOperationParams))
|
||||
),
|
||||
{
|
||||
dbs: [...ALL_DBS, dbThatFails],
|
||||
name: 'testing regional success',
|
||||
logger
|
||||
}
|
||||
)
|
||||
|
||||
await expect(promise).to.eventually.be.rejected
|
||||
|
||||
@@ -129,5 +156,32 @@ isMultiRegionTestMode()
|
||||
expect(scopeRegion1).to.be.undefined
|
||||
expect(scopeRegion2).to.be.undefined
|
||||
})
|
||||
|
||||
it('does not has visibile perfomance issues using 2PC', async () => {
|
||||
const connectionsUsedBefore = main.client.pool.numUsed()
|
||||
|
||||
const oneKnexInstanceCall = async () => {
|
||||
const { buildBasicTestUser, createTestUser } = await import(
|
||||
'@/test/authHelper'
|
||||
)
|
||||
|
||||
const user = buildBasicTestUser()
|
||||
await createTestUser(user) // This uses the asMultireagionOperation helper }
|
||||
}
|
||||
|
||||
const manyParallelCreates = async () => {
|
||||
await Promise.allSettled(Array.from({ length: 1000 }, oneKnexInstanceCall))
|
||||
}
|
||||
|
||||
await manyParallelCreates()
|
||||
|
||||
const [{ count }] = await db('users').count()
|
||||
expect(count).to.eql(1000)
|
||||
|
||||
await sleep(1000) // just in case
|
||||
|
||||
const connectionsUsedAfter = main.client.pool.numUsed()
|
||||
expect(connectionsUsedAfter).to.be.lte(connectionsUsedBefore)
|
||||
})
|
||||
})
|
||||
: null
|
||||
|
||||
@@ -52,13 +52,7 @@ import {
|
||||
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
|
||||
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
getUserFactory,
|
||||
getUsersFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUserFactory, getUsersFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
@@ -68,7 +62,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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,
|
||||
@@ -92,6 +85,8 @@ import {
|
||||
validateStreamAccessFactory
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
@@ -191,33 +186,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
})
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const createPersonalAccessToken = createPersonalAccessTokenFactory({
|
||||
storeApiToken: storeApiTokenFactory({ db }),
|
||||
storeTokenScopes: storeTokenScopesFactory({ db }),
|
||||
@@ -253,23 +221,8 @@ describe('Server stats services @stats-services', function () {
|
||||
describe('Server stats api @stats-api', function () {
|
||||
let sendRequest: Awaited<ReturnType<typeof initializeTestServer>>['sendRequest']
|
||||
|
||||
const adminUser = {
|
||||
name: 'Dimitrie',
|
||||
password: 'TestPasswordSecure',
|
||||
email: 'spam@spam.spam',
|
||||
id: '', // Will be filled in before()
|
||||
goodToken: '',
|
||||
badToken: ''
|
||||
}
|
||||
|
||||
const notAdminUser = {
|
||||
name: 'Andrei',
|
||||
password: 'TestPasswordSecure',
|
||||
email: 'spasm@spam.spam',
|
||||
id: '', // Will be filled in before()
|
||||
goodToken: '',
|
||||
badToken: ''
|
||||
}
|
||||
let adminUser: BasicTestUser & { goodToken?: string; badToken?: string }
|
||||
let notAdminUser: BasicTestUser & { goodToken?: string; badToken?: string }
|
||||
|
||||
const fullQuery = `
|
||||
query{
|
||||
@@ -291,7 +244,12 @@ describe('Server stats api @stats-api', function () {
|
||||
const ctx = await beforeEachContext()
|
||||
;({ sendRequest } = await initializeTestServer(ctx))
|
||||
|
||||
adminUser.id = await createUser(adminUser)
|
||||
adminUser = await createTestUser({
|
||||
name: 'Dimitrie',
|
||||
password: 'TestPasswordSecure',
|
||||
email: 'spam@spam.spam',
|
||||
id: ''
|
||||
})
|
||||
adminUser.goodToken = `Bearer ${await createPersonalAccessToken(
|
||||
adminUser.id,
|
||||
'test token user A',
|
||||
@@ -303,7 +261,12 @@ describe('Server stats api @stats-api', function () {
|
||||
[Scopes.Streams.Read]
|
||||
)}`
|
||||
|
||||
notAdminUser.id = await createUser(notAdminUser)
|
||||
notAdminUser = await createTestUser({
|
||||
name: 'Andrei',
|
||||
password: 'TestPasswordSecure',
|
||||
email: 'spasm@spam.spam',
|
||||
id: ''
|
||||
})
|
||||
notAdminUser.goodToken = `Bearer ${await createPersonalAccessToken(
|
||||
notAdminUser.id,
|
||||
'test token user A',
|
||||
@@ -369,22 +332,20 @@ async function seedDb({
|
||||
numCommits = 10
|
||||
} = {}) {
|
||||
// create users
|
||||
const userPromises = []
|
||||
const users = []
|
||||
for (let i = 0; i < numUsers; i++) {
|
||||
const promise = createUser({
|
||||
const user = await createTestUser({
|
||||
name: `User ${i}`,
|
||||
password: `SuperSecure${i}${i * 3.14}`,
|
||||
email: `user${i}@speckle.systems`
|
||||
})
|
||||
userPromises.push(promise)
|
||||
users.push(user)
|
||||
}
|
||||
|
||||
const userIds = await Promise.all(userPromises)
|
||||
|
||||
// create streams
|
||||
const streamPromises: Array<Promise<{ id: string; ownerId: string }>> = []
|
||||
for (let i = 0; i < numStreams; i++) {
|
||||
const ownerId = userIds[i >= userIds.length ? userIds.length - 1 : i]
|
||||
const { id: ownerId } = users[i >= users.length ? users.length - 1 : i]
|
||||
const promise = createStream({
|
||||
name: `Stream ${i}`,
|
||||
ownerId
|
||||
|
||||
@@ -16,19 +16,12 @@ import {
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
getUserFactory,
|
||||
getUsersFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUserFactory, getUsersFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
createStreamReturnRecordFactory,
|
||||
legacyCreateStreamFactory
|
||||
} from '@/modules/core/services/streams/management'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
@@ -63,6 +56,7 @@ import {
|
||||
validateStreamAccessFactory
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const WEBHOOKS_CONFIG_TABLE = 'webhooks_config'
|
||||
@@ -152,33 +146,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
})
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const countWebhooks = async () => {
|
||||
const [{ count }] = await WebhooksConfig().count()
|
||||
@@ -208,7 +175,7 @@ describe('Webhooks cleanup @webhooks', () => {
|
||||
})
|
||||
|
||||
it('Cleans orphans, leaves live ones intact', async () => {
|
||||
const ownerId = await createUser({
|
||||
const { id: ownerId } = await createTestUser({
|
||||
name: 'User',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
|
||||
@@ -46,13 +46,7 @@ import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/se
|
||||
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { createBranchFactory } from '@/modules/core/repositories/branches'
|
||||
import {
|
||||
getUserFactory,
|
||||
getUsersFactory,
|
||||
storeUserFactory,
|
||||
countAdminUsersFactory,
|
||||
storeUserAclFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { getUserFactory, getUsersFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
findEmailFactory,
|
||||
createUserEmailFactory,
|
||||
@@ -62,7 +56,6 @@ import { requestNewEmailVerificationFactory } from '@/modules/emails/services/ve
|
||||
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,
|
||||
@@ -86,6 +79,7 @@ import {
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import { omit } from 'lodash-es'
|
||||
import { createTestUser, type BasicTestUser } from '@/test/authHelper'
|
||||
import { storeProjectRoleFactory } from '@/modules/core/repositories/projects'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
@@ -173,33 +167,6 @@ const createStream = legacyCreateStreamFactory({
|
||||
})
|
||||
})
|
||||
const grantPermissionsStream = grantStreamPermissionsFactory({ db })
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const createPersonalAccessToken = createPersonalAccessTokenFactory({
|
||||
storeApiToken: storeApiTokenFactory({ db }),
|
||||
storeTokenScopes: storeTokenScopesFactory({ db }),
|
||||
@@ -213,13 +180,7 @@ describe('Webhooks @webhooks', () => {
|
||||
const getWebhook = getWebhookByIdFactory({ db })
|
||||
let sendRequest: Awaited<ReturnType<typeof initializeTestServer>>['sendRequest']
|
||||
|
||||
const userOne = {
|
||||
name: 'User',
|
||||
email: 'user@example.org',
|
||||
password: 'jdsadjsadasfdsa',
|
||||
id: '',
|
||||
token: ''
|
||||
}
|
||||
let userOne: BasicTestUser & { token?: string }
|
||||
|
||||
const streamOne = {
|
||||
name: 'streamOne',
|
||||
@@ -243,7 +204,12 @@ describe('Webhooks @webhooks', () => {
|
||||
const ctx = await beforeEachContext()
|
||||
;({ sendRequest } = await initializeTestServer(ctx))
|
||||
|
||||
userOne.id = await createUser(userOne)
|
||||
userOne = await createTestUser({
|
||||
name: 'User',
|
||||
email: 'user@example.org',
|
||||
password: 'jdsadjsadasfdsa',
|
||||
id: ''
|
||||
})
|
||||
streamOne.ownerId = userOne.id
|
||||
streamOne.id = await createStream(streamOne)
|
||||
|
||||
@@ -380,13 +346,7 @@ describe('Webhooks @webhooks', () => {
|
||||
})
|
||||
|
||||
describe('GraphQL API Webhooks @webhooks-api', () => {
|
||||
const userTwo = {
|
||||
name: 'User2',
|
||||
email: 'user2@example.org',
|
||||
password: 'jdsadjsadasfdsa',
|
||||
id: '',
|
||||
token: ''
|
||||
}
|
||||
let userTwo: BasicTestUser & { token?: string }
|
||||
|
||||
const webhookTwo = {
|
||||
streamId: '',
|
||||
@@ -407,7 +367,12 @@ describe('Webhooks @webhooks', () => {
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
userTwo.id = await createUser(userTwo)
|
||||
userTwo = await createTestUser({
|
||||
name: 'User2',
|
||||
email: 'user2@example.org',
|
||||
password: 'jdsadjsadasfdsa',
|
||||
id: ''
|
||||
})
|
||||
streamTwo.ownerId = userTwo.id
|
||||
streamTwo.id = await createStream(streamTwo)
|
||||
webhookTwo.streamId = streamTwo.id
|
||||
|
||||
@@ -57,7 +57,6 @@ import {
|
||||
findVerifiedEmailsByUserIdFactory,
|
||||
updateUserEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { withTransaction } from '@/modules/shared/helpers/dbHelper'
|
||||
import type { UserWithOptionalRole } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
@@ -142,6 +141,8 @@ import {
|
||||
createWorkspaceSeatFactory,
|
||||
getWorkspaceUserSeatFactory
|
||||
} from '@/modules/gatekeeper/repositories/workspaceSeat'
|
||||
import { getAllRegisteredDbs } from '@/modules/multiregion/utils/dbSelector'
|
||||
import { asMultiregionalOperation } from '@/modules/shared/command'
|
||||
|
||||
const moveAuthParamsToSessionMiddleware = moveAuthParamsToSessionMiddlewareFactory()
|
||||
const sessionMiddleware = sessionMiddlewareFactory()
|
||||
@@ -275,11 +276,11 @@ export const getSsoRouter = (): Router => {
|
||||
}),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
await withTransaction(
|
||||
async ({ db: trx }) => {
|
||||
await asMultiregionalOperation(
|
||||
async ({ mainDb, allDbs }) => {
|
||||
const handleOidcCallback = handleOidcCallbackFactory({
|
||||
getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }),
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db: trx }),
|
||||
getWorkspaceRoles: getWorkspaceRolesFactory({ db: mainDb }),
|
||||
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db: mainDb }),
|
||||
createOidcProvider: createOidcProviderFactory({
|
||||
getOIDCProviderValidationRequest:
|
||||
getOIDCProviderValidationRequestFactory({
|
||||
@@ -288,60 +289,66 @@ export const getSsoRouter = (): Router => {
|
||||
}),
|
||||
saveSsoProviderRegistration: saveSsoProviderRegistrationFactory({
|
||||
getWorkspaceSsoProvider: getWorkspaceSsoProviderFactory({
|
||||
db: trx,
|
||||
db: mainDb,
|
||||
decrypt: getDecryptor()
|
||||
}),
|
||||
storeProviderRecord: storeSsoProviderRecordFactory({
|
||||
db: trx,
|
||||
db: mainDb,
|
||||
encrypt: getEncryptor()
|
||||
}),
|
||||
associateSsoProviderWithWorkspace:
|
||||
associateSsoProviderWithWorkspaceFactory({
|
||||
db: trx
|
||||
db: mainDb
|
||||
})
|
||||
})
|
||||
}),
|
||||
getOidcProvider: getOidcProviderFactory({
|
||||
getWorkspaceSsoProvider: getWorkspaceSsoProviderFactory({
|
||||
db: trx,
|
||||
db: mainDb,
|
||||
decrypt: getDecryptor()
|
||||
})
|
||||
}),
|
||||
getOidcProviderUserData: getOidcProviderUserDataFactory(),
|
||||
tryGetSpeckleUserData: tryGetSpeckleUserDataFactory({
|
||||
findEmail: findEmailFactory({ db: trx }),
|
||||
getUser: getUserFactory({ db: trx }),
|
||||
getUserEmails: findEmailsByUserIdFactory({ db: trx })
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
getUser: getUserFactory({ db: mainDb }),
|
||||
getUserEmails: findEmailsByUserIdFactory({ db: mainDb })
|
||||
}),
|
||||
createWorkspaceUserFromSsoProfile:
|
||||
createWorkspaceUserFromSsoProfileFactory({
|
||||
createUser: createUserFactory({
|
||||
getServerInfo: getServerInfoFactory({ db: trx }),
|
||||
findEmail: findEmailFactory({ db: trx }),
|
||||
storeUser: storeUserFactory({ db: trx }),
|
||||
countAdminUsers: countAdminUsersFactory({ db: trx }),
|
||||
storeUserAcl: storeUserAclFactory({ db: trx }),
|
||||
getServerInfo: getServerInfoFactory({ db: mainDb }),
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
storeUser: async (...params) => {
|
||||
const [user] = await Promise.all(
|
||||
allDbs.map((db) => storeUserFactory({ db })(...params))
|
||||
)
|
||||
|
||||
return user
|
||||
},
|
||||
countAdminUsers: countAdminUsersFactory({ db: mainDb }),
|
||||
storeUserAcl: storeUserAclFactory({ db: mainDb }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db: trx }),
|
||||
createUserEmail: createUserEmailFactory({ db: mainDb }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({
|
||||
db: trx
|
||||
db: mainDb
|
||||
}),
|
||||
findEmail: findEmailFactory({ db: trx }),
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({
|
||||
db: trx
|
||||
db: mainDb
|
||||
}),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({
|
||||
db: trx
|
||||
db: mainDb
|
||||
})
|
||||
}),
|
||||
requestNewEmailVerification: requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db: trx }),
|
||||
getUser: getUserFactory({ db: trx }),
|
||||
getServerInfo: getServerInfoFactory({ db: trx }),
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
getUser: getUserFactory({ db: mainDb }),
|
||||
getServerInfo: getServerInfoFactory({ db: mainDb }),
|
||||
deleteOldAndInsertNewVerification:
|
||||
deleteOldAndInsertNewVerificationFactory({
|
||||
db: trx
|
||||
db: mainDb
|
||||
}),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
@@ -351,46 +358,50 @@ export const getSsoRouter = (): Router => {
|
||||
}),
|
||||
addOrUpdateWorkspaceRole: addOrUpdateWorkspaceRoleFactory({
|
||||
getWorkspaceWithDomains: getWorkspaceWithDomainsFactory({
|
||||
db: trx
|
||||
db: mainDb
|
||||
}),
|
||||
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({
|
||||
db: trx
|
||||
db: mainDb
|
||||
}),
|
||||
getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }),
|
||||
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db: trx }),
|
||||
getWorkspaceRoles: getWorkspaceRolesFactory({ db: mainDb }),
|
||||
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db: mainDb }),
|
||||
emitWorkspaceEvent: getEventBus().emit,
|
||||
ensureValidWorkspaceRoleSeat: ensureValidWorkspaceRoleSeatFactory({
|
||||
createWorkspaceSeat: createWorkspaceSeatFactory({ db: trx }),
|
||||
getWorkspaceUserSeat: getWorkspaceUserSeatFactory({ db: trx }),
|
||||
createWorkspaceSeat: createWorkspaceSeatFactory({ db: mainDb }),
|
||||
getWorkspaceUserSeat: getWorkspaceUserSeatFactory({ db: mainDb }),
|
||||
getWorkspaceDefaultSeatType: getWorkspaceDefaultSeatTypeFactory({
|
||||
getWorkspace: getWorkspaceFactory({ db: trx })
|
||||
getWorkspace: getWorkspaceFactory({ db: mainDb })
|
||||
}),
|
||||
eventEmit: getEventBus().emit
|
||||
}),
|
||||
assignWorkspaceSeat: assignWorkspaceSeatFactory({
|
||||
createWorkspaceSeat: createWorkspaceSeatFactory({ db: trx }),
|
||||
createWorkspaceSeat: createWorkspaceSeatFactory({ db: mainDb }),
|
||||
getWorkspaceRoleForUser: getWorkspaceRoleForUserFactory({
|
||||
db: trx
|
||||
db: mainDb
|
||||
}),
|
||||
eventEmit: getEventBus().emit,
|
||||
getWorkspaceUserSeat: getWorkspaceUserSeatFactory({ db: trx })
|
||||
getWorkspaceUserSeat: getWorkspaceUserSeatFactory({ db: mainDb })
|
||||
})
|
||||
}),
|
||||
findInvite: findInviteFactory({ db: trx }),
|
||||
deleteInvite: deleteInviteFactory({ db: trx })
|
||||
findInvite: findInviteFactory({ db: mainDb }),
|
||||
deleteInvite: deleteInviteFactory({ db: mainDb })
|
||||
}),
|
||||
linkUserWithSsoProvider: linkUserWithSsoProviderFactory({
|
||||
findEmailsByUserId: findEmailsByUserIdFactory({ db: trx }),
|
||||
createUserEmail: createUserEmailFactory({ db: trx }),
|
||||
updateUserEmail: updateUserEmailFactory({ db: trx }),
|
||||
findEmailsByUserId: findEmailsByUserIdFactory({ db: mainDb }),
|
||||
createUserEmail: createUserEmailFactory({ db: mainDb }),
|
||||
updateUserEmail: updateUserEmailFactory({ db: mainDb }),
|
||||
logger: req.log
|
||||
}),
|
||||
upsertUserSsoSession: upsertUserSsoSessionFactory({ db: trx })
|
||||
upsertUserSsoSession: upsertUserSsoSessionFactory({ db: mainDb })
|
||||
})
|
||||
|
||||
await handleOidcCallback(req, res, next)
|
||||
},
|
||||
{ db }
|
||||
{
|
||||
dbs: await getAllRegisteredDbs(),
|
||||
logger: req.log,
|
||||
name: 'oidc callback'
|
||||
}
|
||||
)
|
||||
|
||||
return next()
|
||||
|
||||
@@ -26,6 +26,7 @@ import { beforeEachContext } from '@/test/hooks'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
describe('Workspace workspaceSeat services', () => {
|
||||
describe('assignWorkspaceSeatFactory', () => {
|
||||
@@ -34,7 +35,9 @@ describe('Workspace workspaceSeat services', () => {
|
||||
name: createRandomString(),
|
||||
email: createRandomEmail(),
|
||||
role: Roles.Server.Admin,
|
||||
verified: true
|
||||
verified: true,
|
||||
suuid: v4(),
|
||||
createdAt: new Date()
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
@@ -57,7 +60,9 @@ describe('Workspace workspaceSeat services', () => {
|
||||
name: createRandomString(),
|
||||
email: createRandomEmail(),
|
||||
role: Roles.Server.User,
|
||||
verified: true
|
||||
verified: true,
|
||||
suuid: v4(),
|
||||
createdAt: new Date()
|
||||
}
|
||||
await createTestUser(user)
|
||||
|
||||
@@ -85,14 +90,18 @@ describe('Workspace workspaceSeat services', () => {
|
||||
name: createRandomString(),
|
||||
email: createRandomEmail(),
|
||||
role: Roles.Server.Admin,
|
||||
verified: true
|
||||
verified: true,
|
||||
suuid: v4(),
|
||||
createdAt: new Date()
|
||||
}
|
||||
const testUser: BasicTestUser = {
|
||||
id: '',
|
||||
name: createRandomString(),
|
||||
email: createRandomEmail(),
|
||||
role: Roles.Server.User,
|
||||
verified: true
|
||||
verified: true,
|
||||
suuid: v4(),
|
||||
createdAt: new Date()
|
||||
}
|
||||
const workspace: BasicTestWorkspace = {
|
||||
ownerId: '',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import '../bootstrap.js'
|
||||
import { db } from '@/db/knex'
|
||||
import { logger } from '@/observability/logging'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import {
|
||||
@@ -25,40 +24,12 @@ import {
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import axios from 'axios'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
import { asMultiregionalOperation } from '@/modules/shared/command.js'
|
||||
import { getAllRegisteredDbs } from '@/modules/multiregion/utils/dbSelector.js'
|
||||
|
||||
const main = async () => {
|
||||
const userInputs: Array<Parameters<typeof createUser>[0]> = (
|
||||
const userInputs: Array<Parameters<ReturnType<typeof createUserFactory>>[0]> = (
|
||||
await axios.get('https://randomuser.me/api/?results=250')
|
||||
).data.results.map(
|
||||
(user: {
|
||||
@@ -74,7 +45,55 @@ const main = async () => {
|
||||
}
|
||||
)
|
||||
|
||||
await Promise.all(userInputs.map((userInput) => createUser(userInput)))
|
||||
await Promise.all(
|
||||
userInputs.map(async (userInput) =>
|
||||
asMultiregionalOperation(
|
||||
async ({ mainDb, allDbs, emit }) => {
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo: getServerInfoFactory({ db: mainDb }),
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
storeUser: async (...params) => {
|
||||
const [user] = await Promise.all(
|
||||
allDbs.map((db) => storeUserFactory({ db })(...params))
|
||||
)
|
||||
|
||||
return user
|
||||
},
|
||||
countAdminUsers: countAdminUsersFactory({ db: mainDb }),
|
||||
storeUserAcl: storeUserAclFactory({ db: mainDb }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db: mainDb }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({
|
||||
db: mainDb
|
||||
}),
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db: mainDb }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db: mainDb })
|
||||
}),
|
||||
requestNewEmailVerification: requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
getUser: getUserFactory({ db: mainDb }),
|
||||
getServerInfo: getServerInfoFactory({ db: mainDb }),
|
||||
deleteOldAndInsertNewVerification:
|
||||
deleteOldAndInsertNewVerificationFactory({ db: mainDb }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
}),
|
||||
emitEvent: emit
|
||||
})
|
||||
|
||||
return await createUser(userInput)
|
||||
},
|
||||
{
|
||||
logger,
|
||||
name: 'seedUsers',
|
||||
dbs: await getAllRegisteredDbs()
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
void main()
|
||||
|
||||
@@ -29,47 +29,22 @@ import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repos
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import { getTestRegionClients } from '@/modules/multiregion/tests/helpers'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { asMultiregionalOperation } from '@/modules/shared/command'
|
||||
import { logger } from '@/observability/logging'
|
||||
import { createTestContext, testApolloServer } from '@/test/graphqlHelper'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import type { ServerScope } from '@speckle/shared'
|
||||
import { wait } from '@speckle/shared'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { assign, isArray, isNumber, omit, times } from 'lodash-es'
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail,
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
const createPersonalAccessToken = createPersonalAccessTokenFactory({
|
||||
storeApiToken: storeApiTokenFactory({ db }),
|
||||
storeTokenScopes: storeTokenScopesFactory({ db }),
|
||||
@@ -127,10 +102,68 @@ export async function createTestUser(userObj?: Partial<BasicTestUser>) {
|
||||
setVal('email', createRandomEmail().toLowerCase())
|
||||
}
|
||||
|
||||
const id = await createUser(omit(baseUser, ['id', 'allowPersonalEmail']), {
|
||||
skipPropertyValidation: true,
|
||||
allowPersonalEmail: baseUser.allowPersonalEmail
|
||||
})
|
||||
if (!baseUser.suuid) {
|
||||
setVal('suuid', v4())
|
||||
}
|
||||
|
||||
if (typeof baseUser.verified !== 'boolean') {
|
||||
setVal('verified', false)
|
||||
}
|
||||
|
||||
if (!baseUser.createdAt) {
|
||||
setVal('createdAt', new Date())
|
||||
}
|
||||
|
||||
const id = await asMultiregionalOperation(
|
||||
async ({ mainDb, allDbs, emit }) => {
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo: getServerInfoFactory({ db: mainDb }),
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
storeUser: async (args) => {
|
||||
const p = await Promise.all(
|
||||
allDbs.map(async (db) => storeUserFactory({ db })(args))
|
||||
)
|
||||
|
||||
return p[0]
|
||||
},
|
||||
countAdminUsers: countAdminUsersFactory({ db: mainDb }),
|
||||
storeUserAcl: storeUserAclFactory({ db: mainDb }),
|
||||
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db: mainDb }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({
|
||||
db: mainDb
|
||||
}),
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db: mainDb }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db: mainDb })
|
||||
}),
|
||||
requestNewEmailVerification: requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db: mainDb }),
|
||||
getUser: getUserFactory({ db: mainDb }),
|
||||
getServerInfo: getServerInfoFactory({ db: mainDb }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory(
|
||||
{ db: mainDb }
|
||||
),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
}),
|
||||
emitEvent: emit
|
||||
})
|
||||
|
||||
return await createUser(omit(baseUser, ['id', 'allowPersonalEmail']), {
|
||||
skipPropertyValidation: true,
|
||||
allowPersonalEmail: baseUser.allowPersonalEmail
|
||||
})
|
||||
},
|
||||
{
|
||||
dbs: await getTestRegionClients(),
|
||||
logger,
|
||||
name: 'createUser'
|
||||
}
|
||||
)
|
||||
|
||||
setVal('id', id)
|
||||
|
||||
return baseUser
|
||||
@@ -162,7 +195,9 @@ export const buildBasicTestUser = (overrides?: Partial<BasicTestUser>): BasicTes
|
||||
id: cryptoRandomString({ length: 10 }),
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
email: createRandomEmail(),
|
||||
verified: true
|
||||
verified: true,
|
||||
createdAt: new Date(),
|
||||
suuid: v4()
|
||||
},
|
||||
overrides
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user