mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-13 06:34:39 -04:00
feat(frontend): handle username in lowercase
When handling usernames for login and registering with local or permissions, this makes sure that the username is always in lowercase. Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
0a8945d934
commit
e13055736a
9 changed files with 95 additions and 15 deletions
|
@ -3,7 +3,7 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { useOnInputChange } from '../../../../../../hooks/common/use-on-input-change'
|
import { useLowercaseOnInputChange } from '../../../../../../hooks/common/use-lowercase-on-input-change'
|
||||||
import { UiIcon } from '../../../../../common/icons/ui-icon'
|
import { UiIcon } from '../../../../../common/icons/ui-icon'
|
||||||
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
|
@ -31,7 +31,7 @@ export const PermissionAddEntryField: React.FC<PermissionAddEntryFieldProps & Pe
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [newEntryIdentifier, setNewEntryIdentifier] = useState('')
|
const [newEntryIdentifier, setNewEntryIdentifier] = useState('')
|
||||||
const onChange = useOnInputChange(setNewEntryIdentifier)
|
const onChange = useLowercaseOnInputChange(setNewEntryIdentifier)
|
||||||
|
|
||||||
const onSubmit = useCallback(() => {
|
const onSubmit = useCallback(() => {
|
||||||
onAddEntry(newEntryIdentifier)
|
onAddEntry(newEntryIdentifier)
|
||||||
|
|
|
@ -8,19 +8,26 @@ import React from 'react'
|
||||||
import { Form } from 'react-bootstrap'
|
import { Form } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export interface UsernameFieldProps extends AuthFieldProps {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: This should be replaced with the common username component. See https://github.com/hedgedoc/hedgedoc/issues/4128
|
||||||
/**
|
/**
|
||||||
* Renders an input field for a username.
|
* Renders an input field for a username.
|
||||||
*
|
*
|
||||||
* @param onChange Hook that is called when the input is changed.
|
* @param onChange Hook that is called when the input is changed.
|
||||||
* @param invalid True indicates that the username is invalid, false otherwise.
|
* @param invalid True indicates that the username is invalid, false otherwise.
|
||||||
|
* @param value the username value
|
||||||
*/
|
*/
|
||||||
export const UsernameField: React.FC<AuthFieldProps> = ({ onChange, invalid }) => {
|
export const UsernameField: React.FC<UsernameFieldProps> = ({ onChange, invalid, value }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
isInvalid={invalid}
|
isInvalid={invalid}
|
||||||
|
value={value}
|
||||||
type='text'
|
type='text'
|
||||||
size='sm'
|
size='sm'
|
||||||
placeholder={t('login.auth.username') ?? undefined}
|
placeholder={t('login.auth.username') ?? undefined}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { doLdapLogin } from '../../../api/auth/ldap'
|
import { doLdapLogin } from '../../../api/auth/ldap'
|
||||||
|
import { useLowercaseOnInputChange } from '../../../hooks/common/use-lowercase-on-input-change'
|
||||||
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
|
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
|
||||||
import { PasswordField } from './fields/password-field'
|
import { PasswordField } from './fields/password-field'
|
||||||
import { UsernameField } from './fields/username-field'
|
import { UsernameField } from './fields/username-field'
|
||||||
|
@ -38,7 +39,7 @@ export const ViaLdap: React.FC<ViaLdapProps> = ({ providerName, identifier }) =>
|
||||||
[username, password, identifier]
|
[username, password, identifier]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onUsernameChange = useOnInputChange(setUsername)
|
const onUsernameChange = useLowercaseOnInputChange(setUsername)
|
||||||
const onPasswordChange = useOnInputChange(setPassword)
|
const onPasswordChange = useOnInputChange(setPassword)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -48,7 +49,7 @@ export const ViaLdap: React.FC<ViaLdapProps> = ({ providerName, identifier }) =>
|
||||||
<Trans i18nKey='login.signInVia' values={{ service: providerName }} />
|
<Trans i18nKey='login.signInVia' values={{ service: providerName }} />
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
<Form onSubmit={onLoginSubmit}>
|
<Form onSubmit={onLoginSubmit}>
|
||||||
<UsernameField onChange={onUsernameChange} invalid={!!error} />
|
<UsernameField onChange={onUsernameChange} invalid={!!error} value={username} />
|
||||||
<PasswordField onChange={onPasswordChange} invalid={!!error} />
|
<PasswordField onChange={onPasswordChange} invalid={!!error} />
|
||||||
<Alert className='small' show={!!error} variant='danger'>
|
<Alert className='small' show={!!error} variant='danger'>
|
||||||
<Trans i18nKey={error} />
|
<Trans i18nKey={error} />
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
import { doLocalLogin } from '../../../api/auth/local'
|
import { doLocalLogin } from '../../../api/auth/local'
|
||||||
import { ErrorToI18nKeyMapper } from '../../../api/common/error-to-i18n-key-mapper'
|
import { ErrorToI18nKeyMapper } from '../../../api/common/error-to-i18n-key-mapper'
|
||||||
|
import { useLowercaseOnInputChange } from '../../../hooks/common/use-lowercase-on-input-change'
|
||||||
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
|
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
|
||||||
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
|
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
|
||||||
import { ShowIf } from '../../common/show-if/show-if'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
|
@ -44,7 +45,7 @@ export const ViaLocal: React.FC = () => {
|
||||||
[username, password]
|
[username, password]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onUsernameChange = useOnInputChange(setUsername)
|
const onUsernameChange = useLowercaseOnInputChange(setUsername)
|
||||||
const onPasswordChange = useOnInputChange(setPassword)
|
const onPasswordChange = useOnInputChange(setPassword)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -54,7 +55,7 @@ export const ViaLocal: React.FC = () => {
|
||||||
<Trans i18nKey='login.signInVia' values={{ service: t('login.auth.username') }} />
|
<Trans i18nKey='login.signInVia' values={{ service: t('login.auth.username') }} />
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
<Form onSubmit={onLoginSubmit} className={'d-flex gap-3 flex-column'}>
|
<Form onSubmit={onLoginSubmit} className={'d-flex gap-3 flex-column'}>
|
||||||
<UsernameField onChange={onUsernameChange} invalid={!!error} />
|
<UsernameField onChange={onUsernameChange} invalid={!!error} value={username} />
|
||||||
<PasswordField onChange={onPasswordChange} invalid={!!error} />
|
<PasswordField onChange={onPasswordChange} invalid={!!error} />
|
||||||
<Alert className='small' show={!!error} variant='danger'>
|
<Alert className='small' show={!!error} variant='danger'>
|
||||||
<Trans i18nKey={error} />
|
<Trans i18nKey={error} />
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import { useLowercaseOnInputChange } from './use-lowercase-on-input-change'
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
describe('useOnInputChange', () => {
|
||||||
|
it('executes the setter', async () => {
|
||||||
|
const callback = jest.fn()
|
||||||
|
const testValue = 'TEST VALUE'
|
||||||
|
|
||||||
|
const Test: React.FC = () => {
|
||||||
|
const onChange = useLowercaseOnInputChange(callback)
|
||||||
|
return <input data-testid={'input'} type={'text'} onChange={onChange} />
|
||||||
|
}
|
||||||
|
|
||||||
|
render(<Test></Test>)
|
||||||
|
|
||||||
|
const element: HTMLInputElement = await screen.findByTestId('input')
|
||||||
|
|
||||||
|
fireEvent.change(element, { target: { value: testValue } })
|
||||||
|
|
||||||
|
expect(callback).toBeCalledWith('test value')
|
||||||
|
})
|
||||||
|
})
|
20
frontend/src/hooks/common/use-lowercase-on-input-change.ts
Normal file
20
frontend/src/hooks/common/use-lowercase-on-input-change.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import { useOnInputChange } from './use-on-input-change'
|
||||||
|
import type { ChangeEvent } from 'react'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an input change event and sends the lower case event value to a state setter.
|
||||||
|
*
|
||||||
|
* @param setter The setter method for the state.
|
||||||
|
* @return Hook that can be used as callback for onChange.
|
||||||
|
*/
|
||||||
|
export const useLowercaseOnInputChange = (
|
||||||
|
setter: (value: string) => void
|
||||||
|
): ((event: ChangeEvent<HTMLInputElement>) => void) => {
|
||||||
|
return useOnInputChange(useCallback((value) => setter(value.toLowerCase()), [setter]))
|
||||||
|
}
|
27
frontend/src/hooks/common/use-on-input-change.spec.tsx
Normal file
27
frontend/src/hooks/common/use-on-input-change.spec.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import { useOnInputChange } from './use-on-input-change'
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
describe('useOnInputChange', () => {
|
||||||
|
it('executes the setter', async () => {
|
||||||
|
const callback = jest.fn()
|
||||||
|
const testValue = 'testValue'
|
||||||
|
|
||||||
|
const Test: React.FC = () => {
|
||||||
|
const onChange = useOnInputChange(callback)
|
||||||
|
return <input data-testid={'input'} type={'text'} onChange={onChange} />
|
||||||
|
}
|
||||||
|
|
||||||
|
render(<Test></Test>)
|
||||||
|
|
||||||
|
const element: HTMLInputElement = await screen.findByTestId('input')
|
||||||
|
fireEvent.change(element, { target: { value: testValue } })
|
||||||
|
|
||||||
|
expect(callback).toBeCalledWith(testValue)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -13,10 +13,5 @@ import { useCallback } from 'react'
|
||||||
* @return Hook that can be used as callback for onChange.
|
* @return Hook that can be used as callback for onChange.
|
||||||
*/
|
*/
|
||||||
export const useOnInputChange = (setter: (value: string) => void): ((event: ChangeEvent<HTMLInputElement>) => void) => {
|
export const useOnInputChange = (setter: (value: string) => void): ((event: ChangeEvent<HTMLInputElement>) => void) => {
|
||||||
return useCallback(
|
return useCallback((event) => setter(event.target.value), [setter])
|
||||||
(event) => {
|
|
||||||
setter(event.target.value)
|
|
||||||
},
|
|
||||||
[setter]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { useUiNotifications } from '../components/notifications/ui-notification-
|
||||||
import { RegisterError } from '../components/register-page/register-error'
|
import { RegisterError } from '../components/register-page/register-error'
|
||||||
import { RegisterInfos } from '../components/register-page/register-infos'
|
import { RegisterInfos } from '../components/register-page/register-infos'
|
||||||
import { useApplicationState } from '../hooks/common/use-application-state'
|
import { useApplicationState } from '../hooks/common/use-application-state'
|
||||||
|
import { useLowercaseOnInputChange } from '../hooks/common/use-lowercase-on-input-change'
|
||||||
import { useOnInputChange } from '../hooks/common/use-on-input-change'
|
import { useOnInputChange } from '../hooks/common/use-on-input-change'
|
||||||
import type { NextPage } from 'next'
|
import type { NextPage } from 'next'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
@ -62,7 +63,7 @@ export const RegisterPage: NextPage = () => {
|
||||||
return error?.backendErrorName === 'PasswordTooWeakError'
|
return error?.backendErrorName === 'PasswordTooWeakError'
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const onUsernameChange = useOnInputChange(setUsername)
|
const onUsernameChange = useLowercaseOnInputChange(setUsername)
|
||||||
const onDisplayNameChange = useOnInputChange(setDisplayName)
|
const onDisplayNameChange = useOnInputChange(setDisplayName)
|
||||||
const onPasswordChange = useOnInputChange(setPassword)
|
const onPasswordChange = useOnInputChange(setPassword)
|
||||||
const onPasswordAgainChange = useOnInputChange(setPasswordAgain)
|
const onPasswordAgainChange = useOnInputChange(setPasswordAgain)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue