//ValidateUsername checks if a username is already taken or not func (service *Service) ValidateUsername(w http.ResponseWriter, request *http.Request) { username := request.URL.Query().Get("username") response := struct { Valid bool `json:"valid"` Error string `json:"error"` }{ Valid: true, Error: "", } valid := user.ValidateUsername(username) if !valid { log.Debug("Invalid username format:", username) response.Error = "invalid_username_format" response.Valid = false json.NewEncoder(w).Encode(&response) return } userMgr := user.NewManager(request) userExists, err := userMgr.Exists(username) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if userExists { log.Debug("username ", username, " already taken") response.Error = "duplicate_username" response.Valid = false } json.NewEncoder(w).Encode(&response) return }
//ProcessRegistrationForm processes the user registration form func (service *Service) ProcessRegistrationForm(w http.ResponseWriter, request *http.Request) { response := struct { Redirecturl string `json:"redirecturl"` Error string `json:"error"` }{} values := struct { TwoFAMethod string `json:"twofamethod"` Login string `json:"login"` Email string `json:"email"` Phonenumber string `json:"phonenumber"` TotpCode string `json:"totpcode"` Password string `json:"password"` RedirectParams string `json:"redirectparams"` }{} if err := json.NewDecoder(request.Body).Decode(&values); err != nil { log.Debug("Error decoding the registration request:", err) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } twoFAMethod := values.TwoFAMethod if twoFAMethod != "sms" && twoFAMethod != "totp" { log.Info("Invalid 2fa method during registration: ", twoFAMethod) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } totpsession, err := service.GetSession(request, SessionForRegistration, "totp") if err != nil { log.Error("ERROR while getting the totp registration session", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if totpsession.IsNew { log.Debug("New registration session while processing the registration form") http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } totpsecret, ok := totpsession.Values["secret"].(string) if !ok { log.Error("Unable to convert the stored session totp secret to a string") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } valid := user.ValidateUsername(values.Login) var phonenumber user.Phonenumber if !valid { response.Error = "invalid_username_format" w.WriteHeader(422) json.NewEncoder(w).Encode(response) return } newuser := &user.User{ Username: values.Login, EmailAddresses: []user.EmailAddress{user.EmailAddress{Label: "main", EmailAddress: values.Email}}, } //validate the username is not taken yet userMgr := user.NewManager(request) count, err := userMgr.GetPendingRegistrationsCount() if err != nil { log.Error("Failed to get pending registerations count: ", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } log.Debug("count", count) if count >= MAX_PENDING_REGISTRATION_COUNT { log.Warn("Maximum amount of pending registrations reached") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } //we now just depend on mongo unique index to avoid duplicates when concurrent requests are made userExists, err := userMgr.Exists(newuser.Username) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if userExists { log.Debug("USER ", newuser.Username, " already registered") http.Error(w, http.StatusText(http.StatusConflict), http.StatusConflict) return } if twoFAMethod == "sms" { phonenumber = user.Phonenumber{Label: "main", Phonenumber: values.Phonenumber} if !phonenumber.IsValid() { log.Debug("Invalid phone number") w.WriteHeader(422) response.Error = "invalid_phonenumber" json.NewEncoder(w).Encode(&response) return } newuser.Phonenumbers = []user.Phonenumber{phonenumber} // Remove account after 3 days if it still doesn't have a verified phone by then duration := time.Duration(time.Hour * 24 * 3) expiresAt := time.Now() expiresAt = expiresAt.Add(duration) newuser.Expire = db.DateTime(expiresAt) } else { token := totp.TokenFromSecret(totpsecret) if !token.Validate(values.TotpCode) { log.Debug("Invalid totp code") w.WriteHeader(422) response.Error = "invalid_totpcode" json.NewEncoder(w).Encode(&response) return } } userMgr.Save(newuser) passwdMgr := password.NewManager(request) err = passwdMgr.Save(newuser.Username, values.Password) if err != nil { log.Error(err) if err.Error() != "internal_error" { w.WriteHeader(422) response.Error = "invalid_password" json.NewEncoder(w).Encode(&response) } else { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } return } if twoFAMethod == "sms" { validationkey, err := service.phonenumberValidationService.RequestValidation(request, newuser.Username, phonenumber, fmt.Sprintf("https://%s/phonevalidation", request.Host)) if err != nil { log.Error(err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } registrationSession, err := service.GetSession(request, SessionForRegistration, "registrationdetails") registrationSession.Values["username"] = newuser.Username registrationSession.Values["phonenumbervalidationkey"] = validationkey registrationSession.Values["redirectparams"] = values.RedirectParams sessions.Save(request, w) response.Redirecturl = fmt.Sprintf("https://%s/register#/smsconfirmation", request.Host) json.NewEncoder(w).Encode(&response) return } totpMgr := totp.NewManager(request) totpMgr.Save(newuser.Username, totpsecret) log.Debugf("Registered %s", newuser.Username) service.loginUser(w, request, newuser.Username) }