Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
379e15a
Update password min length in checkPasswordStrength function
ignatiusm Jun 25, 2026
781b757
Check for common base words in checkPasswordStrength function
ignatiusm Jun 25, 2026
44f0333
Start password refactor to constants
ignatiusm Jun 30, 2026
41d402e
Fix audit log backend tests
ignatiusm Jun 30, 2026
3040278
WIP: fix tsoa annotations for password length constraint
ignatiusm Jul 1, 2026
954a69f
WIP: update docs and misc
ignatiusm Jul 1, 2026
db6d61a
WIP: add TestInvites constants
ignatiusm Jul 1, 2026
20788d6
WIP: Auth controller tests passing
ignatiusm Jul 1, 2026
04475ef
WIP: Families controller tests passing
ignatiusm Jul 1, 2026
6821201
WIP: Integrations controller tests passing
ignatiusm Jul 1, 2026
5c63562
WIP: mailer controller tests passing
ignatiusm Jul 1, 2026
574d549
WIP: organisations controller tests passing
ignatiusm Jul 1, 2026
5555b45
WIP: participants controller tests passing
ignatiusm Jul 1, 2026
34f94ec
WIP: profiles controller tests passing
ignatiusm Jul 1, 2026
2602c27
WIP: settings controller tests passing
ignatiusm Jul 1, 2026
1d520db
WIP: studies controller tests passing
ignatiusm Jul 1, 2026
55b22d0
WIP: surveys controller tests passing
ignatiusm Jul 1, 2026
ca6b3c9
WIP: users controller tests passing
ignatiusm Jul 1, 2026
9d22843
WIP: auth integration tests passing
ignatiusm Jul 3, 2026
67d0404
WIP: dependents integration tests passing
ignatiusm Jul 3, 2026
f1986e1
WIP: elsa integration tests passing
ignatiusm Jul 3, 2026
f50cc39
WIP: invites integration tests passing
ignatiusm Jul 3, 2026
3ce4d7e
WIP: tidy some more passwords
ignatiusm Jul 3, 2026
1c86e65
WIP: protectedroutes integration tests passing
ignatiusm Jul 3, 2026
b9fa881
WIP: resetpassword integration tests passing
ignatiusm Jul 3, 2026
cfa4b69
WIP: softdelete integration tests passing
ignatiusm Jul 3, 2026
fe1dcb5
WIP: studies integration tests passing
ignatiusm Jul 3, 2026
666b3df
WIP: survey integration tests passing
ignatiusm Jul 3, 2026
c18a27b
WIP: tidy up redundant seed data
ignatiusm Jul 3, 2026
4254a22
WIP: set up for fixing cypress tests
ignatiusm Jul 3, 2026
f38e830
WIP: half of admin cypress tests passing
ignatiusm Jul 3, 2026
bb766e6
WIP: other half of admin cypress tests passing
ignatiusm Jul 3, 2026
48bf0b6
Refactor user-client cypress tests to use new test data
ignatiusm Jul 8, 2026
25295c4
Remove "password" from type and swagger docs
ignatiusm Jul 8, 2026
3a262e4
Add tsx in all workspaces
ignatiusm Jul 8, 2026
ac35629
Tidy up misc "password1" references
ignatiusm Jul 8, 2026
f979d25
Update README, .env.example and cron.yaml
ignatiusm Jul 8, 2026
fbeff59
Add cypress tests for "easily guessable" passwords
ignatiusm Jul 8, 2026
f4f91d8
Merge branch 'dev' into 867-strengthen-password-policy
ignatiusm Jul 8, 2026
92b9cec
Remove redundant line from admin-client auditLogs.cy.js
ignatiusm Jul 15, 2026
e1407e4
Add comments to explain test user id in backend Survey integration tests
ignatiusm Jul 16, 2026
25ea4a3
Add ResetPassword integration test to verify invalid response
ignatiusm Jul 19, 2026
fc70237
Use TestStudies in participantscontroller tests
ignatiusm Jul 20, 2026
72bc7ac
Use TestStudies in surveyscontroller tests
ignatiusm Jul 20, 2026
ed449e6
Use TestStudies in familiescontroller tests
ignatiusm Jul 20, 2026
7b635f7
Address garvan hardcode todos in userscontroller and resetpassword tests
ignatiusm Jul 20, 2026
abf8ee2
Create commonPasswordBaseWords constant
ignatiusm Jul 20, 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
4 changes: 2 additions & 2 deletions .helm/ctrl/templates/cron.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ spec:
- name: EXAMPLE_ORG_ADMIN_EMAIL
value: admin@example.com
- name: EXAMPLE_ORG_ADMIN_PASSWORD
value: Testpassword1
value: Supersecret123
- name: EXAMPLE_PARTICIPANT_EMAIL
value: user@example.com
- name: EXAMPLE_PARTICIPANT_PASSWORD
value: Testpassword2
value: Supersecret123
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ CTRL, developed by [Garvan Institute of Medical Research](https://www.garvan.org
**User Portal**
URL: [http://ctrldemo.dsp.garvan.org.au/](http://ctrldemo.dsp.garvan.org.au/)
Login: `user@example.com`
Password: `Testpassword2`
Password: `Supersecret123`

**Admin Portal**
URL: [http://admin.ctrldemo.dsp.garvan.org.au/](http://admin.ctrldemo.dsp.garvan.org.au/)
Login: `admin@example.com`
Password: `Testpassword1`
Password: `Supersecret123`

## Features

Expand Down
16 changes: 9 additions & 7 deletions application/admin-client/cypress/e2e/auditLogs.cy.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { UserType } = require('../../../common/cypress/support/commands')
const { TestUsers } = require('../../../common/testing/constants')

before(() => {
cy.task('reset')
Expand All @@ -9,19 +9,19 @@ before(() => {

describe('Audit Logs', () => {
it('Organisation Admin can view Audit Log', () => {
cy.login(UserType.ORG_ADMIN)
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit('/audit-logs')
cy.contains('Audit Logs').should('exist')
})

it('Study Amin can view Audit Log', () => {
cy.login(UserType.STUDY_ADMIN)
cy.login(TestUsers.STUDY_ADMIN.email)
cy.visit('/audit-logs')
cy.contains('Audit Logs').should('exist')
})

it('should toggle the View Payload cell and display JSON', () => {
cy.login(UserType.STUDY_ADMIN)
cy.login(TestUsers.STUDY_ADMIN.email)
cy.visit('/audit-logs')

// Toggle button to view payload
Expand All @@ -33,7 +33,9 @@ describe('Audit Logs', () => {
// Assert JSON appears
// Note: this works because the most recent action is the Study Admin
// logging in at the start of the test :)
cy.get('.MuiCollapse-root').should('be.visible').and('contain.text', UserType.STUDY_ADMIN)
cy.get('.MuiCollapse-root')
.should('be.visible')
.and('contain.text', TestUsers.STUDY_ADMIN.email)

// Toggle button to hide payload
cy.get('[data-cy="toggle-payload-view"]').first().should('contain.text', 'Hide Payload').click()
Expand All @@ -44,7 +46,7 @@ describe('Audit Logs', () => {
})

it('should obscure password fields in JSON payload', () => {
cy.login(UserType.STUDY_ADMIN)
cy.login(TestUsers.STUDY_ADMIN.email)
cy.visit('/audit-logs')

// Toggle button to view payload
Expand All @@ -55,7 +57,7 @@ describe('Audit Logs', () => {
})

it('should obscure redcapToken fields in JSON payload', () => {
cy.login(UserType.STUDY_ADMIN)
cy.login(TestUsers.STUDY_ADMIN.email)
cy.visit('/studies')
cy.get('[data-cy="advanced-toggle"]').eq(0).click()
cy.get('[data-cy="redcapToken"] input').eq(0).type('abc123')
Expand Down
10 changes: 5 additions & 5 deletions application/admin-client/cypress/e2e/auth.cy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="cypress" />

const { UserType } = require('../../../common/cypress/support/commands')
const { TestUsers } = require('../../../common/testing/constants')

beforeEach(() => {
cy.task('reset')
Expand All @@ -15,16 +15,16 @@ describe('auth', () => {

it('can login as admin to the admin portal', () => {
cy.visit('/')
cy.get('input[name="email"]').type(UserType.ORG_ADMIN)
cy.get('input[name="password"]').type('Testpassword1')
cy.get('input[name="email"]').type(TestUsers.ORG_ADMIN.email)
cy.get('input[name="password"]').type(TestUsers.ORG_ADMIN.password)
cy.get('button[type="submit"]').click()
cy.url().should('include', '/surveys') // Redirects to /surveys after login
})

it('cannot login as a participant to the admin portal', () => {
cy.visit('/')
cy.get('input[name="email"]').type(UserType.PARTICIPANT_COMPLETED)
cy.get('input[name="password"]').type('Testpassword1')
cy.get('input[name="email"]').type(TestUsers.PARTICIPANT_COMPLETED.email)
cy.get('input[name="password"]').type(TestUsers.PARTICIPANT_COMPLETED.password)
cy.get('button[type="submit"]').click()

// Expect to stay on the login page & error message popup
Expand Down
8 changes: 4 additions & 4 deletions application/admin-client/cypress/e2e/basic.cy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="cypress" />

const { UserType } = require('../../../common/cypress/support/commands')
const { TestUsers } = require('../../../common/testing/constants')

beforeEach(() => {
cy.task('reset')
Expand All @@ -12,15 +12,15 @@ describe('basic', () => {
cy.contains('Sign in to your account').should('exist')
})
it('can navigate to tabs', () => {
cy.login(UserType.ORG_ADMIN)
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit('/users')
cy.contains('Surveys').click()
cy.url().should('contain', '/surveys')
cy.contains('Participants').click()
cy.url().should('contain', '/participants')
})
it('Sidebar hide and show', () => {
cy.login(UserType.ORG_ADMIN)
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit('/users')
cy.get('button[tabindex="0"]').first().click()
cy.contains('CTRL Admin').should('not.be.visible')
Expand All @@ -29,7 +29,7 @@ describe('basic', () => {
})

it('Is redirected to login when attempting to use expired token', () => {
cy.login(UserType.ORG_ADMIN)
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit('/users')
cy.contains('OrganisationAdmin').should('exist')
cy.login_expired()
Expand Down
4 changes: 2 additions & 2 deletions application/admin-client/cypress/e2e/elsa.cy.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/// <reference types="cypress" />

const { UserType } = require('../../../common/cypress/support/commands')
const { TestUsers } = require('../../../common/testing/constants')

beforeEach(() => {
cy.task('reset')
})

describe('Elsa Integration', () => {
it('Can disable and enable Elsa Integration', () => {
cy.login(UserType.ORG_ADMIN)
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit('/integrations')
cy.get('[data-cy="elsa-checkbox"] input').should('be.checked').click()
cy.get('[data-cy="confirm"]').click()
Expand Down
4 changes: 2 additions & 2 deletions application/admin-client/cypress/e2e/families.cy.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/// <reference types="cypress" />

const { UserType } = require('../../../common/cypress/support/commands')
const { TestUsers } = require('../../../common/testing/constants')

beforeEach(() => {
cy.task('reset')
cy.login(UserType.ORG_ADMIN)
cy.login(TestUsers.ORG_ADMIN.email)
})

describe('Family Editing', () => {
Expand Down
4 changes: 2 additions & 2 deletions application/admin-client/cypress/e2e/invites.cy.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/// <reference types="cypress" />

const { UserType } = require('../../../common/cypress/support/commands')
const { TestUsers } = require('../../../common/testing/constants')

beforeEach(() => {
cy.task('reset')
cy.login(UserType.ORG_ADMIN)
cy.login(TestUsers.ORG_ADMIN.email)
})

describe('', () => {
Expand Down
16 changes: 8 additions & 8 deletions application/admin-client/cypress/e2e/multiStudy.cy.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/// <reference types="cypress" />

const { UserType } = require('../../../common/cypress/support/commands')
const { TestUsers, TestStudies } = require('../../../common/testing/constants')

beforeEach(() => {
cy.task('reset')
cy.login(UserType.ORG_ADMIN)
cy.login(TestUsers.ORG_ADMIN.email)
})

describe('Multi-study features', () => {
Expand All @@ -21,17 +21,17 @@ describe('Multi-study features', () => {
cy.get('[data-cy="study-create"]').click()
cy.visit('/surveys')
//List of surveys
changeStudy('Test Study')
changeStudy(TestStudies.TEST_STUDY.name)
cy.get('[role="rowgroup"]').children().should('have.length', 2)
changeStudy('Study 2')
changeStudy(TestStudies.TEST_STUDY_2.name)
cy.get('[role="rowgroup"]').children().should('have.length', 1)
//Survey version data is updated correctly (`TEST` has no content)
changeStudy('TEST')
cy.get('[data-cy="edit-draft-button"]').click()
cy.get('[data-cy="step-list"]').children().should('have.length', 0)
cy.get('[data-cy="study-dropdown"]').should('be.disabled')
cy.contains('Surveys').click()
changeStudy('Test Study')
changeStudy(TestStudies.TEST_STUDY.name)
cy.get('[data-cy="edit-draft-button"]').click()
cy.get('[data-cy="step-list"]').children().should('have.length', 2)
//List of participants and invitations
Expand All @@ -40,7 +40,7 @@ describe('Multi-study features', () => {
cy.get('[data-cy="pending-list"]').should('contain.text', 'Pending')
cy.get('[data-cy="pending-list"]').should('contain.text', 'Revoked')
cy.get('[data-cy="pending-list"]').should('contain.text', 'Expired')
changeStudy('Study 2')
changeStudy(TestStudies.TEST_STUDY_2.name)
cy.get('[data-cy="participants-list"]').should('not.contain.text', 'Second')
cy.get('[data-cy="pending-list"]').should('not.contain.text', 'Revoked')
cy.get('[data-cy="pending-list"]').should('not.contain.text', 'Expired')
Expand All @@ -49,8 +49,8 @@ describe('Multi-study features', () => {
it('Maintains active study between browsing sessions', () => {
cy.visit('/surveys')
cy.get('[data-cy="study-dropdown"]').should('have.text', 'Test Study')
changeStudy('Study 2')
changeStudy(TestStudies.TEST_STUDY_2.name)
cy.visit('/surveys')
cy.get('[data-cy="study-dropdown"]').should('have.text', 'Study 2')
cy.get('[data-cy="study-dropdown"]').should('have.text', TestStudies.TEST_STUDY_2.name)
})
})
20 changes: 10 additions & 10 deletions application/admin-client/cypress/e2e/participants.cy.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
/// <reference types="cypress" />

const { UserType } = require('../../../common/cypress/support/commands')
const { TestUsers } = require('../../../common/testing/constants')

beforeEach(() => {
cy.task('reset')
})

describe('Participants', () => {
it('List participants', () => {
cy.login(UserType.ORG_ADMIN)
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit('/participants')
cy.contains('Test').should('exist')
cy.contains('Dependent').should('exist')
cy.contains('V1').should('exist')
})
it('View participant details', () => {
cy.login(UserType.ORG_ADMIN)
cy.visit('/participants/98')
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit(`/participants/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Unanswered User').should('exist')
cy.contains('123 smith st').should('exist')
cy.contains('V1').should('exist')
cy.visit('/participants/99')
cy.visit(`/participants/${TestUsers.PARTICIPANT_COMPLETED.id}`)
cy.contains('Family').should('exist')
cy.contains('Dependent').should('exist')
})

it('Edit participant details', () => {
cy.login(UserType.ORG_ADMIN)
cy.visit('/participants/edit/98')
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('1 Smith St')
cy.get('input[name="profile.nextOfKin.firstName"]').clear().type('Betty')
Expand All @@ -41,14 +41,14 @@ describe('Participants', () => {
cy.contains('Invalid email').should('exist')
cy.get('input[name="profile.nextOfKin.email"]').clear().type('valid@email.com')
cy.contains('Save').click()
cy.url().should('contain', 'participants/98')
cy.url().should('contain', `participants/${TestUsers.PARTICIPANT_UNANSWERED.id}`)
cy.contains('Betty').should('exist')
cy.contains('extID').should('exist')
cy.contains('1 Smith St').should('exist')
})

it('View answers', () => {
cy.login(UserType.ORG_ADMIN)
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit('/participants')
cy.get('[data-rowindex="0"]').contains('V1').trigger('mouseover', { force: true })
cy.contains('Incomplete').should('be.visible')
Expand All @@ -59,7 +59,7 @@ describe('Participants', () => {
})

it('Shows completed and partially completed surveys', () => {
cy.login(UserType.ORG_ADMIN)
cy.login(TestUsers.ORG_ADMIN.email)
cy.visit('/participants')
cy.get('[data-rowindex="0"]').contains('V1').trigger('mouseover', { force: true })
cy.contains('Incomplete').should('be.visible')
Expand Down
34 changes: 21 additions & 13 deletions application/admin-client/cypress/e2e/passwordReset.cy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="cypress" />

const { UserType } = require('../../../common/cypress/support/commands')
const { TestUsers } = require('../../../common/testing/constants')

beforeEach(() => {
cy.task('reset')
Expand All @@ -15,11 +15,11 @@ describe('Password Reset', () => {

it('requests password reset with registered email', () => {
cy.visit('/forgot-password')
cy.get('input[name="email"]').type(UserType.ORG_ADMIN)
cy.get('input[name="email"]').type(TestUsers.ORG_ADMIN.email)
cy.intercept(
'POST',
'/users/password/generate-reset-link',
`[{ email: ${UserType.ORG_ADMIN}}]`,
`[{ email: ${TestUsers.ORG_ADMIN.email}}]`,
).as('requestReset')
cy.get('button[type="submit"]').click()
cy.wait('@requestReset')
Expand All @@ -33,6 +33,14 @@ describe('Password Reset', () => {
cy.contains('Invalid password. Password must be at least').should('exist')
})

it('opens password reset page and enters invalid password - easily guessable', () => {
cy.visit('/update-password?token=valid-reset-token')
cy.get('input[id="password"]').type('Testpassword1')
cy.get('input[id="confirmPassword"]').type('Testpassword1{enter}')
cy.contains('Invalid password').should('exist')
cy.contains('must not contain easily guessable').should('exist')
})

it('opens password reset page and enters non-matching passwords', () => {
cy.visit('/update-password?token=valid-reset-token')
cy.get('input[id="password"]').type('pass')
Expand All @@ -42,30 +50,30 @@ describe('Password Reset', () => {

it('requests password reset with invalid token', () => {
cy.visit('/update-password?token=invalid-reset-token')
cy.get('input[id="password"]').type('Password1')
cy.get('input[id="confirmPassword"]').type('Password1{enter}')
cy.get('input[id="password"]').type(TestUsers.ORG_ADMIN.password)
cy.get('input[id="confirmPassword"]').type(`${TestUsers.ORG_ADMIN.password}{enter}`)
cy.contains('Error resetting').should('exist')
})

it('requests password reset with already-used token', () => {
cy.visit('/update-password?token=valid-reset-token')
cy.get('input[id="password"]').type('Password1')
cy.get('input[id="confirmPassword"]').type('Password1{enter}')
cy.get('input[id="password"]').type(TestUsers.ORG_ADMIN.password)
cy.get('input[id="confirmPassword"]').type(`${TestUsers.ORG_ADMIN.password}{enter}`)
cy.contains('Success').should('exist')

cy.visit('/update-password?token=valid-reset-token')
cy.get('input[id="password"]').type('Password1')
cy.get('input[id="confirmPassword"]').type('Password1{enter}')
cy.get('input[id="password"]').type(TestUsers.ORG_ADMIN.password)
cy.get('input[id="confirmPassword"]').type(`${TestUsers.ORG_ADMIN.password}{enter}`)
cy.contains('Error').should('exist')
})

it('resets password and logs in with new password', () => {
cy.visit('/update-password?token=valid-reset-token')
cy.get('input[id="password"]').type('Password1')
cy.get('input[id="confirmPassword"]').type('Password1{enter}')
cy.get('input[id="password"]').type(TestUsers.ORG_ADMIN.password)
cy.get('input[id="confirmPassword"]').type(`${TestUsers.ORG_ADMIN.password}{enter}`)
cy.contains('Success').should('exist')
cy.get('input[name="email"]').type('test-reset-password@example.com')
cy.get('input[name="password"]').type('Password1')
cy.get('input[name="email"]').type(TestUsers.PASSWORD_RESET_USER.email)
cy.get('input[name="password"]').type(TestUsers.PASSWORD_RESET_USER.password)
cy.get('button[type="submit"]').click()
cy.contains('admin priv').should('exist')
})
Expand Down
Loading
Loading