Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7f6c7bd
WIP start dry type constraint refactor
ignatiusm Jul 29, 2026
93944be
WIP start dry type constraint refactor
ignatiusm Jul 29, 2026
5ff96f6
Merge branch '891-validate-input' of github.com:Garvan-Data-Science-P…
ignatiusm Jul 29, 2026
5be124d
Refactor user types
ignatiusm Aug 4, 2026
2ef5a8d
Make survey and study types more dry
ignatiusm Aug 24, 2026
9090112
Make settings types more dry
ignatiusm Aug 24, 2026
904b747
Make participant types more dry
ignatiusm Aug 24, 2026
d67cd8e
Make organisation types more dry
ignatiusm Aug 24, 2026
342f0eb
Make mailer types more dry
ignatiusm Aug 24, 2026
ba0c0db
Make integration types more dry
ignatiusm Aug 24, 2026
95bab83
Make auth types more dry
ignatiusm Aug 24, 2026
31110f5
Make survey types more dry
ignatiusm Aug 24, 2026
73b18c2
Tidy up regex
ignatiusm Aug 24, 2026
f34eafc
Remove illegal character from backend integration/Studies test
ignatiusm Aug 24, 2026
7e47293
Remove illegal character from backend integration/Dependents test
ignatiusm Aug 24, 2026
8844012
Get integration tests passing
ignatiusm Aug 24, 2026
1d6b640
Get auth and profiles tests passing
ignatiusm Aug 24, 2026
459e7aa
Move REGEX to commonTypes so all in one place
ignatiusm Aug 25, 2026
bb436f4
Use original mobile regex pattern (created issue for country code)
ignatiusm Aug 25, 2026
0e085e5
Get user-client tests passing before adding new tests
ignatiusm Aug 26, 2026
60bb6f5
Get admin-client tests passing before adding new tests
ignatiusm Aug 26, 2026
061076f
Fix email invite ExternalID minLength bug
ignatiusm Aug 26, 2026
2344f28
Fix failing Auth tests with country code
ignatiusm Aug 26, 2026
ac12443
Add backend tests for invalid participant and admin details
ignatiusm Aug 26, 2026
f3c77ad
Add user-client cypress test for xss in registration
ignatiusm Aug 26, 2026
0bfd65c
WIP Adds xss tests to user-client profileEdit pages
ignatiusm Aug 26, 2026
e7a6721
Adds xss tests to user-client profileEdit pages
ignatiusm Aug 26, 2026
23898c9
Adds xss tests to backend profiles controller
ignatiusm Aug 26, 2026
a7ce5f7
Tidies up auth controller test
ignatiusm Aug 26, 2026
34955eb
Add backend and user-client tests for dependents xss
ignatiusm Aug 26, 2026
91410c7
Add admin-client xss test for family edit and add warnings
ignatiusm Aug 26, 2026
5c76e20
Add admin-client xss test for setup page
ignatiusm Aug 26, 2026
c163422
Add admin-client xss test for participant edit page
ignatiusm Aug 26, 2026
8f097e2
WIP start moving to validation message constant
ignatiusm Aug 26, 2026
d559046
Move user-client cypress tests to use validation message constants
ignatiusm Aug 26, 2026
852e6dc
Move admin-client cypress tests to use validation message constants
ignatiusm Aug 26, 2026
9fc974e
Move admin-client settings tests to use validation message constants
ignatiusm Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions application/admin-client/cypress/e2e/families.cy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference types="cypress" />

const { TestUsers } = require('../../../common/testing/constants')
const { VALIDATION_MESSAGES } = require('../../../common/src/validation')

