SendEmailLoginCode sends a 6-digit verification code. Always returns success (no email enumeration). Rate limit: 60-sec resend cooldown enforced atomically via the store — effective cap ≈ 60 sends/hour/email.
(ctx context.Context, req *connect.Request[v1pb.SendEmailLoginCodeRequest])
| 1626 | // (no email enumeration). Rate limit: 60-sec resend cooldown enforced atomically |
| 1627 | // via the store — effective cap ≈ 60 sends/hour/email. |
| 1628 | func (s *AuthService) SendEmailLoginCode(ctx context.Context, req *connect.Request[v1pb.SendEmailLoginCodeRequest]) (*connect.Response[emptypb.Empty], error) { |
| 1629 | email := strings.ToLower(strings.TrimSpace(req.Msg.Email)) |
| 1630 | if email == "" { |
| 1631 | return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("email is required")) |
| 1632 | } |
| 1633 | workspaceID, err := parseOptionalWorkspace(req.Msg.Workspace) |
| 1634 | if err != nil { |
| 1635 | return nil, connect.NewError(connect.CodeInvalidArgument, err) |
| 1636 | } |
| 1637 | |
| 1638 | // Gate on AllowEmailCodeSignin — no point emailing a code the workspace won't accept. |
| 1639 | // getAccountRestriction handles all cases (including empty workspace for brand-new SaaS |
| 1640 | // signup, where it resolves via EMAIL_CONFIG + SaaS override). |
| 1641 | restriction, err := getAccountRestriction(ctx, s.store, s.licenseService, s.profile.SaaS, workspaceID) |
| 1642 | if err != nil { |
| 1643 | return nil, err |
| 1644 | } |
| 1645 | if !restriction.AllowEmailCodeSignin { |
| 1646 | return nil, connect.NewError(connect.CodeFailedPrecondition, errors.Errorf("email code login is not enabled for this workspace")) |
| 1647 | } |
| 1648 | |
| 1649 | // Send synchronously so the caller learns about actionable failures (missing EMAIL |
| 1650 | // setting, SMTP unreachable, etc.). No enumeration risk here: LOGIN always attempts to |
| 1651 | // send regardless of whether the email exists (sign-up is handled on verify). |
| 1652 | if err := s.sendEmailVerificationCode( |
| 1653 | ctx, |
| 1654 | req.Msg.Workspace, |
| 1655 | email, |
| 1656 | storepb.EmailVerificationCodePurpose_LOGIN, |
| 1657 | "[Bytebase] Your sign-in code", |
| 1658 | "Hi,\n\nYour sign-in code is: %s\n\nThis code expires in %d minutes. If you didn't request this, you can safely ignore this email.\n\n— Bytebase", |
| 1659 | ); err != nil { |
| 1660 | return nil, connect.NewError(connect.CodeInternal, err) |
| 1661 | } |
| 1662 | |
| 1663 | return connect.NewResponse(&emptypb.Empty{}), nil |
| 1664 | } |
| 1665 | |
| 1666 | // resolvePreLoginEmailSetting returns the EMAIL setting to use for unauthenticated flows |
| 1667 | // (email-code sign-in, password reset). Resolution order: |
nothing calls this directly
no test coverage detected