2022-02-21 22:35:06 +00:00
|
|
|
using VoidCat.Model;
|
2022-09-08 09:41:31 +00:00
|
|
|
using VoidCat.Model.User;
|
2022-02-21 22:35:06 +00:00
|
|
|
using VoidCat.Services.Abstractions;
|
2022-09-08 09:41:31 +00:00
|
|
|
using VoidCat.Services.Users.Auth;
|
2022-02-21 22:35:06 +00:00
|
|
|
|
|
|
|
namespace VoidCat.Services.Users;
|
|
|
|
|
2022-09-08 09:41:31 +00:00
|
|
|
public class UserManager
|
2022-02-21 22:35:06 +00:00
|
|
|
{
|
|
|
|
private readonly IUserStore _store;
|
2022-03-02 11:37:15 +00:00
|
|
|
private readonly IEmailVerification _emailVerification;
|
2022-09-08 09:41:31 +00:00
|
|
|
private readonly IUserAuthTokenStore _tokenStore;
|
|
|
|
private readonly OAuthFactory _oAuthFactory;
|
2022-02-24 12:00:28 +00:00
|
|
|
private static bool _checkFirstRegister;
|
2022-02-21 22:35:06 +00:00
|
|
|
|
2022-09-08 09:41:31 +00:00
|
|
|
public UserManager(IUserStore store, IEmailVerification emailVerification, OAuthFactory oAuthFactory,
|
|
|
|
IUserAuthTokenStore tokenStore)
|
2022-02-21 22:35:06 +00:00
|
|
|
{
|
|
|
|
_store = store;
|
2022-03-02 11:37:15 +00:00
|
|
|
_emailVerification = emailVerification;
|
2022-09-08 09:41:31 +00:00
|
|
|
_oAuthFactory = oAuthFactory;
|
|
|
|
_tokenStore = tokenStore;
|
2022-02-21 22:35:06 +00:00
|
|
|
}
|
2022-02-24 12:00:28 +00:00
|
|
|
|
2022-09-08 09:41:31 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Login an existing user with email/password
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="email"></param>
|
|
|
|
/// <param name="password"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
|
|
public async ValueTask<InternalUser> Login(string email, string password)
|
2022-02-21 22:35:06 +00:00
|
|
|
{
|
|
|
|
var userId = await _store.LookupUser(email);
|
|
|
|
if (!userId.HasValue) throw new InvalidOperationException("User does not exist");
|
|
|
|
|
2022-06-08 16:17:53 +00:00
|
|
|
var user = await _store.GetPrivate(userId.Value);
|
2022-02-21 22:35:06 +00:00
|
|
|
if (!(user?.CheckPassword(password) ?? false)) throw new InvalidOperationException("User does not exist");
|
2022-02-24 12:00:28 +00:00
|
|
|
|
2022-09-08 09:41:31 +00:00
|
|
|
await HandleLogin(user);
|
2022-02-21 22:35:06 +00:00
|
|
|
return user;
|
|
|
|
}
|
2022-02-24 12:00:28 +00:00
|
|
|
|
2022-09-08 09:41:31 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Register a new internal user with email/password
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="email"></param>
|
|
|
|
/// <param name="password"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
|
|
public async ValueTask<InternalUser> Register(string email, string password)
|
2022-02-21 22:35:06 +00:00
|
|
|
{
|
|
|
|
var existingUser = await _store.LookupUser(email);
|
2022-06-08 16:17:53 +00:00
|
|
|
if (existingUser != Guid.Empty && existingUser != null)
|
|
|
|
throw new InvalidOperationException("User already exists");
|
2022-02-21 22:35:06 +00:00
|
|
|
|
2022-09-08 09:41:31 +00:00
|
|
|
var newUser = new InternalUser
|
2022-02-24 12:00:28 +00:00
|
|
|
{
|
2022-06-08 16:17:53 +00:00
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
Email = email,
|
|
|
|
Password = password.HashPassword(),
|
2022-02-24 12:00:28 +00:00
|
|
|
Created = DateTimeOffset.UtcNow,
|
|
|
|
LastLogin = DateTimeOffset.UtcNow
|
|
|
|
};
|
|
|
|
|
2022-09-08 09:41:31 +00:00
|
|
|
await SetupNewUser(newUser);
|
|
|
|
return newUser;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Start OAuth2 authorization flow
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="provider"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
public Uri Authorize(string provider)
|
|
|
|
{
|
|
|
|
var px = _oAuthFactory.GetProvider(provider);
|
|
|
|
return px.Authorize();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Login or Register with OAuth2 auth code
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="code"></param>
|
|
|
|
/// <param name="provider"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
public async ValueTask<InternalUser> LoginOrRegister(string code, string provider)
|
|
|
|
{
|
|
|
|
var px = _oAuthFactory.GetProvider(provider);
|
|
|
|
var token = await px.GetToken(code);
|
|
|
|
|
|
|
|
var user = await px.GetUserDetails(token);
|
|
|
|
if (user == default)
|
|
|
|
{
|
|
|
|
throw new InvalidOperationException($"Could not load user profile from provider: {provider}");
|
|
|
|
}
|
|
|
|
|
|
|
|
var uid = await _store.LookupUser(user.Email);
|
|
|
|
if (uid.HasValue)
|
|
|
|
{
|
|
|
|
var existingUser = await _store.GetPrivate(uid.Value);
|
|
|
|
if (existingUser?.AuthType == AuthType.OAuth2)
|
|
|
|
{
|
|
|
|
return existingUser;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new InvalidOperationException("Auth failure, user type does not match!");
|
|
|
|
}
|
|
|
|
|
|
|
|
await SetupNewUser(user);
|
|
|
|
return user;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async Task SetupNewUser(InternalUser newUser)
|
|
|
|
{
|
2022-02-24 12:00:28 +00:00
|
|
|
// automatically set first user to admin
|
|
|
|
if (!_checkFirstRegister)
|
|
|
|
{
|
|
|
|
_checkFirstRegister = true;
|
|
|
|
var users = await _store.ListUsers(new(0, 1));
|
|
|
|
if (users.TotalResults == 0)
|
|
|
|
{
|
|
|
|
newUser.Roles.Add(Roles.Admin);
|
|
|
|
}
|
|
|
|
}
|
2022-06-06 21:51:25 +00:00
|
|
|
|
2022-03-07 13:38:53 +00:00
|
|
|
await _store.Set(newUser.Id, newUser);
|
2022-03-02 11:37:15 +00:00
|
|
|
await _emailVerification.SendNewCode(newUser);
|
2022-09-08 09:41:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private async Task HandleLogin(InternalUser user)
|
|
|
|
{
|
|
|
|
user.LastLogin = DateTimeOffset.UtcNow;
|
|
|
|
await _store.UpdateLastLogin(user.Id, DateTime.UtcNow);
|
2022-02-21 22:35:06 +00:00
|
|
|
}
|
2022-06-06 21:51:25 +00:00
|
|
|
}
|