From e6c685099351b30eae0aad039798d3346334e1df Mon Sep 17 00:00:00 2001 From: Will Andrews Date: Fri, 2 Jan 2026 12:39:19 +0000 Subject: [PATCH] implement enabling / disabling email auth factor (not currently checked on auth checks though) Signed-off-by: Will Andrews --- models/models.go | 5 +++-- server/handle_server_create_session.go | 2 +- server/handle_server_get_session.go | 2 +- server/handle_server_update_email.go | 31 ++++++++++++++++++++------ 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/models/models.go b/models/models.go index 92c59bf..9d56487 100644 --- a/models/models.go +++ b/models/models.go @@ -29,6 +29,7 @@ type Repo struct { Root []byte Preferences []byte Deactivated bool + EmailAuthFactor bool } func (r *Repo) SignFor(ctx context.Context, did string, msg []byte) ([]byte, error) { @@ -121,8 +122,8 @@ type BlobPart struct { } type ReservedKey struct { - KeyDid string `gorm:"primaryKey"` - Did *string `gorm:"index"` + KeyDid string `gorm:"primaryKey"` + Did *string `gorm:"index"` PrivateKey []byte CreatedAt time.Time `gorm:"index"` } diff --git a/server/handle_server_create_session.go b/server/handle_server_create_session.go index 645192d..aeb8c5b 100644 --- a/server/handle_server_create_session.go +++ b/server/handle_server_create_session.go @@ -103,7 +103,7 @@ func (s *Server) handleCreateSession(e echo.Context) error { Did: repo.Repo.Did, Email: repo.Email, EmailConfirmed: repo.EmailConfirmedAt != nil, - EmailAuthFactor: false, + EmailAuthFactor: repo.EmailAuthFactor, Active: repo.Active(), Status: repo.Status(), }) diff --git a/server/handle_server_get_session.go b/server/handle_server_get_session.go index 38cdf4f..60fd812 100644 --- a/server/handle_server_get_session.go +++ b/server/handle_server_get_session.go @@ -23,7 +23,7 @@ func (s *Server) handleGetSession(e echo.Context) error { Did: repo.Repo.Did, Email: repo.Email, EmailConfirmed: repo.EmailConfirmedAt != nil, - EmailAuthFactor: false, // TODO: todo todo + EmailAuthFactor: repo.EmailAuthFactor, Active: repo.Active(), Status: repo.Status(), }) diff --git a/server/handle_server_update_email.go b/server/handle_server_update_email.go index c94e86a..b51def6 100644 --- a/server/handle_server_update_email.go +++ b/server/handle_server_update_email.go @@ -11,7 +11,7 @@ import ( type ComAtprotoServerUpdateEmailRequest struct { Email string `json:"email" validate:"required"` EmailAuthFactor bool `json:"emailAuthFactor"` - Token string `json:"token" validate:"required"` + Token string `json:"token"` } func (s *Server) handleServerUpdateEmail(e echo.Context) error { @@ -29,19 +29,36 @@ func (s *Server) handleServerUpdateEmail(e echo.Context) error { return helpers.InputError(e, nil) } - if urepo.EmailUpdateCode == nil || urepo.EmailUpdateCodeExpiresAt == nil { + // To disable email auth factor a token is required. + // To enable email auth factor a token is not required. + // If updating an email address, a token will be sent anyway + if urepo.EmailAuthFactor && req.EmailAuthFactor == false && req.Token == "" { return helpers.InvalidTokenError(e) } - if *urepo.EmailUpdateCode != req.Token { - return helpers.InvalidTokenError(e) + if req.Token != "" { + if urepo.EmailUpdateCode == nil || urepo.EmailUpdateCodeExpiresAt == nil { + return helpers.InvalidTokenError(e) + } + + if *urepo.EmailUpdateCode != req.Token { + return helpers.InvalidTokenError(e) + } + + if time.Now().UTC().After(*urepo.EmailUpdateCodeExpiresAt) { + return helpers.ExpiredTokenError(e) + } } - if time.Now().UTC().After(*urepo.EmailUpdateCodeExpiresAt) { - return helpers.ExpiredTokenError(e) + query := "UPDATE repos SET email_update_code = NULL, email_update_code_expires_at = NULL, email_auth_factor = ?, email = ?" + + if urepo.Email != req.Email { + query += ",email_confirmed_at = NULL" } - if err := s.db.Exec(ctx, "UPDATE repos SET email_update_code = NULL, email_update_code_expires_at = NULL, email_confirmed_at = NULL, email = ? WHERE did = ?", nil, req.Email, urepo.Repo.Did).Error; err != nil { + query += " WHERE did = ?" + + if err := s.db.Exec(ctx, query, nil, req.EmailAuthFactor, req.Email, urepo.Repo.Did).Error; err != nil { s.logger.Error("error updating repo", "error", err) return helpers.ServerError(e, nil) } -- 2.51.0 From c1d1e73485193a6ee00d116e031dd847e8ce0a8e Mon Sep 17 00:00:00 2001 From: Will Andrews Date: Fri, 2 Jan 2026 15:06:01 +0000 Subject: [PATCH] implement 2fa on creating a session Signed-off-by: Will Andrews --- server/handle_server_create_session.go | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/server/handle_server_create_session.go b/server/handle_server_create_session.go index aeb8c5b..da65deb 100644 --- a/server/handle_server_create_session.go +++ b/server/handle_server_create_session.go @@ -2,7 +2,9 @@ package server import ( "errors" + "fmt" "strings" + "time" "github.com/Azure/go-autorest/autorest/to" "github.com/bluesky-social/indigo/atproto/syntax" @@ -83,6 +85,39 @@ func (s *Server) handleCreateSession(e echo.Context) error { return helpers.ServerError(e, nil) } + // 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) + return helpers.ServerError(e, nil) + } + + return helpers.InputError(e, to.StringPtr("AuthFactorTokenRequired")) + } + + // 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.EmailUpdateCode != *req.AuthFactorToken { + return helpers.InvalidTokenError(e) + } + + if time.Now().UTC().After(*repo.EmailUpdateCodeExpiresAt) { + return helpers.ExpiredTokenError(e) + } + } + if err := bcrypt.CompareHashAndPassword([]byte(repo.Password), []byte(req.Password)); err != nil { if err != bcrypt.ErrMismatchedHashAndPassword { s.logger.Error("erorr comparing hash and password", "error", err) -- 2.51.0 From 12a6212ad7a681e12a9e3cd4cf2d5bad7221524c Mon Sep 17 00:00:00 2001 From: Will Andrews Date: Fri, 2 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 files changed, 50 insertions(+), 14 deletions(-) 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 da65deb..8185bc8 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" @@ -87,16 +88,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) } @@ -105,15 +99,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) } } @@ -143,3 +143,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 +} -- 2.51.0 From 834dc6d8cd8eb16a6deaa6b8f118723a67de3883 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 | 14 +++--- server/templates/signin.html | 4 ++ 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/server/handle_account_signin.go b/server/handle_account_signin.go index 57b082a..a65e977 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"), } } @@ -82,6 +86,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 @@ -100,7 +109,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 { @@ -110,7 +119,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{ @@ -126,8 +173,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 8185bc8..5018524 100644 --- a/server/handle_server_create_session.go +++ b/server/handle_server_create_session.go @@ -86,6 +86,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 { + s.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) @@ -118,13 +125,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 { - s.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 { s.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.0 From cf6bcf6439360b5fb08a76e437af9bb5634d48f3 Mon Sep 17 00:00:00 2001 From: Will Andrews Date: Fri, 2 Jan 2026 19:36:41 +0000 Subject: [PATCH] refactor so that there's a 2FA type on the repo which replaces EmailAuthFactor Signed-off-by: Will Andrews --- models/models.go | 13 +++++++--- server/handle_account_signin.go | 18 +++++++------- server/handle_server_create_session.go | 33 ++++++++++++++------------ server/handle_server_get_session.go | 2 +- server/handle_server_update_email.go | 11 ++++++--- server/mail.go | 2 +- 6 files changed, 47 insertions(+), 32 deletions(-) diff --git a/models/models.go b/models/models.go index 0c8d574..c9c7236 100644 --- a/models/models.go +++ b/models/models.go @@ -8,6 +8,13 @@ import ( "github.com/bluesky-social/indigo/atproto/atcrypto" ) +type TwoFactorType string + +var ( + TwoFactorTypeNone = TwoFactorType("none") + TwoFactorTypeEmail = TwoFactorType("email") +) + type Repo struct { Did string `gorm:"primaryKey"` CreatedAt time.Time @@ -29,9 +36,9 @@ type Repo struct { Root []byte Preferences []byte Deactivated bool - EmailAuthFactor bool - AuthCode *string - AuthCodeExpiresAt *time.Time + TwoFactorCode *string + TwoFactorCodeExpiresAt *time.Time + TwoFactorType TwoFactorType `gorm:"default:none"` } func (r *Repo) SignFor(ctx context.Context, did string, msg []byte) ([]byte, error) { diff --git a/server/handle_account_signin.go b/server/handle_account_signin.go index a65e977..71ccba3 100644 --- a/server/handle_account_signin.go +++ b/server/handle_account_signin.go @@ -122,9 +122,9 @@ func (s *Server) handleAccountSigninPost(e echo.Context) error { 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 repo requires 2FA token and one hasn't been provided, return error prompting for one + if repo.TwoFactorType != models.TwoFactorTypeNone && req.AuthFactorToken == "" { + err = s.createAndSendTwoFactorCode(ctx, repo) if err != nil { sess.AddFlash("Something went wrong!", "error") sess.Save(e.Request(), e.Response()) @@ -136,10 +136,10 @@ func (s *Server) handleAccountSigninPost(e echo.Context) error { 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 2FAis required, now check that the one provided is valid + if repo.TwoFactorType != models.TwoFactorTypeNone { + if repo.TwoFactorCode == nil || repo.TwoFactorCodeExpiresAt == nil { + err = s.createAndSendTwoFactorCode(ctx, repo) if err != nil { sess.AddFlash("Something went wrong!", "error") sess.Save(e.Request(), e.Response()) @@ -151,11 +151,11 @@ func (s *Server) handleAccountSigninPost(e echo.Context) error { return e.Redirect(303, "/account/signin"+queryParams) } - if *repo.AuthCode != req.AuthFactorToken { + if *repo.TwoFactorCode != req.AuthFactorToken { return helpers.InvalidTokenError(e) } - if time.Now().UTC().After(*repo.AuthCodeExpiresAt) { + if time.Now().UTC().After(*repo.TwoFactorCodeExpiresAt) { return helpers.ExpiredTokenError(e) } } diff --git a/server/handle_server_create_session.go b/server/handle_server_create_session.go index 5018524..05f9ecd 100644 --- a/server/handle_server_create_session.go +++ b/server/handle_server_create_session.go @@ -93,34 +93,34 @@ func (s *Server) handleCreateSession(e echo.Context) error { 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) + // if repo requires 2FA token and one hasn't been provided, return error prompting for one + if repo.TwoFactorType != models.TwoFactorTypeNone && (req.AuthFactorToken == nil || *req.AuthFactorToken == "") { + err = s.createAndSendTwoFactorCode(ctx, repo) if err != nil { - s.logger.Error("sending auth code", "error", err) + s.logger.Error("sending 2FA code", "error", err) return helpers.ServerError(e, nil) } return helpers.InputError(e, to.StringPtr("AuthFactorTokenRequired")) } - // 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 2FA is required, now check that the one provided is valid + if repo.TwoFactorType != models.TwoFactorTypeNone { + if repo.TwoFactorCode == nil || repo.TwoFactorCodeExpiresAt == nil { + err = s.createAndSendTwoFactorCode(ctx, repo) if err != nil { - s.logger.Error("sending auth code", "error", err) + s.logger.Error("sending 2FA code", "error", err) return helpers.ServerError(e, nil) } return helpers.InputError(e, to.StringPtr("AuthFactorTokenRequired")) } - if *repo.AuthCode != *req.AuthFactorToken { + if *repo.TwoFactorCode != *req.AuthFactorToken { return helpers.InvalidTokenError(e) } - if time.Now().UTC().After(*repo.AuthCodeExpiresAt) { + if time.Now().UTC().After(*repo.TwoFactorCodeExpiresAt) { return helpers.ExpiredTokenError(e) } } @@ -138,21 +138,24 @@ func (s *Server) handleCreateSession(e echo.Context) error { Did: repo.Repo.Did, Email: repo.Email, EmailConfirmed: repo.EmailConfirmedAt != nil, - EmailAuthFactor: repo.EmailAuthFactor, + EmailAuthFactor: repo.TwoFactorType != models.TwoFactorTypeNone, Active: repo.Active(), Status: repo.Status(), }) } -func (s *Server) createAndSendAuthCode(ctx context.Context, repo models.RepoActor) error { +func (s *Server) createAndSendTwoFactorCode(ctx context.Context, repo models.RepoActor) error { + // TODO: when implementing a new type of 2FA there should be some logic in here to send the + // right type of code + 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 { + if err := s.db.Exec(ctx, "UPDATE repos SET two_factor_code = ?, two_factor_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 { + if err := s.sendTwoFactorCode(repo.Email, repo.Handle, code); err != nil { return fmt.Errorf("sending email: %w", err) } diff --git a/server/handle_server_get_session.go b/server/handle_server_get_session.go index 60fd812..a5e41e6 100644 --- a/server/handle_server_get_session.go +++ b/server/handle_server_get_session.go @@ -23,7 +23,7 @@ func (s *Server) handleGetSession(e echo.Context) error { Did: repo.Repo.Did, Email: repo.Email, EmailConfirmed: repo.EmailConfirmedAt != nil, - EmailAuthFactor: repo.EmailAuthFactor, + EmailAuthFactor: repo.TwoFactorType != models.TwoFactorTypeNone, Active: repo.Active(), Status: repo.Status(), }) diff --git a/server/handle_server_update_email.go b/server/handle_server_update_email.go index b51def6..8e9927e 100644 --- a/server/handle_server_update_email.go +++ b/server/handle_server_update_email.go @@ -32,7 +32,7 @@ func (s *Server) handleServerUpdateEmail(e echo.Context) error { // To disable email auth factor a token is required. // To enable email auth factor a token is not required. // If updating an email address, a token will be sent anyway - if urepo.EmailAuthFactor && req.EmailAuthFactor == false && req.Token == "" { + if urepo.TwoFactorType != models.TwoFactorTypeNone && req.EmailAuthFactor == false && req.Token == "" { return helpers.InvalidTokenError(e) } @@ -50,7 +50,12 @@ func (s *Server) handleServerUpdateEmail(e echo.Context) error { } } - query := "UPDATE repos SET email_update_code = NULL, email_update_code_expires_at = NULL, email_auth_factor = ?, email = ?" + twoFactorType := models.TwoFactorTypeNone + if req.EmailAuthFactor { + twoFactorType = models.TwoFactorTypeEmail + } + + query := "UPDATE repos SET email_update_code = NULL, email_update_code_expires_at = NULL, two_factor_type = ?, email = ?" if urepo.Email != req.Email { query += ",email_confirmed_at = NULL" @@ -58,7 +63,7 @@ func (s *Server) handleServerUpdateEmail(e echo.Context) error { query += " WHERE did = ?" - if err := s.db.Exec(ctx, query, nil, req.EmailAuthFactor, req.Email, urepo.Repo.Did).Error; err != nil { + if err := s.db.Exec(ctx, query, nil, twoFactorType, req.Email, urepo.Repo.Did).Error; err != nil { s.logger.Error("error updating repo", "error", err) return helpers.ServerError(e, nil) } diff --git a/server/mail.go b/server/mail.go index c7cf4c0..759625c 100644 --- a/server/mail.go +++ b/server/mail.go @@ -97,7 +97,7 @@ func (s *Server) sendEmailVerification(email, handle, code string) error { return nil } -func (s *Server) sendAuthCode(email, handle, code string) error { +func (s *Server) sendTwoFactorCode(email, handle, code string) error { if s.mail == nil { return nil } -- 2.51.0