1
0
mirror of https://github.com/volatiletech/authboss.git synced 2025-02-15 14:03:17 +02:00

Add recovery code logins to sms

This commit is contained in:
Aaron L 2018-08-26 15:43:35 -07:00
parent e79638a05e
commit e4badae1ee
4 changed files with 62 additions and 22 deletions

View File

@ -17,10 +17,11 @@ const (
FormValuePassword = "password"
FormValueUsername = "username"
FormValueConfirm = "cnf"
FormValueToken = "token"
FormValueCode = "code"
FormValuePhoneNumber = "phone_number"
FormValueConfirm = "cnf"
FormValueToken = "token"
FormValueCode = "code"
FormValueRecoveryCode = "recovery_code"
FormValuePhoneNumber = "phone_number"
)
// UserValues from the login form
@ -98,23 +99,31 @@ func (r RecoverEndValues) GetPassword() string { return r.NewPassword }
type TwoFA struct {
HTTPFormValidator
Code string
Code string
RecoveryCode string
}
// GetCode from authenticator
func (t TwoFA) GetCode() string { return t.Code }
// GetRecoveryCode for authenticator
func (t TwoFA) GetRecoveryCode() string { return t.RecoveryCode }
// SMSTwoFA for sms2fa_validate page
type SMSTwoFA struct {
HTTPFormValidator
Code string
PhoneNumber string
Code string
RecoveryCode string
PhoneNumber string
}
// GetCode from sms
func (s SMSTwoFA) GetCode() string { return s.Code }
// GetRecoveryCode from sms
func (s SMSTwoFA) GetRecoveryCode() string { return s.RecoveryCode }
// GetPhoneNumber from authenticator
func (s SMSTwoFA) GetPhoneNumber() string { return s.PhoneNumber }
@ -263,12 +272,14 @@ func (h HTTPBodyReader) Read(page string, r *http.Request) (authboss.Validator,
return TwoFA{
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
Code: values[FormValueCode],
RecoveryCode: values[FormValueRecoveryCode],
}, nil
case "sms2fa_validate":
return SMSTwoFA{
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
Code: values[FormValueCode],
PhoneNumber: values[FormValuePhoneNumber],
RecoveryCode: values[FormValueRecoveryCode],
}, nil
case "register":
arbitrary := make(map[string]string)

View File

@ -272,15 +272,25 @@ func (s *SMSValidator) Post(w http.ResponseWriter, r *http.Request) error {
if err != nil {
return err
}
smsCodeValues := MustHaveSMSValues(validator)
inputCode := smsCodeValues.GetCode()
if len(inputCode) == 0 {
var inputCode, recoveryCode string
inputCode = smsCodeValues.GetCode()
// Only allow recovery codes on login/remove operations
if s.Action == dataValidate || s.Action == dataValidateRemove {
recoveryCode = smsCodeValues.GetRecoveryCode()
}
if len(recoveryCode) == 0 && len(inputCode) == 0 {
return s.sendCode(w, r, user)
}
return s.validateCode(w, r, user, inputCode)
if len(recoveryCode) != 0 {
return s.validateCode(w, r, user, "", recoveryCode)
}
return s.validateCode(w, r, user, inputCode, "")
}
func (s *SMSValidator) sendCode(w http.ResponseWriter, r *http.Request, user User) error {
@ -316,17 +326,35 @@ func (s *SMSValidator) sendCode(w http.ResponseWriter, r *http.Request, user Use
return s.Core.Responder.Respond(w, r, http.StatusOK, PageSMSValidate, data)
}
func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user User, inputCode string) error {
code, ok := authboss.GetSession(r, SessionSMSSecret)
if !ok || len(code) == 0 {
return errors.Errorf("no code in session for user %s", user.GetPID())
func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user User, inputCode, recoveryCode string) error {
logger := s.RequestLogger(r)
var verified bool
if len(recoveryCode) != 0 {
var ok bool
recoveryCodes := twofactor.DecodeRecoveryCodes(user.GetRecoveryCodes())
recoveryCodes, ok = twofactor.UseRecoveryCode(recoveryCodes, recoveryCode)
verified = ok
if verified {
logger.Infof("user %s used recovery code instead of sms2fa", user.GetPID())
user.PutRecoveryCodes(twofactor.EncodeRecoveryCodes(recoveryCodes))
if err := s.Authboss.Config.Storage.Server.Save(r.Context(), user); err != nil {
return err
}
}
} else {
code, ok := authboss.GetSession(r, SessionSMSSecret)
if !ok || len(code) == 0 {
return errors.Errorf("no code in session for user %s", user.GetPID())
}
verified = 1 == subtle.ConstantTimeCompare([]byte(inputCode), []byte(code))
}
verified := 1 == subtle.ConstantTimeCompare([]byte(inputCode), []byte(code))
logger := s.RequestLogger(r)
if !verified {
logger.Infof("user %s sms failure (wrong code)", user.GetPID())
logger.Infof("user %s sms 2fa failure (wrong code)", user.GetPID())
data := authboss.HTMLData{
authboss.DataValidation: map[string][]string{FormValueCode: []string{"2fa code was invalid"}},
DataValidateMode: s.Action,
@ -383,7 +411,7 @@ func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user
authboss.DelSession(w, SessionSMSPendingPID)
authboss.DelSession(w, SessionSMSSecret)
logger.Infof("user %s sms success", user.GetPID())
logger.Infof("user %s sms 2fa success", user.GetPID())
ro := authboss.RedirectOptions{
Code: http.StatusTemporaryRedirect,

View File

@ -11,6 +11,7 @@ type SMSValuer interface {
authboss.Validator
GetCode() string
GetRecoveryCode() string
}
// SMSPhoneNumberValuer returns a phone number from the body

View File

@ -327,7 +327,7 @@ func (t *TOTP) PostValidate(w http.ResponseWriter, r *http.Request) error {
case err != nil:
return err
case !ok:
logger.Infof("user %s totp failure (wrong code)", user.GetPID())
logger.Infof("user %s totp 2fa failure (wrong code)", user.GetPID())
data := authboss.HTMLData{
authboss.DataErr: "totp 2fa code incorrect",
DataValidateMode: dataValidate,
@ -341,7 +341,7 @@ func (t *TOTP) PostValidate(w http.ResponseWriter, r *http.Request) error {
authboss.DelSession(w, SessionTOTPPendingPID)
authboss.DelSession(w, SessionTOTPSecret)
logger.Infof("user %s totp success", user.GetPID())
logger.Infof("user %s totp 2fa success", user.GetPID())
ro := authboss.RedirectOptions{
Code: http.StatusTemporaryRedirect,