dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

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:
Mpkg/web/handlers/data.go | 5+++++
Mpkg/web/handlers/handlers.go | 28+++++++++++++++++++++++-----
Mpkg/web/public/views/pages/sessions-gpg-two-factor.gohtml | 12+++++++++++-
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>