diff --git a/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue b/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue index 2b339b982..96bc6bbd5 100644 --- a/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue +++ b/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue @@ -6,7 +6,7 @@ v-model="email" type="email" name="email" - label="Email" + label="Work email" placeholder="Email" size="lg" color="foundation" @@ -69,17 +69,14 @@ import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables import { ensureError } from '@speckle/shared' import { useAuthManager } from '~~/lib/auth/composables/auth' import { loginRoute } from '~~/lib/common/helpers/route' -import { passwordRules } from '~~/lib/auth/helpers/validation' +import { + passwordRules, + doesNotContainBlockedDomain +} from '~~/lib/auth/helpers/validation' import { graphql } from '~~/lib/common/generated/gql' import type { ServerTermsOfServicePrivacyPolicyFragmentFragment } from '~~/lib/common/generated/gql/graphql' import { useMounted } from '@vueuse/core' -/** - * TODO: - * - (BE) Password strength check? Do we want to use it anymore? - * - Dim's answer: no, `passwordRules` are legit enough for now. - */ - graphql(` fragment ServerTermsOfServicePrivacyPolicyFragment on ServerInfo { termsOfService @@ -99,13 +96,18 @@ const router = useRouter() const { signUpWithEmail, inviteToken } = useAuthManager() const { triggerNotification } = useGlobalToast() const isMounted = useMounted() +const isNoPersonalEmailsEnabled = useIsNoPersonalEmailsEnabled() const newsletterConsent = defineModel('newsletterConsent', { required: true }) const loading = ref(false) const password = ref('') const email = ref('') -const emailRules = [isEmail] +const emailRules = computed(() => + inviteToken.value || !isNoPersonalEmailsEnabled.value + ? [isEmail] + : [isEmail, doesNotContainBlockedDomain] +) const nameRules = [isRequired] const isEmailDisabled = computed(() => !!props.inviteEmail?.length || loading.value) diff --git a/packages/frontend-2/composables/globals.ts b/packages/frontend-2/composables/globals.ts index c4e8b0c97..16da9c187 100644 --- a/packages/frontend-2/composables/globals.ts +++ b/packages/frontend-2/composables/globals.ts @@ -71,4 +71,12 @@ export const useIsBillingIntegrationEnabled = () => { return ref(FF_BILLING_INTEGRATION_ENABLED) } +export const useIsNoPersonalEmailsEnabled = () => { + const { + public: { FF_NO_PERSONAL_EMAILS_ENABLED } + } = useRuntimeConfig() + + return ref(FF_NO_PERSONAL_EMAILS_ENABLED) +} + export { useGlobalToast, useActiveUser, usePageQueryStandardFetchPolicy } diff --git a/packages/frontend-2/lib/auth/helpers/validation.ts b/packages/frontend-2/lib/auth/helpers/validation.ts index 9c877d077..393aec621 100644 --- a/packages/frontend-2/lib/auth/helpers/validation.ts +++ b/packages/frontend-2/lib/auth/helpers/validation.ts @@ -1,4 +1,5 @@ import { isStringOfLength, stringContains } from '~~/lib/common/helpers/validation' +import { blockedDomains } from '@speckle/shared' export const passwordLongEnough = isStringOfLength({ minLength: 8 }) export const passwordHasAtLeastOneNumber = stringContains({ @@ -20,3 +21,10 @@ export const passwordRules = [ passwordHasAtLeastOneLowercaseLetter, passwordHasAtLeastOneUppercaseLetter ] + +export const doesNotContainBlockedDomain = (val: string) => { + const domain = val.split('@')[1]?.toLowerCase() + return domain && blockedDomains.includes(domain) + ? 'Please use your work email instead of a personal email address' + : true +} diff --git a/packages/server/modules/auth/strategies/local.ts b/packages/server/modules/auth/strategies/local.ts index 1bc363ea7..c041028d6 100644 --- a/packages/server/modules/auth/strategies/local.ts +++ b/packages/server/modules/auth/strategies/local.ts @@ -5,8 +5,11 @@ import { } from '@/modules/core/services/ratelimiter' import { getIpFromRequest } from '@/modules/shared/utils/ip' import { InviteNotFoundError } from '@/modules/serverinvites/errors' -import { UserInputError, PasswordTooShortError } from '@/modules/core/errors/userinput' - +import { + UserInputError, + PasswordTooShortError, + BlockedEmailDomainError +} from '@/modules/core/errors/userinput' import { ServerInviteResourceType } from '@/modules/serverinvites/domain/constants' import { getResourceTypeRole } from '@/modules/serverinvites/helpers/core' import { AuthStrategyMetadata, AuthStrategyBuilder } from '@/modules/auth/helpers/types' @@ -117,7 +120,7 @@ const localStrategyBuilderFactory = invite = await deps.validateServerInvite(user.email, req.session.token) } - // 3. at this point we know, that we have one of these cases: + // 3.. at this point we know, that we have one of these cases: // * the server is invite only and the user has a valid invite // * the server public and the user has a valid invite // * the server public and the user doesn't have an invite @@ -155,6 +158,7 @@ const localStrategyBuilderFactory = case PasswordTooShortError: case UserInputError: case InviteNotFoundError: + case BlockedEmailDomainError: req.log.info({ err }, 'Error while registering.') return res.status(400).send({ err: e.message }) default: diff --git a/packages/server/modules/auth/tests/apps.graphql.spec.js b/packages/server/modules/auth/tests/apps.graphql.spec.js index 5f03b216d..91135eb51 100644 --- a/packages/server/modules/auth/tests/apps.graphql.spec.js +++ b/packages/server/modules/auth/tests/apps.graphql.spec.js @@ -130,7 +130,7 @@ describe('GraphQL @apps-api', () => { ;({ sendRequest } = await initializeTestServer(ctx)) testUser = { name: 'Dimitrie Stefanescu', - email: 'didimitrie@gmail.com', + email: 'didimitrie@example.org', password: 'wtfwtfwtf' } diff --git a/packages/server/modules/auth/tests/apps.spec.js b/packages/server/modules/auth/tests/apps.spec.js index 2d25a612d..736b52447 100644 --- a/packages/server/modules/auth/tests/apps.spec.js +++ b/packages/server/modules/auth/tests/apps.spec.js @@ -158,7 +158,7 @@ const validateToken = validateTokenFactory({ describe('Services @apps-services', () => { const actor = { name: 'Dimitrie Stefanescu', - email: 'didimitrie@gmail.com', + email: 'didimitrie@example.org', password: 'wtfwtfwtf' } @@ -495,7 +495,7 @@ describe('Services @apps-services', () => { }) const secondUser = { name: 'Dimitrie Stefanescu', - email: 'didimitrie.wow@gmail.com', + email: 'didimitrie.wow@example.org', password: 'wtfwtfwtf' } diff --git a/packages/server/modules/auth/tests/helpers/registration.ts b/packages/server/modules/auth/tests/helpers/registration.ts index a3f348a51..3e8048d25 100644 --- a/packages/server/modules/auth/tests/helpers/registration.ts +++ b/packages/server/modules/auth/tests/helpers/registration.ts @@ -233,7 +233,7 @@ export type LocalAuthRestApiHelpers = ReturnType export const generateRegistrationParams = (): RegisterParams => ({ challenge: faker.string.uuid(), user: { - email: (random(0, 1000) + faker.internet.email()).toLowerCase(), + email: `${random(0, 1000)}@example.org`.toLowerCase(), password: faker.internet.password(), name: faker.person.fullName() } diff --git a/packages/server/modules/auth/tests/integration/registration.spec.ts b/packages/server/modules/auth/tests/integration/registration.spec.ts index 4af072c44..2bdf85e25 100644 --- a/packages/server/modules/auth/tests/integration/registration.spec.ts +++ b/packages/server/modules/auth/tests/integration/registration.spec.ts @@ -8,6 +8,7 @@ import { import { AllScopes } from '@/modules/core/helpers/mainConstants' import { updateServerInfoFactory } from '@/modules/core/repositories/server' import { findInviteFactory } from '@/modules/serverinvites/repositories/serverInvites' +import { getFeatureFlags } from '@/modules/shared/helpers/envHelper' import { expectToThrow, itEach } from '@/test/assertionHelper' import { BasicTestUser, createTestUsers } from '@/test/authHelper' import { @@ -33,6 +34,8 @@ import { import { Roles } from '@speckle/shared' import { expect } from 'chai' +const { FF_NO_PERSONAL_EMAILS_ENABLED } = getFeatureFlags() + const updateServerInfo = updateServerInfoFactory({ db }) describe('Server registration', () => { @@ -96,6 +99,18 @@ describe('Server registration', () => { expect(user.emails.every((e) => !e.verified)).to.be.true }) + FF_NO_PERSONAL_EMAILS_ENABLED + ? it('rejects registration with blocked email domain', async () => { + const params = generateRegistrationParams() + params.user.email = 'test@gmail.com' + + const error = await expectToThrow(() => restApi.register(params)) + expect(error.message).to.contain( + 'Please use your work email instead of a personal email address' + ) + }) + : null + it('fails without challenge', async () => { const params = generateRegistrationParams() params.challenge = '' diff --git a/packages/server/modules/core/errors/userinput.ts b/packages/server/modules/core/errors/userinput.ts index e6a08cf56..c3bede281 100644 --- a/packages/server/modules/core/errors/userinput.ts +++ b/packages/server/modules/core/errors/userinput.ts @@ -25,3 +25,9 @@ export class UnverifiedEmailSSOLoginError extends UserInputError; }; export type DiscoverableWorkspaceCollaboratorCollection = { @@ -5908,7 +5908,7 @@ export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig = { - avatar?: Resolver; + avatar?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/packages/server/modules/core/services/users/management.ts b/packages/server/modules/core/services/users/management.ts index 99b06e05d..65b9bc0a8 100644 --- a/packages/server/modules/core/services/users/management.ts +++ b/packages/server/modules/core/services/users/management.ts @@ -21,11 +21,16 @@ import { UserUpdateError, UserValidationError } from '@/modules/core/errors/user' -import { PasswordTooShortError, UserInputError } from '@/modules/core/errors/userinput' +import { + BlockedEmailDomainError, + PasswordTooShortError, + UserInputError +} from '@/modules/core/errors/userinput' import { UserUpdateInput } from '@/modules/core/graph/generated/graphql' import type { UserRecord } from '@/modules/core/helpers/userHelper' import { sanitizeImageUrl } from '@/modules/shared/helpers/sanitization' import { + blockedDomains, isNullOrUndefined, NullableKeysToOptional, Roles, @@ -48,6 +53,9 @@ import { DeleteAllUserInvites } from '@/modules/serverinvites/domain/operations' import { GetServerInfo } from '@/modules/core/domain/server/operations' import { EventBusEmit } from '@/modules/shared/services/eventBus' import { UserEvents } from '@/modules/core/domain/users/events' +import { getFeatureFlags } from '@/modules/shared/helpers/envHelper' + +const { FF_NO_PERSONAL_EMAILS_ENABLED } = getFeatureFlags() export const MINIMUM_PASSWORD_LENGTH = 8 @@ -163,6 +171,15 @@ export const createUserFactory = if (!finalUser.email?.length) throw new UserInputError('E-mail address is required') + // Temporary experiment: require work emails for all new users + const isBlockedDomain = blockedDomains.includes( + finalUser.email.split('@')[1]?.toLowerCase() + ) + const requireWorkDomain = + !user?.signUpContext?.isInvite && FF_NO_PERSONAL_EMAILS_ENABLED + + if (requireWorkDomain && isBlockedDomain) throw new BlockedEmailDomainError() + let expectedRole = null if (finalUser.role) { const isValidRole = Object.values(Roles.Server).includes(finalUser.role) diff --git a/packages/server/modules/core/tests/apitokens.spec.ts b/packages/server/modules/core/tests/apitokens.spec.ts index b0e7007ca..3e2a0265d 100644 --- a/packages/server/modules/core/tests/apitokens.spec.ts +++ b/packages/server/modules/core/tests/apitokens.spec.ts @@ -49,7 +49,7 @@ const createAppToken = createAppTokenFactory({ describe('API Tokens', () => { const user1: BasicTestUser = { name: 'Dimitrie Stefanescu', - email: 'didimitrie@gmail.com', + email: 'didimitrie@example.org', password: 'sn3aky-1337-b1m', id: '' } @@ -321,7 +321,7 @@ describe('API Tokens', () => { describe('with limited resource access', () => { const user2: BasicTestUser = { name: 'Some other guy', - email: 'bababooey@gmail.com', + email: 'bababooey@example.org', password: 'sn3aky-1337-b1m', id: '' } diff --git a/packages/server/modules/core/tests/batchCommits.spec.ts b/packages/server/modules/core/tests/batchCommits.spec.ts index 61cb088bc..5c334c2cc 100644 --- a/packages/server/modules/core/tests/batchCommits.spec.ts +++ b/packages/server/modules/core/tests/batchCommits.spec.ts @@ -53,13 +53,13 @@ describe('Batch commits', () => { const me: BasicTestUser = { name: 'batch commit dude', - email: 'batchcommitguy@gmail.com', + email: 'batchcommitguy@example.org', id: '' } const otherGuy: BasicTestUser = { name: 'other batch commit guy', - email: 'otherbatchcommitguy@gmail.com', + email: 'otherbatchcommitguy@example.org', id: '' } diff --git a/packages/server/modules/core/tests/branches.spec.ts b/packages/server/modules/core/tests/branches.spec.ts index eccb96686..09c16817e 100644 --- a/packages/server/modules/core/tests/branches.spec.ts +++ b/packages/server/modules/core/tests/branches.spec.ts @@ -191,7 +191,7 @@ const createObject = createObjectFactory({ describe('Branches @core-branches', () => { const user = { name: 'Dimitrie Stefanescu', - email: 'didimitrie4342@gmail.com', + email: 'didimitrie4342@example.org', password: 'sn3aky-1337-b1m', id: '' } diff --git a/packages/server/modules/core/tests/commits.spec.ts b/packages/server/modules/core/tests/commits.spec.ts index 38de3a446..3bd275480 100644 --- a/packages/server/modules/core/tests/commits.spec.ts +++ b/packages/server/modules/core/tests/commits.spec.ts @@ -211,7 +211,7 @@ const createObject = createObjectFactory({ describe('Commits @core-commits', () => { const user = { name: 'Dimitrie Stefanescu', - email: 'didimitrie4342@gmail.com', + email: 'didimitrie4342@example.org', password: 'sn3aky-1337-b1m', id: '' } diff --git a/packages/server/modules/core/tests/favoriteStreams.spec.js b/packages/server/modules/core/tests/favoriteStreams.spec.js index f64962042..dc60442be 100644 --- a/packages/server/modules/core/tests/favoriteStreams.spec.js +++ b/packages/server/modules/core/tests/favoriteStreams.spec.js @@ -221,12 +221,12 @@ describe('Favorite streams', () => { } const me = { name: 'Itsa Me', - email: 'me@gmail.com', + email: 'me@example.org', password: 'sn3aky-1337-b1m' } const otherGuy = { name: 'Some Other DUde', - email: 'otherguy@gmail.com', + email: 'otherguy@example.org', password: 'sn3aky-1337-b1m' } diff --git a/packages/server/modules/core/tests/generic.spec.js b/packages/server/modules/core/tests/generic.spec.js index f54a2d693..b21465302 100644 --- a/packages/server/modules/core/tests/generic.spec.js +++ b/packages/server/modules/core/tests/generic.spec.js @@ -240,12 +240,12 @@ describe('Generic AuthN & AuthZ controller tests', () => { } const serverOwner = { name: 'Itsa Me', - email: 'me@gmail.com', + email: 'me@example.org', password: 'sn3aky-1337-b1m' } const otherGuy = { name: 'Some Other DUde', - email: 'otherguy@gmail.com', + email: 'otherguy@example.org', password: 'sn3aky-1337-b1m' } diff --git a/packages/server/modules/core/tests/objects.spec.js b/packages/server/modules/core/tests/objects.spec.js index ce45c346b..02546a52b 100644 --- a/packages/server/modules/core/tests/objects.spec.js +++ b/packages/server/modules/core/tests/objects.spec.js @@ -179,7 +179,7 @@ const getObjects = getStreamObjectsFactory({ db }) describe('Objects @core-objects', () => { const userOne = { name: 'Dimitrie Stefanescu', - email: 'didimitrie43@gmail.com', + email: 'didimitrie43@example.org', password: 'sn3aky-1337-b1m' } diff --git a/packages/server/modules/core/tests/streams.spec.ts b/packages/server/modules/core/tests/streams.spec.ts index b9a8481e4..fd4240b4b 100644 --- a/packages/server/modules/core/tests/streams.spec.ts +++ b/packages/server/modules/core/tests/streams.spec.ts @@ -193,14 +193,14 @@ const createObject = createObjectFactory({ describe('Streams @core-streams', () => { const userOne: BasicTestUser = { name: 'Dimitrie Stefanescu', - email: 'didimitrie@gmail.com', + email: 'didimitrie@example.org', password: 'sn3aky-1337-b1m', id: '' } const userTwo: BasicTestUser = { name: 'Dimitrie Stefanescu 2', - email: 'didimitrie2@gmail.com', + email: 'didimitrie2@example.org', password: 'sn3aky-1337-b1m', id: '' } diff --git a/packages/server/modules/core/tests/users.spec.ts b/packages/server/modules/core/tests/users.spec.ts index 7918767e0..1b00f7f1f 100644 --- a/packages/server/modules/core/tests/users.spec.ts +++ b/packages/server/modules/core/tests/users.spec.ts @@ -266,7 +266,7 @@ const createObject = createObjectFactory({ describe('Actors & Tokens @user-services', () => { const myTestActor = { name: 'Dimitrie Stefanescu', - email: 'didimitrie@gmail.com', + email: 'didimitrie@example.org', password: 'sn3aky-1337-b1m', id: '' } diff --git a/packages/server/modules/core/tests/usersAdminList.spec.ts b/packages/server/modules/core/tests/usersAdminList.spec.ts index 7c87f6a19..bbcfde3d4 100644 --- a/packages/server/modules/core/tests/usersAdminList.spec.ts +++ b/packages/server/modules/core/tests/usersAdminList.spec.ts @@ -139,7 +139,7 @@ async function getOrderedUserIds() { describe('[Admin users list]', () => { const me = { name: 'Mr Server Admin Dude', - email: 'adminuserguy@gmail.com', + email: 'adminuserguy@example.org', password: 'sn3aky-1337-b1m', id: undefined as Optional, verified: false @@ -197,7 +197,7 @@ describe('[Admin users list]', () => { name: `User #${i} - ${ remainingSearchQueryUserCount-- >= 1 ? SEARCH_QUERY : '' }`, - email: `speckleuser${i}@gmail.com`, + email: `speckleuser${i}@example.org`, password: 'sn3aky-1337-b1m', verified: false }) @@ -225,7 +225,7 @@ describe('[Admin users list]', () => { { email: `randominvitee${i}.${ remainingSearchQueryInviteCount-- >= 1 ? SEARCH_QUERY : '' - }@gmail.com` + }@example.org` }, randomEl(userIds) ) @@ -237,7 +237,7 @@ describe('[Admin users list]', () => { const { id: streamId, ownerId } = randomEl(streamData) const email = `streamrandominvitee${i}.${ remainingSearchQueryInviteCount-- >= 1 ? SEARCH_QUERY : '' - }@gmail.com` + }@example.org` await createInviteDirectly( { diff --git a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts index 4559b4ecd..69760c280 100644 --- a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts +++ b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts @@ -943,7 +943,7 @@ export type DiscoverableStreamsSortingInput = { export type DiscoverableWorkspaceCollaborator = { __typename?: 'DiscoverableWorkspaceCollaborator'; - avatar: Scalars['String']['output']; + avatar?: Maybe; }; export type DiscoverableWorkspaceCollaboratorCollection = { diff --git a/packages/server/modules/fileuploads/tests/fileuploads.integration.spec.ts b/packages/server/modules/fileuploads/tests/fileuploads.integration.spec.ts index fce77ec0a..146f6741a 100644 --- a/packages/server/modules/fileuploads/tests/fileuploads.integration.spec.ts +++ b/packages/server/modules/fileuploads/tests/fileuploads.integration.spec.ts @@ -130,7 +130,7 @@ describe('FileUploads @fileuploads', () => { const userOne = { name: 'User', - email: 'user@gmail.com', + email: 'user@example.org', password: 'jdsadjsadasfdsa' } diff --git a/packages/server/modules/serverinvites/tests/invites.spec.ts b/packages/server/modules/serverinvites/tests/invites.spec.ts index 1f1b362ec..1cf8172d0 100644 --- a/packages/server/modules/serverinvites/tests/invites.spec.ts +++ b/packages/server/modules/serverinvites/tests/invites.spec.ts @@ -57,14 +57,14 @@ const mailerMock = EmailSendingServiceMock describe('[Stream & Server Invites]', () => { const me: BasicTestUser = { name: 'Authenticated server invites guy', - email: 'serverinvitesguy@gmail.com', + email: 'serverinvitesguy@example.org', password: 'sn3aky-1337-b1m', id: '' } const otherGuy: BasicTestUser = { name: 'Some Other DUde', - email: 'otherguy111@gmail.com', + email: 'otherguy111@example.org', password: 'sn3aky-1337-b1m', id: '' } @@ -883,7 +883,7 @@ describe('[Stream & Server Invites]', () => { const ownInvitesGuy: BasicTestUser = { name: "Some guy who's invited a lot", - email: 'mrinvitedguy111@gmail.com', + email: 'mrinvitedguy111@example.org', password: 'sn3aky-1337-b1m', id: '' } diff --git a/packages/server/modules/webhooks/tests/webhooks.spec.js b/packages/server/modules/webhooks/tests/webhooks.spec.js index 0befc5da0..99d6e83c7 100644 --- a/packages/server/modules/webhooks/tests/webhooks.spec.js +++ b/packages/server/modules/webhooks/tests/webhooks.spec.js @@ -165,7 +165,7 @@ describe('Webhooks @webhooks', () => { const userOne = { name: 'User', - email: 'user@gmail.com', + email: 'user@example.org', password: 'jdsadjsadasfdsa' } @@ -327,7 +327,7 @@ describe('Webhooks @webhooks', () => { describe('GraphQL API Webhooks @webhooks-api', () => { const userTwo = { name: 'User2', - email: 'user2@gmail.com', + email: 'user2@example.org', password: 'jdsadjsadasfdsa' } diff --git a/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts b/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts index 508774b22..38229b754 100644 --- a/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts +++ b/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts @@ -84,13 +84,13 @@ describe('Workspaces Invites GQL', () => { const me: BasicTestUser = { name: 'Authenticated server invites guy', - email: 'serverinvitesguy@gmail.com', + email: 'serverinvitesguy@example.org', id: '' } const otherGuy: BasicTestUser = { name: 'Some Other DUde', - email: 'otherguy111@gmail.com', + email: 'otherguy111@example.org', id: '' } @@ -249,7 +249,7 @@ describe('Workspaces Invites GQL', () => { const res = await gqlHelpers.batchCreateInvites({ workspaceId: myFirstWorkspace.id, input: times(11, () => ({ - email: `asdasasd${Math.random()}@gmail.com`, + email: `asdasasd${Math.random()}@example.org`, role: WorkspaceRole.Member })) }) @@ -264,7 +264,7 @@ describe('Workspaces Invites GQL', () => { const res = await gqlHelpers.batchCreateInvites({ workspaceId: otherGuysWorkspace.id, input: times(10, () => ({ - email: `asdasasd${Math.random()}@gmail.com`, + email: `asdasasd${Math.random()}@example.org`, role: WorkspaceRole.Member })) }) @@ -312,7 +312,7 @@ describe('Workspaces Invites GQL', () => { const res = await gqlHelpers.batchCreateInvites({ workspaceId: myFirstWorkspace.id, input: times(count, () => ({ - email: `asdasasd${Math.random()}@gmail.com`, + email: `asdasasd${Math.random()}@example.org`, role: WorkspaceRole.Member })) }) @@ -483,7 +483,7 @@ describe('Workspaces Invites GQL', () => { const workspaceMemberWithNoProjectAccess: BasicTestUser = { name: 'Workspace Member With No Project Access #1', - email: 'workspaceMemberWithNoProjectAccess1@gmail.com', + email: 'workspaceMemberWithNoProjectAccess1@example.org', id: '' } @@ -678,7 +678,7 @@ describe('Workspaces Invites GQL', () => { { workspaceId: myAdministrationWorkspace.id, input: times(10, () => ({ - email: `aszzzdasasd${Math.random()}@gmail.com`, + email: `aszzzdasasd${Math.random()}@example.org`, role: WorkspaceRole.Member })) }, @@ -1456,7 +1456,7 @@ describe('Workspaces Invites GQL', () => { const otherWorkspaceOwner: BasicTestUser = { name: 'Other Workspace Owner', - email: 'otherworkspaceowner@gmail.com', + email: 'otherworkspaceowner@example.org', id: '' } diff --git a/packages/server/test/authHelper.ts b/packages/server/test/authHelper.ts index add6c5e18..49c162728 100644 --- a/packages/server/test/authHelper.ts +++ b/packages/server/test/authHelper.ts @@ -35,6 +35,7 @@ import { getEventBus } from '@/modules/shared/services/eventBus' import { createTestContext, testApolloServer } from '@/test/graphqlHelper' import { faker } from '@faker-js/faker' import { ServerScope, wait } from '@speckle/shared' +import cryptoRandomString from 'crypto-random-string' import { isArray, isNumber, kebabCase, omit, times } from 'lodash' const getServerInfo = getServerInfoFactory({ db }) @@ -88,7 +89,7 @@ export type BasicTestUser = { const initTestUser = (user: Partial): BasicTestUser => ({ name: faker.person.fullName(), - email: faker.internet.email(), + email: `${cryptoRandomString({ length: 15 })}@example.org`, id: '', ...user }) @@ -115,7 +116,7 @@ export async function createTestUser(userObj?: Partial) { } if (!baseUser.email) { - setVal('email', `${kebabCase(baseUser.name)}@someemail.com`) + setVal('email', `${kebabCase(baseUser.name)}@example.org`) } const id = await createUser(omit(baseUser, ['id']), { skipPropertyValidation: true }) diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index f5ceb015e..106fad938 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -944,7 +944,7 @@ export type DiscoverableStreamsSortingInput = { export type DiscoverableWorkspaceCollaborator = { __typename?: 'DiscoverableWorkspaceCollaborator'; - avatar: Scalars['String']['output']; + avatar?: Maybe; }; export type DiscoverableWorkspaceCollaboratorCollection = { @@ -5663,7 +5663,7 @@ export type GetWorkspaceBySlugQuery = { __typename?: 'Query', workspaceBySlug: { export type GetActiveUserDiscoverableWorkspacesQueryVariables = Exact<{ [key: string]: never; }>; -export type GetActiveUserDiscoverableWorkspacesQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', discoverableWorkspaces: Array<{ __typename?: 'LimitedWorkspace', id: string, name: string, description?: string | null, team?: { __typename?: 'DiscoverableWorkspaceCollaboratorCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'DiscoverableWorkspaceCollaborator', avatar: string }> } | null }> } | null }; +export type GetActiveUserDiscoverableWorkspacesQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', discoverableWorkspaces: Array<{ __typename?: 'LimitedWorkspace', id: string, name: string, description?: string | null, team?: { __typename?: 'DiscoverableWorkspaceCollaboratorCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'DiscoverableWorkspaceCollaborator', avatar?: string | null }> } | null }> } | null }; export type UpdateWorkspaceMutationVariables = Exact<{ input: WorkspaceUpdateInput; diff --git a/packages/shared/src/environment/index.ts b/packages/shared/src/environment/index.ts index c20c99896..27edff8ac 100644 --- a/packages/shared/src/environment/index.ts +++ b/packages/shared/src/environment/index.ts @@ -66,6 +66,11 @@ const parseFeatureFlags = () => { schema: z.boolean(), defaults: { production: false, _: false } }, + // Enable to not allow personal emails + FF_NO_PERSONAL_EMAILS_ENABLED: { + schema: z.boolean(), + defaults: { production: false, _: false } + }, // Fixes the streaming of objects by ensuring that the database stream is closed properly FF_OBJECTS_STREAMING_FIX: { schema: z.boolean(), @@ -104,6 +109,7 @@ export function getFeatureFlags(): { FF_FORCE_ONBOARDING: boolean FF_OBJECTS_STREAMING_FIX: boolean FF_MOVE_PROJECT_REGION_ENABLED: boolean + FF_NO_PERSONAL_EMAILS_ENABLED: boolean } { if (!parsedFlags) parsedFlags = parseFeatureFlags() return parsedFlags diff --git a/utils/helm/speckle-server/templates/_helpers.tpl b/utils/helm/speckle-server/templates/_helpers.tpl index 548eaf656..f035e5558 100644 --- a/utils/helm/speckle-server/templates/_helpers.tpl +++ b/utils/helm/speckle-server/templates/_helpers.tpl @@ -563,6 +563,9 @@ Generate the environment variables for Speckle server and Speckle objects deploy - name: FF_WORKSPACES_MODULE_ENABLED value: {{ .Values.featureFlags.workspacesModuleEnabled | quote }} +- name: FF_NO_PERSONAL_EMAILS_ENABLED + value: {{ .Values.featureFlags.noPersonalEmailsEnabled | quote }} + - name: FF_WORKSPACES_SSO_ENABLED value: {{ .Values.featureFlags.workspacesSSOEnabled | quote }} diff --git a/utils/helm/speckle-server/templates/frontend_2/deployment.yml b/utils/helm/speckle-server/templates/frontend_2/deployment.yml index 05ad65e85..638412dd1 100644 --- a/utils/helm/speckle-server/templates/frontend_2/deployment.yml +++ b/utils/helm/speckle-server/templates/frontend_2/deployment.yml @@ -138,6 +138,8 @@ spec: value: {{ .Values.featureFlags.gendoAIModuleEnabled | quote }} - name: NUXT_PUBLIC_FF_FORCE_ONBOARDING value: {{ .Values.featureFlags.forceOnboarding | quote }} + - name: NUXT_PUBLIC_FF_NO_PERSONAL_EMAILS_ENABLED + value: {{ .Values.featureFlags.noPersonalEmailsEnabled | quote }} {{- if .Values.analytics.survicate_workspace_key }} - name: NUXT_PUBLIC_SURVICATE_WORKSPACE_KEY value: {{ .Values.analytics.survicate_workspace_key | quote }} diff --git a/utils/helm/speckle-server/values.schema.json b/utils/helm/speckle-server/values.schema.json index 69196ccfd..08b9a9ea9 100644 --- a/utils/helm/speckle-server/values.schema.json +++ b/utils/helm/speckle-server/values.schema.json @@ -89,6 +89,11 @@ "type": "boolean", "description": "Forces onboarding for all users", "default": false + }, + "noPersonalEmailsEnabled": { + "type": "boolean", + "description": "Disables the ability sign up with personal email addresses", + "default": false } } }, diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 7bcb5684f..b7cb79eb9 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -57,6 +57,8 @@ featureFlags: forceEmailVerification: false ## @param featureFlags.forceOnboarding Forces onboarding for all users forceOnboarding: false + ## @param featureFlags.noPersonalEmailsEnabled Disables the ability sign up with personal email addresses + noPersonalEmailsEnabled: false analytics: ## @param analytics.enabled Enable or disable analytics