beforeEach(() => {
cy.task('reset')
Expand Down Expand Up @@ -41,6 +42,34 @@ describe('Family Editing', () => {
cy.get('[data-cy="in-study-checkbox"] input').last().should('be.checked')
})

it('Cannot use xss first name when adding new dependent to family', () => {
cy.visit('/participants/family/edit/100')
cy.get('[data-cy="add-member-button"]').click()
cy.get('[data-cy="registered-no"]').click()
cy.get('[data-cy="new-dependent"]').click()
cy.get('[data-cy=dep-first]').type("{{7*7}}<script>alert('xss-dep-first')</script>$#", {
parseSpecialCharSequences: false,
})
cy.get('[data-cy=dep-surname]').type('Smith')
cy.get('[data-cy="dep-dob"]').type('2020-01-01')
cy.get('[data-cy="add-dep-button"]').click()
cy.contains(VALIDATION_MESSAGES.NAME_INVALID).should('exist')
})

it('Cannot use xss surname when adding new dependent to family', () => {
cy.visit('/participants/family/edit/100')
cy.get('[data-cy="add-member-button"]').click()
cy.get('[data-cy="registered-no"]').click()
cy.get('[data-cy="new-dependent"]').click()
cy.get('[data-cy=dep-first]').type('Alfred')
cy.get('[data-cy=dep-surname]').type("{{7*7}}<script>alert('xss-dep-surname')</script>$#", {
parseSpecialCharSequences: false,
})
cy.get('[data-cy="dep-dob"]').type('2020-01-01')
cy.get('[data-cy="add-dep-button"]').click()
cy.contains(VALIDATION_MESSAGES.NAME_INVALID).should('exist')
})

it('Remove member from family', () => {
cy.visit('/participants/family/edit/100')
cy.get('[data-cy="remove-member-button"]').click()
Expand Down
161 changes: 159 additions & 2 deletions application/admin-client/cypress/e2e/participants.cy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference types="cypress" />

const { TestUsers } = require('../../../common/testing/constants')
const { VALIDATION_MESSAGES } = require('../../../common/src/validation')

beforeEach(() => {
cy.task('reset')
Expand Down Expand Up @@ -34,11 +35,11 @@ describe('Participants', () => {
cy.get('input[name="profile.postcode"]').clear().type('222a')
cy.get('input[name="externalId"]').clear().type('extID')
cy.contains('Save').click()
cy.contains('Invalid postcode').should('exist')
cy.contains(VALIDATION_MESSAGES.POSTCODE_INVALID).should('exist')
cy.get('input[name="profile.postcode"]').clear().type('2222')
cy.get('input[name="profile.nextOfKin.email"]').clear().type('invalid')
cy.contains('Save').click()
cy.contains('Invalid email').should('exist')
cy.contains(VALIDATION_MESSAGES.EMAIL_INVALID).should('exist')
cy.get('input[name="profile.nextOfKin.email"]').clear().type('valid@email.com')
cy.contains('Save').click()
cy.url().should('contain', `participants/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
Expand Down Expand Up @@ -68,4 +69,160 @@ describe('Participants', () => {
cy.get('[data-rowindex="2"]').contains('V1').trigger('mouseover', { force: true })
cy.contains('Incomplete').should('be.visible')
})

it('Edit participant, validate xss first name', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="profile.firstName"]')
.clear()
.type("{{7*7}}<script>alert('xss-first-name')</script>", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.NAME_INVALID).should('exist')
})

it('Edit participant, validate xss last name', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="profile.lastName"]')
.clear()
.type("{{7*7}}<script>alert('xss-last-name')</script>", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.NAME_INVALID).should('exist')
})

it('Edit participant, validate xss email', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="profile.email"]')
.clear()
.type("{{7*7}}<script>alert('xss-email')</script>@gmail.com", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.EMAIL_INVALID).should('exist')
})

it('Edit participant, validate xss externalId', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="externalId"]')
.clear()
.type("{{7*7}}<script>alert('xss-externalId')</script>", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.EXTERNALID_INVALID).should('exist')
})

it('Edit participant, validate xss address', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="profile.addressLine"]')
.clear()
.type("{{7*7}}<script>alert('xss-address')</script>", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.ADDRESS_INVALID).should('exist')
})

it('Edit participant, validate xss suburb', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="profile.suburb"]')
.clear()
.type("{{7*7}}<script>alert('xss-suburb')</script>", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.ADDRESS_INVALID).should('exist')
})

it('Edit participant, validate xss postcode', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="profile.postcode"]')
.clear()
.type("{{7*7}}<script>alert('xss-postcode')</script>", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.POSTCODE_INVALID).should('exist')
})

it('Edit participant, validate xss mobile', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="profile.mobile"]')
.clear()
.type("{{7*7}}<script>alert('xss-mobile')</script>", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.MOBILE_INVALID).should('exist')
})

it('Edit participant, validate xss nok first name', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="profile.nextOfKin.firstName"]')
.clear()
.type("{{7*7}}<script>alert('xss-nok-first-name')</script>", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.NAME_INVALID).should('exist')
})

it('Edit participant, validate xss nok last name', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="profile.nextOfKin.lastName"]')
.clear()
.type("{{7*7}}<script>alert('xss-nok-last-name')</script>", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.NAME_INVALID).should('exist')
})

it('Edit participant, validate xss nok email', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="profile.nextOfKin.email"]')
.clear()
.type("{{7*7}}<script>alert('xss-nok-email')</script>@gmail.com", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.EMAIL_INVALID).should('exist')
})

it('Edit participant, validate xss nok mobile', () => {
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/edit/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Edit Participant').should('exist')
cy.get('input[name="profile.nextOfKin.mobile"]')
.clear()
.type("{{7*7}}<script>alert('xss-nok-mobile')</script>", {
parseSpecialCharSequences: false,
})
cy.contains('Save').click()
cy.contains(VALIDATION_MESSAGES.MOBILE_INVALID).should('exist')
})
})
107 changes: 82 additions & 25 deletions application/admin-client/cypress/e2e/settings.cy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference types="cypress" />

const { TestUsers } = require('../../../common/testing/constants')
const { VALIDATION_MESSAGES } = require('../../../common/src/validation')

beforeEach(() => {
cy.task('reset')
Expand Down Expand Up @@ -52,31 +53,6 @@ describe('Settings page', () => {
)
})

it('Invalid values prevent saving and show appropriate error messages', () => {
const values = { ...fieldMap, primaryColour: 'abc' }

cy.visit('/settings')

cy.get('[data-cy="tcLink"] input').should(
'have.value',
'https://garvan-data-science-platform.github.io/ctrl-docs/docs/terms-and-conditions',
)

for (const [key, value] of Object.entries(values)) {
cy.get(`[data-cy="${key}"] input`).clear().type(value)
}
cy.get('[data-cy="save-button"]').click()

cy.contains('Invalid colour').should('exist')

for (const [key, value] of Object.entries(fieldMap)) {
cy.get(`[data-cy="${key}"] input`).clear().type(value)
}

cy.contains('Invalid colour').should('not.exist')
cy.contains('Invalid url').should('not.exist')
})

it('Can upload a logo', () => {
cy.visit('/settings')
cy.uploadCommonFile('[data-cy="logo-upload"]', 'valid_logo.png')
Expand Down Expand Up @@ -160,4 +136,85 @@ describe('Settings page', () => {
cy.contains('Deleted logo').should('exist')
cy.get('[data-cy="logo-preview"]').should('not.exist')
})

it('Invalid tcLink url prevent saving and show appropriate error messages', () => {
cy.visit('/settings')

cy.get('[data-cy="tcLink"] input').clear().type("{{7*7}}<script>alert('xss-tcLink')</script>", {
parseSpecialCharSequences: false,
})

cy.get('[data-cy="save-button"]').click()

cy.contains(VALIDATION_MESSAGES.URL_INVALID).should('exist')
})

it('Invalid newsLink url prevent saving and show appropriate error messages', () => {
cy.visit('/settings')

cy.get('[data-cy="newsLink"] input')
.clear()
.type("{{7*7}}<script>alert('xss-newsLink')</script>", {
parseSpecialCharSequences: false,
})

cy.get('[data-cy="save-button"]').click()

cy.contains(VALIDATION_MESSAGES.URL_INVALID).should('exist')
})

it('Invalid primary colour prevent saving and show appropriate error messages', () => {
const values = { ...fieldMap, primaryColour: 'abc' }

cy.visit('/settings')

for (const [key, value] of Object.entries(values)) {
cy.get(`[data-cy="${key}"] input`).clear().type(value)
}
cy.get('[data-cy="save-button"]').click()

cy.contains('Invalid colour').should('exist')

for (const [key, value] of Object.entries(fieldMap)) {
cy.get(`[data-cy="${key}"] input`).clear().type(value)
}

cy.contains('Invalid colour').should('not.exist')
})

it('Invalid xss primary colour prevent saving and show appropriate error messages', () => {
const values = {
...fieldMap,
primaryColour: "{{7*7}}<script>alert('xss-primary-colour')</script>",
}

cy.visit('/settings')

for (const [key, value] of Object.entries(values)) {
cy.get(`[data-cy="${key}"] input`).clear().type(value, {
parseSpecialCharSequences: false,
})
}
cy.get('[data-cy="save-button"]').click()

cy.contains('Invalid colour').should('exist') //TODO: move to common?
})

it('Invalid xss secondary colour prevent saving and show appropriate error messages', () => {
const values = {
...fieldMap,
secondaryColour: "{{7*7}}<script>alert('xss-secondary-colour')</script>",
}

cy.visit('/settings')

for (const [key, value] of Object.entries(values)) {
cy.get(`[data-cy="${key}"] input`).clear().type(value, {
parseSpecialCharSequences: false,
})
}
cy.get('[data-cy="save-button"]').click()

cy.contains('Invalid colour').should('exist') //TODO: move to common?
})
})
26 changes: 26 additions & 0 deletions application/admin-client/cypress/e2e/setup.cy.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="cypress" />
const { TestUsers } = require('../../../common/testing/constants')
const { VALIDATION_MESSAGES } = require('../../../common/src/validation')

describe('Setup', () => {
it('Redirects to setup page if database empty, can register', () => {
Expand All @@ -13,4 +14,29 @@ describe('Setup', () => {
cy.visit('/surveys')
cy.contains('Current Draft').should('exist')
})

it('Cannot register with xss', () => {
cy.task('wipe')
cy.visit('/')
cy.url().should('contain', '/setup')
cy.get('[data-cy="setup-email"]').type(
"{{7*7}}<script>alert('xss-dep-first')</script>@gmail.com",
{
parseSpecialCharSequences: false,
},
)
cy.get('[data-cy="setup-password"]').type(TestUsers.ORG_ADMIN.password) // Using test data to conform to pr requirements
cy.get('[data-cy="setup-submit"]').click()
cy.contains(VALIDATION_MESSAGES.EMAIL_INVALID).should('exist')
})

it('Cannot register with weak password', () => {
cy.task('wipe')
cy.visit('/')
cy.url().should('contain', '/setup')
cy.get('[data-cy="setup-email"]').type('abc@d.com')
cy.get('[data-cy="setup-password"]').type('password')
cy.get('[data-cy="setup-submit"]').click()
cy.contains('Invalid password').should('exist')
})
})
Loading
Loading