Fix: Always force email verification (#3990)
This commit is contained in:
@@ -54,38 +54,24 @@ import {
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import { requestNewEmailVerificationFactory as requestNewEmailVerificationFactoryOld } from '@/modules/emails/services/verification/request.old'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { initializeEventListenerFactory } from '@/modules/auth/services/postAuth'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
|
||||
const { FF_FORCE_EMAIL_VERIFICATION } = getFeatureFlags()
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const requestNewEmailVerification = FF_FORCE_EMAIL_VERIFICATION
|
||||
? requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
|
||||
db
|
||||
}),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
: requestNewEmailVerificationFactoryOld({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
|
||||
db
|
||||
}),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail,
|
||||
getUser: getUserFactory({ db }),
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
|
||||
db
|
||||
}),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { db } from '@/db/knex'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import { requestNewEmailVerificationFactory as requestNewEmailVerificationFactoryOld } from '@/modules/emails/services/verification/request.old'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
@@ -31,32 +30,18 @@ import {
|
||||
verifyUserEmailFactory
|
||||
} from '@/modules/core/services/users/emailVerification'
|
||||
import { commandFactory } from '@/modules/shared/command'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
|
||||
const { FF_FORCE_EMAIL_VERIFICATION } = getFeatureFlags()
|
||||
|
||||
const getUser = getUserFactory({ db })
|
||||
const requestNewEmailVerification = FF_FORCE_EMAIL_VERIFICATION
|
||||
? requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db }),
|
||||
getUser,
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
|
||||
db
|
||||
}),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
: requestNewEmailVerificationFactoryOld({
|
||||
findEmail: findEmailFactory({ db }),
|
||||
getUser,
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
|
||||
db
|
||||
}),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db }),
|
||||
getUser,
|
||||
getServerInfo: getServerInfoFactory({ db }),
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
|
||||
db
|
||||
}),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
|
||||
export = {
|
||||
ActiveUserMutations: {
|
||||
|
||||
@@ -41,9 +41,7 @@ import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { createTestUser, login } from '@/test/authHelper'
|
||||
import { EmailVerificationFinalizationError } from '@/modules/emails/errors'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
|
||||
const { FF_FORCE_EMAIL_VERIFICATION } = getFeatureFlags()
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUser = legacyGetUserFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
@@ -178,91 +176,89 @@ describe('User emails graphql @core', () => {
|
||||
).to.eq(email.toLowerCase())
|
||||
})
|
||||
})
|
||||
;(FF_FORCE_EMAIL_VERIFICATION ? describe : describe.skip)(
|
||||
'verify user email mutation',
|
||||
() => {
|
||||
it('should throw an error if there is no pending verification for the email', async () => {
|
||||
const email = createRandomEmail()
|
||||
const user = await createTestUser({
|
||||
email,
|
||||
role: Roles.Server.User
|
||||
})
|
||||
const session = await login(user)
|
||||
|
||||
// Delete email verification
|
||||
await db(EmailVerifications.name).where({ email }).delete()
|
||||
|
||||
const res = await session.execute(VerifyUserEmailDocument, {
|
||||
input: { email, code: '123456' }
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors({
|
||||
code: EmailVerificationFinalizationError.code
|
||||
})
|
||||
describe('verify user email mutation', () => {
|
||||
it('should throw an error if there is no pending verification for the email', async () => {
|
||||
const email = createRandomEmail()
|
||||
const user = await createTestUser({
|
||||
email,
|
||||
role: Roles.Server.User
|
||||
})
|
||||
it('should throw an error if verification is expired', async () => {
|
||||
const email = createRandomEmail()
|
||||
const user = await createTestUser({
|
||||
email,
|
||||
role: Roles.Server.User
|
||||
})
|
||||
const session = await login(user)
|
||||
const session = await login(user)
|
||||
|
||||
// Manually reset email verification code
|
||||
const verificationCode = await deleteOldAndInsertNewVerificationFactory({ db })(
|
||||
email
|
||||
)
|
||||
// Manually expire email verification
|
||||
await db(EmailVerifications.name)
|
||||
.where({ email })
|
||||
.update({ createdAt: new Date('2020-01-01') })
|
||||
// Delete email verification
|
||||
await db(EmailVerifications.name).where({ email }).delete()
|
||||
|
||||
const res = await session.execute(VerifyUserEmailDocument, {
|
||||
input: { email, code: verificationCode }
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors({
|
||||
code: EmailVerificationFinalizationError.code
|
||||
})
|
||||
const res = await session.execute(VerifyUserEmailDocument, {
|
||||
input: { email, code: '123456' }
|
||||
})
|
||||
it('should throw an error if code is not correct', async () => {
|
||||
const email = createRandomEmail()
|
||||
const user = await createTestUser({
|
||||
email,
|
||||
role: Roles.Server.User
|
||||
})
|
||||
const session = await login(user)
|
||||
|
||||
const res = await session.execute(VerifyUserEmailDocument, {
|
||||
input: { email, code: '123456' }
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors({
|
||||
code: EmailVerificationFinalizationError.code
|
||||
})
|
||||
expect(res).to.haveGraphQLErrors({
|
||||
code: EmailVerificationFinalizationError.code
|
||||
})
|
||||
it('should mark user email as verified', async () => {
|
||||
const email = createRandomEmail()
|
||||
const user = await createTestUser({
|
||||
email,
|
||||
role: Roles.Server.User
|
||||
})
|
||||
const session = await login(user)
|
||||
|
||||
// Manually reset email verification code
|
||||
const verificationCode = await deleteOldAndInsertNewVerificationFactory({ db })(
|
||||
email
|
||||
)
|
||||
|
||||
const res = await session.execute(VerifyUserEmailDocument, {
|
||||
input: { email, code: verificationCode }
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
|
||||
const userEmail = await findEmailFactory({ db })({ email, userId: user.id })
|
||||
expect(userEmail?.verified).to.be.true
|
||||
})
|
||||
it('should throw an error if verification is expired', async () => {
|
||||
const email = createRandomEmail()
|
||||
const user = await createTestUser({
|
||||
email,
|
||||
role: Roles.Server.User
|
||||
})
|
||||
}
|
||||
)
|
||||
const session = await login(user)
|
||||
|
||||
// Manually reset email verification code
|
||||
const verificationCode = await deleteOldAndInsertNewVerificationFactory({ db })(
|
||||
email
|
||||
)
|
||||
// Manually expire email verification
|
||||
await db(EmailVerifications.name)
|
||||
.where({ email })
|
||||
.update({ createdAt: new Date('2020-01-01') })
|
||||
|
||||
const res = await session.execute(VerifyUserEmailDocument, {
|
||||
input: { email, code: verificationCode }
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors({
|
||||
code: EmailVerificationFinalizationError.code
|
||||
})
|
||||
})
|
||||
it('should throw an error if code is not correct', async () => {
|
||||
const email = createRandomEmail()
|
||||
const user = await createTestUser({
|
||||
email,
|
||||
role: Roles.Server.User
|
||||
})
|
||||
const session = await login(user)
|
||||
|
||||
const res = await session.execute(VerifyUserEmailDocument, {
|
||||
input: { email, code: '123456' }
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors({
|
||||
code: EmailVerificationFinalizationError.code
|
||||
})
|
||||
})
|
||||
it('should mark user email as verified', async () => {
|
||||
const email = createRandomEmail()
|
||||
const user = await createTestUser({
|
||||
email,
|
||||
role: Roles.Server.User
|
||||
})
|
||||
const session = await login(user)
|
||||
|
||||
// Manually reset email verification code
|
||||
const verificationCode = await deleteOldAndInsertNewVerificationFactory({ db })(
|
||||
email
|
||||
)
|
||||
|
||||
const res = await session.execute(VerifyUserEmailDocument, {
|
||||
input: { email, code: verificationCode }
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
|
||||
const userEmail = await findEmailFactory({ db })({ email, userId: user.id })
|
||||
expect(userEmail?.verified).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,7 +11,6 @@ import dayjs from 'dayjs'
|
||||
import { Knex } from 'knex'
|
||||
import { hash } from 'bcrypt'
|
||||
import { EmailVerification } from '@/modules/emails/domain/types'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
|
||||
const tables = {
|
||||
emailVerifications: (db: Knex) => db<EmailVerification>(EmailVerifications.name)
|
||||
@@ -64,7 +63,6 @@ function generateEmailVerificationCode() {
|
||||
return cryptoRandomString({ length: 6, type: 'numeric' })
|
||||
}
|
||||
|
||||
const { FF_FORCE_EMAIL_VERIFICATION } = getFeatureFlags()
|
||||
/**
|
||||
* Delete all previous verification entries and create a new one
|
||||
*/
|
||||
@@ -81,15 +79,14 @@ export const deleteOldAndInsertNewVerificationFactory =
|
||||
withoutTablePrefix: true
|
||||
}).col
|
||||
|
||||
const newId = cryptoRandomString({ length: 20 })
|
||||
const code = generateEmailVerificationCode()
|
||||
await tables.emailVerifications(deps.db).insert({
|
||||
[EmailVerificationCols.id]: newId,
|
||||
[EmailVerificationCols.id]: cryptoRandomString({ length: 20 }),
|
||||
[EmailVerificationCols.email]: email,
|
||||
[EmailVerificationCols.code]: await hashEmailVerificationCode(code)
|
||||
})
|
||||
|
||||
return FF_FORCE_EMAIL_VERIFICATION ? code : newId
|
||||
return code
|
||||
}
|
||||
|
||||
export const getPendingVerificationByEmailFactory =
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
import {
|
||||
FindEmail,
|
||||
FindPrimaryEmailForUser
|
||||
} from '@/modules/core/domain/userEmails/operations'
|
||||
import { UserEmail } from '@/modules/core/domain/userEmails/types'
|
||||
import { getEmailVerificationFinalizationRoute } from '@/modules/core/helpers/routeHelper'
|
||||
import { ServerInfo, UserRecord } from '@/modules/core/helpers/types'
|
||||
import { EmailVerificationRequestError } from '@/modules/emails/errors'
|
||||
import { getServerOrigin } from '@/modules/shared/helpers/envHelper'
|
||||
import {
|
||||
DeleteOldAndInsertNewVerification,
|
||||
EmailTemplateParams,
|
||||
RenderEmail,
|
||||
RequestEmailVerification,
|
||||
RequestNewEmailVerification,
|
||||
SendEmail
|
||||
} from '@/modules/emails/domain/operations'
|
||||
import { GetUser } from '@/modules/core/domain/users/operations'
|
||||
import { GetServerInfo } from '@/modules/core/domain/server/operations'
|
||||
|
||||
const EMAIL_SUBJECT = 'Speckle Account E-mail Verification'
|
||||
|
||||
type CreateNewVerificationDeps = {
|
||||
getUser: GetUser
|
||||
findPrimaryEmailForUser: FindPrimaryEmailForUser
|
||||
getServerInfo: GetServerInfo
|
||||
deleteOldAndInsertNewVerification: DeleteOldAndInsertNewVerification
|
||||
}
|
||||
|
||||
const createNewVerificationFactory =
|
||||
(deps: CreateNewVerificationDeps) =>
|
||||
async (userId: string): Promise<VerificationRequestContext> => {
|
||||
if (!userId)
|
||||
throw new EmailVerificationRequestError('User for verification not specified')
|
||||
|
||||
const [user, email, serverInfo] = await Promise.all([
|
||||
deps.getUser(userId),
|
||||
deps.findPrimaryEmailForUser({ userId }),
|
||||
deps.getServerInfo()
|
||||
])
|
||||
|
||||
if (!user || !email)
|
||||
throw new EmailVerificationRequestError(
|
||||
'Unable to resolve verification target user'
|
||||
)
|
||||
|
||||
if (user.verified)
|
||||
throw new EmailVerificationRequestError("User's email is already verified")
|
||||
|
||||
const verificationId = await deps.deleteOldAndInsertNewVerification(user.email)
|
||||
|
||||
return {
|
||||
user,
|
||||
email,
|
||||
verificationId,
|
||||
serverInfo
|
||||
}
|
||||
}
|
||||
|
||||
type VerificationRequestContext = {
|
||||
user: UserRecord
|
||||
verificationId: string
|
||||
serverInfo: ServerInfo
|
||||
email: UserEmail
|
||||
}
|
||||
|
||||
type CreateNewEmailVerificationFactoryDeps = {
|
||||
findEmail: FindEmail
|
||||
getUser: GetUser
|
||||
getServerInfo: GetServerInfo
|
||||
deleteOldAndInsertNewVerification: DeleteOldAndInsertNewVerification
|
||||
}
|
||||
|
||||
const createNewEmailVerificationFactory =
|
||||
(deps: CreateNewEmailVerificationFactoryDeps) =>
|
||||
async (emailId: string): Promise<VerificationRequestContext> => {
|
||||
const emailRecord = await deps.findEmail({ id: emailId })
|
||||
|
||||
if (!emailRecord) throw new EmailVerificationRequestError('Email not found')
|
||||
|
||||
if (emailRecord.verified)
|
||||
throw new EmailVerificationRequestError('Email is already verified')
|
||||
|
||||
const [user, serverInfo] = await Promise.all([
|
||||
deps.getUser(emailRecord.userId),
|
||||
deps.getServerInfo()
|
||||
])
|
||||
|
||||
if (!user)
|
||||
throw new EmailVerificationRequestError(
|
||||
'Unable to resolve verification target user'
|
||||
)
|
||||
|
||||
const verificationId = await deps.deleteOldAndInsertNewVerification(
|
||||
emailRecord.email
|
||||
)
|
||||
return {
|
||||
user,
|
||||
email: emailRecord,
|
||||
verificationId,
|
||||
serverInfo
|
||||
}
|
||||
}
|
||||
|
||||
function buildMjmlBody() {
|
||||
const bodyStart = `<mj-text>Hello,<br/><br/>You have just registered to the Speckle server, or initiated the email verification process manually. To finalize the verification process, click the button below:</mj-text>`
|
||||
const bodyEnd = `<mj-text>This link expires in <strong>1 week</strong>.<br/>
|
||||
If the link does not work, please proceed by</mj-text><br/>
|
||||
<mj-list>
|
||||
<mj-li>Logging in with your e-mail address and password</mj-li>
|
||||
<mj-li>Clicking on the Notification icon</mj-li>
|
||||
<mj-li>Selecting "Send Verification"</mj-li>
|
||||
<mj-li>Verifying your e-mail address by clicking on the link in the e-mail you will receive</mj-li>
|
||||
</mj-list><br/>
|
||||
<mj-text>
|
||||
See you soon,<br/>
|
||||
Speckle
|
||||
</mj-text>
|
||||
`
|
||||
|
||||
return { bodyStart, bodyEnd }
|
||||
}
|
||||
|
||||
function buildTextBody() {
|
||||
const bodyStart = `Hello,\n\nYou have just registered to the Speckle server, or initiated the email verification process manually. To finalize the verification process, open the link below:`
|
||||
const bodyEnd = `This link expires in 1 week. If the link does not work, please proceed by logging in to your Speckle account with your e-mail address and password, clicking the Notification icon, selecting "Send Verification" and verifying your e-mail address by clicking on the link in the e-mail you will receive.\n\nSee you soon,\nSpeckle
|
||||
`
|
||||
|
||||
return { bodyStart, bodyEnd }
|
||||
}
|
||||
|
||||
function buildEmailLink(verificationId: string): string {
|
||||
return new URL(
|
||||
getEmailVerificationFinalizationRoute(verificationId),
|
||||
getServerOrigin()
|
||||
).toString()
|
||||
}
|
||||
|
||||
function buildEmailTemplateParams(verificationId: string): EmailTemplateParams {
|
||||
return {
|
||||
mjml: buildMjmlBody(),
|
||||
text: buildTextBody(),
|
||||
cta: {
|
||||
title: 'Verify your E-mail',
|
||||
url: buildEmailLink(verificationId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SendVerificationEmailDeps = {
|
||||
sendEmail: SendEmail
|
||||
renderEmail: RenderEmail
|
||||
}
|
||||
|
||||
const sendVerificationEmailFactory =
|
||||
(deps: SendVerificationEmailDeps) => async (state: VerificationRequestContext) => {
|
||||
const emailTemplateParams = buildEmailTemplateParams(state.verificationId)
|
||||
const { html, text } = await deps.renderEmail(
|
||||
emailTemplateParams,
|
||||
state.serverInfo,
|
||||
// im deliberately setting this to null, so that the email will not show the unsubscribe bit
|
||||
null
|
||||
)
|
||||
await deps.sendEmail({
|
||||
to: state.email.email,
|
||||
subject: EMAIL_SUBJECT,
|
||||
text,
|
||||
html
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Request email verification (send out verification message) for user with specified ID
|
||||
*/
|
||||
export const requestEmailVerificationFactory =
|
||||
(
|
||||
deps: CreateNewVerificationDeps & SendVerificationEmailDeps
|
||||
): RequestEmailVerification =>
|
||||
async (userId) => {
|
||||
const newVerificationState = await createNewVerificationFactory(deps)(userId)
|
||||
await sendVerificationEmailFactory(deps)(newVerificationState)
|
||||
}
|
||||
|
||||
type RequestNewEmailVerificationDeps = CreateNewEmailVerificationFactoryDeps
|
||||
|
||||
/**
|
||||
* Request email verification for email with specified ID
|
||||
*/
|
||||
export const requestNewEmailVerificationFactory =
|
||||
(
|
||||
deps: RequestNewEmailVerificationDeps & SendVerificationEmailDeps
|
||||
): RequestNewEmailVerification =>
|
||||
async (emailId) => {
|
||||
const createNewEmailVerification = createNewEmailVerificationFactory(deps)
|
||||
const newVerificationState = await createNewEmailVerification(emailId)
|
||||
|
||||
await sendVerificationEmailFactory(deps)(newVerificationState)
|
||||
}
|
||||
@@ -51,11 +51,6 @@ const parseFeatureFlags = () => {
|
||||
schema: z.boolean(),
|
||||
defaults: { production: false, _: false }
|
||||
},
|
||||
// Forces email verification for all users
|
||||
FF_FORCE_EMAIL_VERIFICATION: {
|
||||
schema: z.boolean(),
|
||||
defaults: { production: false, _: false }
|
||||
},
|
||||
// Forces onboarding for all users
|
||||
FF_FORCE_ONBOARDING: {
|
||||
schema: z.boolean(),
|
||||
@@ -94,7 +89,6 @@ export function getFeatureFlags(): {
|
||||
FF_BILLING_INTEGRATION_ENABLED: boolean
|
||||
FF_WORKSPACES_MULTI_REGION_ENABLED: boolean
|
||||
FF_FILEIMPORT_IFC_DOTNET_ENABLED: boolean
|
||||
FF_FORCE_EMAIL_VERIFICATION: boolean
|
||||
FF_FORCE_ONBOARDING: boolean
|
||||
FF_OBJECTS_STREAMING_FIX: boolean
|
||||
FF_MOVE_PROJECT_REGION_ENABLED: boolean
|
||||
|
||||
@@ -589,9 +589,6 @@ Generate the environment variables for Speckle server and Speckle objects deploy
|
||||
- name: FF_WORKSPACES_MULTI_REGION_ENABLED
|
||||
value: {{ .Values.featureFlags.workspacesMultiRegionEnabled | quote }}
|
||||
|
||||
- name: FF_FORCE_EMAIL_VERIFICATION
|
||||
value: {{ .Values.featureFlags.forceEmailVerification | quote }}
|
||||
|
||||
- name: FF_FORCE_ONBOARDING
|
||||
value: {{ .Values.featureFlags.forceOnboarding | quote }}
|
||||
|
||||
|
||||
@@ -137,8 +137,6 @@ spec:
|
||||
value: {{ .Values.featureFlags.workspacesMultiRegionEnabled | quote }}
|
||||
- name: NUXT_PUBLIC_FF_GENDOAI_MODULE_ENABLED
|
||||
value: {{ .Values.featureFlags.gendoAIModuleEnabled | quote }}
|
||||
- name: NUXT_PUBLIC_FF_FORCE_EMAIL_VERIFICATION
|
||||
value: {{ .Values.featureFlags.forceEmailVerification | quote }}
|
||||
- name: NUXT_PUBLIC_FF_FORCE_ONBOARDING
|
||||
value: {{ .Values.featureFlags.forceOnboarding | quote }}
|
||||
{{- if .Values.analytics.survicate_workspace_key }}
|
||||
|
||||
Reference in New Issue
Block a user