void.cat/VoidCat/Controllers/AuthController.cs

209 lines
6.6 KiB
C#
Raw Normal View History

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;
private readonly IApiKeyStore _apiKeyStore;
private readonly IUserStore _userStore;
2022-02-21 22:35:06 +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;
_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
{
if (!TryValidateModel(req))
{
var error = ControllerContext.ModelState.FirstOrDefault().Value?.Errors.FirstOrDefault()?.ErrorMessage;
return new(null, error);
}
2022-03-11 15:59:08 +00:00
// check captcha
if (!await _captchaVerifier.Verify(req.Captcha))
{
return new(null, "Captcha verification failed");
}
2022-02-21 22:35:06 +00:00
var user = await _manager.Login(req.Username, req.Password);
var token = CreateToken(user);
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
{
if (!TryValidateModel(req))
{
var error = ControllerContext.ModelState.FirstOrDefault().Value?.Errors.FirstOrDefault()?.ErrorMessage;
return new(null, error);
}
2022-03-11 15:59:08 +00:00
// check captcha
if (!await _captchaVerifier.Verify(req.Captcha))
{
return new(null, "Captcha verification failed");
}
2022-02-21 22:35:06 +00:00
var newUser = await _manager.Register(req.Username, req.Password);
var token = CreateToken(newUser);
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);
}
}
/// <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,
Token = new JwtSecurityTokenHandler().WriteToken(CreateApiToken(user, expiry)),
Expiry = expiry
};
await _apiKeyStore.Add(key.Id, key);
return Json(key);
}
private JwtSecurityToken CreateApiToken(VoidUser user, DateTime expiry)
{
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.Aud, "API"),
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-02-21 22:35:06 +00:00
private JwtSecurityToken CreateToken(VoidUser user)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.JwtSettings.Key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
2022-02-24 12:15:12 +00:00
var claims = new List<Claim>()
2022-02-21 22:35:06 +00:00
{
2022-02-24 23:05:33 +00:00
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
2022-02-26 14:22:22 +00:00
new(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddHours(6).ToUnixTimeSeconds().ToString()),
new(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())
2022-02-21 22:35:06 +00:00
};
2022-02-24 12:15:12 +00:00
claims.AddRange(user.Roles.Select(a => new Claim(ClaimTypes.Role, a)));
2022-02-21 22:35:06 +00:00
2022-02-22 14:20:31 +00:00
return new JwtSecurityToken(_settings.JwtSettings.Issuer, claims: claims,
2022-02-21 22:35:06 +00:00
signingCredentials: credentials);
}
2022-03-11 15:59:08 +00:00
public sealed class LoginRequest
{
public LoginRequest(string username, string password)
{
Username = username;
Password = password;
}
[Required]
[EmailAddress]
2022-03-11 15:59:08 +00:00
public string Username { get; }
[Required]
[MinLength(6)]
2022-03-11 15:59:08 +00:00
public string Password { get; }
public string? Captcha { get; init; }
}
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);
public sealed record CreateApiKeyRequest(DateTime Expiry);
2022-02-21 22:35:06 +00:00
}