implemented register + email verify requests

This commit is contained in:
Daniel Balk 2023-11-02 19:42:20 +01:00
parent c0533d78fb
commit 2b85ffd93b
3 changed files with 229 additions and 3 deletions

View file

@ -0,0 +1,50 @@
using Moonlight.App.Database.Entities;
using Moonlight.App.Models.Enums;
using Moonlight.App.Repositories;
using Moonlight.App.Services;
using Moonlight.App.Services.Users;
namespace Moonlight.App.Api.Requests.Auth;
[ApiRequest(5)]
public class IsEmailVerifiedRequest : AbstractRequest
{
public bool SendMail { get; set; }
public bool MailVerified { get; set; }
public override async Task ProcessRequest()
{
if(Context.User == null)
return;
var userRepository = ServiceProvider.GetRequiredService<Repository<User>>();
Context.User = userRepository.Get().Where(x => x.Id == Context.User.Id).ToArray()[0];
if (SendMail && Context.User != null)
{
var userAuthService = ServiceProvider.GetRequiredService<UserAuthService>();
await userAuthService.SendVerification(Context!.User);
}
var configService = ServiceProvider.GetRequiredService<ConfigService>();
var reqEmailVerify = configService.Get().Security.EnableEmailVerify;
if (Context?.User?.Flags!.Contains(UserFlag.MailVerified.ToString()) ?? false)
MailVerified = true;
else
MailVerified = !reqEmailVerify;
await Task.Delay(200);
}
public override ResponseDataBuilder CreateResponse(ResponseDataBuilder builder)
{
builder.WriteBoolean(MailVerified);
return builder;
}
public override void ReadData(RequestDataContext dataContext)
{
SendMail = dataContext.ReadBoolean();
}
}

View file

@ -1,22 +1,181 @@
namespace Moonlight.App.Api.Requests.Auth;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using Moonlight.App.Database.Entities;
using Moonlight.App.Event;
using Moonlight.App.Exceptions;
using Moonlight.App.Extensions;
using Moonlight.App.Helpers;
using Moonlight.App.Repositories;
using Moonlight.App.Services;
using Moonlight.App.Services.Utils;
namespace Moonlight.App.Api.Requests.Auth;
[ApiRequest(4)]
public class RegisterRequest: AbstractRequest
{
[Required(ErrorMessage = "9")]
[EmailAddress(ErrorMessage = "10")]
public string Email { get; set; } = "";
[Required(ErrorMessage = "8")]
[MinLength(7, ErrorMessage = "7")]
[MaxLength(20, ErrorMessage = "7")]
[RegularExpression("^[a-z][a-z0-9]*$", ErrorMessage = "6")]
public string Username { get; set; } = "";
[Required(ErrorMessage = "4")]
[MinLength(8, ErrorMessage = "5")]
[MaxLength(256, ErrorMessage = "5")]
public string Password { get; set; } = "";
public string PasswordConfirm { get; set; } = "";
public bool Success { get; set; }
public bool RequireEmailVerify { get; set; }
/// <summary>
/// Error Codes:
/// - 0 all successful
/// - 1 email exists
/// - 2 username exists
/// - 3 passwords do not match
/// - 4 password needs to be provided
/// - 5 password needs to be between 8 and 256 characters
/// - 6 Usernames can only contain lowercase characters and numbers
/// - 7 username has to be between 7 and 20 chars
/// - 8 username required
/// - 9 email required
/// - 10 email invalid
/// </summary>
public int ErrorCode { get; set; }
public string Token { get; set; } = "";
public override ResponseDataBuilder CreateResponse(ResponseDataBuilder builder)
{
builder.WriteBoolean(Success);
builder.WriteBoolean(RequireEmailVerify);
builder.WriteInt(ErrorCode);
builder.WriteString(Token);
return builder;
}
public override void ReadData(RequestDataContext dataContext)
{
Email = dataContext.ReadString();
Username = dataContext.ReadString();
Password = dataContext.ReadString();
PasswordConfirm = dataContext.ReadString();
}
public override async Task ProcessRequest()
{
var userRepository = ServiceProvider.GetRequiredService<Repository<User>>();
var configService = ServiceProvider.GetRequiredService<ConfigService>();
var reqEmailVerify = configService.Get().Security.EnableEmailVerify;
// Event though we have form validation i want to
// ensure that at least these basic formatting things are done
Email = Email.ToLower().Trim();
Username = Username.ToLower().Trim();
if (PasswordConfirm != Password)
{
Token = "";
Success = false;
RequireEmailVerify = false;
ErrorCode = 3;
return;
}
if (!IsPropertyValid(this.GetProperty(x => x.Email)!, out var errorCd))
{
Token = "";
Success = false;
RequireEmailVerify = false;
ErrorCode = errorCd;
return;
}
if (!IsPropertyValid(this.GetProperty(x => x.Password)!, out var errorCd1))
{
Token = "";
Success = false;
RequireEmailVerify = false;
ErrorCode = errorCd1;
return;
}
if (!IsPropertyValid(this.GetProperty(x => x.Username)!, out var errorCd2))
{
Token = "";
Success = false;
RequireEmailVerify = false;
ErrorCode = errorCd2;
return;
}
// Prevent duplication or username and/or email
if (userRepository.Get().Any(x => x.Email == Email))
{
Token = "";
Success = false;
RequireEmailVerify = false;
ErrorCode = 1;
return;
}
if (userRepository.Get().Any(x => x.Username == Username))
{
Token = "";
Success = false;
RequireEmailVerify = false;
ErrorCode = 2;
return;
}
var user = new User()
{
Username = Username,
Email = Email,
Password = HashHelper.HashToString(Password)
};
var result = userRepository.Add(user);
await Events.OnUserRegistered.InvokeAsync(result);
Token = await GenerateToken(user);
Success = true;
RequireEmailVerify = reqEmailVerify;
ErrorCode = 0;
}
public async Task<string> GenerateToken(User user)
{
var jwtService = ServiceProvider.GetService<JwtService>();
var token = await jwtService.Create(data =>
{
data.Add("userId", user.Id.ToString());
data.Add("issuedAt", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString());
}, TimeSpan.FromDays(365));
return token;
}
private bool IsPropertyValid(PropertyInfo property, out int errorCode)
{
var attribs = property.GetCustomAttributes<ValidationAttribute>();
foreach (var a in attribs)
{
if (!a.IsValid(property.GetValue(this)))
{
errorCode = int.Parse(a.ErrorMessage);
return false;
}
}
errorCode = 0;
return true;
}
}

View file

@ -0,0 +1,17 @@
using System.Linq.Expressions;
using System.Reflection;
namespace Moonlight.App.Extensions;
public static class TypeExtensions
{
public static PropertyInfo? GetProperty<T, TValue>(this T type, Expression<Func<T, TValue>> selector)
where T : class
{
Expression expression = selector.Body;
return expression.NodeType == ExpressionType.MemberAccess
? (PropertyInfo) ((MemberExpression) expression).Member
: null;
}
}