Skip to content

Commit

Permalink
feat: MRF encryption scheme
Browse files Browse the repository at this point in the history
  • Loading branch information
justynoh committed Dec 12, 2023
1 parent 0bc1b54 commit f253e6d
Show file tree
Hide file tree
Showing 76 changed files with 4,472 additions and 3,479 deletions.
54 changes: 17 additions & 37 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@emotion/styled": "^11.6.0",
"@floating-ui/react-dom-interactions": "^0.9.3",
"@growthbook/growthbook-react": "^0.17.0",
"@opengovsg/formsg-sdk": "^0.11.0",
"@opengovsg/formsg-sdk": "github:opengovsg/formsg-javascript-sdk#feat/mrf-crypto",
"@stablelib/base64": "^1.0.1",
"@stripe/react-stripe-js": "^1.15.0",
"@stripe/stripe-js": "^1.44.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const SecretKeyVerificationInput = ({
required: "Please enter the form's secret key",
validate: (secretKey: string) => {
// Should not see this error message.
if (!publicKey) return 'This form is not a storage mode form'
if (!publicKey) return 'Unexpected form mode'

const trimmedSecretKey = secretKey.trim()
const isKeypairValid =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ import { useDesignColorTheme } from '../utils/useDesignColorTheme'
import FieldRow from './FieldRow'

interface BuilderFieldsProps {
responseMode: AdminFormDto['responseMode']
fields: AdminFormDto['form_fields']
visibleFieldIds: FieldIdSet
isDraggingOver: boolean
}

export const BuilderFields = ({
responseMode,
fields,
visibleFieldIds,
isDraggingOver,
Expand Down Expand Up @@ -54,6 +56,7 @@ export const BuilderFields = ({
: {}
return (
<FieldRow
responseMode={responseMode}
index={i}
key={f._id}
field={f}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from '@chakra-ui/react'
import { isEqual, times } from 'lodash'

import { FormColorTheme } from '~shared/types'
import { FormColorTheme, FormResponseMode } from '~shared/types'
import { BasicField, FormFieldDto } from '~shared/types/field'

import { useIsMobile } from '~hooks/useIsMobile'
Expand Down Expand Up @@ -82,6 +82,7 @@ import { SectionFieldRow } from './SectionFieldRow'
import { VerifiableFieldBuilderContainer } from './VerifiableFieldBuilderContainer'

export interface FieldRowContainerProps {
responseMode: FormResponseMode
field: FormFieldDto
index: number
isHiddenByLogic: boolean
Expand All @@ -95,6 +96,7 @@ export interface FieldRowContainerProps {
}

const FieldRowContainer = ({
responseMode,
field,
index,
isHiddenByLogic,
Expand Down Expand Up @@ -293,6 +295,7 @@ const FieldRowContainer = ({
<FieldRow
field={field}
colorTheme={colorTheme}
responseMode={responseMode}
showMyInfoBadge={isMyInfoField}
/>
</FormProvider>
Expand Down Expand Up @@ -440,6 +443,7 @@ const FieldButtonGroup = ({
type FieldRowProps = {
field: FormFieldDto
colorTheme?: FormColorTheme
responseMode: FormResponseMode
showMyInfoBadge?: boolean
}

Expand All @@ -458,7 +462,7 @@ const FieldRow = ({ field, ...rest }: FieldRowProps) => {
case BasicField.Mobile:
return field.isVerifiable ? (
<VerifiableFieldBuilderContainer schema={field} {...rest}>
<MobileFieldInput schema={field} />
<MobileFieldInput schema={field} responseMode={rest.responseMode} />
</VerifiableFieldBuilderContainer>
) : (
<MobileField schema={field} {...rest} />
Expand All @@ -468,7 +472,7 @@ const FieldRow = ({ field, ...rest }: FieldRowProps) => {
case BasicField.Email:
return field.isVerifiable ? (
<VerifiableFieldBuilderContainer schema={field} {...rest}>
<EmailFieldInput schema={field} />
<EmailFieldInput schema={field} responseMode={rest.responseMode} />
</VerifiableFieldBuilderContainer>
) : (
<EmailField schema={field} {...rest} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Box, Flex, FlexProps, Skeleton, Stack } from '@chakra-ui/react'

import Button from '~components/Button'

import { useAdminForm } from '~features/admin-form/common/queries'
import { getVisibleFieldIds } from '~features/logic/utils'
import { useBgColor } from '~features/public-form/components/PublicFormWrapper'

Expand All @@ -14,7 +15,6 @@ import {
stateSelector as endPageStateSelector,
useEndPageStore,
} from '../../end-page/useEndPageStore'
import { useAdminFormLogic } from '../../logic/hooks/useAdminFormLogic'
import {
setToInactiveSelector as setPaymentToInactiveSelector,
usePaymentStore,
Expand Down Expand Up @@ -43,9 +43,9 @@ interface FormBuilderProps extends FlexProps {
export const FormBuilder = ({
placeholderProps,
...props
}: FormBuilderProps): JSX.Element => {
}: FormBuilderProps) => {
const { builderFields, isLoading } = useBuilderFields()
const { formLogics } = useAdminFormLogic()
const { data: form } = useAdminForm()
const { handleBuilderClick, handleEndpageClick } = useCreatePageSidebar()
const setFieldBuilderToInactive = useFieldBuilderStore(
setFieldBuilderToInactiveSelector,
Expand All @@ -61,9 +61,12 @@ export const FormBuilder = ({
() =>
getVisibleFieldIds(
{}, // Assume form has no inputs yet.
{ formFields: builderFields ?? [], formLogics: formLogics ?? [] },
{
formFields: builderFields ?? [],
formLogics: form?.form_logics ?? [],
},
),
[builderFields, formLogics],
[builderFields, form?.form_logics],
)

const handlePlaceholderClick = useCallback(
Expand All @@ -89,6 +92,8 @@ export const FormBuilder = ({

const bg = useBgColor({ colorTheme: useDesignColorTheme() })

if (!form) return null

return (
<Flex
mb={0}
Expand Down Expand Up @@ -136,6 +141,7 @@ export const FormBuilder = ({
{...provided.droppableProps}
>
<BuilderFields
responseMode={form?.responseMode}
fields={builderFields}
visibleFieldIds={visibleFieldIds}
isDraggingOver={snapshot.isDraggingOver}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { DateString } from '~shared/types'
import {
FormSubmissionMetadataQueryDto,
StorageModeChartsDto,
StorageModeSubmissionDto,
StorageModeSubmissionMetadataList,
SubmissionCountQueryDto,
SubmissionDto,
SubmissionMetadataList,
SubmissionType,
} from '~shared/types/submission'

import formsgSdk from '~utils/formSdk'
Expand All @@ -13,7 +14,10 @@ import { ApiService } from '~services/ApiService'
import { ADMIN_FORM_ENDPOINT } from '../common/AdminViewFormService'

import { augmentDecryptedResponses } from './ResponsesPage/storage/utils/augmentDecryptedResponses'
import { processDecryptedContent } from './ResponsesPage/storage/utils/processDecryptedContent'
import {
processDecryptedContent,
processDecryptedContentV3,
} from './ResponsesPage/storage/utils/processDecryptedContent'

/**
* Counts the number of submissions for a given form
Expand Down Expand Up @@ -44,7 +48,7 @@ export const countFormSubmissions = async ({
export const getFormSubmissionsMetadata = async (
formId: string,
queryParams: FormSubmissionMetadataQueryDto,
): Promise<StorageModeSubmissionMetadataList> => {
): Promise<SubmissionMetadataList> => {
return ApiService.get(
`${ADMIN_FORM_ENDPOINT}/${formId}/submissions/metadata`,
{
Expand All @@ -65,8 +69,8 @@ const getEncryptedSubmissionById = async ({
}: {
formId: string
submissionId: string
}): Promise<StorageModeSubmissionDto> => {
return ApiService.get<StorageModeSubmissionDto>(
}): Promise<SubmissionDto> => {
return ApiService.get<SubmissionDto>(
`${ADMIN_FORM_ENDPOINT}/${formId}/submissions/${submissionId}`,
).then(({ data }) => data)
}
Expand All @@ -82,24 +86,58 @@ export const getDecryptedSubmissionById = async ({
}) => {
if (!secretKey) return

const { content, version, verified, attachmentMetadata, ...rest } =
await getEncryptedSubmissionById({ formId, submissionId })

const decryptedContent = formsgSdk.crypto.decrypt(secretKey, {
encryptedContent: content,
verifiedContent: verified,
version,
const encryptedSubmission = await getEncryptedSubmissionById({
formId,
submissionId,
})
if (!decryptedContent) throw new Error('Could not decrypt the response')
const processedContent = augmentDecryptedResponses(
processDecryptedContent(decryptedContent),
attachmentMetadata,

let processedContent, submissionSecretKey
switch (encryptedSubmission.submissionType) {
case SubmissionType.Encrypt: {
const decryptedContent = formsgSdk.crypto.decrypt(secretKey, {
encryptedContent: encryptedSubmission.content,
verifiedContent: encryptedSubmission.verified,
version: encryptedSubmission.version,
})
if (!decryptedContent) throw new Error('Could not decrypt the response')
processedContent = processDecryptedContent(decryptedContent)
break
}
case SubmissionType.Multirespondent: {
const decryptedContent = formsgSdk.cryptoV3.decrypt(secretKey, {
encryptedContent: encryptedSubmission.encryptedContent,
encryptedSubmissionSecretKey:
encryptedSubmission.encryptedSubmissionSecretKey,
version: encryptedSubmission.version,
})
if (!decryptedContent) throw new Error('Could not decrypt the response')
processedContent = await processDecryptedContentV3(
encryptedSubmission.form_fields,
decryptedContent,
)
submissionSecretKey = decryptedContent.submissionSecretKey
break
}
}

const responses = augmentDecryptedResponses(
processedContent,
//TODO(MRF): fix this
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
encryptedSubmission.attachmentMetadata ?? {},
)

// Add metadata for display.
return {
...rest,
responses: processedContent,
refNo: encryptedSubmission.refNo,
submissionTime: encryptedSubmission.submissionTime,
submissionSecretKey,
payment:
encryptedSubmission.submissionType === SubmissionType.Encrypt
? encryptedSubmission.payment
: undefined,
responses,
}
}

Expand Down
Loading

0 comments on commit f253e6d

Please sign in to comment.