121 lines
4.0 KiB
C#
121 lines
4.0 KiB
C#
using FictionArchive.Service.UserService.Models.Database;
|
|
using FictionArchive.Service.UserService.Services.AuthenticationClient;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace FictionArchive.Service.UserService.Services;
|
|
|
|
public class UserManagementService
|
|
{
|
|
private readonly ILogger<UserManagementService> _logger;
|
|
private readonly UserServiceDbContext _dbContext;
|
|
private readonly IAuthenticationServiceClient _authClient;
|
|
|
|
public UserManagementService(
|
|
UserServiceDbContext dbContext,
|
|
ILogger<UserManagementService> logger,
|
|
IAuthenticationServiceClient authClient)
|
|
{
|
|
_dbContext = dbContext;
|
|
_logger = logger;
|
|
_authClient = authClient;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invites a new user by creating them in Authentik, saving to the database, and sending a recovery email.
|
|
/// </summary>
|
|
/// <param name="inviter">The user sending the invite</param>
|
|
/// <param name="email">Email address of the invitee</param>
|
|
/// <param name="username">Username for the invitee</param>
|
|
/// <returns>The created user, or null if the invite failed</returns>
|
|
public async Task<User?> InviteUserAsync(User inviter, string email, string username)
|
|
{
|
|
// Check if inviter has available invites
|
|
if (inviter.AvailableInvites <= 0)
|
|
{
|
|
_logger.LogWarning("User {InviterId} has no available invites", inviter.Id);
|
|
return null;
|
|
}
|
|
|
|
// Check if email is already in use
|
|
var existingUser = await _dbContext.Users
|
|
.AsQueryable()
|
|
.FirstOrDefaultAsync(u => u.Email == email);
|
|
|
|
if (existingUser != null)
|
|
{
|
|
_logger.LogWarning("Email {Email} is already in use", email);
|
|
return null;
|
|
}
|
|
|
|
// Check if username is already in use
|
|
var existingUsername = await _dbContext.Users
|
|
.AsQueryable()
|
|
.FirstOrDefaultAsync(u => u.Username == username);
|
|
|
|
if (existingUsername != null)
|
|
{
|
|
_logger.LogWarning("Username {Username} is already in use", username);
|
|
return null;
|
|
}
|
|
|
|
// Create user in Authentik
|
|
var authentikUser = await _authClient.CreateUserAsync(username, email, username);
|
|
if (authentikUser == null)
|
|
{
|
|
_logger.LogError("Failed to create user {Username} in Authentik", username);
|
|
return null;
|
|
}
|
|
|
|
// Send recovery email via Authentik
|
|
var emailSent = await _authClient.SendRecoveryEmailAsync(authentikUser.Pk);
|
|
if (!emailSent)
|
|
{
|
|
_logger.LogWarning(
|
|
"User {Username} was created in Authentik but recovery email failed to send. Authentik pk: {Pk}",
|
|
username, authentikUser.Pk);
|
|
// Continue anyway - the user is created, admin can resend email manually
|
|
}
|
|
|
|
// Create user in local database
|
|
var newUser = new User
|
|
{
|
|
Username = username,
|
|
Email = email,
|
|
OAuthProviderId = authentikUser.Uid,
|
|
Disabled = false,
|
|
AvailableInvites = 0,
|
|
InviterId = inviter.Id
|
|
};
|
|
|
|
_dbContext.Users.Add(newUser);
|
|
|
|
// Decrement inviter's available invites
|
|
inviter.AvailableInvites--;
|
|
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
_logger.LogInformation(
|
|
"User {Username} was successfully invited by {InviterId}. New user id: {NewUserId}",
|
|
username, inviter.Id, newUser.Id);
|
|
|
|
return newUser;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a user by their OAuth provider ID (Authentik UID).
|
|
/// </summary>
|
|
public async Task<User?> GetUserByOAuthProviderIdAsync(string oAuthProviderId)
|
|
{
|
|
return await _dbContext.Users
|
|
.AsQueryable()
|
|
.FirstOrDefaultAsync(u => u.OAuthProviderId == oAuthProviderId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all users as a queryable for GraphQL.
|
|
/// </summary>
|
|
public IQueryable<User> GetUsers()
|
|
{
|
|
return _dbContext.Users.AsQueryable();
|
|
}
|
|
} |