From 11920453a6fb7d1e1fe614ce3331916638c5af96 Mon Sep 17 00:00:00 2001 From: Will Andrews Date: Fri, 2 Jan 2026 18:49:34 +0000 Subject: [PATCH] implement providing 2FA token on PDS account login screen Signed-off-by: Will Andrews --- server/handle_account_signin.go | 65 ++++++++++++++++++++++---- server/handle_server_create_session.go | 16 +++---- server/templates/signin.html | 4 ++ 3 files changed, 68 insertions(+), 17 deletions(-) diff --git a/server/handle_account_signin.go b/server/handle_account_signin.go index a024f00..e42c444 100644 --- a/server/handle_account_signin.go +++ b/server/handle_account_signin.go @@ -2,7 +2,9 @@ package server import ( "errors" + "fmt" "strings" + "time" "github.com/bluesky-social/indigo/atproto/syntax" "github.com/gorilla/sessions" @@ -15,9 +17,10 @@ import ( ) type OauthSigninInput struct { - Username string `form:"username"` - Password string `form:"password"` - QueryParams string `form:"query_params"` + Username string `form:"username"` + Password string `form:"password"` + AuthFactorToken string `form:"token"` + QueryParams string `form:"query_params"` } func (s *Server) getSessionRepoOrErr(e echo.Context) (*models.RepoActor, *sessions.Session, error) { @@ -44,8 +47,9 @@ func (s *Server) getSessionRepoOrErr(e echo.Context) (*models.RepoActor, *sessio func getFlashesFromSession(e echo.Context, sess *sessions.Session) map[string]any { defer sess.Save(e.Request(), e.Response()) return map[string]any{ - "errors": sess.Flashes("error"), - "successes": sess.Flashes("success"), + "errors": sess.Flashes("error"), + "successes": sess.Flashes("success"), + "tokenrequired": sess.Flashes("tokenrequired"), } } @@ -83,6 +87,11 @@ func (s *Server) handleAccountSigninPost(e echo.Context) error { idtype = "email" } + queryParams := "" + if req.QueryParams != "" { + queryParams = fmt.Sprintf("?%s", req.QueryParams) + } + // TODO: we should make this a helper since we do it for the base create_session as well var repo models.RepoActor var err error @@ -101,7 +110,7 @@ func (s *Server) handleAccountSigninPost(e echo.Context) error { sess.AddFlash("Something went wrong!", "error") } sess.Save(e.Request(), e.Response()) - return e.Redirect(303, "/account/signin") + return e.Redirect(303, "/account/signin"+queryParams) } if err := bcrypt.CompareHashAndPassword([]byte(repo.Password), []byte(req.Password)); err != nil { @@ -111,7 +120,45 @@ func (s *Server) handleAccountSigninPost(e echo.Context) error { sess.AddFlash("Something went wrong!", "error") } sess.Save(e.Request(), e.Response()) - return e.Redirect(303, "/account/signin") + return e.Redirect(303, "/account/signin"+queryParams) + } + + // if repo requires auth factor token and one hasn't been provided, return error prompting for one + if repo.EmailAuthFactor && req.AuthFactorToken == "" { + err = s.createAndSendAuthCode(ctx, repo) + if err != nil { + sess.AddFlash("Something went wrong!", "error") + sess.Save(e.Request(), e.Response()) + return e.Redirect(303, "/account/signin"+queryParams) + } + + sess.AddFlash("requires 2FA token", "tokenrequired") + sess.Save(e.Request(), e.Response()) + return e.Redirect(303, "/account/signin"+queryParams) + } + + // if auth factor is required, now check that the one provided is valid + if repo.EmailAuthFactor { + if repo.AuthCode == nil || repo.AuthCodeExpiresAt == nil { + err = s.createAndSendAuthCode(ctx, repo) + if err != nil { + sess.AddFlash("Something went wrong!", "error") + sess.Save(e.Request(), e.Response()) + return e.Redirect(303, "/account/signin"+queryParams) + } + + sess.AddFlash("requires 2FA token", "tokenrequired") + sess.Save(e.Request(), e.Response()) + return e.Redirect(303, "/account/signin"+queryParams) + } + + if *repo.AuthCode != req.AuthFactorToken { + return helpers.InvalidTokenError(e) + } + + if time.Now().UTC().After(*repo.AuthCodeExpiresAt) { + return helpers.ExpiredTokenError(e) + } } sess.Options = &sessions.Options{ @@ -127,8 +174,8 @@ func (s *Server) handleAccountSigninPost(e echo.Context) error { return err } - if req.QueryParams != "" { - return e.Redirect(303, "/oauth/authorize?"+req.QueryParams) + if queryParams != "" { + return e.Redirect(303, "/oauth/authorize"+queryParams) } else { return e.Redirect(303, "/account") } diff --git a/server/handle_server_create_session.go b/server/handle_server_create_session.go index 37a952f..869e8ff 100644 --- a/server/handle_server_create_session.go +++ b/server/handle_server_create_session.go @@ -87,6 +87,13 @@ func (s *Server) handleCreateSession(e echo.Context) error { return helpers.ServerError(e, nil) } + if err := bcrypt.CompareHashAndPassword([]byte(repo.Password), []byte(req.Password)); err != nil { + if err != bcrypt.ErrMismatchedHashAndPassword { + logger.Error("erorr comparing hash and password", "error", err) + } + return helpers.InputError(e, to.StringPtr("InvalidRequest")) + } + // if repo requires auth factor token and one hasn't been provided, return error prompting for one if repo.EmailAuthFactor && (req.AuthFactorToken == nil || *req.AuthFactorToken == "") { err = s.createAndSendAuthCode(ctx, repo) @@ -103,7 +110,7 @@ func (s *Server) handleCreateSession(e echo.Context) error { if repo.AuthCode == nil || repo.AuthCodeExpiresAt == nil { err = s.createAndSendAuthCode(ctx, repo) if err != nil { - s.logger.Error("sending auth code", "error", err) + logger.Error("sending auth code", "error", err) return helpers.ServerError(e, nil) } @@ -119,13 +126,6 @@ func (s *Server) handleCreateSession(e echo.Context) error { } } - if err := bcrypt.CompareHashAndPassword([]byte(repo.Password), []byte(req.Password)); err != nil { - if err != bcrypt.ErrMismatchedHashAndPassword { - logger.Error("erorr comparing hash and password", "error", err) - } - return helpers.InputError(e, to.StringPtr("InvalidRequest")) - } - sess, err := s.createSession(ctx, &repo.Repo) if err != nil { logger.Error("error creating session", "error", err) diff --git a/server/templates/signin.html b/server/templates/signin.html index e589a33..3348c26 100644 --- a/server/templates/signin.html +++ b/server/templates/signin.html @@ -26,6 +26,10 @@ type="password" placeholder="Password" /> + {{ if .flashes.tokenrequired }} +
+ + {{ end }} -- 2.51.2