Идентификация Подтверждение электронной почты проблема - PullRequest
0 голосов
/ 30 апреля 2020

Ищу какой-то вклад в проблему, с которой я столкнулся, и являюсь новичком в разработке.

Я использую Microsoft Identity для управления регистрациями пользователей и входами в систему. При попытке создать в этом разделе подтверждающую электронную почту я сталкиваюсь с сообщением об ошибке:

Аргумент 1: невозможно преобразовать строку 'в строку' в Project.API.Models.User '

Это бросается в контроллере. Вот соответствующие файлы:

IAuthRepository:

using System.Threading.Tasks;
using Outmatch.API.Models;

namespace Outmatch.API.Data
{
    public interface IAuthRepository
    {
        // Register the user
        // Return a task of User.  We will call this method Register. The method is passed a User object and string of Password
        Task<User> Register(User user, string password);

        // Login the user to the API
        // Return a User.  We will call this method Login, pass a string of username, and a string of Password 
        Task<User> Login(string username, string password);

        // Check if the user already exists
        // Return a boolean task, call the method UserExists.  Check against a string of username to see if that username already exists.
        Task<bool> UserExists(string username);

        // Check
        Task<User> ConfirmEmailAsync(string userId, string token);
    }
}

AuthRepository:

using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Outmatch.API.Models;

namespace Outmatch.API.Data
{
    public class AuthRepository : IAuthRepository
    {
        // Inject the DataContext so the AuthRepository can query the database
        private readonly DataContext _context;
        private readonly UserManager<User> _userManager;
        private readonly IConfiguration _configuration;
        private readonly IMailRepository _mailRepository;
        public AuthRepository(DataContext context, UserManager<User> userManager, IConfiguration configuration, IMailRepository mailRepository)
        {
            _mailRepository = mailRepository;
            _configuration = configuration;
            _userManager = userManager;
            _context = context;
        }

        // Login the user.  Return a task of type User. Takes a string of username and a string of password
        // Use the username to identify the user in the db.  Take the password and compare it with the hashed password of the user to autenticate
        public async Task<User> Login(string username, string password)
        {
            // Created a variable to store the user in.  _context.Users signifies the Users table in the db
            // This will look in the DB for a user that matches the entered username and return it, or return null if it doesnt exist
            var user = await _context.Users.FirstOrDefaultAsync(x => x.UserName == username);

            // If the user is returned null from the DB (IE, username does not exist), then return null.  This would return a 401 not authorized in the browser
            if (user == null)
                return null;

            // Return true or false depending on weather the password matches or doesnt match what the user supplied when loggin in (in a hashed format)
            // If the password returns null, the we will return null and a 401 not authorized in the browser

            // Added Microsoft Identity, Signin manager will now take care of the below  
            // if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
            //     return null;

            // If the comparison of the verifyPasswordHash method returns true, then return the user.
            return user;
        }

        private bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
        {
            // Use the password salt as a key to hash and salt the password of the user logging in, so it can be compared with the password in the DB. 
            using (var hmac = new System.Security.Cryptography.HMACSHA512(passwordSalt))
            {
                // Compute the hash from the password, using the key being passed.  Comupted has will be the same as the Register method hash 
                var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));

