Skip to content

Commit 2b3343d

Browse files
build: release v6.255.0 (#8796)
* Merge pull request #8793 from opengovsg/fix/chromatic-ignore-url-diff fix: ignore url line in printable response * Merge pull request #8794 from opengovsg/fix/empty-tooltip-bug fix: hide empty tooltip for main save draft button * feat: signatures v1.4 (webhooks) (#8782) * add encryptedWebhookContent to req.formsg * generate separate encrypted webhook content * pass encryptedWebhookContent through to postsubmission actions and substitute it for encryptedContent before firing webhook * standardize constants in encrypt mode * included webhookresponsev3 for signatures, and separated webhook encryption for mrf * added encryptedSubmissionSecretKey to mrf webhook payload * add encryptedSubmissionSecretKey to webhookdata payload * added signature webhook conversion to string in MRF create and update actions * moved spy function to outside test * added submission object to postSubmission functions * refactored encrypt submission service spec * added webhook tests for MRF submissions * resolved webhook firing for subsequent steps test * changes from PR comments, added more verbose logging * added tests for v3 prepareWebhookContent util function * updated tests * fixed backend lint * resolved failing tests * feat: add tc for middleware * fire webhook in encrypt only if encryptedWebhookContent exists * feat: add tc to cover webhook not fired for undefined encrypted wh content --------- Co-authored-by: Kevin Foong <[email protected]> * chore: bump version to v6.255.0 --------- Co-authored-by: scottheng96 <[email protected]>
2 parents b7e1488 + 01c6450 commit 2b3343d

26 files changed

+944
-79
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,22 @@ All notable changes to this project will be documented in this file. Dates are d
44

55
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
66

7+
#### [v6.255.0](https://github.com/opengovsg/FormSG/compare/v6.254.0...v6.255.0)
8+
9+
- feat: signatures v1.4 (webhooks) [`#8782`](https://github.com/opengovsg/FormSG/pull/8782)
10+
- fix: hide empty tooltip for main save draft button [`#8794`](https://github.com/opengovsg/FormSG/pull/8794)
11+
- build: merge release v6.254.0 to develop [`#8792`](https://github.com/opengovsg/FormSG/pull/8792)
12+
- fix: ignore url line in printable response [`#8793`](https://github.com/opengovsg/FormSG/pull/8793)
13+
- build: release v6.254.0 [`#8791`](https://github.com/opengovsg/FormSG/pull/8791)
14+
715
#### [v6.254.0](https://github.com/opengovsg/FormSG/compare/v6.253.0...v6.254.0)
816

17+
> 6 October 2025
18+
919
- feat: create local pdf print version of results page [`#8700`](https://github.com/opengovsg/FormSG/pull/8700)
1020
- build: merge release v6.253.0 to develop [`#8788`](https://github.com/opengovsg/FormSG/pull/8788)
1121
- build: release v6.253.0 [`#8787`](https://github.com/opengovsg/FormSG/pull/8787)
22+
- chore: bump version to v6.254.0 [`c66e690`](https://github.com/opengovsg/FormSG/commit/c66e69075eb62bf1c2227f8610c8c9c5f428a834)
1223

1324
#### [v6.253.0](https://github.com/opengovsg/FormSG/compare/v6.252.0...v6.253.0)
1425

frontend/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "form-frontend",
3-
"version": "6.254.0",
3+
"version": "6.255.0",
44
"homepage": ".",
55
"type": "module",
66
"private": true,

frontend/src/features/admin-form/responses/IndividualResponsePage/PrintableResponse.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { forwardRef } from 'react'
2-
import { Box, Center, Text } from '@chakra-ui/react'
2+
import { Box, Text } from '@chakra-ui/react'
33
import { FieldType } from '@opengovsg/formsg-sdk/dist/types'
44

55
import { BasicField } from '~shared/types/field'
@@ -166,9 +166,9 @@ export const PrintableResponse = ({
166166
color="white"
167167
textDecor="underline"
168168
textDecorationColor="white"
169+
data-chromatic="ignore"
169170
>
170-
<span data-chromatic="ignore">{window.location.origin}</span>
171-
<span>{`/${formId}`}</span>
171+
{window.location.origin}/{formId}
172172
</Text>
173173
</Box>
174174
<Box mx="5%" my="30px">

frontend/src/features/public-form/components/FormFields/PublicFormSubmitButton.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const PublicFormSaveDraftButton = (props: ButtonProps) => {
4141
return (
4242
<Tooltip
4343
placement="top"
44+
isDisabled={!tooltipLabel}
4445
label={<Text data-chromatic="ignore">{tooltipLabel}</Text>}
4546
>
4647
<Button variant="outline" onClick={onSaveDraft} {...props}>

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "FormSG",
33
"description": "Form Manager for Government",
4-
"version": "6.254.0",
4+
"version": "6.255.0",
55
"homepage": "https://form.gov.sg",
66
"authors": [
77
"FormSG <[email protected]>"

shared/types/response-v3.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,8 @@ export type SignatureFieldResponseV3 = {
144144
type: 'draw'
145145
value: SignatureVectorArray
146146
}
147+
148+
export type SignatureFieldWebhookResponseV3 = {
149+
fieldType: BasicField.Signature
150+
answer: string[]
151+
}

src/app/models/submission.server.model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ MultirespondentSubmissionSchema.methods.getWebhookView = async function (
592592
formId,
593593
submissionId: String(this._id),
594594
encryptedContent: this.encryptedContent,
595+
encryptedSubmissionSecretKey: this.encryptedSubmissionSecretKey,
595596
verifiedContent: '',
596597
version: this.version,
597598
created: this.created,
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { ObjectId } from 'bson'
2+
3+
import { BasicField } from '../../../../../../shared/types'
4+
import formsgSdk from '../../../../config/formsg-sdk'
5+
import { getEncryptedAttachmentsMapFromAttachmentsMap } from '../../submission.utils'
6+
import { encryptSubmission } from '../encrypt-submission.middleware'
7+
import { prepareWebhookResponseContent } from '../encrypt-submission.utils'
8+
9+
jest.mock('../encrypt-submission.utils', () => ({
10+
...jest.requireActual('../encrypt-submission.utils'),
11+
prepareWebhookResponseContent: jest.fn(),
12+
}))
13+
jest.mock('../../../../config/formsg-sdk')
14+
jest.mock('../../submission.utils')
15+
16+
describe('encryptSubmission', () => {
17+
const MOCK_FORM_ID = new ObjectId().toHexString()
18+
const MOCK_PUBLIC_KEY = 'mock-public-key'
19+
const MOCK_ENCRYPTED_WEBHOOK_CONTENT = 'mock-encrypted-webhook-content'
20+
const MOCK_WEBHOOK_RESPONSES: ReturnType<
21+
typeof prepareWebhookResponseContent
22+
> = [
23+
{
24+
fieldType: BasicField.ShortText,
25+
answer: 'test answer',
26+
_id: 'field1',
27+
question: 'Test Question 1',
28+
},
29+
{
30+
fieldType: BasicField.Email,
31+
answer: '[email protected]',
32+
_id: 'field2',
33+
question: 'Test Question 2',
34+
},
35+
]
36+
37+
const MOCK_ENCRYPTED_FORM_DEF = {
38+
_id: MOCK_FORM_ID,
39+
publicKey: MOCK_PUBLIC_KEY,
40+
title: 'Test Form',
41+
}
42+
43+
const MOCK_RESPONSES = [
44+
{
45+
fieldType: BasicField.ShortText,
46+
answer: 'test answer',
47+
_id: 'field1',
48+
question: 'Test Question 1',
49+
isVisible: true,
50+
},
51+
{
52+
fieldType: BasicField.Email,
53+
answer: '[email protected]',
54+
_id: 'field2',
55+
question: 'Test Question 2',
56+
isVisible: true,
57+
},
58+
]
59+
60+
const MOCK_STRIPPED_BODY_RESPONSES = [
61+
{
62+
fieldType: BasicField.ShortText,
63+
answer: 'test answer',
64+
_id: 'field1',
65+
question: 'Test Question 1',
66+
// isVisible property should be removed by omitResponseKeys
67+
},
68+
{
69+
fieldType: BasicField.Email,
70+
answer: '[email protected]',
71+
_id: 'field2',
72+
question: 'Test Question 2',
73+
// isVisible property should be removed by omitResponseKeys
74+
},
75+
]
76+
77+
const createMockReq = (params: { formId: string }) =>
78+
({
79+
params,
80+
body: {
81+
responses: MOCK_RESPONSES,
82+
version: 1,
83+
},
84+
formsg: {
85+
encryptedFormDef: MOCK_ENCRYPTED_FORM_DEF,
86+
},
87+
get: jest.fn((name: string) => {
88+
if (name === 'cf-connecting-ip') return '127.0.0.1'
89+
if (name === 'cf-ray') return 'mock-cf-ray'
90+
return undefined
91+
}),
92+
ip: '127.0.0.1',
93+
id: 'mock-request-id',
94+
headers: {
95+
'cf-connecting-ip': '127.0.0.1',
96+
'cf-ray': 'mock-cf-ray',
97+
'x-request-id': 'mock-request-id',
98+
},
99+
baseUrl: '/api/v3',
100+
path: '/forms/mock-form-id/submissions',
101+
originalUrl: '/api/v3/forms/mock-form-id/submissions?param=value',
102+
}) as any
103+
104+
const createMockRes = () => ({
105+
status: jest.fn().mockReturnThis(),
106+
json: jest.fn().mockReturnThis(),
107+
send: jest.fn().mockReturnThis(),
108+
})
109+
110+
const mockNext = jest.fn()
111+
112+
beforeEach(() => {
113+
jest.clearAllMocks()
114+
jest.resetAllMocks()
115+
116+
// Mock formsgSdk.crypto.encrypt
117+
jest
118+
.mocked(formsgSdk.crypto.encrypt)
119+
.mockReturnValue(MOCK_ENCRYPTED_WEBHOOK_CONTENT)
120+
121+
// Mock prepareWebhookResponseContent
122+
jest
123+
.mocked(prepareWebhookResponseContent)
124+
.mockReturnValue(MOCK_WEBHOOK_RESPONSES)
125+
126+
// Mock getEncryptedAttachmentsMapFromAttachmentsMap
127+
jest
128+
.mocked(getEncryptedAttachmentsMapFromAttachmentsMap)
129+
.mockResolvedValue({})
130+
})
131+
132+
it('should include encryptedWebhookContent in req.formsg', async () => {
133+
const mockReq = createMockReq({
134+
formId: MOCK_FORM_ID,
135+
})
136+
const mockRes = createMockRes()
137+
138+
// Act
139+
await encryptSubmission(mockReq, mockRes as any, mockNext)
140+
141+
// Assert
142+
expect(mockNext).toHaveBeenCalled()
143+
expect(mockReq.formsg).toHaveProperty('encryptedWebhookContent')
144+
expect(mockReq.formsg.encryptedWebhookContent).toBe(
145+
MOCK_ENCRYPTED_WEBHOOK_CONTENT,
146+
)
147+
148+
// Verify that prepareWebhookResponseContent was called with correct parameters
149+
expect(prepareWebhookResponseContent).toHaveBeenCalledWith(
150+
MOCK_STRIPPED_BODY_RESPONSES,
151+
)
152+
153+
// Verify that formsgSdk.crypto.encrypt was called with correct parameters
154+
expect(formsgSdk.crypto.encrypt).toHaveBeenCalledWith(
155+
MOCK_WEBHOOK_RESPONSES,
156+
MOCK_PUBLIC_KEY,
157+
)
158+
})
159+
})

0 commit comments

Comments
 (0)