Implement Email Verification In Auth Flow
This commit is contained in:
parent
5abc3d8221
commit
4085267499
@ -3,8 +3,12 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using qtc_api.Dtos.User;
|
||||
using qtc_api.Services.EmailService;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using ZstdSharp.Unsafe;
|
||||
|
||||
namespace qtc_api.Controllers
|
||||
{
|
||||
@ -13,17 +17,21 @@ namespace qtc_api.Controllers
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEmailService _emailService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly IHubContext<ChatHub> _chatGWContext;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
private readonly ServerConfig serverConfig;
|
||||
private readonly DataContext dataContext;
|
||||
|
||||
public AuthController(IUserService userService, ITokenService tokenService, IHubContext<ChatHub> chatGWContext, DataContext dataContext)
|
||||
public AuthController(IUserService userService, ITokenService tokenService, IHubContext<ChatHub> chatGWContext, DataContext dataContext, IConfiguration configuration, IEmailService emailService)
|
||||
{
|
||||
_userService = userService;
|
||||
_tokenService = tokenService;
|
||||
_chatGWContext = chatGWContext;
|
||||
_configuration = configuration;
|
||||
_emailService = emailService;
|
||||
|
||||
serverConfig = JsonSerializer.Deserialize<ServerConfig>(JsonDocument.Parse(System.IO.File.ReadAllText("./ServerConfig.json")));
|
||||
this.dataContext = dataContext;
|
||||
@ -36,8 +44,14 @@ namespace qtc_api.Controllers
|
||||
{
|
||||
var response = await _userService.AddUser(userDto);
|
||||
await _chatGWContext.Clients.All.SendAsync("RefreshUserLists");
|
||||
if(response.Success != false)
|
||||
if(response.Success != false && response.Data != null)
|
||||
{
|
||||
// send confirmation email (shouldn't do anything if email confirmation is disabled)
|
||||
var confirmationToken = _tokenService.GenerateEmailConfirmationToken(response.Data);
|
||||
var confirmationUrl = $"{Request.Scheme}://{Request.Host}/api/auth/verify-email?token={confirmationToken}";
|
||||
|
||||
await _emailService.SendConfirmationEmail(response.Data.Email, response.Data.Username, confirmationUrl);
|
||||
|
||||
return Ok(response);
|
||||
} else
|
||||
{
|
||||
@ -70,6 +84,15 @@ namespace qtc_api.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
if(!dbUser.Data.IsEmailVerified && _configuration.GetValue<bool>("EmailConfig:EmailConfirmationRequired"))
|
||||
{
|
||||
return Ok(new ServiceResponse<string>
|
||||
{
|
||||
Message = "You need to verify your email on this server. Check your inbox or spam. If you have not received an email, click 'Resend Verification Email'",
|
||||
Success = false
|
||||
});
|
||||
}
|
||||
|
||||
if (dbUser.Data.Id == serverConfig.AdminUserId && dbUser.Data.Role != "Admin")
|
||||
{
|
||||
dbUser.Data.Role = "Admin";
|
||||
@ -96,5 +119,48 @@ namespace qtc_api.Controllers
|
||||
var response = await _tokenService.ValidateRefreshToken(token);
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost("verify-email")]
|
||||
public async Task<ActionResult<string>> VerifyEmail(string token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
handler.InboundClaimTypeMap = new Dictionary<string, string>();
|
||||
|
||||
var jwt = handler.ReadJwtToken(token);
|
||||
|
||||
if (jwt != null)
|
||||
{
|
||||
var email = jwt.Claims.FirstOrDefault(e => e.Type == ClaimTypes.Email);
|
||||
var id = jwt.Claims.FirstOrDefault(e => e.Type == ClaimTypes.NameIdentifier);
|
||||
|
||||
if (email != null && id != null)
|
||||
{
|
||||
// get the user from id claim
|
||||
var user = await _userService.GetUserById(id.Value);
|
||||
if (user != null && user.Success && user.Data != null)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if(user.Data.Email == email.Value && jwt.ValidTo.ToUniversalTime() < now)
|
||||
{
|
||||
user.Data.IsEmailVerified = true;
|
||||
await dataContext.SaveChangesAsync();
|
||||
|
||||
return Ok("Email Verified! You may now login on the client.");
|
||||
}
|
||||
} else
|
||||
{
|
||||
return Ok("The User This Confirmation Link Is Associated With No Longer Exists.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok("Token Invalid. You may need to be sent another confirmation link.");
|
||||
} catch (SecurityTokenMalformedException)
|
||||
{
|
||||
return Ok("Token Invalid. You may need to be sent another confirmation link.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddDbContext<DataContext>();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSignalR();
|
||||
|
||||
builder.Services.AddStackExchangeRedisCache(options =>
|
||||
|
@ -37,7 +37,7 @@ namespace qtc_api.Services.EmailService
|
||||
string emailSubject = "QtC.NET Email Confirmation";
|
||||
|
||||
// build confirmation email body
|
||||
StringBuilder emailBody = new StringBuilder();
|
||||
StringBuilder emailBody = new();
|
||||
emailBody.AppendLine("Hello! This email was used to create an account on a QtC.NET Server.");
|
||||
emailBody.AppendLine();
|
||||
emailBody.AppendLine($"You can confirm your email by clicking here - {confirmUrl}");
|
||||
|
@ -3,6 +3,7 @@
|
||||
public interface ITokenService
|
||||
{
|
||||
public Task<ServiceResponse<string>> GenerateAccessTokenAndRefreshToken(User user, bool generateRefToken = true, bool remember = false);
|
||||
public ServiceResponse<string> GenerateEmailConfirmationToken(User user);
|
||||
public Task<ServiceResponse<bool>> ValidateAccessToken(string accessToken);
|
||||
public Task<ServiceResponse<string>> ValidateRefreshToken(string refreshToken);
|
||||
public ServiceResponse<TokenValidationParameters> GetValidationParams();
|
||||
|
@ -78,6 +78,40 @@ namespace qtc_api.Services.TokenService
|
||||
return serviceResponse;
|
||||
}
|
||||
|
||||
public ServiceResponse<string> GenerateEmailConfirmationToken(User user)
|
||||
{
|
||||
var serviceResponse = new ServiceResponse<string>();
|
||||
|
||||
// Generate JWT Access Token
|
||||
|
||||
List<Claim> claims = new List<Claim>()
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id),
|
||||
new Claim(ClaimTypes.Email, user.Email)
|
||||
};
|
||||
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Jwt:Key").Value ?? Environment.GetEnvironmentVariable("JWT_KEY")!));
|
||||
var issuer = _configuration["Jwt:Issuer"];
|
||||
var audience = _configuration["Jwt:Audience"];
|
||||
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: issuer,
|
||||
audience: audience,
|
||||
claims: claims,
|
||||
expires: DateTime.UtcNow.AddHours(24),
|
||||
signingCredentials: creds
|
||||
);
|
||||
|
||||
var jwt = new JwtSecurityTokenHandler().WriteToken(token);
|
||||
|
||||
serviceResponse.Success = true;
|
||||
serviceResponse.Data = jwt;
|
||||
|
||||
return serviceResponse;
|
||||
}
|
||||
|
||||
public async Task<ServiceResponse<string>> ValidateRefreshToken(string refreshToken)
|
||||
{
|
||||
var serviceResponse = new ServiceResponse<string>();
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace qtc_api.Services.UserService
|
||||
using qtc_api.Services.EmailService;
|
||||
|
||||
namespace qtc_api.Services.UserService
|
||||
{
|
||||
public class UserService : IUserService
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user