From e26abbba723328a0b982fb11ed99ab7342ffcae1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 4 Nov 2021 10:25:26 +0000 Subject: [PATCH] Do pre-submit availability check on username during registration (#6978) --- .../structures/auth/Registration.tsx | 3 ++- .../views/auth/RegistrationForm.tsx | 25 ++++++++++++++++++- src/components/views/elements/Validation.tsx | 14 ++++++++--- src/i18n/strings/en_EN.json | 3 ++- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index ab1956dd6a..e91700243e 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -304,7 +304,7 @@ export default class Registration extends React.Component { 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."); } @@ -510,6 +510,7 @@ export default class Registration extends React.Component { flows={this.state.flows} serverConfig={this.props.serverConfig} canSubmit={!this.state.serverErrorIsFatal} + matrixClient={this.state.matrixClient} /> ; } diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 24e73f2992..b593853f34 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -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'; @@ -57,6 +58,7 @@ interface IProps { }[]; serverConfig: ValidatedServerConfig; canSubmit?: boolean; + matrixClient: MatrixClient; onRegisterClick(params: { username: string; @@ -366,7 +368,11 @@ export default class RegistrationForm extends React.PureComponent _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: [ { @@ -379,6 +385,23 @@ export default class RegistrationForm extends React.PureComponent !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."), + }, ], }); diff --git a/src/components/views/elements/Validation.tsx b/src/components/views/elements/Validation.tsx index f887741ff7..a7dbf66ac1 100644 --- a/src/components/views/elements/Validation.tsx +++ b/src/components/views/elements/Validation.tsx @@ -21,6 +21,12 @@ import classNames from "classnames"; type Data = Pick; +interface IResult { + key: string; + valid: boolean; + text: string; +} + interface IRule { key: string; final?: boolean; @@ -32,7 +38,7 @@ interface IRule { interface IArgs { rules: IRule[]; - description?(this: T, derivedData: D): React.ReactChild; + description?(this: T, derivedData: D, results: IResult[]): React.ReactChild; hideDescriptionIfValid?: boolean; deriveData?(data: Data): Promise; } @@ -88,7 +94,7 @@ export default function withValidation({ 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) { @@ -164,8 +170,8 @@ export default function withValidation({ 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 =
{ content }
; + const content = description.call(this, derivedData, results); + summary = content ?
{ content }
: undefined; } let feedback; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 96da5b9bd0..f8359526ec 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2792,6 +2792,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.", @@ -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",