diff --git a/cypress/e2e/register/register.spec.ts b/cypress/e2e/register/register.spec.ts index 8d1076f0b7..a0c120414d 100644 --- a/cypress/e2e/register/register.spec.ts +++ b/cypress/e2e/register/register.spec.ts @@ -85,4 +85,48 @@ describe("Registration", () => { cy.get(".mx_DevicesPanel_myDevice .mx_DevicesPanel_deviceTrust .mx_E2EIcon") .should("have.class", "mx_E2EIcon_verified"); }); + + it("should require username to fulfil requirements and be available", () => { + cy.get(".mx_ServerPicker_change", { timeout: 15000 }).click(); + cy.get(".mx_ServerPickerDialog_continue").should("be.visible"); + cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl); + cy.get(".mx_ServerPickerDialog_continue").click(); + // wait for the dialog to go away + cy.get('.mx_ServerPickerDialog').should('not.exist'); + + cy.get("#mx_RegistrationForm_username").should("be.visible"); + + cy.intercept("**/_matrix/client/*/register/available?username=_alice", { + statusCode: 400, + headers: { + "Content-Type": "application/json", + }, + body: { + errcode: "M_INVALID_USERNAME", + error: "User ID may not begin with _", + }, + }); + cy.get("#mx_RegistrationForm_username").type("_alice"); + cy.get(".mx_Field_tooltip") + .should("have.class", "mx_Tooltip_visible") + .should("contain.text", "Some characters not allowed"); + + cy.intercept("**/_matrix/client/*/register/available?username=bob", { + statusCode: 400, + headers: { + "Content-Type": "application/json", + }, + body: { + errcode: "M_USER_IN_USE", + error: "The desired username is already taken", + }, + }); + cy.get("#mx_RegistrationForm_username").type("{selectAll}{backspace}bob"); + cy.get(".mx_Field_tooltip") + .should("have.class", "mx_Tooltip_visible") + .should("contain.text", "Someone already has that username"); + + cy.get("#mx_RegistrationForm_username").type("{selectAll}{backspace}foobar"); + cy.get(".mx_Field_tooltip").should("not.have.class", "mx_Tooltip_visible"); + }); }); diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 690acd5c31..d1f7c9acc4 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -18,6 +18,7 @@ limitations under the License. import React from 'react'; import { MatrixClient } from 'matrix-js-sdk/src/client'; import { logger } from "matrix-js-sdk/src/logger"; +import { MatrixError } from 'matrix-js-sdk/src/matrix'; import * as Email from '../../../email'; import { looksValid as phoneNumberLooksValid } from '../../../phonenumber'; @@ -48,6 +49,7 @@ enum UsernameAvailableStatus { Available, Unavailable, Error, + Invalid, } export const PASSWORD_MIN_SCORE = 3; // safely unguessable: moderate protection from offline slow-hash scenario. @@ -363,6 +365,9 @@ export default class RegistrationForm extends React.PureComponent !value || SAFE_LOCALPART_REGEX.test(value), + test: ({ value }, usernameAvailable) => (!value || SAFE_LOCALPART_REGEX.test(value)) + && usernameAvailable !== UsernameAvailableStatus.Invalid, invalid: () => _t("Some characters not allowed"), }, {