Walidacja danych logowania - gdzie?

0

Gdzie powinno się umieszczać logikę walidacji danych logowania użytkownika: w walidatorach (korzystam z FluentValidation) czy w serwisach, z których korzystają kontrolery? Jeśli nie da się zalogować użytkownika, to powinienem zwrócić 401, więc pierwsza opcja chyba odpada. Z drugiej strony, mam wrażenie, że nie po to piszę własne walidatory, żeby potem walidację przeprowadzać w serwisach.

1

Coś takiego jak wymagane pola czy minimalna długość loginu możesz sprawdzić walidatorami w rodzaju FluentValidation. Natomiast do wywołanie serwisu logującego, providera SSO czy bazy danych, to już musisz jakiś swój "serwis" napisać.

2

Moje podejście jest takie, że używam fluent validation do sprawdzenia czy nie jest puste hasło bądź user. (korzystam z ModelState.IsValid). Jeśli to przechodzi i trafia do serwisu to z serwisu zwracam obiekt, który zawiera pola {bool Success, string Message}. I jeśli logowanie się nie powiedzie to mam info w message.

0

@szydlak: Właściwie, to mam coś podobnego u siebie.
@somekind: A co jeśli miałbym formularz rejestracji i chciałbym sprawdzić, czy użytkownik nie wybrał jakiegoś zajętego nicka? Tego typu walidacja powinna być już w walidatorze czy gdzieś "dalej"? Przy użyciu Fluent Validation sprawdza się to dość przyjemnie. Zakładam, że logikę sprawdzającą dostępność nazwy użytkownika mam wydzieloną.

0

Sprawdzenie zajętości nicka wymaga sprawdzenia w jakimś źródle tych nicków (bazie, serwisie, itp.) To nie jest zadanie dla walidatora inputu.

0

W sumie to racja, walidatory nie powinny wiedzieć o bazie danych. Dotychczas miałem taki walidator:

public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        private readonly UserService userService;

        public RegisterModelValidator(UserService userService)
        {
            this.userService = userService;

            RuleFor(m => m.UserName)
                .NotEmpty()
                .WithMessage("Nazwa użytkownika nie może być pusta.")
                .MinimumLength(2)
                .WithMessage("Nazwa użytkownika musi mieć co najmniej 2 znaki.")
                .MustAsync(async (userName, cancellationToken) => await userService.IsUserNameAvailableAsync(userName, cancellationToken))
                .WithMessage("Użytkownik o podanej nazwie już istnieje.");

            RuleFor(m => m.Email)
                .NotEmpty()
                .WithMessage("Adres email nie może być pusty.")
                .EmailAddress()
                .WithMessage("Adres email musi być poprawny.")
                .MustAsync(async (email, cancellationToken) => await userService.IsEmailAvailableAsync(email, cancellationToken))
                .WithMessage("Użytkownik o podanym adresie email już istnieje.");

            RuleFor(m => m.Password)
                .NotEmpty()
                .WithMessage("Hasło nie może być puste.")
                .Matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{6,}")
                .WithMessage("Hasło nie spełnia wymagań.");

            RuleFor(m => m.PasswordConfirmation)
                .NotEmpty()
                .WithMessage("Hasło musi zostać potwierdzone.")
                .Equal(m => m.Password)
                .WithMessage("Hasła nie zgadzają się.");
        }
    }

Ale tworzenie takich zależności raczej nie pachnie za dobrze...

0

Czyli tak to ma wyglądać?

public async Task<Result> RegisterAsync(RegisterModel model)
        {
            var validationResult = await ValidateRegisterModel(model);

            if (!validationResult.ActionSucceeded)
            {
                return validationResult;
            }

            var user = new User
            {
                UserName = model.UserName,
                Email = model.Email,
            };

            var creationResult = await userManager.CreateAsync(user, model.Password);

            if (!creationResult.Succeeded)
            {
                return new Result(new Error(ErrorName.NotSpecified, "Rejestracja nie powiodła się z nieznanych przyczyn."));
            }

            // TODO: Send email

            return new Result(null);
        }

        private async Task<Result> ValidateRegisterModel(RegisterModel model)
        {
            var isUserNameAvailableTask = userService.IsUserNameAvailableAsync(model.UserName);
            var isEmailAvailableTask = userService.IsEmailAvailableAsync(model.Email);

            var isUserNameAvailable = await isUserNameAvailableTask;
            var isEmailAvailable = await isEmailAvailableTask;

            var errors = new Dictionary<string, string>();

            if (!isEmailAvailable)
            {
                errors[nameof(model.UserName)] = "Użytkownik o podanej nazwie już istnieje.";
            }

            if (!isEmailAvailable)
            {
                errors[nameof(model.Email)] = "Użytkownik o podanym adresie email już istnieje.";
            }

            if (errors.Count > 0)
            {
                return new Result(new Error(ErrorName.NotValid, errors));
            }

            return new Result(null);
        }

EDIT:
Dlaczego, gdy kopiuję kod z VS, robią się te dziwne wcięcia?

1 użytkowników online, w tym zalogowanych: 0, gości: 1