mirror of
https://github.com/volatiletech/authboss.git
synced 2025-01-24 05:17:10 +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"
|
||||
)
|
||||
|
||||
// TODO: implement time protection on resend-sms
|
||||
// TODO: resend logic
|
||||
|
||||
// Session keys
|
||||
const (
|
||||
SessionSMSNumber = "sms_number"
|
||||
@ -41,7 +38,6 @@ const (
|
||||
|
||||
// Data constants
|
||||
const (
|
||||
DataRecoveryCodes = "recovery_codes"
|
||||
DataValidateMode = "validate_mode"
|
||||
DataSMSSecret = SessionSMSSecret
|
||||
DataSMSPhoneNumber = "sms_phone_number"
|
||||
@ -332,8 +328,8 @@ func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user
|
||||
if !verified {
|
||||
logger.Infof("user %s sms failure (wrong code)", user.GetPID())
|
||||
data := authboss.HTMLData{
|
||||
authboss.DataErr: "sms 2fa code incorrect",
|
||||
DataValidateMode: s.Action,
|
||||
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)
|
||||
}
|
||||
@ -370,7 +366,7 @@ 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[DataRecoveryCodes] = codes
|
||||
data[twofactor.DataRecoveryCodes] = codes
|
||||
case dataValidateRemove:
|
||||
user.PutSMSPhoneNumber("")
|
||||
if err := s.Authboss.Config.Storage.Server.Save(r.Context(), user); err != nil {
|
||||
|
@ -40,9 +40,8 @@ const (
|
||||
|
||||
// Data constants
|
||||
const (
|
||||
DataRecoveryCodes = "recovery_codes"
|
||||
DataValidateMode = "validate_mode"
|
||||
DataTOTPSecret = SessionTOTPSecret
|
||||
DataValidateMode = "validate_mode"
|
||||
DataTOTPSecret = SessionTOTPSecret
|
||||
|
||||
dataValidate = "validate"
|
||||
dataValidateSetup = "setup"
|
||||
@ -260,8 +259,8 @@ func (t *TOTP) PostConfirm(w http.ResponseWriter, r *http.Request) error {
|
||||
logger.Infof("user %s enabled totp 2fa", user.GetPID())
|
||||
|
||||
data := authboss.HTMLData{
|
||||
DataRecoveryCodes: codes,
|
||||
DataValidateMode: dataValidateConfirm,
|
||||
twofactor.DataRecoveryCodes: codes,
|
||||
DataValidateMode: dataValidateConfirm,
|
||||
}
|
||||
|
||||
return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidateSuccess, data)
|
||||
@ -287,8 +286,8 @@ func (t *TOTP) PostRemove(w http.ResponseWriter, r *http.Request) error {
|
||||
return err
|
||||
case !ok:
|
||||
data := authboss.HTMLData{
|
||||
authboss.DataErr: "totp 2fa code incorrect",
|
||||
DataValidateMode: dataValidateRemove,
|
||||
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)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ package twofactor
|
||||
import (
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/volatiletech/authboss"
|
||||
@ -23,119 +24,89 @@ type User interface {
|
||||
PutRecoveryCodes(codes string)
|
||||
}
|
||||
|
||||
// TOTPUser interface
|
||||
type TOTPUser interface {
|
||||
User
|
||||
// Page constants
|
||||
const (
|
||||
PageRecovery2FA = "recovery2fa"
|
||||
)
|
||||
|
||||
GetTOTPSecretKey() string
|
||||
PutTOTPSecretKey(string)
|
||||
// Data constants
|
||||
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
|
||||
type SMSUser interface {
|
||||
User
|
||||
// Setup the module to provide recovery regeneration routes
|
||||
func (rc *Recovery) Setup() error {
|
||||
rc.Authboss.Core.ViewRenderer.Load(PageRecovery2FA)
|
||||
|
||||
GetPhoneNumber() string
|
||||
PutPhoneNumber(string)
|
||||
rc.Authboss.Core.Router.Get("/2fa/recovery/regen", rc.Authboss.Core.ErrorHandler.Wrap(rc.GetRegen))
|
||||
rc.Authboss.Core.Router.Post("/2fa/recovery/regen", rc.Authboss.Core.ErrorHandler.Wrap(rc.PostRegen))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SMSPhoneGetter retrieves an initial phone number
|
||||
// to use as the SMS 2fa number.
|
||||
type SMSPhoneGetter interface {
|
||||
GetInitialPhoneNumber() string
|
||||
// GetRegen shows a button that enables a user to regen their codes
|
||||
// as well as how many codes are currently remaining.
|
||||
func (rc *Recovery) GetRegen(w http.ResponseWriter, r *http.Request) error {
|
||||
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}
|
||||
return rc.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageRecovery2FA, data)
|
||||
}
|
||||
|
||||
/*
|
||||
GET /2fa/setup/{sms,totp}
|
||||
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
|
||||
// PostRegen regenerates the codes
|
||||
func (rc *Recovery) PostRegen(w http.ResponseWriter, r *http.Request) error {
|
||||
abUser, err := rc.CurrentUser(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user := abUser.(User)
|
||||
|
||||
- totp:
|
||||
- generate a private key and store it, temporarily in session
|
||||
codes, err := GenerateRecoveryCodes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
GET /2fa/qr/{sms,totp}
|
||||
- totp:
|
||||
- send back an image of the secret key that's in the session, fallback to the database
|
||||
hashedCodes, err := BCryptRecoveryCodes(codes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
user.PutRecoveryCodes(EncodeRecoveryCodes(hashedCodes))
|
||||
if err = rc.Authboss.Config.Storage.Server.Save(r.Context(), user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
data := authboss.HTMLData{DataRecoveryCodes: codes}
|
||||
return rc.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageRecovery2FA, data)
|
||||
}
|
||||
|
||||
// Authenticator is the basic functionality for a second factor authenticator
|
||||
type Authenticator interface {
|
||||
Secret(User) (secret string, err error)
|
||||
Code(user User, secret string) (code string, err error)
|
||||
Verify(user User, code, secret string) error
|
||||
|
||||
Enabled(User) bool
|
||||
Enable(User) error
|
||||
Disable(User) error
|
||||
}
|
||||
|
||||
// QRAuthenticator is able to provide a QR code to represent it's secrets
|
||||
type QRAuthenticator interface {
|
||||
// Returns a file as []byte and a mime type
|
||||
QRCode(User) ([]byte, string)
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
const recoveryCodeLength = 10
|
||||
|
||||
// GenerateRecoveryCodes creates 10 recovery codes of the form:
|
||||
// abd34-1b24do (using alphabet, of length recoveryCodeLength).
|
||||
func GenerateRecoveryCodes() ([]string, error) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user