(w http.ResponseWriter, r *http.Request, params *VerifyFactorParams)
| 727 | } |
| 728 | |
| 729 | func (a *API) verifyPhoneFactor(w http.ResponseWriter, r *http.Request, params *VerifyFactorParams) error { |
| 730 | ctx := r.Context() |
| 731 | config := a.config |
| 732 | user := getUser(ctx) |
| 733 | factor := getFactor(ctx) |
| 734 | db := a.db.WithContext(ctx) |
| 735 | currentIP := utilities.GetIPAddress(r) |
| 736 | |
| 737 | challenge, err := a.validateChallenge(r, db, factor, params.ChallengeID) |
| 738 | if err != nil { |
| 739 | return err |
| 740 | } |
| 741 | |
| 742 | if challenge.VerifiedAt != nil || challenge.IPAddress != currentIP { |
| 743 | return apierrors.NewUnprocessableEntityError(apierrors.ErrorCodeMFAIPAddressMismatch, "Challenge and verify IP addresses mismatch") |
| 744 | } |
| 745 | |
| 746 | if challenge.HasExpired(config.MFA.ChallengeExpiryDuration) { |
| 747 | if err := db.Destroy(challenge); err != nil { |
| 748 | return apierrors.NewInternalServerError("Database error deleting challenge").WithInternalError(err) |
| 749 | } |
| 750 | return apierrors.NewUnprocessableEntityError(apierrors.ErrorCodeMFAChallengeExpired, "MFA challenge %v has expired, verify against another challenge or create a new challenge.", challenge.ID) |
| 751 | } |
| 752 | var valid bool |
| 753 | var otpCode string |
| 754 | var shouldReEncrypt bool |
| 755 | if config.Sms.IsTwilioVerifyProvider() { |
| 756 | smsProvider, err := sms_provider.GetSmsProvider(*config) |
| 757 | if err != nil { |
| 758 | return apierrors.NewInternalServerError("Failed to get SMS provider").WithInternalError(err) |
| 759 | } |
| 760 | if err := smsProvider.VerifyOTP(factor.Phone.String(), params.Code); err != nil { |
| 761 | return apierrors.NewForbiddenError(apierrors.ErrorCodeOTPExpired, "Token has expired or is invalid").WithInternalError(err) |
| 762 | } |
| 763 | valid = true |
| 764 | } else { |
| 765 | otpCode, shouldReEncrypt, err = challenge.GetOtpCode(config.Security.DBEncryption.DecryptionKeys, config.Security.DBEncryption.Encrypt, config.Security.DBEncryption.EncryptionKeyID) |
| 766 | if err != nil { |
| 767 | return apierrors.NewInternalServerError("Database error verifying MFA TOTP secret").WithInternalError(err) |
| 768 | } |
| 769 | valid = subtle.ConstantTimeCompare([]byte(otpCode), []byte(params.Code)) == 1 |
| 770 | } |
| 771 | if config.Hook.MFAVerificationAttempt.Enabled { |
| 772 | input := v0hooks.NewMFAVerificationAttemptInput( |
| 773 | r, |
| 774 | user.ID, |
| 775 | factor.ID, |
| 776 | factor.FactorType, |
| 777 | valid, |
| 778 | ) |
| 779 | |
| 780 | output := v0hooks.MFAVerificationAttemptOutput{} |
| 781 | err := a.hooksMgr.InvokeHook(nil, r, input, &output) |
| 782 | if err != nil { |
| 783 | return err |
| 784 | } |
| 785 | |
| 786 | if output.Decision == v0hooks.HookRejection { |
no test coverage detected