Feat: prevent non work emails (#3993)
This commit is contained in:
@@ -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<boolean>('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)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ export type LocalAuthRestApiHelpers = ReturnType<typeof localAuthRestApi>
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
@@ -25,3 +25,9 @@ export class UnverifiedEmailSSOLoginError extends UserInputError<UnverifiedEmail
|
||||
'Email already in use by a user with unverified email. Verify the email on the existing user to be able to log in with this method.'
|
||||
static code = 'UNVERIFIED_EMAIL_SSO_LOGIN_ERROR'
|
||||
}
|
||||
|
||||
export class BlockedEmailDomainError extends UserInputError {
|
||||
static defaultMessage =
|
||||
'Please use your work email instead of a personal email address'
|
||||
static code = 'BLOCKED_EMAIL_DOMAIN_ERROR'
|
||||
}
|
||||
|
||||
@@ -962,7 +962,7 @@ export type DiscoverableStreamsSortingInput = {
|
||||
|
||||
export type DiscoverableWorkspaceCollaborator = {
|
||||
__typename?: 'DiscoverableWorkspaceCollaborator';
|
||||
avatar: Scalars['String']['output'];
|
||||
avatar?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type DiscoverableWorkspaceCollaboratorCollection = {
|
||||
@@ -5908,7 +5908,7 @@ export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig<ResolversT
|
||||
}
|
||||
|
||||
export type DiscoverableWorkspaceCollaboratorResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['DiscoverableWorkspaceCollaborator'] = ResolversParentTypes['DiscoverableWorkspaceCollaborator']> = {
|
||||
avatar?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
avatar?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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: ''
|
||||
}
|
||||
|
||||
@@ -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: ''
|
||||
}
|
||||
|
||||
|
||||
@@ -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: ''
|
||||
}
|
||||
|
||||
@@ -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: ''
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -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: ''
|
||||
}
|
||||
|
||||
@@ -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: ''
|
||||
}
|
||||
|
||||
@@ -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<string>,
|
||||
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(
|
||||
{
|
||||
|
||||
@@ -943,7 +943,7 @@ export type DiscoverableStreamsSortingInput = {
|
||||
|
||||
export type DiscoverableWorkspaceCollaborator = {
|
||||
__typename?: 'DiscoverableWorkspaceCollaborator';
|
||||
avatar: Scalars['String']['output'];
|
||||
avatar?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type DiscoverableWorkspaceCollaboratorCollection = {
|
||||
|
||||
@@ -130,7 +130,7 @@ describe('FileUploads @fileuploads', () => {
|
||||
|
||||
const userOne = {
|
||||
name: 'User',
|
||||
email: 'user@gmail.com',
|
||||
email: 'user@example.org',
|
||||
password: 'jdsadjsadasfdsa'
|
||||
}
|
||||
|
||||
|
||||
@@ -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: ''
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -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: ''
|
||||
}
|
||||
|
||||
|
||||
@@ -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>): 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<BasicTestUser>) {
|
||||
}
|
||||
|
||||
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 })
|
||||
|
||||
@@ -944,7 +944,7 @@ export type DiscoverableStreamsSortingInput = {
|
||||
|
||||
export type DiscoverableWorkspaceCollaborator = {
|
||||
__typename?: 'DiscoverableWorkspaceCollaborator';
|
||||
avatar: Scalars['String']['output'];
|
||||
avatar?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user