                // Loop through the hashed password and compare it to the eash element in the array to ensure the has matches what is stores in the DB
                for (int i = 0; i < computedHash.Length; i++)
                {
                    if (computedHash[i] != passwordHash[i])
                        return false;
                }
            }
            // If each element of the password hash array matches, return true
            return true;
        }

        // Takes the user model (entity) and their chosen password.  
        public async Task<User> Register(User user, string password)
        {
            //Turn the password which is in plain text and store is as a salted hash
            byte[] passwordHash, passwordSalt;
            // We want to pass the passwordHash and passwordSalt as a referece, and not as a value.  So this will be done using thr out keyword
            CreatePasswordHash(password, out passwordHash, out passwordSalt);

            // Forward the new user to the database to be stored.
            await _context.Users.AddAsync(user);
            await _context.SaveChangesAsync();

            // Generate a token to be used for the user to confirm their email. 
            var confirmEmailToken = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            var encodedEmailToken = Encoding.UTF8.GetBytes(confirmEmailToken);
            var validEmailToken = WebEncoders.Base64UrlEncode(encodedEmailToken);

            string url = $"{_configuration["AppUrl"]}/api/auth/confirmemail?userId={user.Id}&token={validEmailToken}";

            await _mailRepository.SendEmailAsync(user.Email, "Confirm Your Email", "<h1><Welcome to Outmatched.</h1>" + 
                $"<p>Please confirm your email by <a herf='{url}'> clicking here</a></p>");

            return user;
        }

        private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
        {
            // Hash the password using SHA512
            using (var hmac = new System.Security.Cryptography.HMACSHA512())
            {
                // Set the password salt to a randomly generated key
                passwordSalt = hmac.Key;
                // Compute the hash
                passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
                // Once complete, the values are stores in the byte[] array variabled just a few lines up
            }

        }

        //  Check to see if the username exists in the databse. 
        public async Task<bool> UserExists(string username)
        {
            if (await _context.Users.AnyAsync(x => x.UserName == username))
                return true;
            return false;
        }

        // Confirm a users email address after it is registered. 
        public async Task<User> ConfirmEmailAsync(string userId, string token)
        {
            throw new NotImplementedException();
        }
    }
}

AuthController:

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Outmatch.API.Data;
using Outmatch.API.Dtos;
using Outmatch.API.Models;

namespace Outmatch.API.Controllers
{
    // Route will be api/auth (http://localhost:5000/api/auth)
    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        // Inject the auth repository and the programs configuration into the controller.
        private readonly IConfiguration _config;
        private readonly IMapper _mapper;
        private readonly SignInManager<User> _signInManager;
        private readonly UserManager<User> _userManager;
        private readonly IClientRepository _repo;
        private readonly IMailRepository _MailRepository;
        public AuthController(IConfiguration config, IMapper mapper, UserManager<User> userManager, SignInManager<User> signInManager, IClientRepository repo, IMailRepository MailRepository)
        {
            _MailRepository = MailRepository;
            _repo = repo;
            _userManager = userManager;
            _signInManager = signInManager;
            _mapper = mapper;
            _config = config;
        }

        // Create a new HTTP Post method (http://localhost:5000/api/auth/register) to login the user.  JSON Serialized Object will be passed
        // from the user when they enter it to sign in. Call the Username from the UserForRegisterDto class
        [Authorize(Policy = "RequireGlobalAdminRole")]
        [HttpPost("register")]
        public async Task<IActionResult> Register(UserForRegisterDto userForRegisterDto)
        {
            // Check if user already exists
            var newUser = await _userManager.FindByNameAsync(userForRegisterDto.username);

            if (newUser != null)
                return BadRequest("Username already exists");

            // If the user does not already exist, create the user and use AutoMapper to map the details to the database
            var userToCreate = _mapper.Map<User>(userForRegisterDto);

            var result = await _userManager.CreateAsync(userToCreate, userForRegisterDto.Password);

            var userToReturn = _mapper.Map<UserForRegisterDto>(userToCreate);

            if (result.Succeeded)
            {
                // Add client to organization
                await addClientToOrg(userToCreate.Id, userForRegisterDto.OrgId);

                // Send back a location header with the request, and the ID of the user. 
                return CreatedAtRoute("GetUser", new { controller = "Users", Id = userToCreate.Id }, userToReturn);
            }

            return BadRequest(result.Errors);
        }

        // Assign a client to an organization 
        [HttpPost("{userId}/clienttoorg/{organizationId}")]
        public async Task<IActionResult> addClientToOrg(int userId, int organizationId)
        {
            // Check if the user assigning the client to the org has the authoirization to do so
            var userRole = User.FindFirst(ClaimTypes.Role).ToString();

            if (userRole != "http://schemas.microsoft.com/ws/2008/06/identity/claims/role: GlobalAdmin")
                return Unauthorized();

            var association = await _repo.GetUserOrg(userId, organizationId);

            // Check if the user is already assigned to the organization in the database
            if (association != null)
                return BadRequest("This client is already associated with this organization");

            // checked if the organization exists or not
            if (await _repo.GetUser(organizationId) == null)
                return BadRequest("The selected organization the user is being assigned to does not exist");

            // Assign the assiciiation to an OrgToClient object
            association = new OrgToClients
            {
                UserId = userId,
                OrganizationId = organizationId
            };

            // Pass the client to the organization 
            _repo.Add<OrgToClients>(association);

            // Save the information to the OrgToClient tabe
            if (await _repo.SaveAll())
                return Ok();

            // If unable to save to the table, pass an error to the user
            return BadRequest("Failed to add user to an organization");
        }

