dkforest

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

commit c1cc4c60f3e68fa7c64c05627583f79e6e95802e
parent a430e3f5f20a84fb3cbb3c67715fb9f906add0cf
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Wed, 14 Dec 2022 10:18:37 -0500

no longer use cookie for login with 2fa process

Diffstat:
Mpkg/web/handlers/data.go | 4++++
Mpkg/web/handlers/handlers.go | 261+++++++++++++++++++++++++++++++++++--------------------------------------------
Mpkg/web/handlers/utils/utils.go | 11+++++------
Mpkg/web/public/views/pages/sessions-gpg-sign-two-factor.gohtml | 3++-
Mpkg/web/public/views/pages/sessions-gpg-two-factor.gohtml | 3++-
Mpkg/web/public/views/pages/sessions-two-factor-recovery.gohtml | 2++
Mpkg/web/public/views/pages/sessions-two-factor.gohtml | 43+++++++++++++++++++++++++------------------
Mpkg/web/web.go | 8--------
8 files changed, 157 insertions(+), 178 deletions(-)

diff --git a/pkg/web/handlers/data.go b/pkg/web/handlers/data.go @@ -37,10 +37,12 @@ type loginData struct { } type sessionsTwoFactorData struct { + Token string Error string } type sessionsGpgTwoFactorData struct { + Token string EncryptedMessage string Code string Error string @@ -48,6 +50,7 @@ type sessionsGpgTwoFactorData struct { } type sessionsGpgSignTwoFactorData struct { + Token string ToBeSignedMessage string SignedMessage string Error string @@ -55,6 +58,7 @@ type sessionsGpgSignTwoFactorData struct { } type sessionsTwoFactorRecoveryData struct { + Token string Error string } diff --git a/pkg/web/handlers/handlers.go b/pkg/web/handlers/handlers.go @@ -228,112 +228,117 @@ func LoginAttackHandler(c echo.Context) error { } func loginHandler(c echo.Context) error { - var data loginData - data.Autofocus = 0 - data.HomeUsersList = config.HomeUsersList.Load() - - if data.HomeUsersList { - data.Online = managers.ActiveUsers.GetActiveUsers() - } + formName := c.Request().PostFormValue("formName") + if formName == "" { + var data loginData + data.Autofocus = 0 + data.HomeUsersList = config.HomeUsersList.Load() - actualLogin := func(username, password string, captchaSolved bool) error { - username = strings.TrimSpace(username) - user, err := database.GetVerifiedUserByUsername(username) - if err != nil { - time.Sleep(utils.RandMs(50, 200)) - data.Error = "Invalid username/password" - return c.Render(http.StatusOK, "login", data) + if data.HomeUsersList { + data.Online = managers.ActiveUsers.GetActiveUsers() } - user.LoginAttempts++ - user.DoSave() - - if user.LoginAttempts > 4 && !captchaSolved { - data.CaptchaRequired = true - data.Autofocus = 2 - data.Error = "Captcha required" - data.CaptchaID, data.CaptchaImg = captcha.New() - data.Password = password - captchaID := c.Request().PostFormValue("captcha_id") - captchaInput := c.Request().PostFormValue("captcha") - if captchaInput == "" { + actualLogin := func(username, password string, captchaSolved bool) error { + username = strings.TrimSpace(username) + user, err := database.GetVerifiedUserByUsername(username) + if err != nil { + time.Sleep(utils.RandMs(50, 200)) + data.Error = "Invalid username/password" return c.Render(http.StatusOK, "login", data) - } else { - if err := hutils.CaptchaVerifyString(c, captchaID, captchaInput); err != nil { - data.Error = "Invalid captcha" + } + + user.LoginAttempts++ + user.DoSave() + + if user.LoginAttempts > 4 && !captchaSolved { + data.CaptchaRequired = true + data.Autofocus = 2 + data.Error = "Captcha required" + data.CaptchaID, data.CaptchaImg = captcha.New() + data.Password = password + captchaID := c.Request().PostFormValue("captcha_id") + captchaInput := c.Request().PostFormValue("captcha") + if captchaInput == "" { return c.Render(http.StatusOK, "login", data) + } else { + if err := hutils.CaptchaVerifyString(c, captchaID, captchaInput); err != nil { + data.Error = "Invalid captcha" + return c.Render(http.StatusOK, "login", data) + } } } - } - if !user.CheckPassword(password) { - data.Password = "" - data.Autofocus = 1 - data.Error = "Invalid username/password" - return c.Render(http.StatusOK, "login", data) - } + if !user.CheckPassword(password) { + data.Password = "" + data.Autofocus = 1 + data.Error = "Invalid username/password" + return c.Render(http.StatusOK, "login", data) + } - redir := c.QueryParam("redirect") + if user.GpgTwoFactorEnabled { + token := utils.GenerateToken32() + partialAuthCache.Set(token, user.ID, cache1.DefaultExpiration) + if user.GpgTwoFactorMode { + return SessionsGpgSignTwoFactorHandler(c, true, token) + } + return SessionsGpgTwoFactorHandler(c, true, token) - if user.GpgTwoFactorEnabled { - token := utils.GenerateToken32() - partialAuthCache.Set(token, user.ID, cache1.DefaultExpiration) - c.SetCookie(createPartialSessionCookie(token)) - redirectURL := "/sessions/gpg-two-factor" - if user.GpgTwoFactorMode { - redirectURL = "/sessions/gpg-sign-two-factor" - } - if redir != "" { - redirectURL += "?redirect=" + redir + } else if string(user.TwoFactorSecret) != "" { + token := utils.GenerateToken32() + partialAuthCache.Set(token, user.ID, cache1.DefaultExpiration) + return SessionsTwoFactorHandler(c, true, token) } - return c.Redirect(http.StatusFound, redirectURL) - } else if string(user.TwoFactorSecret) != "" { - token := utils.GenerateToken32() - partialAuthCache.Set(token, user.ID, cache1.DefaultExpiration) - c.SetCookie(createPartialSessionCookie(token)) - redirectURL := "/sessions/two-factor" - if redir != "" { - redirectURL += "?redirect=" + redir - } - return c.Redirect(http.StatusFound, redirectURL) + return completeLogin(c, user) } - return completeLogin(c, user) - } - - usernameQuery := c.QueryParam("u") - passwordQuery := c.QueryParam("p") - if usernameQuery == "darkforestAdmin" && passwordQuery != "" { - return actualLogin(usernameQuery, passwordQuery, false) - } + usernameQuery := c.QueryParam("u") + passwordQuery := c.QueryParam("p") + if usernameQuery == "darkforestAdmin" && passwordQuery != "" { + return actualLogin(usernameQuery, passwordQuery, false) + } - if config.ForceLoginCaptcha.IsTrue() { - data.CaptchaID, data.CaptchaImg = captcha.New() - data.CaptchaRequired = true - } + if config.ForceLoginCaptcha.IsTrue() { + data.CaptchaID, data.CaptchaImg = captcha.New() + data.CaptchaRequired = true + } - if c.Request().Method == http.MethodGet { - return c.Render(http.StatusOK, "login", data) - } + if c.Request().Method == http.MethodGet { + return c.Render(http.StatusOK, "login", data) + } - captchaSolved := false + captchaSolved := false - data.Username = strings.TrimSpace(c.FormValue("username")) - password := c.FormValue("password") + data.Username = strings.TrimSpace(c.FormValue("username")) + password := c.FormValue("password") - if config.ForceLoginCaptcha.IsTrue() { - data.CaptchaRequired = true - captchaID := c.Request().PostFormValue("captcha_id") - captchaInput := c.Request().PostFormValue("captcha") - if err := hutils.CaptchaVerifyString(c, captchaID, captchaInput); err != nil { - data.ErrCaptcha = err.Error() - return c.Render(http.StatusOK, "login", data) + if config.ForceLoginCaptcha.IsTrue() { + data.CaptchaRequired = true + captchaID := c.Request().PostFormValue("captcha_id") + captchaInput := c.Request().PostFormValue("captcha") + if err := hutils.CaptchaVerifyString(c, captchaID, captchaInput); err != nil { + data.ErrCaptcha = err.Error() + return c.Render(http.StatusOK, "login", data) + } + captchaSolved = true } - captchaSolved = true - } - return actualLogin(data.Username, password, captchaSolved) + return actualLogin(data.Username, password, captchaSolved) + + } else if formName == "pgp_2fa" { + token := c.Request().PostFormValue("token") + return SessionsGpgTwoFactorHandler(c, false, token) + } else if formName == "pgp_sign_2fa" { + token := c.Request().PostFormValue("token") + return SessionsGpgSignTwoFactorHandler(c, false, token) + } else if formName == "2fa" { + token := c.Request().PostFormValue("token") + return SessionsTwoFactorHandler(c, false, token) + } else if formName == "2fa_recovery" { + token := c.Request().PostFormValue("token") + return SessionsTwoFactorRecoveryHandler(c, token) + } + return c.Redirect(http.StatusOK, "/") } func completeLogin(c echo.Context, user database.User) error { @@ -374,12 +379,8 @@ func LoginCompletedHandler(c echo.Context) error { } // SessionsGpgTwoFactorHandler ... -func SessionsGpgTwoFactorHandler(c echo.Context) error { - partialAuthCookie, err := c.Cookie(hutils.PartialAuthCookieName) - if err != nil { - return c.Redirect(http.StatusFound, "/") - } - userID, found := partialAuthCache.Get(partialAuthCookie.Value) +func SessionsGpgTwoFactorHandler(c echo.Context, step1 bool, token string) error { + userID, found := partialAuthCache.Get(token) if !found { return c.Redirect(http.StatusFound, "/") } @@ -391,8 +392,9 @@ func SessionsGpgTwoFactorHandler(c echo.Context) error { } var data sessionsGpgTwoFactorData + data.Token = token - if c.Request().Method == http.MethodGet { + if step1 { msg, err := generatePgpEncryptedTokenMessage(user.ID, user.GPGPublicKey) if err != nil { data.Error = err.Error() @@ -402,42 +404,31 @@ func SessionsGpgTwoFactorHandler(c echo.Context) error { return c.Render(http.StatusOK, "sessions-gpg-two-factor", data) } - token, found := pgpTokenCache.Get(user.ID) + pgpToken, found := pgpTokenCache.Get(user.ID) if !found { return c.Redirect(http.StatusFound, "/") } data.EncryptedMessage = c.Request().PostFormValue("encrypted_message") data.Code = c.Request().PostFormValue("pgp_code") - if data.Code != token.Value { + if data.Code != pgpToken.Value { data.ErrorCode = "invalid code" return c.Render(http.StatusOK, "sessions-gpg-two-factor", data) } pgpTokenCache.Delete(user.ID) - partialAuthCache.Delete(partialAuthCookie.Value) - c.SetCookie(hutils.DeleteCookie(hutils.PartialAuthCookieName)) + partialAuthCache.Delete(token) if string(user.TwoFactorSecret) != "" { token := utils.GenerateToken32() partialAuthCache.Set(token, user.ID, cache1.DefaultExpiration) - c.SetCookie(createPartialSessionCookie(token)) - redirectURL := "/sessions/two-factor" - redir := c.QueryParam("redirect") - if redir != "" { - redirectURL += "?redirect=" + redir - } - return c.Redirect(http.StatusFound, redirectURL) + return SessionsTwoFactorHandler(c, true, token) } return completeLogin(c, user) } // SessionsGpgSignTwoFactorHandler ... -func SessionsGpgSignTwoFactorHandler(c echo.Context) error { - partialAuthCookie, err := c.Cookie(hutils.PartialAuthCookieName) - if err != nil { - return c.Redirect(http.StatusFound, "/") - } - userID, found := partialAuthCache.Get(partialAuthCookie.Value) +func SessionsGpgSignTwoFactorHandler(c echo.Context, step1 bool, token string) error { + userID, found := partialAuthCache.Get(token) if !found { return c.Redirect(http.StatusFound, "/") } @@ -449,55 +440,46 @@ func SessionsGpgSignTwoFactorHandler(c echo.Context) error { } var data sessionsGpgSignTwoFactorData + data.Token = token - if c.Request().Method == http.MethodGet { + if step1 { data.ToBeSignedMessage = generatePgpToBeSignedTokenMessage(user.ID, user.GPGPublicKey) return c.Render(http.StatusOK, "sessions-gpg-sign-two-factor", data) } - token, found := pgpTokenCache.Get(user.ID) + pgpToken, found := pgpTokenCache.Get(user.ID) if !found { return c.Redirect(http.StatusFound, "/") } data.ToBeSignedMessage = c.Request().PostFormValue("to_be_signed_message") data.SignedMessage = c.Request().PostFormValue("signed_message") - if !utils.PgpCheckSignMessage(token.PKey, token.Value, data.SignedMessage) { + if !utils.PgpCheckSignMessage(pgpToken.PKey, pgpToken.Value, data.SignedMessage) { data.ErrorSignedMessage = "invalid signature" return c.Render(http.StatusOK, "sessions-gpg-sign-two-factor", data) } pgpTokenCache.Delete(user.ID) - partialAuthCache.Delete(partialAuthCookie.Value) - c.SetCookie(hutils.DeleteCookie(hutils.PartialAuthCookieName)) + partialAuthCache.Delete(token) if string(user.TwoFactorSecret) != "" { token := utils.GenerateToken32() partialAuthCache.Set(token, user.ID, cache1.DefaultExpiration) - c.SetCookie(createPartialSessionCookie(token)) - redirectURL := "/sessions/two-factor" - redir := c.QueryParam("redirect") - if redir != "" { - redirectURL += "?redirect=" + redir - } - return c.Redirect(http.StatusFound, redirectURL) + return SessionsTwoFactorHandler(c, true, token) } return completeLogin(c, user) } // SessionsTwoFactorHandler ... -func SessionsTwoFactorHandler(c echo.Context) error { - partialAuthCookie, err := c.Cookie(hutils.PartialAuthCookieName) - if err != nil { - return c.Redirect(http.StatusFound, "/") - } - userID, found := partialAuthCache.Get(partialAuthCookie.Value) +func SessionsTwoFactorHandler(c echo.Context, step1 bool, token string) error { + userID, found := partialAuthCache.Get(token) if !found { return c.Redirect(http.StatusFound, "/") } var data sessionsTwoFactorData - if c.Request().Method == http.MethodPost { + data.Token = token + if !step1 { code := c.Request().PostFormValue("code") user, err := database.GetUserByID(userID) if err != nil { @@ -510,8 +492,7 @@ func SessionsTwoFactorHandler(c echo.Context) error { return c.Render(http.StatusOK, "sessions-two-factor", data) } - partialAuthCache.Delete(partialAuthCookie.Value) - c.SetCookie(hutils.DeleteCookie(hutils.PartialAuthCookieName)) + partialAuthCache.Delete(token) return completeLogin(c, user) } @@ -519,19 +500,16 @@ func SessionsTwoFactorHandler(c echo.Context) error { } // SessionsTwoFactorRecoveryHandler ... -func SessionsTwoFactorRecoveryHandler(c echo.Context) error { - partialAuthCookie, err := c.Cookie(hutils.PartialAuthCookieName) - if err != nil { - return c.Redirect(http.StatusFound, "/") - } - userID, found := partialAuthCache.Get(partialAuthCookie.Value) +func SessionsTwoFactorRecoveryHandler(c echo.Context, token string) error { + userID, found := partialAuthCache.Get(token) if !found { return c.Redirect(http.StatusFound, "/") } var data sessionsTwoFactorRecoveryData - if c.Request().Method == http.MethodPost { - recoveryCode := c.Request().PostFormValue("code") + data.Token = token + recoveryCode := c.Request().PostFormValue("code") + if recoveryCode != "" { user, err := database.GetUserByID(userID) if err != nil { logrus.Errorf("failed to get user %d", userID) @@ -542,8 +520,7 @@ func SessionsTwoFactorRecoveryHandler(c echo.Context) error { return c.Render(http.StatusOK, "sessions-two-factor-recovery", data) } - partialAuthCache.Delete(partialAuthCookie.Value) - c.SetCookie(hutils.DeleteCookie(hutils.PartialAuthCookieName)) + partialAuthCache.Delete(token) return completeLogin(c, user) } @@ -574,10 +551,6 @@ func LogoutHandler(ctx echo.Context) error { return ctx.Redirect(http.StatusFound, "/") } -func createPartialSessionCookie(value string) *http.Cookie { - return hutils.CreateCookie(hutils.PartialAuthCookieName, value, 10*utils.OneMinuteSecs) -} - func createSessionCookie(value string) *http.Cookie { return hutils.CreateCookie(hutils.AuthCookieName, value, utils.OneMonthSecs) } diff --git a/pkg/web/handlers/utils/utils.go b/pkg/web/handlers/utils/utils.go @@ -14,12 +14,11 @@ import ( ) const ( - HBCookieName = "dkft" // dkf troll - SignupCookieName = "signup-token" - AuthCookieName = "auth-token" - PartialAuthCookieName = "partial-auth-token" - AprilFoolCookieName = "april_fool" - ByteRoadCookieName = "challenge_byte_road_session" + HBCookieName = "dkft" // dkf troll + SignupCookieName = "signup-token" + AuthCookieName = "auth-token" + AprilFoolCookieName = "april_fool" + ByteRoadCookieName = "challenge_byte_road_session" ) var AccountTooYoungErr = errors.New("account must be at least 3 days old") diff --git a/pkg/web/public/views/pages/sessions-gpg-sign-two-factor.gohtml b/pkg/web/public/views/pages/sessions-gpg-sign-two-factor.gohtml @@ -22,7 +22,8 @@ <div class="card-body"> <form method="post" novalidate> <input type="hidden" name="csrf" value="{{ .CSRF }}" /> - <input type="hidden" name="formName" value="pgp_step2" /> + <input type="hidden" name="formName" value="pgp_sign_2fa" /> + <input type="hidden" name="token" value="{{ .Data.Token }}" /> <div class="form-group"> <label for="encrypted_message">{{ t "Please sign the following message with your private key and send the signature" . }}</label> <p><code>gpg --armor --detach-sign file</code></p> diff --git a/pkg/web/public/views/pages/sessions-gpg-two-factor.gohtml b/pkg/web/public/views/pages/sessions-gpg-two-factor.gohtml @@ -22,7 +22,8 @@ <div class="card-body"> <form method="post" novalidate> <input type="hidden" name="csrf" value="{{ .CSRF }}" /> - <input type="hidden" name="formName" value="pgp_step2" /> + <input type="hidden" name="formName" value="pgp_2fa" /> + <input type="hidden" name="token" value="{{ .Data.Token }}" /> <div class="form-group"> <label for="encrypted_message">{{ t "Please decrypt the following message with your private key and send the required code" . }}</label> <textarea name="encrypted_message" id="encrypted_message" rows="10" class="form-control" style="font-family: SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono','Courier New',monospace;" readonly>{{ .Data.EncryptedMessage }}</textarea> diff --git a/pkg/web/public/views/pages/sessions-two-factor-recovery.gohtml b/pkg/web/public/views/pages/sessions-two-factor-recovery.gohtml @@ -9,6 +9,8 @@ <div class="card-body"> <form autocomplete="on" method="post"> <input type="hidden" name="csrf" value="{{ .CSRF }}" /> + <input type="hidden" name="formName" value="2fa_recovery" /> + <input type="hidden" name="token" value="{{ .Data.Token }}" /> <fieldset> <div class="row"> <div class="center-block"> diff --git a/pkg/web/public/views/pages/sessions-two-factor.gohtml b/pkg/web/public/views/pages/sessions-two-factor.gohtml @@ -7,33 +7,40 @@ <strong>{{ t "Two-factor authentication" . }}</strong> </div> <div class="card-body"> - <form autocomplete="on" method="post"> - <input type="hidden" name="csrf" value="{{ .CSRF }}" /> - <fieldset> - <div class="row"> - <div class="center-block"> - </div> + <fieldset> + <div class="row"> + <div class="center-block"> </div> - <div class="row"> - <div class="col-sm-12 col-md-10 offset-md-1 "> - {{ if .Data.Error }} - <div class="alert alert-danger"> - {{ .Data.Error }} - </div> - {{ end }} + </div> + <div class="row"> + <div class="col-sm-12 col-md-10 offset-md-1 "> + {{ if .Data.Error }} + <div class="alert alert-danger"> + {{ .Data.Error }} + </div> + {{ end }} + <form autocomplete="on" method="post"> + <input type="hidden" name="csrf" value="{{ .CSRF }}" /> + <input type="hidden" name="formName" value="2fa" /> + <input type="hidden" name="token" value="{{ .Data.Token }}" /> <div class="form-group"> - <input class="form-control{{ if .Data.Error }} is-invalid{{ end }}" placeholder="{{ t "6-digit code" . }}" name="code" type="text" autocomplete="off" autocorrect="off" autocapitalize="none" autofocus required /> + <input class="form-control{{ if .Data.Error }} is-invalid{{ end }}" placeholder="{{ t "6-digit code" . }}" name="code" type="text" maxlength="6" autocomplete="off" autocorrect="off" autocapitalize="none" autofocus required /> </div> <div class="form-group"> <input type="submit" class="btn btn-lg btn-primary btn-block" value="{{ t "Verify" . }}" /> </div> + </form> + <form autocomplete="on" method="post"> + <input type="hidden" name="csrf" value="{{ .CSRF }}" /> + <input type="hidden" name="formName" value="2fa_recovery" /> + <input type="hidden" name="token" value="{{ .Data.Token }}" /> <div class="form-group"> - <a href="/sessions/two-factor/recovery">{{ t "Enter a two-factor recovery code" . }}</a> + <button type="submit" class="ml-0 pl-0 btn btn-link btn-block">{{ t "Enter a two-factor recovery code" . }}</button> </div> - </div> + </form> </div> - </fieldset> - </form> + </div> + </fieldset> </div> <div class="card-footer "> </div> diff --git a/pkg/web/web.go b/pkg/web/web.go @@ -76,14 +76,6 @@ func getMainServer() echo.HandlerFunc { noAuthGroup.POST("/login", handlers.LoginHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 4, false)) noAuthGroup.GET("/login/:loginToken", handlers.LoginAttackHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 4, false)) noAuthGroup.POST("/login/:loginToken", handlers.LoginAttackHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 2, false)) - noAuthGroup.GET("/sessions/gpg-two-factor", handlers.SessionsGpgTwoFactorHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 4, false)) - noAuthGroup.POST("/sessions/gpg-two-factor", handlers.SessionsGpgTwoFactorHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 2, false)) - noAuthGroup.GET("/sessions/gpg-sign-two-factor", handlers.SessionsGpgSignTwoFactorHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 4, false)) - noAuthGroup.POST("/sessions/gpg-sign-two-factor", handlers.SessionsGpgSignTwoFactorHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 2, false)) - noAuthGroup.GET("/sessions/two-factor", handlers.SessionsTwoFactorHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 4, false)) - noAuthGroup.POST("/sessions/two-factor", handlers.SessionsTwoFactorHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 2, false)) - noAuthGroup.GET("/sessions/two-factor/recovery", handlers.SessionsTwoFactorRecoveryHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 4, false)) - noAuthGroup.POST("/sessions/two-factor/recovery", handlers.SessionsTwoFactorRecoveryHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 2, false)) noAuthGroup.GET("/signup", handlers.SignupHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 5, false)) noAuthGroup.POST("/signup", handlers.SignupHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 4, false)) noAuthGroup.GET("/signup/invitation", handlers.SignupInvitationHandler, middlewares.CircuitRateLimitMiddleware(1*time.Second, 5, false))