mirror of
https://github.com/volatiletech/authboss.git
synced 2025-01-10 04:17:59 +02:00
Split 2fa pages apart
- Add a config option to control the authboss.Middleware redirecting
This commit is contained in:
parent
5af4d392ab
commit
4420666f2b
11
config.go
11
config.go
@ -90,11 +90,12 @@ type Config struct {
|
||||
// for google authenticator.
|
||||
TOTP2FAIssuer string
|
||||
|
||||
// TwoFactorRedirectOnUnauthed controls whether or not a user is redirected or given
|
||||
// a 404 when they are unauthenticated. The two factor modules all use authboss.Middleware
|
||||
// to protect their routes and this is the redirectToLogin parameter in that middleware
|
||||
// that they pass through.
|
||||
TwoFactorRedirectOnUnauthed bool
|
||||
// RoutesRedirectOnUnauthed controls whether or not a user is redirected or given
|
||||
// a 404 when they are unauthenticated and attempting to access a route that's
|
||||
// login-protected inside Authboss itself. The otp/twofactor modules all use
|
||||
// authboss.Middleware to protect their routes and this is the
|
||||
// redirectToLogin parameter in that middleware that they pass through.
|
||||
RoutesRedirectOnUnauthed bool
|
||||
}
|
||||
|
||||
Mail struct {
|
||||
|
@ -268,13 +268,13 @@ func (h HTTPBodyReader) Read(page string, r *http.Request) (authboss.Validator,
|
||||
Token: values[FormValueToken],
|
||||
NewPassword: values[FormValuePassword],
|
||||
}, nil
|
||||
case "totp2fa_validate":
|
||||
case "totp2fa_confirm", "totp2fa_remove", "totp2fa_validate":
|
||||
return TwoFA{
|
||||
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
|
||||
Code: values[FormValueCode],
|
||||
RecoveryCode: values[FormValueRecoveryCode],
|
||||
}, nil
|
||||
case "sms2fa_validate":
|
||||
case "sms2fa_setup", "sms2fa_remove", "sms2fa_confirm", "sms2fa_validate":
|
||||
return SMSTwoFA{
|
||||
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
|
||||
Code: values[FormValueCode],
|
||||
|
@ -75,7 +75,7 @@ func (o *OTP) Init(ab *authboss.Authboss) (err error) {
|
||||
o.Authboss.Config.Core.Router.Get("/otp/login", o.Authboss.Core.ErrorHandler.Wrap(o.LoginGet))
|
||||
o.Authboss.Config.Core.Router.Post("/otp/login", o.Authboss.Core.ErrorHandler.Wrap(o.LoginPost))
|
||||
|
||||
middleware := authboss.MountedMiddleware(ab, true, true, false, false)
|
||||
middleware := authboss.MountedMiddleware(ab, true, ab.Config.Modules.RoutesRedirectOnUnauthed, false, false)
|
||||
o.Authboss.Config.Core.Router.Get("/otp/add", middleware(o.Authboss.Core.ErrorHandler.Wrap(o.AddGet)))
|
||||
o.Authboss.Config.Core.Router.Post("/otp/add", middleware(o.Authboss.Core.ErrorHandler.Wrap(o.AddPost)))
|
||||
|
||||
|
@ -32,20 +32,20 @@ const (
|
||||
|
||||
// Pages
|
||||
const (
|
||||
PageSMSValidate = "sms2fa_validate"
|
||||
PageSMSValidateSuccess = "sms2fa_validate_success"
|
||||
successSuffix = "_success"
|
||||
|
||||
PageSMSConfirm = "sms2fa_confirm"
|
||||
PageSMSConfirmSuccess = "sms2fa_confirm_success"
|
||||
PageSMSRemove = "sms2fa_remove"
|
||||
PageSMSRemoveSuccess = "sms2fa_remove_success"
|
||||
PageSMSSetup = "sms2fa_setup"
|
||||
PageSMSValidate = "sms2fa_validate"
|
||||
)
|
||||
|
||||
// Data constants
|
||||
const (
|
||||
DataValidateMode = "validate_mode"
|
||||
DataSMSSecret = SessionSMSSecret
|
||||
DataSMSPhoneNumber = "sms_phone_number"
|
||||
|
||||
dataValidate = "validate"
|
||||
dataValidateSetup = "setup"
|
||||
dataValidateConfirm = "confirm"
|
||||
dataValidateRemove = "remove"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -88,7 +88,7 @@ type SMS struct {
|
||||
// SMSValidator abstracts the send code/resend code/submit code workflow
|
||||
type SMSValidator struct {
|
||||
*SMS
|
||||
Action string
|
||||
Page string
|
||||
}
|
||||
|
||||
// Setup the module
|
||||
@ -97,25 +97,32 @@ func (s *SMS) Setup() error {
|
||||
return errors.New("must have SMS.Sender set")
|
||||
}
|
||||
|
||||
middleware := authboss.MountedMiddleware(s.Authboss, true, s.Authboss.Config.Modules.TwoFactorRedirectOnUnauthed, false, false)
|
||||
middleware := authboss.MountedMiddleware(s.Authboss, true, s.Authboss.Config.Modules.RoutesRedirectOnUnauthed, false, false)
|
||||
s.Authboss.Core.Router.Get("/2fa/sms/setup", middleware(s.Core.ErrorHandler.Wrap(s.GetSetup)))
|
||||
s.Authboss.Core.Router.Post("/2fa/sms/setup", middleware(s.Core.ErrorHandler.Wrap(s.PostSetup)))
|
||||
|
||||
confirm := &SMSValidator{SMS: s, Action: dataValidateConfirm}
|
||||
confirm := &SMSValidator{SMS: s, Page: PageSMSConfirm}
|
||||
s.Authboss.Core.Router.Get("/2fa/sms/confirm", middleware(s.Core.ErrorHandler.Wrap(confirm.Get)))
|
||||
s.Authboss.Core.Router.Post("/2fa/sms/confirm", middleware(s.Core.ErrorHandler.Wrap(confirm.Post)))
|
||||
|
||||
remove := &SMSValidator{SMS: s, Action: dataValidateRemove}
|
||||
remove := &SMSValidator{SMS: s, Page: PageSMSRemove}
|
||||
s.Authboss.Core.Router.Get("/2fa/sms/remove", middleware(s.Core.ErrorHandler.Wrap(remove.Get)))
|
||||
s.Authboss.Core.Router.Post("/2fa/sms/remove", middleware(s.Core.ErrorHandler.Wrap(remove.Post)))
|
||||
|
||||
validate := &SMSValidator{SMS: s, Action: dataValidate}
|
||||
validate := &SMSValidator{SMS: s, Page: PageSMSValidate}
|
||||
s.Authboss.Core.Router.Get("/2fa/sms/validate", s.Core.ErrorHandler.Wrap(validate.Get))
|
||||
s.Authboss.Core.Router.Post("/2fa/sms/validate", s.Core.ErrorHandler.Wrap(validate.Post))
|
||||
|
||||
s.Authboss.Events.Before(authboss.EventAuth, s.BeforeAuth)
|
||||
|
||||
return s.Authboss.Core.ViewRenderer.Load(PageSMSValidate, PageSMSValidateSuccess)
|
||||
return s.Authboss.Core.ViewRenderer.Load(
|
||||
PageSMSConfirm,
|
||||
PageSMSConfirmSuccess,
|
||||
PageSMSRemove,
|
||||
PageSMSRemoveSuccess,
|
||||
PageSMSSetup,
|
||||
PageSMSValidate,
|
||||
)
|
||||
}
|
||||
|
||||
// BeforeAuth stores the user's pid in a special temporary session variable
|
||||
@ -197,18 +204,18 @@ func (s *SMS) GetSetup(w http.ResponseWriter, r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
data := authboss.HTMLData{DataValidateMode: dataValidateSetup}
|
||||
var data authboss.HTMLData
|
||||
numberProvider, ok := abUser.(SMSNumberProvider)
|
||||
if ok {
|
||||
if val := numberProvider.GetSMSPhoneNumberSeed(); len(val) != 0 {
|
||||
data[DataSMSPhoneNumber] = val
|
||||
data = authboss.HTMLData{DataSMSPhoneNumber: val}
|
||||
}
|
||||
}
|
||||
|
||||
authboss.DelSession(w, SessionSMSSecret)
|
||||
authboss.DelSession(w, SessionSMSNumber)
|
||||
|
||||
return s.Core.Responder.Respond(w, r, http.StatusOK, PageSMSValidate, data)
|
||||
return s.Core.Responder.Respond(w, r, http.StatusOK, PageSMSSetup, data)
|
||||
}
|
||||
|
||||
// PostSetup adds the phone number provided to the user's session and sends
|
||||
@ -220,7 +227,7 @@ func (s *SMS) PostSetup(w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
user := abUser.(User)
|
||||
|
||||
validator, err := s.Authboss.Config.Core.BodyReader.Read(PageSMSValidate, r)
|
||||
validator, err := s.Authboss.Config.Core.BodyReader.Read(PageSMSSetup, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -231,9 +238,8 @@ func (s *SMS) PostSetup(w http.ResponseWriter, r *http.Request) error {
|
||||
if len(number) == 0 {
|
||||
data := authboss.HTMLData{
|
||||
authboss.DataValidation: map[string][]string{FormValuePhoneNumber: []string{"must provide a phone number"}},
|
||||
DataValidateMode: dataValidateSetup,
|
||||
}
|
||||
return s.Core.Responder.Respond(w, r, http.StatusOK, PageSMSValidate, data)
|
||||
return s.Core.Responder.Respond(w, r, http.StatusOK, PageSMSSetup, data)
|
||||
}
|
||||
|
||||
authboss.PutSession(w, SessionSMSNumber, number)
|
||||
@ -251,8 +257,7 @@ func (s *SMS) PostSetup(w http.ResponseWriter, r *http.Request) error {
|
||||
// Get shows an empty page typically, this allows us to prompt
|
||||
// a second time for the action.
|
||||
func (s *SMSValidator) Get(w http.ResponseWriter, r *http.Request) error {
|
||||
data := authboss.HTMLData{DataValidateMode: s.Action}
|
||||
return s.Core.Responder.Respond(w, r, http.StatusOK, PageSMSValidate, data)
|
||||
return s.Core.Responder.Respond(w, r, http.StatusOK, s.Page, nil)
|
||||
}
|
||||
|
||||
// Post receives a code in the body and validates it, if the code is
|
||||
@ -273,7 +278,7 @@ func (s *SMSValidator) Post(w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
user := abUser.(User)
|
||||
|
||||
validator, err := s.Authboss.Config.Core.BodyReader.Read(PageSMSValidate, r)
|
||||
validator, err := s.Authboss.Config.Core.BodyReader.Read(s.Page, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -283,7 +288,7 @@ func (s *SMSValidator) Post(w http.ResponseWriter, r *http.Request) error {
|
||||
inputCode = smsCodeValues.GetCode()
|
||||
|
||||
// Only allow recovery codes on login/remove operations
|
||||
if s.Action == dataValidate || s.Action == dataValidateRemove {
|
||||
if s.Page == PageSMSValidate || s.Page == PageSMSRemove {
|
||||
recoveryCode = smsCodeValues.GetRecoveryCode()
|
||||
}
|
||||
|
||||
@ -303,15 +308,15 @@ func (s *SMSValidator) sendCode(w http.ResponseWriter, r *http.Request, user Use
|
||||
|
||||
// Get the phone number, when we're confirming the phone number is not
|
||||
// yet stored in the user but inside the session.
|
||||
switch s.Action {
|
||||
case dataValidateConfirm:
|
||||
switch s.Page {
|
||||
case PageSMSConfirm:
|
||||
var ok bool
|
||||
phoneNumber, ok = authboss.GetSession(r, SessionSMSNumber)
|
||||
if !ok {
|
||||
return errors.New("request failed, no sms number present in session")
|
||||
}
|
||||
|
||||
case dataValidate, dataValidateRemove:
|
||||
case PageSMSValidate, PageSMSRemove:
|
||||
phoneNumber = user.GetSMSPhoneNumber()
|
||||
}
|
||||
|
||||
@ -319,16 +324,15 @@ func (s *SMSValidator) sendCode(w http.ResponseWriter, r *http.Request, user Use
|
||||
return errors.Errorf("no phone number was available in PostSendCode for user %s", user.GetPID())
|
||||
}
|
||||
|
||||
data := authboss.HTMLData{DataValidateMode: s.Action}
|
||||
|
||||
var data authboss.HTMLData
|
||||
err := s.SendCodeToUser(w, r, user.GetPID(), phoneNumber)
|
||||
if err == errSMSRateLimit {
|
||||
data[authboss.DataErr] = "please wait a few moments before resending SMS code"
|
||||
data = authboss.HTMLData{authboss.DataErr: "please wait a few moments before resending SMS code"}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.Core.Responder.Respond(w, r, http.StatusOK, PageSMSValidate, data)
|
||||
return s.Core.Responder.Respond(w, r, http.StatusOK, s.Page, data)
|
||||
}
|
||||
|
||||
func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user User, inputCode, recoveryCode string) error {
|
||||
@ -362,17 +366,14 @@ func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user
|
||||
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,
|
||||
}
|
||||
return s.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageSMSValidate, data)
|
||||
return s.Authboss.Core.Responder.Respond(w, r, http.StatusOK, s.Page, data)
|
||||
}
|
||||
|
||||
data := authboss.HTMLData{
|
||||
DataValidateMode: s.Action,
|
||||
}
|
||||
var data authboss.HTMLData
|
||||
|
||||
switch s.Action {
|
||||
case dataValidateConfirm:
|
||||
switch s.Page {
|
||||
case PageSMSConfirm:
|
||||
phoneNumber, ok := authboss.GetSession(r, SessionSMSNumber)
|
||||
if !ok {
|
||||
return errors.New("request failed, no sms number present in session")
|
||||
@ -399,8 +400,8 @@ func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user
|
||||
authboss.DelSession(w, SessionSMSNumber)
|
||||
|
||||
logger.Infof("user %s enabled sms 2fa", user.GetPID())
|
||||
data[twofactor.DataRecoveryCodes] = codes
|
||||
case dataValidateRemove:
|
||||
data = authboss.HTMLData{twofactor.DataRecoveryCodes: codes}
|
||||
case PageSMSRemove:
|
||||
user.PutSMSPhoneNumber("")
|
||||
if err := s.Authboss.Config.Storage.Server.Save(r.Context(), user); err != nil {
|
||||
return err
|
||||
@ -409,7 +410,7 @@ func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user
|
||||
authboss.DelSession(w, authboss.Session2FA)
|
||||
|
||||
logger.Infof("user %s disabled sms 2fa", user.GetPID())
|
||||
case dataValidate:
|
||||
case PageSMSValidate:
|
||||
authboss.PutSession(w, authboss.SessionKey, user.GetPID())
|
||||
authboss.PutSession(w, authboss.Session2FA, "sms")
|
||||
|
||||
@ -430,7 +431,7 @@ func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user
|
||||
return errors.New("unknown action for sms validate")
|
||||
}
|
||||
|
||||
return s.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageSMSValidateSuccess, data)
|
||||
return s.Authboss.Core.Responder.Respond(w, r, http.StatusOK, s.Page+successSuffix, data)
|
||||
}
|
||||
|
||||
// generateRandomCode for sms auth
|
||||
|
@ -242,12 +242,9 @@ func TestGetSetup(t *testing.T) {
|
||||
t.Error("session sms number should be cleared")
|
||||
}
|
||||
|
||||
if h.responder.Page != PageSMSValidate {
|
||||
if h.responder.Page != PageSMSSetup {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateSetup {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
if got := h.responder.Data[DataSMSPhoneNumber]; got != "seednumber" {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
@ -271,12 +268,9 @@ func TestPostSetup(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if h.responder.Page != PageSMSValidate {
|
||||
if h.responder.Page != PageSMSSetup {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateSetup {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
validation := h.responder.Data[authboss.DataValidation].(map[string][]string)
|
||||
if got := validation[FormValuePhoneNumber][0]; got != "must provide a phone number" {
|
||||
t.Error("data wrong:", got)
|
||||
@ -327,26 +321,23 @@ func TestValidatorGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := testSetup()
|
||||
validator := &SMSValidator{SMS: h.sms, Action: dataValidateConfirm}
|
||||
validator := &SMSValidator{SMS: h.sms, Page: PageSMSConfirm}
|
||||
|
||||
r, w, _ := h.newHTTP("GET")
|
||||
if err := validator.Get(w, r); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if h.responder.Page != PageSMSValidate {
|
||||
if h.responder.Page != PageSMSConfirm {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateConfirm {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatorPostSend(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := testSetup()
|
||||
validator := &SMSValidator{SMS: h.sms, Action: dataValidate}
|
||||
validator := &SMSValidator{SMS: h.sms, Page: PageSMSValidate}
|
||||
|
||||
r, w, _ := h.newHTTP("POST")
|
||||
|
||||
@ -368,7 +359,7 @@ func TestValidatorPostSend(t *testing.T) {
|
||||
|
||||
// When action is confirm, it retrieves the phone number from
|
||||
// the session, not the user.
|
||||
validator.Action = dataValidateConfirm
|
||||
validator.Page = PageSMSConfirm
|
||||
user.SMSPhoneNumber = ""
|
||||
h.setSession(SessionSMSNumber, "number")
|
||||
h.loadClientState(w, &r)
|
||||
@ -382,17 +373,13 @@ func TestValidatorPostSend(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// recovery code, normal code
|
||||
// user in session, user in pending
|
||||
// successful code paths for: validate, remove, and confirm
|
||||
|
||||
func TestValidatorPostOk(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OkConfirm", func(t *testing.T) {
|
||||
h := testSetup()
|
||||
r, w, _ := h.newHTTP("POST")
|
||||
v := &SMSValidator{SMS: h.sms, Action: dataValidateConfirm}
|
||||
v := &SMSValidator{SMS: h.sms, Page: PageSMSConfirm}
|
||||
|
||||
user := &mocks.User{Email: "test@test.com"}
|
||||
h.storer.Users[user.Email] = user
|
||||
@ -412,12 +399,9 @@ func TestValidatorPostOk(t *testing.T) {
|
||||
// Flush client state
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
if h.responder.Page != PageSMSValidateSuccess {
|
||||
if h.responder.Page != PageSMSConfirmSuccess {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateConfirm {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
if got := h.responder.Data[twofactor.DataRecoveryCodes].([]string); len(got) == 0 {
|
||||
t.Error("recovery codes should have been returned")
|
||||
}
|
||||
@ -440,7 +424,7 @@ func TestValidatorPostOk(t *testing.T) {
|
||||
t.Run("OkRemoveWithRecovery", func(t *testing.T) {
|
||||
h := testSetup()
|
||||
r, w, _ := h.newHTTP("POST")
|
||||
v := &SMSValidator{SMS: h.sms, Action: dataValidateRemove}
|
||||
v := &SMSValidator{SMS: h.sms, Page: PageSMSRemove}
|
||||
|
||||
user := &mocks.User{Email: "test@test.com", SMSPhoneNumber: "number"}
|
||||
h.storer.Users[user.Email] = user
|
||||
@ -468,12 +452,9 @@ func TestValidatorPostOk(t *testing.T) {
|
||||
// Flush client state
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
if h.responder.Page != PageSMSValidateSuccess {
|
||||
if h.responder.Page != PageSMSRemoveSuccess {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateRemove {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
|
||||
if h.session.ClientValues[authboss.Session2FA] != "" {
|
||||
t.Error("session 2fa should be cleared")
|
||||
@ -490,7 +471,7 @@ func TestValidatorPostOk(t *testing.T) {
|
||||
t.Run("OkValidateWithCode", func(t *testing.T) {
|
||||
h := testSetup()
|
||||
r, w, _ := h.newHTTP("POST")
|
||||
v := &SMSValidator{SMS: h.sms, Action: dataValidate}
|
||||
v := &SMSValidator{SMS: h.sms, Page: PageSMSValidate}
|
||||
|
||||
user := &mocks.User{Email: "test@test.com", SMSPhoneNumber: "number"}
|
||||
h.storer.Users[user.Email] = user
|
||||
@ -550,7 +531,7 @@ func TestValidatorPostOk(t *testing.T) {
|
||||
t.Run("FailRemoveCode", func(t *testing.T) {
|
||||
h := testSetup()
|
||||
r, w, _ := h.newHTTP("POST")
|
||||
v := &SMSValidator{SMS: h.sms, Action: dataValidateRemove}
|
||||
v := &SMSValidator{SMS: h.sms, Page: PageSMSRemove}
|
||||
|
||||
user := &mocks.User{Email: "test@test.com"}
|
||||
h.storer.Users[user.Email] = user
|
||||
@ -565,12 +546,9 @@ func TestValidatorPostOk(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if h.responder.Page != PageSMSValidate {
|
||||
if h.responder.Page != PageSMSRemove {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateRemove {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
validation := h.responder.Data[authboss.DataValidation].(map[string][]string)
|
||||
if got := validation[FormValueCode][0]; got != "2fa code was invalid" {
|
||||
t.Error("data wrong:", got)
|
||||
|
@ -29,8 +29,12 @@ const (
|
||||
|
||||
// Pages
|
||||
const (
|
||||
PageTOTPValidate = "totp2fa_validate"
|
||||
PageTOTPValidateSuccess = "totp2fa_validate_success"
|
||||
PageTOTPConfirm = "totp2fa_confirm"
|
||||
PageTOTPConfirmSuccess = "totp2fa_confirm_success"
|
||||
PageTOTPRemove = "totp2fa_remove"
|
||||
PageTOTPRemoveSuccess = "totp2fa_remove_success"
|
||||
PageTOTPSetup = "totp2fa_setup"
|
||||
PageTOTPValidate = "totp2fa_validate"
|
||||
)
|
||||
|
||||
// Form value constants
|
||||
@ -40,13 +44,7 @@ const (
|
||||
|
||||
// Data constants
|
||||
const (
|
||||
DataValidateMode = "validate_mode"
|
||||
DataTOTPSecret = SessionTOTPSecret
|
||||
|
||||
dataValidate = "validate"
|
||||
dataValidateSetup = "setup"
|
||||
dataValidateConfirm = "confirm"
|
||||
dataValidateRemove = "remove"
|
||||
DataTOTPSecret = SessionTOTPSecret
|
||||
)
|
||||
|
||||
var (
|
||||
@ -68,7 +66,7 @@ type TOTP struct {
|
||||
|
||||
// Setup the module
|
||||
func (t *TOTP) Setup() error {
|
||||
middleware := authboss.MountedMiddleware(t.Authboss, true, t.Authboss.Config.Modules.TwoFactorRedirectOnUnauthed, true, false)
|
||||
middleware := authboss.MountedMiddleware(t.Authboss, true, t.Authboss.Config.Modules.RoutesRedirectOnUnauthed, true, false)
|
||||
t.Authboss.Core.Router.Get("/2fa/totp/setup", middleware(t.Core.ErrorHandler.Wrap(t.GetSetup)))
|
||||
t.Authboss.Core.Router.Post("/2fa/totp/setup", middleware(t.Core.ErrorHandler.Wrap(t.PostSetup)))
|
||||
|
||||
@ -85,7 +83,14 @@ func (t *TOTP) Setup() error {
|
||||
|
||||
t.Authboss.Events.Before(authboss.EventAuth, t.BeforeAuth)
|
||||
|
||||
return t.Authboss.Core.ViewRenderer.Load(PageTOTPValidate, PageTOTPValidateSuccess)
|
||||
return t.Authboss.Core.ViewRenderer.Load(
|
||||
PageTOTPSetup,
|
||||
PageTOTPValidate,
|
||||
PageTOTPConfirm,
|
||||
PageTOTPConfirmSuccess,
|
||||
PageTOTPRemove,
|
||||
PageTOTPRemoveSuccess,
|
||||
)
|
||||
}
|
||||
|
||||
// BeforeAuth stores the user's pid in a special temporary session variable
|
||||
@ -117,8 +122,7 @@ func (t *TOTP) BeforeAuth(w http.ResponseWriter, r *http.Request, handled bool)
|
||||
// GetSetup shows a screen allows a user to opt in to setting up totp 2fa
|
||||
func (t *TOTP) GetSetup(w http.ResponseWriter, r *http.Request) error {
|
||||
authboss.DelSession(w, SessionTOTPSecret)
|
||||
data := authboss.HTMLData{DataValidateMode: dataValidateSetup}
|
||||
return t.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data)
|
||||
return t.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPSetup, nil)
|
||||
}
|
||||
|
||||
// PostSetup prepares adds a key to the user's session
|
||||
@ -203,11 +207,8 @@ func (t *TOTP) GetConfirm(w http.ResponseWriter, r *http.Request) error {
|
||||
return errors.New("request failed, no totp secret present in session")
|
||||
}
|
||||
|
||||
data := authboss.HTMLData{
|
||||
DataValidateMode: dataValidateConfirm,
|
||||
DataTOTPSecret: totpSecret,
|
||||
}
|
||||
return t.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data)
|
||||
data := authboss.HTMLData{DataTOTPSecret: totpSecret}
|
||||
return t.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPConfirm, data)
|
||||
}
|
||||
|
||||
// PostConfirm finally activates totp if the code matches
|
||||
@ -223,7 +224,7 @@ func (t *TOTP) PostConfirm(w http.ResponseWriter, r *http.Request) error {
|
||||
return errors.New("request failed, no totp secret present in session")
|
||||
}
|
||||
|
||||
validator, err := t.Authboss.Config.Core.BodyReader.Read(PageTOTPValidate, r)
|
||||
validator, err := t.Authboss.Config.Core.BodyReader.Read(PageTOTPConfirm, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -235,10 +236,9 @@ func (t *TOTP) PostConfirm(w http.ResponseWriter, r *http.Request) error {
|
||||
if !ok {
|
||||
data := authboss.HTMLData{
|
||||
authboss.DataValidation: map[string][]string{FormValueCode: []string{"2fa code was invalid"}},
|
||||
DataValidateMode: dataValidateConfirm,
|
||||
DataTOTPSecret: totpSecret,
|
||||
}
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data)
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPConfirm, data)
|
||||
}
|
||||
|
||||
codes, err := twofactor.GenerateRecoveryCodes()
|
||||
@ -263,18 +263,13 @@ func (t *TOTP) PostConfirm(w http.ResponseWriter, r *http.Request) error {
|
||||
logger := t.RequestLogger(r)
|
||||
logger.Infof("user %s enabled totp 2fa", user.GetPID())
|
||||
|
||||
data := authboss.HTMLData{
|
||||
twofactor.DataRecoveryCodes: codes,
|
||||
DataValidateMode: dataValidateConfirm,
|
||||
}
|
||||
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidateSuccess, data)
|
||||
data := authboss.HTMLData{twofactor.DataRecoveryCodes: codes}
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPConfirmSuccess, data)
|
||||
}
|
||||
|
||||
// GetRemove starts removal
|
||||
func (t *TOTP) GetRemove(w http.ResponseWriter, r *http.Request) error {
|
||||
data := authboss.HTMLData{DataValidateMode: dataValidateRemove}
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data)
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPRemove, nil)
|
||||
}
|
||||
|
||||
// PostRemove removes totp
|
||||
@ -282,19 +277,15 @@ func (t *TOTP) PostRemove(w http.ResponseWriter, r *http.Request) error {
|
||||
user, ok, err := t.validate(r)
|
||||
switch {
|
||||
case err == errNoTOTPEnabled:
|
||||
data := authboss.HTMLData{
|
||||
authboss.DataErr: "totp 2fa not active",
|
||||
DataValidateMode: dataValidateRemove,
|
||||
}
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data)
|
||||
data := authboss.HTMLData{authboss.DataErr: "totp 2fa not active"}
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPRemove, data)
|
||||
case err != nil:
|
||||
return err
|
||||
case !ok:
|
||||
data := authboss.HTMLData{
|
||||
authboss.DataValidation: map[string][]string{FormValueCode: []string{"2fa code was invalid"}},
|
||||
DataValidateMode: dataValidateRemove,
|
||||
}
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data)
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPRemove, data)
|
||||
}
|
||||
|
||||
authboss.DelSession(w, authboss.Session2FA)
|
||||
@ -306,14 +297,12 @@ func (t *TOTP) PostRemove(w http.ResponseWriter, r *http.Request) error {
|
||||
logger := t.RequestLogger(r)
|
||||
logger.Infof("user %s disabled totp 2fa", user.GetPID())
|
||||
|
||||
data := authboss.HTMLData{DataValidateMode: dataValidateRemove}
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidateSuccess, data)
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPRemoveSuccess, nil)
|
||||
}
|
||||
|
||||
// GetValidate shows a page to enter a code into
|
||||
func (t *TOTP) GetValidate(w http.ResponseWriter, r *http.Request) error {
|
||||
data := authboss.HTMLData{DataValidateMode: dataValidate}
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data)
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, nil)
|
||||
}
|
||||
|
||||
// PostValidate redirects on success
|
||||
@ -324,10 +313,7 @@ func (t *TOTP) PostValidate(w http.ResponseWriter, r *http.Request) error {
|
||||
switch {
|
||||
case err == errNoTOTPEnabled:
|
||||
logger.Infof("user %s totp failure (not enabled)", user.GetPID())
|
||||
data := authboss.HTMLData{
|
||||
authboss.DataErr: "totp 2fa not active",
|
||||
DataValidateMode: dataValidate,
|
||||
}
|
||||
data := authboss.HTMLData{authboss.DataErr: "totp 2fa not active"}
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data)
|
||||
case err != nil:
|
||||
return err
|
||||
@ -335,7 +321,6 @@ func (t *TOTP) PostValidate(w http.ResponseWriter, r *http.Request) error {
|
||||
logger.Infof("user %s totp 2fa failure (wrong code)", user.GetPID())
|
||||
data := authboss.HTMLData{
|
||||
authboss.DataValidation: map[string][]string{FormValueCode: []string{"2fa code was invalid"}},
|
||||
DataValidateMode: dataValidate,
|
||||
}
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data)
|
||||
}
|
||||
|
@ -197,12 +197,9 @@ func TestGetSetup(t *testing.T) {
|
||||
t.Error("session totp secret should be cleared")
|
||||
}
|
||||
|
||||
if h.responder.Page != PageTOTPValidate {
|
||||
if h.responder.Page != PageTOTPSetup {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateSetup {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostSetup(t *testing.T) {
|
||||
@ -282,12 +279,9 @@ func TestGetConfirm(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if h.responder.Page != PageTOTPValidate {
|
||||
if h.responder.Page != PageTOTPConfirm {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateConfirm {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
if got := h.responder.Data[DataTOTPSecret]; got != secret {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
@ -334,12 +328,9 @@ func TestPostConfirm(t *testing.T) {
|
||||
t.Error("session totp secret not deleted")
|
||||
}
|
||||
|
||||
if h.responder.Page != PageTOTPValidateSuccess {
|
||||
if h.responder.Page != PageTOTPConfirmSuccess {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateConfirm {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
if got := h.responder.Data[twofactor.DataRecoveryCodes].([]string); len(got) == 0 {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
@ -355,12 +346,9 @@ func TestGetRemove(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if h.responder.Page != PageTOTPValidate {
|
||||
if h.responder.Page != PageTOTPRemove {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateRemove {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostRemove(t *testing.T) {
|
||||
@ -386,12 +374,9 @@ func TestPostRemove(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if h.responder.Page != PageTOTPValidate {
|
||||
if h.responder.Page != PageTOTPRemove {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateRemove {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
if got := h.responder.Data[authboss.DataErr]; got != "totp 2fa not active" {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
@ -413,12 +398,9 @@ func TestPostRemove(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if h.responder.Page != PageTOTPValidate {
|
||||
if h.responder.Page != PageTOTPRemove {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateRemove {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
if got := h.responder.Data[authboss.DataValidation].(map[string][]string); got[FormValueCode][0] != "2fa code was invalid" {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
@ -445,12 +427,9 @@ func TestPostRemove(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if h.responder.Page != PageTOTPValidateSuccess {
|
||||
if h.responder.Page != PageTOTPRemoveSuccess {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidateRemove {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
|
||||
// Flush client state
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@ -474,9 +453,6 @@ func TestGetValidate(t *testing.T) {
|
||||
if h.responder.Page != PageTOTPValidate {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidate {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostValidate(t *testing.T) {
|
||||
@ -507,9 +483,6 @@ func TestPostValidate(t *testing.T) {
|
||||
if h.responder.Page != PageTOTPValidate {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidate {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
if got := h.responder.Data[authboss.DataErr]; got != "totp 2fa not active" {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
@ -533,9 +506,6 @@ func TestPostValidate(t *testing.T) {
|
||||
if h.responder.Page != PageTOTPValidate {
|
||||
t.Error("page wrong:", h.responder.Page)
|
||||
}
|
||||
if got := h.responder.Data[DataValidateMode]; got != dataValidate {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
if got := h.responder.Data[authboss.DataValidation].(map[string][]string); got[FormValueCode][0] != "2fa code was invalid" {
|
||||
t.Error("data wrong:", got)
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ type Recovery struct {
|
||||
func (rc *Recovery) Setup() error {
|
||||
rc.Authboss.Core.ViewRenderer.Load(PageRecovery2FA)
|
||||
|
||||
middleware := authboss.MountedMiddleware(rc.Authboss, true, rc.Authboss.Config.Modules.TwoFactorRedirectOnUnauthed, true, false)
|
||||
middleware := authboss.MountedMiddleware(rc.Authboss, true, rc.Authboss.Config.Modules.RoutesRedirectOnUnauthed, true, false)
|
||||
rc.Authboss.Core.Router.Get("/2fa/recovery/regen", middleware(rc.Authboss.Core.ErrorHandler.Wrap(rc.GetRegen)))
|
||||
rc.Authboss.Core.Router.Post("/2fa/recovery/regen", middleware(rc.Authboss.Core.ErrorHandler.Wrap(rc.PostRegen)))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user