Skip to content

Commit

Permalink
fix: update to recent decisions [DHIS2-15704]
Browse files Browse the repository at this point in the history
  • Loading branch information
tomzemp committed Jan 30, 2025
1 parent e25c32b commit e5d874b
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 100 deletions.
10 changes: 4 additions & 6 deletions src/components/UserForm/SecuritySection.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ import { createRepeatPasswordValidator } from './validators.js'

const SecuritySection = React.memo(
({ user, inviteUser, externalAuth, changePassword, password }) => {
const {
minPasswordLength,
maxPasswordLength,
passwordValidationPattern,
} = useSystemInformation()
const passwordRegex = new RegExp(passwordValidationPattern)
const { minPasswordLength, maxPasswordLength } = useSystemInformation()
const passwordRegex = new RegExp(
`^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[\\W_])[A-Za-z\\d\\W_]{${minPasswordLength},${maxPasswordLength}}$`
)
const passwordRegExValidator = createPattern(
passwordRegex,
i18n.t('Invalid password')
Expand Down
177 changes: 96 additions & 81 deletions src/components/UserForm/SecuritySection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,87 +55,102 @@ describe('SecuritySection', () => {
expect(passwordHelpText).toBeInTheDocument()
})

it('warns if inputted password fails validation against system passwordValidationPattern', async () => {
useDataQuery.mockReturnValue({
data: {
systemSettings: {
minPasswordLength: 20,
maxPasswordLength: 30,
passwordValidationPattern: '^cat$',
it.each([
['A1!abcdef', 10, 30, 'is too short'],
['A1abcdefghij', 10, 30, 'does not have symbol'],
['A!abcdefghij', 10, 30, 'does not have number'],
['1!abcdefghij', 10, 30, 'does not have uppercase'],
['A1!ABCDEFGHIJKLM', 10, 30, 'does not have lowercase'],
['A1!abcdefgi', 4, 8, 'is too long'],
])(
'shows invalid passport for %s with minPasswordLength %s and maxPasswordLength %s because password %s',
async (invalidPassword, minPasswordLength, maxPasswordLength) => {
useDataQuery.mockReturnValue({
data: {
systemSettings: {
minPasswordLength,
maxPasswordLength,
},
},
},
})
render(
<RenderWrapper>
<SecuritySection
changePassword={true}
externalAuth={false}
inviteUser={'SET_PASSWORD'}
password={undefined}
user={{}}
/>
</RenderWrapper>
)
const passwordInputNode = screen.getByLabelText('New password', {
selector: 'input',
})
const repeatPasswordInputNode = screen.getByLabelText(
'Repeat new password',
{ selector: 'input' }
)

// enter invalid password
userEvent.type(passwordInputNode, 'dog')

// click away
userEvent.click(repeatPasswordInputNode)

// expect invalid password warning to appear
const invalidPasswordValidationError =
screen.getByText('Invalid password')
expect(invalidPasswordValidationError).toBeInTheDocument()
})

it('passes if input password satisfies system passwordValidationPattern', async () => {
useDataQuery.mockReturnValue({
data: {
systemSettings: {
minPasswordLength: 20,
maxPasswordLength: 30,
passwordValidationPattern: '^cat$',
})
render(
<RenderWrapper>
<SecuritySection
changePassword={true}
externalAuth={false}
inviteUser={'SET_PASSWORD'}
password={undefined}
user={{}}
/>
</RenderWrapper>
)
const passwordInputNode = screen.getByLabelText('New password', {
selector: 'input',
})
const repeatPasswordInputNode = screen.getByLabelText(
'Repeat new password',
{ selector: 'input' }
)

// enter invalid password
await userEvent.type(passwordInputNode, invalidPassword)

// click away
await userEvent.click(repeatPasswordInputNode)

// expect invalid password warning to appear
const invalidPasswordValidationError =
screen.getByText('Invalid password')
expect(invalidPasswordValidationError).toBeInTheDocument()
}
)

it.each([
['A1!abcdefghijk', 10, 30],
['A1!abc', 5, 10],
['abcdefghijabcdefghij4#B', 20, 25],
])(
'passes validation for password %s with minPasswordLength %s and maxPasswordLength %s',
async (invalidPassword, minPasswordLength, maxPasswordLength) => {
useDataQuery.mockReturnValue({
data: {
systemSettings: {
minPasswordLength,
maxPasswordLength,
},
},
},
})
render(
<RenderWrapper>
<SecuritySection
changePassword={true}
externalAuth={false}
inviteUser={'SET_PASSWORD'}
password={undefined}
user={{}}
/>
</RenderWrapper>
)
const passwordInputNode = screen.getByLabelText('New password', {
selector: 'input',
})
const repeatPasswordInputNode = screen.getByLabelText(
'Repeat new password',
{ selector: 'input' }
)

// enter invalid password
userEvent.type(passwordInputNode, 'cat')

// click away
userEvent.click(repeatPasswordInputNode)

// expect invalid password warning to appear
const invalidPasswordValidationError =
screen.queryByText('Invalid password')
expect(invalidPasswordValidationError).not.toBeInTheDocument()
})
})
render(
<RenderWrapper>
<SecuritySection
changePassword={true}
externalAuth={false}
inviteUser={'SET_PASSWORD'}
password={undefined}
user={{}}
/>
</RenderWrapper>
)
const passwordInputNode = screen.getByLabelText('New password', {
selector: 'input',
})
const repeatPasswordInputNode = screen.getByLabelText(
'Repeat new password',
{ selector: 'input' }
)

// enter valid password
await userEvent.type(passwordInputNode, invalidPassword)

// click away
await userEvent.click(repeatPasswordInputNode)

// expect invalid password warning not to appear
const invalidPasswordValidationError =
screen.queryByText('Invalid password')
expect(invalidPasswordValidationError).not.toBeInTheDocument()
}
)

it('uses default values if system settings is missing', async () => {
useDataQuery.mockReturnValue({ data: {} })
Expand All @@ -152,7 +167,7 @@ describe('SecuritySection', () => {
)

const passwordHelpText = screen.getByText(
'Password should be between 8 and 34 characters long, with at least one lowercase character, one uppercase character, one number, and one special character.'
'Password should be between 8 and 72 characters long, with at least one lowercase character, one uppercase character, one number, and one special character.'
)
expect(passwordHelpText).toBeInTheDocument()

Expand Down Expand Up @@ -208,7 +223,7 @@ describe('SecuritySection', () => {

// we display an invalid value if one was provided in the warning
const passwordHelpText = screen.getByText(
'Password should be between 2.3 and 34 characters long, with at least one lowercase character, one uppercase character, one number, and one special character.'
'Password should be between 8 and 72 characters long, with at least one lowercase character, one uppercase character, one number, and one special character.'
)
expect(passwordHelpText).toBeInTheDocument()

Expand Down
23 changes: 10 additions & 13 deletions src/providers/system/SystemProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,16 @@ export const SystemProvider = ({ children }) => {
usersCanAssignOwnUserRoles: Boolean(
data.systemSettings?.keyCanGrantOwnUserAuthorityGroups
),
minPasswordLength: data.systemSettings?.minPasswordLength ?? 8,
maxPasswordLength: data.systemSettings?.maxPasswordLength ?? 34,
passwordValidationPattern:
data?.loginConfig?.loginConfig?.passwordValidationPattern ??
`^(?=.*[A-Z])(?=.*\\d)(?=.*[\\W_])[A-Za-z\\d\\W_]{${
Number.isInteger(Number(data.systemSettings?.minPasswordLength))
? Number(data.systemSettings?.minPasswordLength)
: 8
},${
Number.isInteger(Number(data.systemSettings?.maxPasswordLength))
? Number(data.systemSettings?.maxPasswordLength)
: 34
}}$`,
minPasswordLength: Number.isInteger(
Number(data.systemSettings?.minPasswordLength)
)
? data.systemSettings?.minPasswordLength
: 8,
maxPasswordLength: Number.isInteger(
Number(data.systemSettings?.maxPasswordLength)
)
? data.systemSettings?.maxPasswordLength
: 72,
}

return (
Expand Down

0 comments on commit e5d874b

Please sign in to comment.