        // Create a method to allow users to login to the webAPI by returning a token to the users once logged in.
        // Route will be http://localhost:5000/api/auth/login
        [AllowAnonymous]
        [HttpPost("login")]
        public async Task<IActionResult> Login(UserForLoginDto userForLoginDto)
        {
            // Check that the user trying to login exists
            var user = await _userManager.FindByNameAsync(userForLoginDto.Username);

            if (user == null)
                return Unauthorized();

            var result = await _signInManager.CheckPasswordSignInAsync(user, userForLoginDto.Password, false);

            // Check to see if there is anything inside the user from Repo.  If there  is, a user object is present. If not, the username doesnt exist.
            if (result.Succeeded)
            {
                var appUser = _mapper.Map<ClientForListDto>(user);
                var currentDate = DateTime.UtcNow;

                if (currentDate <= user.EndDate)
                {
                    // await _MailRepository.SendEmailAsync(user.UserName, "New Login", "<h1>You have successfully logged in to you Instacom account</h1> <p>New login to your account at " + DateTime.Now + "</p>");
                    // Return the token to the client as an object
                    return Ok(new
                    {
                        token = GenerateJwtToken(user).Result,
                        user = appUser
                    });
                }
            }
            return Unauthorized();
        }

        private async Task<string> GenerateJwtToken(User user)
        {
            // Build a token that will be returned to the user when they login. Contains users ID and their username. 
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.UserName),
                // new Claim(ClaimTypes.Role, userFromRepo.AccessLevel)   - THIS IS A ROLE BASED KEY FOR USER ACCESS. NOT USED AND LOOKING FOR AN ALTERNATIVE
            };

            // Get a list of roles the user is in

            var roles = await _userManager.GetRolesAsync(user);

            foreach (var role in roles)
            {
                claims.Add(new Claim(ClaimTypes.Role, role));
            }

            // Create a secret key to sign the token. This key is hashed and not readable in the token.  
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.GetSection("AppSettings:Token").Value));

            // Generate signing credentials
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

            // Create a security token descriptor to contain the claims, exp date of the token, and signing credentials for the JWT token
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),
                Expires = DateTime.Now.AddDays(1),
                SigningCredentials = creds
            };

            // Generate Token Handler
            var tokenHandler = new JwtSecurityTokenHandler();

            // Create a token and pass in the token descriptor
            var token = tokenHandler.CreateToken(tokenDescriptor);

            return tokenHandler.WriteToken(token);
        }
        [HttpGet("confirmemail")]
        public async Task<IActionResult> ConfirmEmail(string userId, string token)
        {
            if (string.IsNullOrWhiteSpace(userId) || string.IsNullOrWhiteSpace(token))
                return NotFound();

            var result = await _userManager.ConfirmEmailAsync(userId, token);
        }
    }
}

Модель пользователя

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;

namespace Outmatch.API.Models
{
    // List of properties for the User (Client) table in the db
    public class User : IdentityUser<int>
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime ActiveDate { get; set; }
        public DateTime EndDate { get; set; }

        // User Roles Management
        public virtual ICollection<UserRole> UserRoles { get; set; }

        // Organization to Client table ties
        public ICollection<OrgToClients> OrganizationId { get; set; }
    }
}

Ошибка в AuthController появляется в 4-й последней строке под userId:

var result = await _userManager.ConfirmEmailAsyn c (userId, token);

Any идеи о том, как я могу решить это?

enter image description here

1 Ответ

1 голос
/ 30 апреля 2020

Ошибка, поскольку userManager.ConfirmEmailAsync() принимает тип Outmatch.API.Models.User в качестве первого аргумента. Поэтому предоставление userId строки не будет работать. Вы должны получить объект пользователя из метода userManager.FindByIdAsync().

var user = await _userManager.FindByIdAsync(userId);
await _userManager.ConfirmEmailAsync(user, token);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...