commit 20fc5525c39f1c7a86d22f6affcadc04e821bfea
parent 1f2f4e4e0aac96077dcbdb2ce40944be2109ef35
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Tue, 6 Jun 2023 13:13:02 -0700
Add captcha on fifth gpg 2fa attempt and onward
Diffstat:
3 files changed, 39 insertions(+), 6 deletions(-)
diff --git a/pkg/web/handlers/data.go b/pkg/web/handlers/data.go
@@ -45,9 +45,14 @@ type sessionsTwoFactorData struct {
}
type sessionsGpgTwoFactorData struct {
+ Autofocus int64
Token string
EncryptedMessage string
Code string
+ CaptchaRequired bool
+ ErrCaptcha string
+ CaptchaID string
+ CaptchaImg string
Error string
ErrorCode string
}
diff --git a/pkg/web/handlers/handlers.go b/pkg/web/handlers/handlers.go
@@ -234,16 +234,17 @@ func protectHomeHandler(c echo.Context) error {
// partialAuthCache keep track of partial auth token -> user id.
// When a user login and have 2fa enabled, we create a "partial" auth cookie.
// The token can be used to complete the 2fa authentication.
-var partialAuthCache = cache.New[PartialAuthItem](2*time.Minute, time.Hour)
+var partialAuthCache = cache.New[*PartialAuthItem](2*time.Minute, time.Hour)
type PartialAuthItem struct {
UserID database.UserID
Step PartialAuthStep // Inform which type of 2fa the user is supposed to complete
SessionDuration time.Duration
+ Attempt int
}
-func NewPartialAuthItem(userID database.UserID, step PartialAuthStep, sessionDuration time.Duration) PartialAuthItem {
- return PartialAuthItem{UserID: userID, Step: step, SessionDuration: sessionDuration}
+func NewPartialAuthItem(userID database.UserID, step PartialAuthStep, sessionDuration time.Duration) *PartialAuthItem {
+ return &PartialAuthItem{UserID: userID, Step: step, SessionDuration: sessionDuration}
}
type PartialAuthStep string
@@ -449,6 +450,7 @@ func SessionsGpgTwoFactorHandler(c echo.Context, step1 bool, token string) error
}
var data sessionsGpgTwoFactorData
+ data.Autofocus = 1
data.Token = token
if step1 {
@@ -468,6 +470,22 @@ func SessionsGpgTwoFactorHandler(c echo.Context, step1 bool, token string) error
data.EncryptedMessage = c.Request().PostFormValue("encrypted_message")
data.Code = c.Request().PostFormValue("pgp_code")
if data.Code != pgpToken.Value {
+ item.Attempt++
+ if item.Attempt > 4 {
+ data.CaptchaRequired = true
+ data.Autofocus = 2
+ data.CaptchaID, data.CaptchaImg = captcha.New()
+ captchaID := c.Request().PostFormValue("captcha_id")
+ captchaInput := c.Request().PostFormValue("captcha")
+ if captchaInput == "" {
+ return c.Render(http.StatusOK, "sessions-gpg-two-factor", data)
+ } else {
+ if err := hutils.CaptchaVerifyString(c, captchaID, captchaInput); err != nil {
+ data.ErrCaptcha = "Invalid captcha"
+ return c.Render(http.StatusOK, "sessions-gpg-two-factor", data)
+ }
+ }
+ }
data.ErrorCode = "invalid code"
return c.Render(http.StatusOK, "sessions-gpg-two-factor", data)
}
@@ -3416,8 +3434,8 @@ type ValueTokenCache struct {
PKey string // age/pgp public key
}
-var ageTokenCache = cache.NewWithKey[database.UserID, ValueTokenCache](10*time.Minute, time.Hour)
-var pgpTokenCache = cache.NewWithKey[database.UserID, ValueTokenCache](10*time.Minute, time.Hour)
+var ageTokenCache = cache.NewWithKey[database.UserID, ValueTokenCache](2*time.Minute, time.Hour)
+var pgpTokenCache = cache.NewWithKey[database.UserID, ValueTokenCache](2*time.Minute, time.Hour)
func SettingsPGPHandler(c echo.Context) error {
authUser := c.Get("authUser").(*database.User)
diff --git a/pkg/web/public/views/pages/sessions-gpg-two-factor.gohtml b/pkg/web/public/views/pages/sessions-gpg-two-factor.gohtml
@@ -21,11 +21,21 @@
</div>
<div class="form-group">
<label for="pgp_code">{{ t "Your decrypted code" . }}</label>
- <input name="pgp_code" id="pgp_code" value="{{ .Data.Code }}" type="text" class="form-control{{ if .Data.ErrorCode }} is-invalid{{ end }}" autocomplete="off" autocorrect="off" autocapitalize="none" autofocus />
+ <input name="pgp_code" id="pgp_code" value="{{ .Data.Code }}" type="text" class="form-control{{ if .Data.ErrorCode }} is-invalid{{ end }}" autocomplete="off" autocorrect="off" autocapitalize="none"{{ if eq .Data.Autofocus 1 }} autofocus{{ end }} />
{{ if .Data.ErrorCode }}
<div class="invalid-feedback">{{ .Data.ErrorCode }}</div>
{{ end }}
</div>
+ {{ if .Data.CaptchaRequired }}
+ <input type="hidden" name="captcha_id" value="{{ .Data.CaptchaID }}" />
+ <div class="form-group">
+ <div class="mb-2 text-center">
+ <img src="data:image/png;base64,{{ .Data.CaptchaImg }}" alt="captcha" style="background-color: hsl(0, 0%, 90%);" class="captcha-img" />
+ </div>
+ <input class="form-control{{ if .Data.ErrCaptcha }} is-invalid{{ end }}" placeholder="{{ t "Captcha" . }}" name="captcha" type="text" maxlength="6" required{{ if eq .Data.Autofocus 2 }} autofocus{{ end }} autocomplete="off" />
+ {{ if .Data.ErrCaptcha }}<div class="invalid-feedback d-block">{{ .Data.ErrCaptcha }}</div>{{ end }}
+ </div>
+ {{ end }}
<div class="form-group">
<input type="submit" value="{{ t "Continue login" . }}" class="btn btn-primary" />
<a href="/settings/pgp" class="btn btn-secondary">{{ t "Cancel" . }}</a>