(r *http.Request, tx *storage.Connection, u *models.User, flowType models.FlowType)
| 477 | } |
| 478 | |
| 479 | func (a *API) sendMagicLink(r *http.Request, tx *storage.Connection, u *models.User, flowType models.FlowType) error { |
| 480 | var err error |
| 481 | config := a.config |
| 482 | otpLength := config.Mailer.OtpLength |
| 483 | |
| 484 | // since Magic Link is just a recovery with a different template and behaviour |
| 485 | // around new users we will reuse the recovery db timer to prevent potential abuse |
| 486 | if err := validateSentWithinFrequencyLimit(u.RecoverySentAt, config.SMTP.MaxFrequency); err != nil { |
| 487 | return err |
| 488 | } |
| 489 | |
| 490 | oldToken := u.RecoveryToken |
| 491 | otp := crypto.GenerateOtp(otpLength) |
| 492 | |
| 493 | token := crypto.GenerateTokenHash(u.GetEmail(), otp) |
| 494 | u.RecoveryToken = addFlowPrefixToToken(token, flowType) |
| 495 | |
| 496 | now := time.Now() |
| 497 | if err = a.sendEmail(r, tx, u, sendEmailParams{ |
| 498 | emailActionType: mail.MagicLinkVerification, |
| 499 | otp: otp, |
| 500 | tokenHashWithPrefix: u.RecoveryToken, |
| 501 | }); err != nil { |
| 502 | u.RecoveryToken = oldToken |
| 503 | if errors.Is(err, EmailRateLimitExceeded) { |
| 504 | return apierrors.NewTooManyRequestsError(apierrors.ErrorCodeOverEmailSendRateLimit, "%s", EmailRateLimitExceeded.Error()) |
| 505 | } else if herr, ok := err.(*HTTPError); ok { |
| 506 | return herr |
| 507 | } |
| 508 | return apierrors.NewInternalServerError("Error sending magic link email").WithInternalError(err) |
| 509 | } |
| 510 | u.RecoverySentAt = &now |
| 511 | if err := tx.UpdateOnly(u, "recovery_token", "recovery_sent_at"); err != nil { |
| 512 | return apierrors.NewInternalServerError("Error sending magic link email").WithInternalError(errors.Wrap(err, "Database error updating user for recovery")) |
| 513 | } |
| 514 | |
| 515 | if err := models.CreateOneTimeToken(tx, u.ID, u.GetEmail(), u.RecoveryToken, models.RecoveryToken); err != nil { |
| 516 | return apierrors.NewInternalServerError("Error sending magic link email").WithInternalError(errors.Wrap(err, "Database error creating recovery token")) |
| 517 | } |
| 518 | |
| 519 | return nil |
| 520 | } |
| 521 | |
| 522 | // sendEmailChange sends out an email change token to the new email. |
| 523 | func (a *API) sendEmailChange(r *http.Request, tx *storage.Connection, u *models.User, email string, flowType models.FlowType) error { |
no test coverage detected