Ищу какой-то вклад в проблему, с которой я столкнулся, и являюсь новичком в разработке.
Я использую 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 идеи о том, как я могу решить это?