mirror of
https://github.com/volatiletech/authboss.git
synced 2025-01-26 05:27:33 +02:00
Add regeneration of recovery codes
- Refactor some constants that pertain to recovery codes
This commit is contained in:
parent
bdb449c0f6
commit
e79638a05e
@ -16,9 +16,6 @@ import (
|
|||||||
"github.com/volatiletech/authboss/otp/twofactor"
|
"github.com/volatiletech/authboss/otp/twofactor"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: implement time protection on resend-sms
|
|
||||||
// TODO: resend logic
|
|
||||||
|
|
||||||
// Session keys
|
// Session keys
|
||||||
const (
|
const (
|
||||||
SessionSMSNumber = "sms_number"
|
SessionSMSNumber = "sms_number"
|
||||||
@ -41,7 +38,6 @@ const (
|
|||||||
|
|
||||||
// Data constants
|
// Data constants
|
||||||
const (
|
const (
|
||||||
DataRecoveryCodes = "recovery_codes"
|
|
||||||
DataValidateMode = "validate_mode"
|
DataValidateMode = "validate_mode"
|
||||||
DataSMSSecret = SessionSMSSecret
|
DataSMSSecret = SessionSMSSecret
|
||||||
DataSMSPhoneNumber = "sms_phone_number"
|
DataSMSPhoneNumber = "sms_phone_number"
|
||||||
@ -332,7 +328,7 @@ func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user
|
|||||||
if !verified {
|
if !verified {
|
||||||
logger.Infof("user %s sms failure (wrong code)", user.GetPID())
|
logger.Infof("user %s sms failure (wrong code)", user.GetPID())
|
||||||
data := authboss.HTMLData{
|
data := authboss.HTMLData{
|
||||||
authboss.DataErr: "sms 2fa code incorrect",
|
authboss.DataValidation: map[string][]string{FormValueCode: []string{"2fa code was invalid"}},
|
||||||
DataValidateMode: s.Action,
|
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, PageSMSValidate, data)
|
||||||
@ -370,7 +366,7 @@ func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user
|
|||||||
authboss.DelSession(w, SessionSMSNumber)
|
authboss.DelSession(w, SessionSMSNumber)
|
||||||
|
|
||||||
logger.Infof("user %s enabled sms 2fa", user.GetPID())
|
logger.Infof("user %s enabled sms 2fa", user.GetPID())
|
||||||
data[DataRecoveryCodes] = codes
|
data[twofactor.DataRecoveryCodes] = codes
|
||||||
case dataValidateRemove:
|
case dataValidateRemove:
|
||||||
user.PutSMSPhoneNumber("")
|
user.PutSMSPhoneNumber("")
|
||||||
if err := s.Authboss.Config.Storage.Server.Save(r.Context(), user); err != nil {
|
if err := s.Authboss.Config.Storage.Server.Save(r.Context(), user); err != nil {
|
||||||
|
@ -40,7 +40,6 @@ const (
|
|||||||
|
|
||||||
// Data constants
|
// Data constants
|
||||||
const (
|
const (
|
||||||
DataRecoveryCodes = "recovery_codes"
|
|
||||||
DataValidateMode = "validate_mode"
|
DataValidateMode = "validate_mode"
|
||||||
DataTOTPSecret = SessionTOTPSecret
|
DataTOTPSecret = SessionTOTPSecret
|
||||||
|
|
||||||
@ -260,7 +259,7 @@ func (t *TOTP) PostConfirm(w http.ResponseWriter, r *http.Request) error {
|
|||||||
logger.Infof("user %s enabled totp 2fa", user.GetPID())
|
logger.Infof("user %s enabled totp 2fa", user.GetPID())
|
||||||
|
|
||||||
data := authboss.HTMLData{
|
data := authboss.HTMLData{
|
||||||
DataRecoveryCodes: codes,
|
twofactor.DataRecoveryCodes: codes,
|
||||||
DataValidateMode: dataValidateConfirm,
|
DataValidateMode: dataValidateConfirm,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,7 +286,7 @@ func (t *TOTP) PostRemove(w http.ResponseWriter, r *http.Request) error {
|
|||||||
return err
|
return err
|
||||||
case !ok:
|
case !ok:
|
||||||
data := authboss.HTMLData{
|
data := authboss.HTMLData{
|
||||||
authboss.DataErr: "totp 2fa code incorrect",
|
authboss.DataValidation: map[string][]string{FormValueCode: []string{"2fa code was invalid"}},
|
||||||
DataValidateMode: dataValidateRemove,
|
DataValidateMode: dataValidateRemove,
|
||||||
}
|
}
|
||||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data)
|
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data)
|
||||||
|
@ -4,6 +4,7 @@ package twofactor
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/volatiletech/authboss"
|
"github.com/volatiletech/authboss"
|
||||||
@ -23,118 +24,88 @@ type User interface {
|
|||||||
PutRecoveryCodes(codes string)
|
PutRecoveryCodes(codes string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TOTPUser interface
|
// Page constants
|
||||||
type TOTPUser interface {
|
const (
|
||||||
User
|
PageRecovery2FA = "recovery2fa"
|
||||||
|
)
|
||||||
|
|
||||||
GetTOTPSecretKey() string
|
// Data constants
|
||||||
PutTOTPSecretKey(string)
|
const (
|
||||||
|
DataRecoveryCode = "recovery_code"
|
||||||
|
DataRecoveryCodes = "recovery_codes"
|
||||||
|
DataNumRecoveryCodes = "n_recovery_codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
recoveryCodeLength = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recovery for two-factor authentication is handled by this type
|
||||||
|
type Recovery struct {
|
||||||
|
*authboss.Authboss
|
||||||
}
|
}
|
||||||
|
|
||||||
// SMSUser interface
|
// Setup the module to provide recovery regeneration routes
|
||||||
type SMSUser interface {
|
func (rc *Recovery) Setup() error {
|
||||||
User
|
rc.Authboss.Core.ViewRenderer.Load(PageRecovery2FA)
|
||||||
|
|
||||||
GetPhoneNumber() string
|
rc.Authboss.Core.Router.Get("/2fa/recovery/regen", rc.Authboss.Core.ErrorHandler.Wrap(rc.GetRegen))
|
||||||
PutPhoneNumber(string)
|
rc.Authboss.Core.Router.Post("/2fa/recovery/regen", rc.Authboss.Core.ErrorHandler.Wrap(rc.PostRegen))
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SMSPhoneGetter retrieves an initial phone number
|
// GetRegen shows a button that enables a user to regen their codes
|
||||||
// to use as the SMS 2fa number.
|
// as well as how many codes are currently remaining.
|
||||||
type SMSPhoneGetter interface {
|
func (rc *Recovery) GetRegen(w http.ResponseWriter, r *http.Request) error {
|
||||||
GetInitialPhoneNumber() string
|
abUser, err := rc.CurrentUser(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user := abUser.(User)
|
||||||
|
|
||||||
|
var nCodes int
|
||||||
|
codes := user.GetRecoveryCodes()
|
||||||
|
if len(codes) != 0 {
|
||||||
|
nCodes++
|
||||||
|
}
|
||||||
|
for _, c := range codes {
|
||||||
|
if c == ',' {
|
||||||
|
nCodes++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
data := authboss.HTMLData{DataNumRecoveryCodes: nCodes}
|
||||||
GET /2fa/setup/{sms,totp}
|
return rc.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageRecovery2FA, data)
|
||||||
POST /2fa/setup/{sms,totp}
|
|
||||||
- sms:
|
|
||||||
- send a 6-8 digit code to the users's phone number
|
|
||||||
- save this temporary code in the session for the next API call
|
|
||||||
|
|
||||||
- totp:
|
|
||||||
- generate a private key and store it, temporarily in session
|
|
||||||
|
|
||||||
GET /2fa/qr/{sms,totp}
|
|
||||||
- totp:
|
|
||||||
- send back an image of the secret key that's in the session, fallback to the database
|
|
||||||
|
|
||||||
GET /2fa/confirm/{sms,totp}
|
|
||||||
POST /2fa/confirm/{sms,totp}
|
|
||||||
- totp:
|
|
||||||
- post the 2fa code, this finalizes the secret key in the session by storing it into the database
|
|
||||||
- generate and save 10 recovery codes, return in data
|
|
||||||
- sms:
|
|
||||||
- post the sms code delivered to your phone, to finalize that sms phone number
|
|
||||||
- generate and save 10 recovery codes, return in data
|
|
||||||
|
|
||||||
GET /2fa/remove/{sms,totp}
|
|
||||||
- totp:
|
|
||||||
- ask for a code
|
|
||||||
- sms:
|
|
||||||
- send code to fone
|
|
||||||
|
|
||||||
POST /2fa/remove/{sms,totp}
|
|
||||||
- totp:
|
|
||||||
- if code matches, remove 2fa
|
|
||||||
- sms:
|
|
||||||
- if code matches, remove 2fa
|
|
||||||
|
|
||||||
GET /2fa/recovery DOES NOT EXIST LOL, WAT 2 SHO?
|
|
||||||
- show recovery codes
|
|
||||||
POST /2fa/recovery/regenerate
|
|
||||||
- regenerate 10 recovery codes and display them
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
// Authenticator is a type that implements the basic functionality
|
|
||||||
// to be able to authenticate via a one time password as a second factor.
|
|
||||||
type Authenticator interface {
|
|
||||||
// Setup a secret and generate a code from it so that the 2fa method can be attached
|
|
||||||
// to the user if they correctly pass back the code. The secret itself
|
|
||||||
// is stored in the session and will be passed back to be stored on the
|
|
||||||
// user object in the enable step.
|
|
||||||
Setup(User) (code string, secret string, err error)
|
|
||||||
|
|
||||||
// Enable 2fa on the user, requires the code produced
|
|
||||||
// by the secret and the secret itself that will have come
|
|
||||||
// from Setup.
|
|
||||||
Enable(user User, code string, secret string) error
|
|
||||||
|
|
||||||
// Teardown prepares to disable 2fa on the user.
|
|
||||||
Teardown(User) (code string, err error)
|
|
||||||
|
|
||||||
// Disable 2fa on the user, requires a code sent by teardown
|
|
||||||
// or in some cases that the user will already know.
|
|
||||||
Disable(user User, code string, secret string) error
|
|
||||||
|
|
||||||
// IsActive checks if this authenticator is active on the current user
|
|
||||||
// This is to ensure that only one authentication method is active at a time
|
|
||||||
IsActive(User) bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticator is the basic functionality for a second factor authenticator
|
// PostRegen regenerates the codes
|
||||||
type Authenticator interface {
|
func (rc *Recovery) PostRegen(w http.ResponseWriter, r *http.Request) error {
|
||||||
Secret(User) (secret string, err error)
|
abUser, err := rc.CurrentUser(r)
|
||||||
Code(user User, secret string) (code string, err error)
|
if err != nil {
|
||||||
Verify(user User, code, secret string) error
|
return err
|
||||||
|
}
|
||||||
|
user := abUser.(User)
|
||||||
|
|
||||||
Enabled(User) bool
|
codes, err := GenerateRecoveryCodes()
|
||||||
Enable(User) error
|
if err != nil {
|
||||||
Disable(User) error
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// QRAuthenticator is able to provide a QR code to represent it's secrets
|
hashedCodes, err := BCryptRecoveryCodes(codes)
|
||||||
type QRAuthenticator interface {
|
if err != nil {
|
||||||
// Returns a file as []byte and a mime type
|
return err
|
||||||
QRCode(User) ([]byte, string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
user.PutRecoveryCodes(EncodeRecoveryCodes(hashedCodes))
|
||||||
|
if err = rc.Authboss.Config.Storage.Server.Save(r.Context(), user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
data := authboss.HTMLData{DataRecoveryCodes: codes}
|
||||||
const recoveryCodeLength = 10
|
return rc.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageRecovery2FA, data)
|
||||||
|
}
|
||||||
|
|
||||||
// GenerateRecoveryCodes creates 10 recovery codes of the form:
|
// GenerateRecoveryCodes creates 10 recovery codes of the form:
|
||||||
// abd34-1b24do (using alphabet, of length recoveryCodeLength).
|
// abd34-1b24do (using alphabet, of length recoveryCodeLength).
|
||||||
|
Loading…
x
Reference in New Issue
Block a user