187 lines
7.3 KiB
C#

using Azure;
using Microsoft.AspNetCore.Authorization;
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
{
[Route("api/auth")]
[ApiController]
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, 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;
}
[HttpPost("register")]
public async Task<ActionResult<ServiceResponse<User>>> Register(UserDto userDto)
{
if(userDto != null)
{
var response = await _userService.AddUser(userDto);
await _chatGWContext.Clients.All.SendAsync("RefreshUserLists");
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.Data}";
await _emailService.SendConfirmationEmail(response.Data.Email, response.Data.Username, confirmationUrl);
return Ok(response);
} else
{
return StatusCode(500, response.Message);
}
} else
{
return BadRequest();
}
}
[HttpPost("login")]
public async Task<ActionResult<ServiceResponse<string>>> Login(UserLoginDto request)
{
var dbUser = await _userService.GetUserByEmail(request.Email);
if (dbUser.Data == null)
{
return Ok(new ServiceResponse<string>
{
Message = "User not found.",
Success = false
});
} else if(!BCrypt.Net.BCrypt.Verify(request.Password, dbUser.Data.PasswordHash))
{
return Ok(new ServiceResponse<string>
{
Message = "Incorrect password.",
Success = false
});
}
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";
dataContext.SaveChanges();
}
if (dbUser.Data.Status == 1)
{
return Ok(new ServiceResponse<string>
{
Message = "User is already signed in.",
Success = false
});
}
var token = await _tokenService.GenerateAccessTokenAndRefreshToken(dbUser.Data, true, request.RememberMe);
return Ok(token);
}
[HttpPost("refresh")]
public async Task<ActionResult<ServiceResponse<string>>> RefreshLogin(string token)
{
var response = await _tokenService.ValidateRefreshToken(token);
return Ok(response);
}
[HttpPost("resend-email")]
public async Task<ActionResult<ServiceResponse<bool>>> ResendVerificationEmail(string email)
{
var user = await _userService.GetUserByEmail(email);
if (user != null && user.Success && user.Data != null)
{
var confirmationToken = _tokenService.GenerateEmailConfirmationToken(user.Data);
var confirmationUrl = $"{Request.Scheme}://{Request.Host}/api/auth/verify-email?token={confirmationToken.Data}";
await _emailService.SendConfirmationEmail(user.Data.Email, user.Data.Username, confirmationUrl);
return Ok(new ServiceResponse<bool> { Success = true, Data = true });
}
return Ok(new ServiceResponse<bool> { Success = false });
}
[HttpGet("verify-email")]
public async Task<ActionResult<string>> VerifyEmail(string token)
{
try
{
var handler = new JwtSecurityTokenHandler()
{
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 && now < jwt.ValidTo.ToUniversalTime())
{
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.");
}
}
}
}