diff --git a/frontend/app/components/auth/auth.spec.tsx b/frontend/app/components/auth/auth.spec.tsx
index 5a2d205e..f5db67b8 100644
--- a/frontend/app/components/auth/auth.spec.tsx
+++ b/frontend/app/components/auth/auth.spec.tsx
@@ -211,4 +211,42 @@ describe('', () => {
expect(getByRole('presentation')).toHaveAttribute('aria-label', 'Loading...');
await waitFor(() => expect(api.anonymousSignin).toBeCalled());
});
+
+ it.each`
+ value | expected
+ ${'username '} | ${'username'}
+ ${' username'} | ${'username'}
+ ${' username '} | ${'username'}
+ `('should remove spaces in the first/last postion in username', async ({ value, expected }) => {
+ StaticStore.config.auth_providers = ['email'];
+
+ const { getByText, getByPlaceholderText } = render();
+ fireEvent.click(getByText('Sign In'));
+
+ const input = getByPlaceholderText('Username');
+
+ fireEvent.change(input, { target: { value } });
+ fireEvent.blur(input);
+
+ expect(input).toHaveValue(expected);
+ });
+
+ it.each`
+ value | expected
+ ${'user name '} | ${'user name'}
+ ${' user name'} | ${'user name'}
+ ${' user name '} | ${'user name'}
+ `('should leave spaces in the middle of username', ({ value, expected }) => {
+ StaticStore.config.auth_providers = ['email'];
+
+ const { getByText, getByPlaceholderText } = render();
+ fireEvent.click(getByText('Sign In'));
+
+ const input = getByPlaceholderText('Username');
+
+ fireEvent.change(input, { target: { value } });
+ fireEvent.blur(input);
+
+ expect(input).toHaveValue(expected);
+ });
});
diff --git a/frontend/app/components/auth/auth.tsx b/frontend/app/components/auth/auth.tsx
index b09c75ac..94f5ef73 100644
--- a/frontend/app/components/auth/auth.tsx
+++ b/frontend/app/components/auth/auth.tsx
@@ -233,10 +233,14 @@ export default function Auth() {
required
name="username"
minLength={3}
- pattern="^[\p{L}\d_]+$"
+ pattern="[\p{L}\d\s_]+"
title={intl.formatMessage(messages.usernameRestriction)}
placeholder={intl.formatMessage(messages.username)}
disabled={isLoading}
+ onBlur={(evt) => {
+ const element = evt.target as HTMLInputElement;
+ element.value = element.value.trim();
+ }}
/>
{view === 'email' && (