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" ··· 88 89 89 90 // if repo requires auth factor token and one hasn't been provided, return error prompting for one 90 91 if repo.EmailAuthFactor && (req.AuthFactorToken == nil || *req.AuthFactorToken == "") { 91 - code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 92 - eat := time.Now().Add(10 * time.Minute).UTC() 93 - 94 - 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 { 95 - s.logger.Error("error updating repo", "error", err) 96 - return helpers.ServerError(e, nil) 97 - } 98 - 99 - if err := s.sendEmailUpdate(repo.Email, repo.Handle, code); err != nil { 100 - s.logger.Error("error sending email", "error", err) 92 + err = s.createAndSendAuthCode(ctx, repo) 93 + if err != nil { 94 + s.logger.Error("sending auth code", "error", err) 101 95 return helpers.ServerError(e, nil) 102 96 } 103 97 ··· 106 100 107 101 // if auth factor is required, now check that the one provided is valid 108 102 if repo.EmailAuthFactor { 109 - if repo.EmailUpdateCode == nil || repo.EmailUpdateCodeExpiresAt == nil { 110 - return helpers.InvalidTokenError(e) 103 + if repo.AuthCode == nil || repo.AuthCodeExpiresAt == nil { 104 + err = s.createAndSendAuthCode(ctx, repo) 105 + if err != nil { 106 + s.logger.Error("sending auth code", "error", err) 107 + return helpers.ServerError(e, nil) 108 + } 109 + 110 + return helpers.InputError(e, to.StringPtr("AuthFactorTokenRequired")) 111 111 } 112 112 113 - if *repo.EmailUpdateCode != *req.AuthFactorToken { 113 + if *repo.AuthCode != *req.AuthFactorToken { 114 114 return helpers.InvalidTokenError(e) 115 115 } 116 116 117 - if time.Now().UTC().After(*repo.EmailUpdateCodeExpiresAt) { 117 + if time.Now().UTC().After(*repo.AuthCodeExpiresAt) { 118 118 return helpers.ExpiredTokenError(e) 119 119 } 120 120 } ··· 144 144 Status: repo.Status(), 145 145 }) 146 146 } 147 + 148 + func (s *Server) createAndSendAuthCode(ctx context.Context, repo models.RepoActor) error { 149 + code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 150 + eat := time.Now().Add(10 * time.Minute).UTC() 151 + 152 + 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 { 153 + return fmt.Errorf("updating repo: %w", err) 154 + } 155 + 156 + if err := s.sendAuthCode(repo.Email, repo.Handle, code); err != nil { 157 + return fmt.Errorf("sending email: %w", err) 158 + } 159 + 160 + return nil 161 + }
+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 + }