Implement Email Verification In Auth Flow
This commit is contained in:
parent
437698a1f3
commit
39a1373678
@ -3,8 +3,12 @@ using Microsoft.AspNetCore.Http;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
using qtc_api.Dtos.User;
|
using qtc_api.Dtos.User;
|
||||||
|
using qtc_api.Services.EmailService;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Security.Claims;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using ZstdSharp.Unsafe;
|
||||||
|
|
||||||
namespace qtc_api.Controllers
|
namespace qtc_api.Controllers
|
||||||
{
|
{
|
||||||
@ -13,17 +17,21 @@ namespace qtc_api.Controllers
|
|||||||
public class AuthController : ControllerBase
|
public class AuthController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
|
private readonly IEmailService _emailService;
|
||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
private readonly IHubContext<ChatHub> _chatGWContext;
|
private readonly IHubContext<ChatHub> _chatGWContext;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
private readonly ServerConfig serverConfig;
|
private readonly ServerConfig serverConfig;
|
||||||
private readonly DataContext dataContext;
|
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;
|
_userService = userService;
|
||||||
_tokenService = tokenService;
|
_tokenService = tokenService;
|
||||||
_chatGWContext = chatGWContext;
|
_chatGWContext = chatGWContext;
|
||||||
|
_configuration = configuration;
|
||||||
|
_emailService = emailService;
|
||||||
|
|
||||||
serverConfig = JsonSerializer.Deserialize<ServerConfig>(JsonDocument.Parse(System.IO.File.ReadAllText("./ServerConfig.json")));
|
serverConfig = JsonSerializer.Deserialize<ServerConfig>(JsonDocument.Parse(System.IO.File.ReadAllText("./ServerConfig.json")));
|
||||||
this.dataContext = dataContext;
|
this.dataContext = dataContext;
|
||||||
@ -36,8 +44,14 @@ namespace qtc_api.Controllers
|
|||||||
{
|
{
|
||||||
var response = await _userService.AddUser(userDto);
|
var response = await _userService.AddUser(userDto);
|
||||||
await _chatGWContext.Clients.All.SendAsync("RefreshUserLists");
|
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);
|
return Ok(response);
|
||||||
} else
|
} 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")
|
if (dbUser.Data.Id == serverConfig.AdminUserId && dbUser.Data.Role != "Admin")
|
||||||
{
|
{
|
||||||
dbUser.Data.Role = "Admin";
|
dbUser.Data.Role = "Admin";
|
||||||
@ -96,5 +119,48 @@ namespace qtc_api.Controllers
|
|||||||
var response = await _tokenService.ValidateRefreshToken(token);
|
var response = await _tokenService.ValidateRefreshToken(token);
|
||||||
return Ok(response);
|
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();
|
builder.Services.AddControllers();
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
builder.Services.AddDbContext<DataContext>();
|
builder.Services.AddDbContext<DataContext>();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
|
||||||
builder.Services.AddSignalR();
|
builder.Services.AddSignalR();
|
||||||
|
|
||||||
builder.Services.AddStackExchangeRedisCache(options =>
|
builder.Services.AddStackExchangeRedisCache(options =>
|
||||||
|
@ -37,7 +37,7 @@ namespace qtc_api.Services.EmailService
|
|||||||
string emailSubject = "QtC.NET Email Confirmation";
|
string emailSubject = "QtC.NET Email Confirmation";
|
||||||
|
|
||||||
// build confirmation email body
|
// 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("Hello! This email was used to create an account on a QtC.NET Server.");
|
||||||
emailBody.AppendLine();
|
emailBody.AppendLine();
|
||||||
emailBody.AppendLine($"You can confirm your email by clicking here - {confirmUrl}");
|
emailBody.AppendLine($"You can confirm your email by clicking here - {confirmUrl}");
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
public interface ITokenService
|
public interface ITokenService
|
||||||
{
|
{
|
||||||
public Task<ServiceResponse<string>> GenerateAccessTokenAndRefreshToken(User user, bool generateRefToken = true, bool remember = false);
|
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<bool>> ValidateAccessToken(string accessToken);
|
||||||
public Task<ServiceResponse<string>> ValidateRefreshToken(string refreshToken);
|
public Task<ServiceResponse<string>> ValidateRefreshToken(string refreshToken);
|
||||||
public ServiceResponse<TokenValidationParameters> GetValidationParams();
|
public ServiceResponse<TokenValidationParameters> GetValidationParams();
|
||||||
|
@ -78,6 +78,40 @@ namespace qtc_api.Services.TokenService
|
|||||||
return serviceResponse;
|
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)
|
public async Task<ServiceResponse<string>> ValidateRefreshToken(string refreshToken)
|
||||||
{
|
{
|
||||||
var serviceResponse = new ServiceResponse<string>();
|
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
|
public class UserService : IUserService
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user