// GetTwoFactorAuthenticationMethods returns the possible two factor authentication methods the user can use to login with. func (service *Service) GetTwoFactorAuthenticationMethods(w http.ResponseWriter, request *http.Request) { loginSession, err := service.GetSession(request, SessionLogin, "loginsession") if err != nil { log.Error(err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } username, ok := loginSession.Values["username"].(string) if username == "" || !ok { http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } userMgr := user.NewManager(request) userFromDB, err := userMgr.GetByName(username) if err != nil { log.Error(err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } response := struct { Totp bool `json:"totp"` Sms map[string]string `json:"sms"` }{Sms: make(map[string]string)} totpMgr := totp.NewManager(request) response.Totp, err = totpMgr.HasTOTP(username) if err != nil { log.Error(err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } valMgr := validationdb.NewManager(request) verifiedPhones, err := valMgr.GetByUsernameValidatedPhonenumbers(username) if err != nil { log.Error(err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } for _, validatedPhoneNumber := range verifiedPhones { for _, number := range userFromDB.Phonenumbers { if number.Phonenumber == string(validatedPhoneNumber.Phonenumber) { response.Sms[number.Label] = string(validatedPhoneNumber.Phonenumber) } } } json.NewEncoder(w).Encode(response) return }
//ProcessTOTPConfirmation checks the totp 2 factor authentication code func (service *Service) ProcessTOTPConfirmation(w http.ResponseWriter, request *http.Request) { username, err := service.getUserLoggingIn(request) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if username == "" { sessions.Save(request, w) http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } values := struct { Totpcode string `json:"totpcode"` }{} if err := json.NewDecoder(request.Body).Decode(&values); err != nil { log.Debug("Error decoding the totp confirmation request:", err) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } var validtotpcode bool totpMgr := totp.NewManager(request) if validtotpcode, err = totpMgr.Validate(username, values.Totpcode); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if !validtotpcode { //TODO: limit to 3 failed attempts w.WriteHeader(422) return } //add last 2fa date if logging in with oauth2 service.storeLast2FALogin(request, username) service.loginUser(w, request, username) }
//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 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) }