2022-02-23 17:06:44 +00:00
|
|
|
using System.ComponentModel.DataAnnotations;
|
2022-02-21 22:35:06 +00:00
|
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
|
|
using System.Security.Claims;
|
|
|
|
using System.Text;
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
using VoidCat.Model;
|
|
|
|
using VoidCat.Services.Abstractions;
|
|
|
|
|
|
|
|
namespace VoidCat.Controllers;
|
|
|
|
|
|
|
|
[Route("auth")]
|
|
|
|
public class AuthController : Controller
|
|
|
|
{
|
|
|
|
private readonly IUserManager _manager;
|
|
|
|
private readonly VoidSettings _settings;
|
2022-03-11 15:59:08 +00:00
|
|
|
private readonly ICaptchaVerifier _captchaVerifier;
|
2022-07-25 17:59:32 +00:00
|
|
|
private readonly IApiKeyStore _apiKeyStore;
|
|
|
|
private readonly IUserStore _userStore;
|
2022-02-21 22:35:06 +00:00
|
|
|
|
2022-07-25 17:59:32 +00:00
|
|
|
public AuthController(IUserManager userStore, VoidSettings settings, ICaptchaVerifier captchaVerifier, IApiKeyStore apiKeyStore,
|
|
|
|
IUserStore userStore1)
|
2022-02-21 22:35:06 +00:00
|
|
|
{
|
|
|
|
_manager = userStore;
|
|
|
|
_settings = settings;
|
2022-03-11 15:59:08 +00:00
|
|
|
_captchaVerifier = captchaVerifier;
|
2022-07-25 17:59:32 +00:00
|
|
|
_apiKeyStore = apiKeyStore;
|
|
|
|
_userStore = userStore1;
|
2022-02-21 22:35:06 +00:00
|
|
|
}
|
|
|
|
|
2022-03-08 13:47:42 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Login to a user account
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="req"></param>
|
|
|
|
/// <returns></returns>
|
2022-02-21 22:35:06 +00:00
|
|
|
[HttpPost]
|
|
|
|
[Route("login")]
|
|
|
|
public async Task<LoginResponse> Login([FromBody] LoginRequest req)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2022-02-23 17:06:44 +00:00
|
|
|
if (!TryValidateModel(req))
|
|
|
|
{
|
|
|
|
var error = ControllerContext.ModelState.FirstOrDefault().Value?.Errors.FirstOrDefault()?.ErrorMessage;
|
|
|
|
return new(null, error);
|
|
|
|
}
|
2022-07-25 17:59:32 +00:00
|
|
|
|
2022-03-11 15:59:08 +00:00
|
|
|
// check captcha
|
|
|
|
if (!await _captchaVerifier.Verify(req.Captcha))
|
|
|
|
{
|
|
|
|
return new(null, "Captcha verification failed");
|
|
|
|
}
|
2022-07-25 17:59:32 +00:00
|
|
|
|
2022-02-21 22:35:06 +00:00
|
|
|
var user = await _manager.Login(req.Username, req.Password);
|
2022-07-25 19:05:31 +00:00
|
|
|
var token = CreateToken(user, DateTime.UtcNow.AddHours(12));
|
2022-02-21 22:35:06 +00:00
|
|
|
var tokenWriter = new JwtSecurityTokenHandler();
|
2022-02-24 23:05:33 +00:00
|
|
|
return new(tokenWriter.WriteToken(token), Profile: user.ToPublic());
|
2022-02-21 22:35:06 +00:00
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
return new(null, ex.Message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-08 13:47:42 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Register a new account
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="req"></param>
|
|
|
|
/// <returns></returns>
|
2022-02-21 22:35:06 +00:00
|
|
|
[HttpPost]
|
|
|
|
[Route("register")]
|
|
|
|
public async Task<LoginResponse> Register([FromBody] LoginRequest req)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2022-02-23 17:06:44 +00:00
|
|
|
if (!TryValidateModel(req))
|
|
|
|
{
|
|
|
|
var error = ControllerContext.ModelState.FirstOrDefault().Value?.Errors.FirstOrDefault()?.ErrorMessage;
|
|
|
|
return new(null, error);
|
|
|
|
}
|
2022-07-25 17:59:32 +00:00
|
|
|
|
2022-03-11 15:59:08 +00:00
|
|
|
// check captcha
|
|
|
|
if (!await _captchaVerifier.Verify(req.Captcha))
|
|
|
|
{
|
|
|
|
return new(null, "Captcha verification failed");
|
|
|
|
}
|
2022-07-25 17:59:32 +00:00
|
|
|
|
2022-02-21 22:35:06 +00:00
|
|
|
var newUser = await _manager.Register(req.Username, req.Password);
|
2022-07-25 19:05:31 +00:00
|
|
|
var token = CreateToken(newUser, DateTime.UtcNow.AddHours(12));
|
2022-02-21 22:35:06 +00:00
|
|
|
var tokenWriter = new JwtSecurityTokenHandler();
|
2022-02-24 23:05:33 +00:00
|
|
|
return new(tokenWriter.WriteToken(token), Profile: newUser.ToPublic());
|
2022-02-21 22:35:06 +00:00
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
return new(null, ex.Message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-25 17:59:32 +00:00
|
|
|
/// <summary>
|
|
|
|
/// List api keys for the user
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="id"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
[HttpGet]
|
|
|
|
[Route("api-key")]
|
|
|
|
public async Task<IActionResult> ListApiKeys()
|
|
|
|
{
|
|
|
|
var uid = HttpContext.GetUserId();
|
|
|
|
if (uid == default) return Unauthorized();
|
|
|
|
|
|
|
|
return Json(await _apiKeyStore.ListKeys(uid.Value));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Create a new API key for the logged in user
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="id"></param>
|
|
|
|
/// <param name="request"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
[HttpPost]
|
|
|
|
[Route("api-key")]
|
|
|
|
public async Task<IActionResult> CreateApiKey([FromBody] CreateApiKeyRequest request)
|
|
|
|
{
|
|
|
|
var uid = HttpContext.GetUserId();
|
|
|
|
if (uid == default) return Unauthorized();
|
|
|
|
|
|
|
|
var user = await _userStore.Get(uid.Value);
|
|
|
|
if (user == default) return Unauthorized();
|
|
|
|
|
|
|
|
var expiry = DateTime.SpecifyKind(request.Expiry, DateTimeKind.Utc);
|
|
|
|
if (expiry > DateTime.UtcNow.AddYears(1))
|
|
|
|
{
|
|
|
|
return BadRequest();
|
|
|
|
}
|
|
|
|
|
|
|
|
var key = new ApiKey()
|
|
|
|
{
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
UserId = user.Id,
|
2022-07-25 19:05:31 +00:00
|
|
|
Token = new JwtSecurityTokenHandler().WriteToken(CreateToken(user, expiry)),
|
2022-07-25 17:59:32 +00:00
|
|
|
Expiry = expiry
|
|
|
|
};
|
|
|
|
|
|
|
|
await _apiKeyStore.Add(key.Id, key);
|
|
|
|
return Json(key);
|
|
|
|
}
|
|
|
|
|
2022-07-25 19:05:31 +00:00
|
|
|
private JwtSecurityToken CreateToken(VoidUser user, DateTime expiry)
|
2022-07-25 17:59:32 +00:00
|
|
|
{
|
|
|
|
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.JwtSettings.Key));
|
|
|
|
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
|
|
|
|
|
|
|
var claims = new List<Claim>()
|
|
|
|
{
|
|
|
|
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
|
|
|
new(JwtRegisteredClaimNames.Exp, new DateTimeOffset(expiry).ToUnixTimeSeconds().ToString()),
|
|
|
|
new(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())
|
|
|
|
};
|
|
|
|
|
|
|
|
claims.AddRange(user.Roles.Select(a => new Claim(ClaimTypes.Role, a)));
|
|
|
|
|
|
|
|
return new JwtSecurityToken(_settings.JwtSettings.Issuer, claims: claims,
|
|
|
|
signingCredentials: credentials);
|
|
|
|
}
|
|
|
|
|
2022-03-11 15:59:08 +00:00
|
|
|
public sealed class LoginRequest
|
2022-02-23 17:06:44 +00:00
|
|
|
{
|
|
|
|
public LoginRequest(string username, string password)
|
|
|
|
{
|
|
|
|
Username = username;
|
|
|
|
Password = password;
|
|
|
|
}
|
|
|
|
|
|
|
|
[Required]
|
|
|
|
[EmailAddress]
|
2022-03-11 15:59:08 +00:00
|
|
|
public string Username { get; }
|
2022-07-25 17:59:32 +00:00
|
|
|
|
2022-02-23 17:06:44 +00:00
|
|
|
[Required]
|
|
|
|
[MinLength(6)]
|
2022-03-11 15:59:08 +00:00
|
|
|
public string Password { get; }
|
|
|
|
|
|
|
|
public string? Captcha { get; init; }
|
2022-02-23 17:06:44 +00:00
|
|
|
}
|
2022-02-21 22:35:06 +00:00
|
|
|
|
2022-03-11 15:59:08 +00:00
|
|
|
public sealed record LoginResponse(string? Jwt, string? Error = null, VoidUser? Profile = null);
|
2022-07-25 17:59:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
public sealed record CreateApiKeyRequest(DateTime Expiry);
|
2022-02-21 22:35:06 +00:00
|
|
|
}
|