forked from hailey.at/cocoon
An atproto PDS written in Go

refactor the 2FA code into it's own field on model and generate a new email type

Signed-off-by: Will Andrews <did:plc:dadhhalkfcq3gucaq25hjqon>

Changed files
+50 -14
models
server
+2
models/models.go
··· 30 30 Preferences []byte 31 31 Deactivated bool 32 32 EmailAuthFactor bool 33 + AuthCode *string 34 + AuthCodeExpiresAt *time.Time 33 35 } 34 36 35 37 func (r *Repo) SignFor(ctx context.Context, did string, msg []byte) ([]byte, error) {
+29 -14
server/handle_server_create_session.go
··· 1 1 package server 2 2 3 3 import ( 4 + "context" 4 5 "errors" 5 6 "fmt" 6 7 "strings" ··· 87 88 88 89 // if repo requires auth factor token and one hasn't been provided, return error prompting for one 89 90 if repo.EmailAuthFactor && (req.AuthFactorToken == nil || *req.AuthFactorToken == "") { 90 - code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 91 - eat := time.Now().Add(10 * time.Minute).UTC() 92 - 93 - 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 { 94 - s.logger.Error("error updating repo", "error", err) 95 - return helpers.ServerError(e, nil) 96 - } 97 - 98 - if err := s.sendEmailUpdate(repo.Email, repo.Handle, code); err != nil { 99 - s.logger.Error("error sending email", "error", err) 91 + err = s.createAndSendAuthCode(ctx, repo) 92 + if err != nil { 93 + s.logger.Error("sending auth code", "error", err) 100 94 return helpers.ServerError(e, nil) 101 95 } 102 96 ··· 105 99 106 100 // if auth factor is required, now check that the one provided is valid 107 101 if repo.EmailAuthFactor { 108 - if repo.EmailUpdateCode == nil || repo.EmailUpdateCodeExpiresAt == nil { 109 - return helpers.InvalidTokenError(e) 102 + if repo.AuthCode == nil || repo.AuthCodeExpiresAt == nil { 103 + err = s.createAndSendAuthCode(ctx, repo) 104 + if err != nil { 105 + s.logger.Error("sending auth code", "error", err) 106 + return helpers.ServerError(e, nil) 107 + } 108 + 109 + return helpers.InputError(e, to.StringPtr("AuthFactorTokenRequired")) 110 110 } 111 111 112 - if *repo.EmailUpdateCode != *req.AuthFactorToken { 112 + if *repo.AuthCode != *req.AuthFactorToken { 113 113 return helpers.InvalidTokenError(e) 114 114 } 115 115 116 - if time.Now().UTC().After(*repo.EmailUpdateCodeExpiresAt) { 116 + if time.Now().UTC().After(*repo.AuthCodeExpiresAt) { 117 117 return helpers.ExpiredTokenError(e) 118 118 } 119 119 } ··· 143 143 Status: repo.Status(), 144 144 }) 145 145 } 146 + 147 + func (s *Server) createAndSendAuthCode(ctx context.Context, repo models.RepoActor) error { 148 + code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 149 + eat := time.Now().Add(10 * time.Minute).UTC() 150 + 151 + 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 { 152 + return fmt.Errorf("updating repo: %w", err) 153 + } 154 + 155 + if err := s.sendAuthCode(repo.Email, repo.Handle, code); err != nil { 156 + return fmt.Errorf("sending email: %w", err) 157 + } 158 + 159 + return nil 160 + }
+19
server/mail.go
··· 96 96 97 97 return nil 98 98 } 99 + 100 + func (s *Server) sendAuthCode(email, handle, code string) error { 101 + if s.mail == nil { 102 + return nil 103 + } 104 + 105 + s.mailLk.Lock() 106 + defer s.mailLk.Unlock() 107 + 108 + s.mail.To(email) 109 + s.mail.Subject("2FA code for " + s.config.Hostname) 110 + s.mail.Plain().Set(fmt.Sprintf("Hello %s. Your 2FA code is %s. This code will expire in ten minutes.", handle, code)) 111 + 112 + if err := s.mail.Send(); err != nil { 113 + return err 114 + } 115 + 116 + return nil 117 + }