@@ -18,6 +18,7 @@ import (
1818 "github.com/gofrs/uuid"
1919 "github.com/pquerna/otp"
2020 "github.com/pquerna/otp/totp"
21+ "github.com/sirupsen/logrus"
2122 "github.com/supabase/auth/internal/api/apierrors"
2223 "github.com/supabase/auth/internal/api/sms_provider"
2324 "github.com/supabase/auth/internal/conf"
@@ -686,7 +687,7 @@ func (a *API) verifyTOTPFactor(w http.ResponseWriter, r *http.Request, params *V
686687 }
687688
688689 var token * AccessTokenResponse
689-
690+ verified := false
690691 err = db .Transaction (func (tx * storage.Connection ) error {
691692 var terr error
692693 if terr = models .NewAuditLogEntry (config .AuditLog , r , tx , user , models .VerifyFactorAction , r .RemoteAddr , map [string ]interface {}{
@@ -703,6 +704,7 @@ func (a *API) verifyTOTPFactor(w http.ResponseWriter, r *http.Request, params *V
703704 if terr = factor .UpdateStatus (tx , models .FactorStateVerified ); terr != nil {
704705 return terr
705706 }
707+ verified = true
706708 }
707709 if shouldReEncrypt && config .Security .DBEncryption .Encrypt {
708710 es , terr := crypto .NewEncryptedString (factor .ID .String (), []byte (secret ), config .Security .DBEncryption .EncryptionKeyID , config .Security .DBEncryption .EncryptionKey )
@@ -738,6 +740,14 @@ func (a *API) verifyTOTPFactor(w http.ResponseWriter, r *http.Request, params *V
738740 return err
739741 }
740742
743+ // Send MFA factor enrolled notification email if enabled and the factor was just verified
744+ if verified && config .Mailer .Notifications .MFAFactorEnrolledEnabled && user .GetEmail () != "" {
745+ if err := a .sendMFAFactorEnrolledNotification (r , db , user , factor .FactorType ); err != nil {
746+ // Log the error but don't fail the verification
747+ logrus .WithError (err ).Warn ("Unable to send MFA factor enrolled notification email" )
748+ }
749+ }
750+
741751 metering .RecordLogin (metering .LoginTypeMFA , user .ID , & metering.LoginData {
742752 Provider : metering .ProviderMFATOTP ,
743753 })
@@ -828,7 +838,7 @@ func (a *API) verifyPhoneFactor(w http.ResponseWriter, r *http.Request, params *
828838 }
829839
830840 var token * AccessTokenResponse
831-
841+ verified := false
832842 err = db .Transaction (func (tx * storage.Connection ) error {
833843 var terr error
834844 if terr = models .NewAuditLogEntry (config .AuditLog , r , tx , user , models .VerifyFactorAction , r .RemoteAddr , map [string ]interface {}{
@@ -845,6 +855,7 @@ func (a *API) verifyPhoneFactor(w http.ResponseWriter, r *http.Request, params *
845855 if terr = factor .UpdateStatus (tx , models .FactorStateVerified ); terr != nil {
846856 return terr
847857 }
858+ verified = true
848859 }
849860 user , terr = models .FindUserByID (tx , user .ID )
850861 if terr != nil {
@@ -869,6 +880,14 @@ func (a *API) verifyPhoneFactor(w http.ResponseWriter, r *http.Request, params *
869880 return err
870881 }
871882
883+ // Send MFA factor enrolled notification email if enabled and the factor was just verified
884+ if verified && config .Mailer .Notifications .MFAFactorEnrolledEnabled && user .GetEmail () != "" {
885+ if err := a .sendMFAFactorEnrolledNotification (r , db , user , factor .FactorType ); err != nil {
886+ // Log the error but don't fail the verification
887+ logrus .WithError (err ).Warn ("Unable to send MFA factor enrolled notification email" )
888+ }
889+ }
890+
872891 metering .RecordLogin (metering .LoginTypeMFA , user .ID , & metering.LoginData {
873892 Provider : metering .ProviderMFAPhone ,
874893 })
@@ -935,6 +954,7 @@ func (a *API) verifyWebAuthnFactor(w http.ResponseWriter, r *http.Request, param
935954 return apierrors .NewInternalServerError ("Database error deleting challenge" ).WithInternalError (err )
936955 }
937956 var token * AccessTokenResponse
957+ verified := false
938958 err = db .Transaction (func (tx * storage.Connection ) error {
939959 var terr error
940960 if terr = models .NewAuditLogEntry (config .AuditLog , r , tx , user , models .VerifyFactorAction , r .RemoteAddr , map [string ]interface {}{
@@ -952,6 +972,7 @@ func (a *API) verifyWebAuthnFactor(w http.ResponseWriter, r *http.Request, param
952972 if terr = factor .SaveWebAuthnCredential (tx , credential ); terr != nil {
953973 return terr
954974 }
975+ verified = true
955976 }
956977
957978 if terr = factor .UpdateLastWebAuthnChallenge (tx , challenge , params .WebAuthn .Type , parsedResponse ); terr != nil {
@@ -979,6 +1000,14 @@ func (a *API) verifyWebAuthnFactor(w http.ResponseWriter, r *http.Request, param
9791000 return err
9801001 }
9811002
1003+ // Send MFA factor enrolled notification email if enabled and the factor was just verified
1004+ if verified && config .Mailer .Notifications .MFAFactorEnrolledEnabled && user .GetEmail () != "" {
1005+ if err := a .sendMFAFactorEnrolledNotification (r , db , user , factor .FactorType ); err != nil {
1006+ // Log the error but don't fail the verification
1007+ logrus .WithError (err ).Warn ("Unable to send MFA factor enrolled notification email" )
1008+ }
1009+ }
1010+
9821011 metering .RecordLogin (metering .LoginTypeMFA , user .ID , & metering.LoginData {
9831012 Provider : metering .ProviderMFAWebAuthn ,
9841013 })
@@ -1039,6 +1068,8 @@ func (a *API) UnenrollFactor(w http.ResponseWriter, r *http.Request) error {
10391068 return apierrors .NewUnprocessableEntityError (apierrors .ErrorCodeInsufficientAAL , "AAL2 required to unenroll verified factor" )
10401069 }
10411070
1071+ factorType := factor .FactorType
1072+
10421073 err = db .Transaction (func (tx * storage.Connection ) error {
10431074 var terr error
10441075 if terr := tx .Destroy (factor ); terr != nil {
@@ -1060,6 +1091,14 @@ func (a *API) UnenrollFactor(w http.ResponseWriter, r *http.Request) error {
10601091 return err
10611092 }
10621093
1094+ // Send MFA factor unenrolled notification email if enabled
1095+ if config .Mailer .Notifications .MFAFactorUnenrolledEnabled && user .GetEmail () != "" {
1096+ if err := a .sendMFAFactorUnenrolledNotification (r , db , user , factorType ); err != nil {
1097+ // Log the error but don't fail the unenrollment
1098+ logrus .WithError (err ).Warn ("Unable to send MFA factor unenrolled notification email" )
1099+ }
1100+ }
1101+
10631102 return sendJSON (w , http .StatusOK , & UnenrollFactorResponse {
10641103 ID : factor .ID ,
10651104 })
0 commit comments