//RequestPasswordReset Request a password reset func (service *IYOEmailAddressValidationService) RequestPasswordReset(request *http.Request, username string, emails []string) (key string, err error) { pwdMngr := password.NewManager(request) token, err := pwdMngr.NewResetToken(username) if err != nil { return } if err = pwdMngr.SaveResetToken(token); err != nil { return } passwordreseturl := fmt.Sprintf("https://%s/login#/resetpassword/%s", request.Host, url.QueryEscape(token.Token)) templateParameters := struct { Url string Username string Title string Text string ButtonText string Reason string }{ Url: passwordreseturl, Username: username, Title: "It's You Online password reset", Text: "To reset your ItsYou.Online password, click the button below.", ButtonText: "Reset password", Reason: "You’re receiving this email because you recently requested to reset your password at ItsYou.Online. If this wasn’t you, please ignore this email.", } message, err := tools.RenderTemplate(emailWithButtonTemplateName, templateParameters) if err != nil { return } go service.EmailService.Send(emails, "ItsYou.Online password reset", message) key = token.Token return }
//ResetPassword handler for POST /login/resetpassword func (service *Service) ResetPassword(w http.ResponseWriter, request *http.Request) { values := struct { Token string `json:"token"` Password string `json:"password"` }{} if err := json.NewDecoder(request.Body).Decode(&values); err != nil { log.Debug("Error decoding the ResetPassword request:", err) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } pwdMngr := password.NewManager(request) token, err := pwdMngr.FindResetToken(values.Token) if err != nil { log.Debug("Failed to find password reset token - ", err) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } err = pwdMngr.Save(token.Username, values.Password) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if err = pwdMngr.DeleteResetToken(values.Token); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) 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) }
//ProcessLoginForm logs a user in if the credentials are valid func (service *Service) ProcessLoginForm(w http.ResponseWriter, request *http.Request) { //TODO: validate csrf token //TODO: limit the number of failed/concurrent requests err := request.ParseForm() if err != nil { log.Debug("ERROR parsing registration form") http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } values := struct { Login string `json:"login"` Password string `json:"password"` }{} if err = json.NewDecoder(request.Body).Decode(&values); err != nil { log.Debug("Error decoding the login request:", err) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } login := strings.ToLower(values.Login) u, err := organization.SearchUser(request, login) if err == mgo.ErrNotFound { w.WriteHeader(422) return } else if err != nil { log.Error("Failed to search for user: "******"client_id") // Remove last 2FA entry if an invalid password is entered validcredentials := userexists && validpassword if !validcredentials { if client != "" { l2faMgr := organizationdb.NewLast2FAManager(request) if l2faMgr.Exists(client, u.Username) { l2faMgr.RemoveLast2FA(client, u.Username) } } w.WriteHeader(422) return } loginSession, err := service.GetSession(request, SessionLogin, "loginsession") if err != nil { log.Error(err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } loginSession.Values["username"] = u.Username //check if 2fa validity has passed if client != "" { l2faMgr := organizationdb.NewLast2FAManager(request) if l2faMgr.Exists(client, u.Username) { timestamp, err := l2faMgr.GetLast2FA(client, u.Username) if err != nil { log.Error(err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } mgr := organizationdb.NewManager(request) seconds, err := mgr.GetValidity(client) if err != nil { log.Error(err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } timeconverted := time.Time(timestamp) if timeconverted.Add(time.Second * time.Duration(seconds)).After(time.Now()) { service.loginUser(w, request, u.Username) return } } } sessions.Save(request, w) w.WriteHeader(http.StatusNoContent) }
//ProcessLoginForm logs a user in if the credentials are valid func (service *Service) ProcessLoginForm(w http.ResponseWriter, request *http.Request) { //TODO: validate csrf token //TODO: limit the number of failed/concurrent requests log.Debug(request.RequestURI) err := request.ParseForm() if err != nil { log.Debug("ERROR parsing registration form") http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } values := request.Form username := values.Get("login") //validate the username exists var userexists bool userMgr := user.NewManager(request) if userexists, err = userMgr.Exists(username); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } var validpassword bool passwdMgr := password.NewManager(request) if validpassword, err = passwdMgr.Validate(username, values.Get("password")); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } var validtotpcode bool totpMgr := totp.NewManager(request) if validtotpcode, err = totpMgr.Validate(username, values.Get("totpcode")); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } validcredentials := userexists && validpassword && validtotpcode if !validcredentials { service.renderLoginForm(w, request, true, request.RequestURI) return } if err := service.SetLoggedInUser(w, request, username); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } sessions.Save(request, w) log.Debugf("Successfull login by '%s'", username) redirectURL := "" queryValues := request.URL.Query() endpoint := queryValues.Get("endpoint") if endpoint != "" { queryValues.Del("endpoint") redirectURL = endpoint + "?" + queryValues.Encode() } http.Redirect(w, request, redirectURL, http.StatusFound) }
//ProcessRegistrationForm processes the user registration form func (service *Service) ProcessRegistrationForm(w http.ResponseWriter, request *http.Request) { err := request.ParseForm() if err != nil { log.Debug("ERROR parsing registration form:", err) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } validationErrors := make([]string, 0, 0) values := request.Form totpsession, err := service.GetSession(request, SessionForRegistration, "totp") if err != nil { log.Error("EROR while getting the totp registration session", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if totpsession.IsNew { //TODO: indicate expired registration session log.Debug("New registration session while processing the registration form") service.ShowRegistrationForm(w, request) 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 } newuser := &user.User{ Username: values.Get("login"), Email: map[string]string{"registration": values.Get("email")}, } //TODO: validate newuser //validate the username is not taken yet userMgr := user.NewManager(request) //TODO: distributed lock userExists, err := userMgr.Exists(newuser.Username) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if userExists { validationErrors = append(validationErrors, "duplicateusername") log.Debug("USER ", newuser.Username, " already registered") service.renderRegistrationFrom(w, request, validationErrors, totpsecret) return } totpcode := values.Get("totpcode") token := totp.TokenFromSecret(totpsecret) if !token.Validate(totpcode) { log.Debug("Invalid totp code") validationErrors = append(validationErrors, "invalidtotpcode") service.renderRegistrationFrom(w, request, validationErrors, totpsecret) return } userMgr.Save(newuser) passwdMgr := password.NewManager(request) passwdMgr.Save(newuser.Username, values.Get("password")) totpMgr := totp.NewManager(request) totpMgr.Save(newuser.Username, totpsecret) log.Debugf("Registered %s", newuser.Username) service.SetLoggedInUser(w, request, newuser.Username) sessions.Save(request, w) http.Redirect(w, request, "/", http.StatusFound) }