diff --git a/models/models.go b/models/models.go index 9d56487..0c8d574 100644 --- a/models/models.go +++ b/models/models.go @@ -30,6 +30,8 @@ type Repo struct { 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 index eaf445e..37a952f 100644 --- 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 @@ func (s *Server) handleCreateSession(e echo.Context) error { // 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 @@ func (s *Server) handleCreateSession(e echo.Context) error { // 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 @@ func (s *Server) handleCreateSession(e echo.Context) error { 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 index 2f153de..c7cf4c0 100644 --- a/server/mail.go +++ b/server/mail.go @@ -96,3 +96,22 @@ func (s *Server) sendEmailVerification(email, handle, code string) error { 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 +}