Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Do pre-submit availability check on username during registration #6978

Merged
merged 4 commits into from
Nov 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/components/structures/auth/Registration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ export default class Registration extends React.Component<IProps, IState> {
errorText = _t('This server does not support authentication with a phone number.');
}
} else if (response.errcode === "M_USER_IN_USE") {
errorText = _t("That username already exists, please try another.");
errorText = _t("Someone already has that username, please try another.");
} else if (response.errcode === "M_THREEPID_IN_USE") {
errorText = _t("That e-mail address is already in use.");
}
Expand Down Expand Up @@ -510,6 +510,7 @@ export default class Registration extends React.Component<IProps, IState> {
flows={this.state.flows}
serverConfig={this.props.serverConfig}
canSubmit={!this.state.serverErrorIsFatal}
matrixClient={this.state.matrixClient}
/>
</React.Fragment>;
}
Expand Down
25 changes: 24 additions & 1 deletion src/components/views/auth/RegistrationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
*/

import React from 'react';
import { MatrixClient } from 'matrix-js-sdk/src/client';

import * as Email from '../../../email';
import { looksValid as phoneNumberLooksValid } from '../../../phonenumber';
Expand Down Expand Up @@ -57,6 +58,7 @@ interface IProps {
}[];
serverConfig: ValidatedServerConfig;
canSubmit?: boolean;
matrixClient: MatrixClient;

onRegisterClick(params: {
username: string;
Expand Down Expand Up @@ -366,7 +368,11 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
};

private validateUsernameRules = withValidation({
description: () => _t("Use lowercase letters, numbers, dashes and underscores only"),
description: (_, results) => {
// omit the description if the only failing result is the `available` one as it makes no sense for it.
if (results.every(({ key, valid }) => key === "available" || valid)) return;
return _t("Use lowercase letters, numbers, dashes and underscores only");
},
hideDescriptionIfValid: true,
rules: [
{
Expand All @@ -379,6 +385,23 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
test: ({ value }) => !value || SAFE_LOCALPART_REGEX.test(value),
invalid: () => _t("Some characters not allowed"),
},
{
key: "available",
final: true,
test: async ({ value }) => {
if (!value) {
return true;
}

try {
await this.props.matrixClient.isUsernameAvailable(value);
return true;
} catch (err) {
return false;
}
},
invalid: () => _t("Someone already has that username. Try another or if it is you, sign in below."),
},
],
});

Expand Down
14 changes: 10 additions & 4 deletions src/components/views/elements/Validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import classNames from "classnames";

type Data = Pick<IFieldState, "value" | "allowEmpty">;

interface IResult {
key: string;
valid: boolean;
text: string;
}

interface IRule<T, D = void> {
key: string;
final?: boolean;
Expand All @@ -32,7 +38,7 @@ interface IRule<T, D = void> {

interface IArgs<T, D = void> {
rules: IRule<T, D>[];
description?(this: T, derivedData: D): React.ReactChild;
description?(this: T, derivedData: D, results: IResult[]): React.ReactChild;
hideDescriptionIfValid?: boolean;
deriveData?(data: Data): Promise<D>;
}
Expand Down Expand Up @@ -88,7 +94,7 @@ export default function withValidation<T = undefined, D = void>({
const data = { value, allowEmpty };
const derivedData = deriveData ? await deriveData(data) : undefined;

const results = [];
const results: IResult[] = [];
let valid = true;
if (rules && rules.length) {
for (const rule of rules) {
Expand Down Expand Up @@ -164,8 +170,8 @@ export default function withValidation<T = undefined, D = void>({
if (description && (details || !hideDescriptionIfValid)) {
// We're setting `this` to whichever component holds the validation
// function. That allows rules to access the state of the component.
const content = description.call(this, derivedData);
summary = <div className="mx_Validation_description">{ content }</div>;
const content = description.call(this, derivedData, results);
summary = content ? <div className="mx_Validation_description">{ content }</div> : undefined;
}

let feedback;
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2790,6 +2790,7 @@
"Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details",
"Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)",
"Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only",
"Someone already has that username. Try another or if it is you, sign in below.": "Someone already has that username. Try another or if it is you, sign in below.",
"Phone (optional)": "Phone (optional)",
"Register": "Register",
"Add an email to be able to reset your password.": "Add an email to be able to reset your password.",
Expand Down Expand Up @@ -3079,7 +3080,7 @@
"Unable to query for supported registration methods.": "Unable to query for supported registration methods.",
"Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.",
"This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.",
"That username already exists, please try another.": "That username already exists, please try another.",
"Someone already has that username, please try another.": "Someone already has that username, please try another.",
"That e-mail address is already in use.": "That e-mail address is already in use.",
"Continue with %(ssoButtons)s": "Continue with %(ssoButtons)s",
"%(ssoButtons)s Or %(usernamePassword)s": "%(ssoButtons)s Or %(usernamePassword)s",
Expand Down