mirror of
https://github.com/PrivateCaptcha/PrivateCaptcha.git
synced 2026-02-09 15:28:49 -06:00
Enforce 2FA code expiration timeout before session timeout
This commit is contained in:
@@ -155,6 +155,7 @@ func (s *Server) postLogin(w http.ResponseWriter, r *http.Request) {
|
||||
_ = sess.Set(session.KeyUserEmail, user.Email)
|
||||
_ = sess.Set(session.KeyUserName, user.Name)
|
||||
_ = sess.Set(session.KeyTwoFactorCode, code)
|
||||
_ = sess.Set(session.KeyTwoFactorCodeTimestamp, time.Now().UTC())
|
||||
_ = sess.Set(session.KeyUserID, user.ID)
|
||||
// this is needed in case we will be routed to another server that does not have our session in memory
|
||||
// (previously we persisted ONLY logged in sessions, but if we're rerouted during login, it will break)
|
||||
|
||||
@@ -299,6 +299,7 @@ func (s *Server) editEmail(w http.ResponseWriter, r *http.Request) (*ViewModel,
|
||||
}
|
||||
|
||||
func (s *Server) putGeneralSettings(w http.ResponseWriter, r *http.Request) (*ViewModel, error) {
|
||||
tnow := time.Now().UTC()
|
||||
ctx := r.Context()
|
||||
|
||||
user, err := s.SessionUser(ctx, s.Session(w, r))
|
||||
@@ -332,13 +333,18 @@ func (s *Server) putGeneralSettings(w http.ResponseWriter, r *http.Request) (*Vi
|
||||
}
|
||||
|
||||
sentCode, hasSentCode := sess.Get(ctx, session.KeyTwoFactorCode).(int)
|
||||
codeTimestamp, ok := sess.Get(ctx, session.KeyTwoFactorCodeTimestamp).(time.Time)
|
||||
if !ok {
|
||||
slog.ErrorContext(ctx, "Failed to get verification code timestamp")
|
||||
}
|
||||
formCode := r.FormValue(common.ParamVerificationCode)
|
||||
|
||||
// we "used" the code now
|
||||
_ = sess.Delete(session.KeyTwoFactorCode)
|
||||
_ = sess.Delete(session.KeyTwoFactorCodeTimestamp)
|
||||
|
||||
if enteredCode, err := strconv.Atoi(formCode); !hasSentCode || (err != nil) || (enteredCode != sentCode) {
|
||||
slog.WarnContext(ctx, "Code verification failed", "actual", formCode, "expected", sentCode, common.ErrAttr(err))
|
||||
if enteredCode, err := strconv.Atoi(formCode); !hasSentCode || (err != nil) || (enteredCode != sentCode) || (!codeTimestamp.IsZero() && tnow.After(codeTimestamp.Add(twoFactorCodeDuration))) {
|
||||
slog.WarnContext(ctx, "Code verification failed", "actual", formCode, "expected", sentCode, "timestamp", codeTimestamp, common.ErrAttr(err))
|
||||
renderCtx.TwoFactorError = "Code is not valid."
|
||||
return &ViewModel{Model: renderCtx, View: settingsGeneralFormTemplate}, nil
|
||||
}
|
||||
|
||||
@@ -6,16 +6,22 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PrivateCaptcha/PrivateCaptcha/pkg/common"
|
||||
"github.com/PrivateCaptcha/PrivateCaptcha/pkg/session"
|
||||
)
|
||||
|
||||
const (
|
||||
twoFactorCodeDuration = 10 * time.Minute
|
||||
)
|
||||
|
||||
var (
|
||||
renderContextNothing = struct{}{}
|
||||
)
|
||||
|
||||
func (s *Server) postTwoFactor(w http.ResponseWriter, r *http.Request) {
|
||||
tnow := time.Now().UTC()
|
||||
ctx := r.Context()
|
||||
|
||||
err := r.ParseForm()
|
||||
@@ -51,6 +57,11 @@ func (s *Server) postTwoFactor(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
codeTimestamp, ok := sess.Get(ctx, session.KeyTwoFactorCodeTimestamp).(time.Time)
|
||||
if !ok {
|
||||
slog.ErrorContext(ctx, "Failed to get verification code timestamp")
|
||||
}
|
||||
|
||||
data := &loginRenderContext{
|
||||
CsrfRenderContext: CsrfRenderContext{
|
||||
Token: s.XSRF.Token(email),
|
||||
@@ -59,9 +70,9 @@ func (s *Server) postTwoFactor(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
formCode := strings.TrimSpace(r.FormValue(common.ParamVerificationCode))
|
||||
if enteredCode, err := strconv.Atoi(formCode); (err != nil) || (enteredCode != sentCode) {
|
||||
if enteredCode, err := strconv.Atoi(formCode); (err != nil) || (enteredCode != sentCode) || (!codeTimestamp.IsZero() && tnow.After(codeTimestamp.Add(twoFactorCodeDuration))) {
|
||||
data.CodeError = "Code is not valid."
|
||||
slog.WarnContext(ctx, "Code verification failed", "actual", formCode, "expected", sentCode, common.ErrAttr(err))
|
||||
slog.WarnContext(ctx, "Code verification failed", "actual", formCode, "expected", sentCode, "timestamp", codeTimestamp, common.ErrAttr(err))
|
||||
s.render(w, r, "login/twofactor-form.html", data)
|
||||
return
|
||||
}
|
||||
@@ -84,6 +95,7 @@ func (s *Server) postTwoFactor(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
_ = sess.Set(session.KeyLoginStep, loginStepCompleted)
|
||||
_ = sess.Delete(session.KeyTwoFactorCode)
|
||||
_ = sess.Delete(session.KeyTwoFactorCodeTimestamp)
|
||||
_ = sess.Delete(session.KeyUserEmail)
|
||||
_ = sess.Set(session.KeyPersistent, true)
|
||||
|
||||
@@ -124,5 +136,6 @@ func (s *Server) resend2fa(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
_ = sess.Set(session.KeyTwoFactorCode, code)
|
||||
_ = sess.Set(session.KeyTwoFactorCodeTimestamp, time.Now().UTC())
|
||||
s.render(w, r, "login/resend.html", renderContextNothing)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ const (
|
||||
KeyPersistent
|
||||
KeyNotificationID
|
||||
KeyReturnURL
|
||||
KeyTwoFactorCodeTimestamp
|
||||
// Add new fields _above_
|
||||
SESSION_KEYS_COUNT
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user