feat: sync login email verification support

This commit is contained in:
A 2026-07-08 17:09:44 +08:00
parent e0cabb4930
commit 9a501f900a
39 changed files with 2198 additions and 117 deletions

View file

@ -5,14 +5,19 @@ import (
"database/sql"
"errors"
"fmt"
"strings"
"time"
"github.com/jackc/pgerrcode"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"telesrv/internal/domain"
"telesrv/internal/store/postgres/sqlcgen"
)
const accountPasswordsLoginEmailUniqueIdx = "account_passwords_login_email_lower_unique_idx"
// PasswordStore 用 PostgreSQL 实现 store.PasswordStore。
type PasswordStore struct {
db sqlcgen.DBTX
@ -70,7 +75,29 @@ WHERE user_id = $1`, userID)
return settings, true, nil
}
func (s *PasswordStore) LoginEmailOwner(ctx context.Context, email string) (int64, bool, error) {
email = normalizeStoredLoginEmail(email)
if email == "" {
return 0, false, nil
}
row := s.db.QueryRow(ctx, `
SELECT user_id
FROM account_passwords
WHERE login_email <> '' AND lower(login_email) = $1
LIMIT 1`, email)
var userID int64
if err := row.Scan(&userID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return 0, false, nil
}
return 0, false, fmt.Errorf("get login email owner: %w", err)
}
return userID, true, nil
}
func (s *PasswordStore) Save(ctx context.Context, userID int64, settings domain.PasswordSettings) error {
settings.LoginEmail = normalizeStoredLoginEmail(settings.LoginEmail)
settings.LoginEmailPattern = domain.MaskEmail(settings.LoginEmail)
algo := settings.NewAlgo
if settings.CurrentAlgo != nil {
algo = *settings.CurrentAlgo
@ -117,11 +144,25 @@ ON CONFLICT (user_id) DO UPDATE SET
settings.RecoveryEmail, settings.RecoveryCode, recoveryExpires, settings.LoginEmail,
)
if err != nil {
if isAccountPasswordLoginEmailUnique(err) {
return domain.ErrEmailOccupied
}
return fmt.Errorf("upsert account password: %w", err)
}
return nil
}
func normalizeStoredLoginEmail(email string) string {
return strings.ToLower(strings.TrimSpace(email))
}
func isAccountPasswordLoginEmailUnique(err error) bool {
var pgErr *pgconn.PgError
return errors.As(err, &pgErr) &&
pgErr.Code == pgerrcode.UniqueViolation &&
pgErr.ConstraintName == accountPasswordsLoginEmailUniqueIdx
}
func nonNilBytea(in []byte) []byte {
if in != nil {
return in