From 86972c4f572bbdb864ab3472153ac78bcaf13004 Mon Sep 17 00:00:00 2001 From: Will Andrews Date: Fri, 02 Jan 2026 15:48:49 +0000 Subject: [PATCH] refactor the 2FA code into it's own field on model and generate a new email type Signed-off-by: Will Andrews --- models/models.go | 2 ++ server/handle_server_create_session.go | 43 +++++++++++++++++++++++++++++-------------- server/mail.go | 19 +++++++++++++++++++ 3 file(s) changed, 50 insertion(s)(+), 14 deletion(s)(-) diff --git a/models/models.go b/models/models.go --- a/models/models.go +++ b/models/models.go @@ -30,6 +30,8 @@ Root []byte Preferences []byte Deactivated bool EmailAuthFactor bool + AuthCode *string + AuthCodeExpiresAt *time.Time } func (r *Repo) SignFor(ctx context.Context, did string, msg []byte) ([]byte, error) { diff --git a/server/handle_server_create_session.go b/server/handle_server_create_session.go --- a/server/handle_server_create_session.go +++ b/server/handle_server_create_session.go @@ -1,6 +1,7 @@ package server import ( + "context" "errors" "fmt" "strings" @@ -88,16 +89,9 @@ } // 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 == "") { - code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) - eat := time.Now().Add(10 * time.Minute).UTC() - - if err := s.db.Exec(ctx, "UPDATE repos SET email_update_code = ?, email_update_code_expires_at = ? WHERE did = ?", nil, code, eat, repo.Repo.Did).Error; err != nil { - s.logger.Error("error updating repo", "error", err) - return helpers.ServerError(e, nil) - } - - if err := s.sendEmailUpdate(repo.Email, repo.Handle, code); err != nil { - s.logger.Error("error sending email", "error", err) + err = s.createAndSendAuthCode(ctx, repo) + if err != nil { + s.logger.Error("sending auth code", "error", err) return helpers.ServerError(e, nil) } @@ -106,15 +100,21 @@ } // if auth factor is required, now check that the one provided is valid if repo.EmailAuthFactor { - if repo.EmailUpdateCode == nil || repo.EmailUpdateCodeExpiresAt == nil { - return helpers.InvalidTokenError(e) + if repo.AuthCode == nil || repo.AuthCodeExpiresAt == nil { + err = s.createAndSendAuthCode(ctx, repo) + if err != nil { + s.logger.Error("sending auth code", "error", err) + return helpers.ServerError(e, nil) + } + + return helpers.InputError(e, to.StringPtr("AuthFactorTokenRequired")) } - if *repo.EmailUpdateCode != *req.AuthFactorToken { + if *repo.AuthCode != *req.AuthFactorToken { return helpers.InvalidTokenError(e) } - if time.Now().UTC().After(*repo.EmailUpdateCodeExpiresAt) { + if time.Now().UTC().After(*repo.AuthCodeExpiresAt) { return helpers.ExpiredTokenError(e) } } @@ -144,3 +144,18 @@ Active: repo.Active(), Status: repo.Status(), }) } + +func (s *Server) createAndSendAuthCode(ctx context.Context, repo models.RepoActor) error { + code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) + eat := time.Now().Add(10 * time.Minute).UTC() + + if err := s.db.Exec(ctx, "UPDATE repos SET auth_code = ?, auth_code_expires_at = ? WHERE did = ?", nil, code, eat, repo.Repo.Did).Error; err != nil { + return fmt.Errorf("updating repo: %w", err) + } + + if err := s.sendAuthCode(repo.Email, repo.Handle, code); err != nil { + return fmt.Errorf("sending email: %w", err) + } + + return nil +} diff --git a/server/mail.go b/server/mail.go --- a/server/mail.go +++ b/server/mail.go @@ -96,3 +96,22 @@ } return nil } + +func (s *Server) sendAuthCode(email, handle, code string) error { + if s.mail == nil { + return nil + } + + s.mailLk.Lock() + defer s.mailLk.Unlock() + + s.mail.To(email) + s.mail.Subject("2FA code for " + s.config.Hostname) + s.mail.Plain().Set(fmt.Sprintf("Hello %s. Your 2FA code is %s. This code will expire in ten minutes.", handle, code)) + + if err := s.mail.Send(); err != nil { + return err + } + + return nil +} -- tangled.sh