Implement /register/available API
MODIFY
src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go
src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go
@@ -314,3 +314,13 @@ func (d *Database) GetThreePIDsForLocalpart(
) (threepids []authtypes.ThreePID, err error) {
return d.threepids.selectThreePIDsForLocalpart(ctx, localpart)
}
+
+// CheckAccountAvailability checks if the username/localpart is already present in the database.
+// If the DB returns sql.ErrNoRows the Localpart isn't taken.
+func (d *Database) CheckAccountAvailability(ctx context.Context, localpart string) (bool, error) {
+ _, err := d.accounts.selectAccountByLocalpart(ctx, localpart)
+ if err == sql.ErrNoRows {
+ return true, nil
+ }
+ return false, err
+}

MODIFY
src/github.com/matrix-org/dendrite/clientapi/routing/routing.go
src/github.com/matrix-org/dendrite/clientapi/routing/routing.go
@@ -131,6 +131,10 @@ func Setup(
return writers.LegacyRegister(req, accountDB, deviceDB, &cfg)
})).Methods("POST", "OPTIONS")
+ r0mux.Handle("/register/available", common.MakeExternalAPI("registerAvailable", func(req *http.Request) util.JSONResponse {
+ return writers.RegisterAvailable(req, accountDB)
+ })).Methods("GET")
+
r0mux.Handle("/directory/room/{roomAlias}",
common.MakeAuthAPI("directory_room", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
vars := mux.Vars(req)

MODIFY
src/github.com/matrix-org/dendrite/clientapi/writers/register.go
src/github.com/matrix-org/dendrite/clientapi/writers/register.go
@@ -91,33 +91,40 @@ type registerResponse struct {
DeviceID string `json:"device_id"`
}
-// Validate returns an error response if the username/password are invalid
-func validate(username, password string) *util.JSONResponse {
+// validateUserName returns an error response if the username is invalid
+func validateUserName(username string) *util.JSONResponse {
// https://github.com/matrix-org/synapse/blob/v0.20.0/synapse/rest/client/v2_alpha/register.py#L161
- if len(password) > maxPasswordLength {
+ if len(username) > maxUsernameLength {
return &util.JSONResponse{
Code: 400,
- JSON: jsonerror.BadJSON(fmt.Sprintf("'password' >%d characters", maxPasswordLength)),
+ JSON: jsonerror.BadJSON(fmt.Sprintf("'username' >%d characters", maxUsernameLength)),
}
- } else if len(username) > maxUsernameLength {
+ } else if !validUsernameRegex.MatchString(username) {
return &util.JSONResponse{
Code: 400,
- JSON: jsonerror.BadJSON(fmt.Sprintf("'username' >%d characters", maxUsernameLength)),
+ JSON: jsonerror.InvalidUsername("User ID can only contain characters a-z, 0-9, or '_-./'"),
}
- } else if len(password) > 0 && len(password) < minPasswordLength {
+ } else if username[0] == '_' { // Regex checks its not a zero length string
return &util.JSONResponse{
Code: 400,
- JSON: jsonerror.WeakPassword(fmt.Sprintf("password too weak: min %d chars", minPasswordLength)),
+ JSON: jsonerror.InvalidUsername("User ID can't start with a '_'"),
}
- } else if !validUsernameRegex.MatchString(username) {
+ }
+ return nil
+}
+
+// validatePassword returns an error response if the password is invalid
+func validatePassword(password string) *util.JSONResponse {
+ // https://github.com/matrix-org/synapse/blob/v0.20.0/synapse/rest/client/v2_alpha/register.py#L161
+ if len(password) > maxPasswordLength {
return &util.JSONResponse{
Code: 400,
- JSON: jsonerror.InvalidUsername("User ID can only contain characters a-z, 0-9, or '_-./'"),
+ JSON: jsonerror.BadJSON(fmt.Sprintf("'password' >%d characters", maxPasswordLength)),
}
- } else if username[0] == '_' { // Regex checks its not a zero length string
+ } else if len(password) > 0 && len(password) < minPasswordLength {
return &util.JSONResponse{
Code: 400,
- JSON: jsonerror.InvalidUsername("User ID can't start with a '_'"),
+ JSON: jsonerror.WeakPassword(fmt.Sprintf("password too weak: min %d chars", minPasswordLength)),
}
}
return nil
@@ -149,7 +156,10 @@ func Register(
}
}
- if resErr = validate(r.Username, r.Password); resErr != nil {
+ if resErr = validateUserName(r.Username); resErr != nil {
+ return *resErr
+ }
+ if resErr = validatePassword(r.Password); resErr != nil {
return *resErr
}
@@ -209,7 +219,10 @@ func LegacyRegister(
if resErr != nil {
return *resErr
}
- if resErr = validate(r.Username, r.Password); resErr != nil {
+ if resErr = validateUserName(r.Username); resErr != nil {
+ return *resErr
+ }
+ if resErr = validatePassword(r.Password); resErr != nil {
return *resErr
}
@@ -344,3 +357,40 @@ func isValidMacLogin(
return hmac.Equal(givenMac, expectedMAC), nil
}
+
+type availableResponse struct {
+ Available bool `json:"available"`
+}
+
+// RegisterAvailable checks if the username is already taken or invalid
+func RegisterAvailable(
+ req *http.Request,
+ accountDB *accounts.Database,
+) util.JSONResponse {
+ username := req.URL.Query().Get("username")
+
+ if err := validateUserName(username); err != nil {
+ return *err
+ }
+
+ availability, availabilityErr := accountDB.CheckAccountAvailability(req.Context(), username)
+ if availabilityErr != nil {
+ return util.JSONResponse{
+ Code: 500,
+ JSON: jsonerror.Unknown("failed to check availability: " + availabilityErr.Error()),
+ }
+ }
+ if !availability {
+ return util.JSONResponse{
+ Code: 400,
+ JSON: jsonerror.InvalidUsername("A different user ID has already been registered for this session"),
+ }
+ }
+
+ return util.JSONResponse{
+ Code: 200,
+ JSON: availableResponse{
+ Available: true,
+ },
+ }
+}

Implement /register/available API (#291)

Signed-off-by: MTRNord <mtrnord1@gmail.com>