[FA-55] Finished aside from deactivation/integration events
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -17,7 +17,7 @@ public class Mutation
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
// Get the current user's OAuth provider ID from claims
|
||||
var oAuthProviderId = claimsPrincipal.FindFirst("sub")?.Value;
|
||||
var oAuthProviderId = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(oAuthProviderId))
|
||||
{
|
||||
throw new InvalidOperationException("Unable to determine current user identity");
|
||||
|
||||
@@ -2,53 +2,42 @@ using System.Security.Claims;
|
||||
using FictionArchive.Service.UserService.Models.DTOs;
|
||||
using FictionArchive.Service.UserService.Services;
|
||||
using HotChocolate.Authorization;
|
||||
using HotChocolate.Data;
|
||||
|
||||
namespace FictionArchive.Service.UserService.GraphQL;
|
||||
|
||||
public class Query
|
||||
{
|
||||
[Authorize]
|
||||
public async Task<int> GetAvailableInvites(
|
||||
UserManagementService userManagementService,
|
||||
[UseProjection]
|
||||
[UseFirstOrDefault]
|
||||
public IQueryable<UserDto> GetCurrentUser(
|
||||
UserServiceDbContext dbContext,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var oAuthProviderId = claimsPrincipal.FindFirst("sub")?.Value;
|
||||
var oAuthProviderId = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(oAuthProviderId))
|
||||
{
|
||||
return 0;
|
||||
return Enumerable.Empty<UserDto>().AsQueryable();
|
||||
}
|
||||
|
||||
var user = await userManagementService.GetUserByOAuthProviderIdAsync(oAuthProviderId);
|
||||
return user?.AvailableInvites ?? 0;
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
public async Task<UserDto?> GetCurrentUser(
|
||||
UserManagementService userManagementService,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var oAuthProviderId = claimsPrincipal.FindFirst("sub")?.Value;
|
||||
if (string.IsNullOrEmpty(oAuthProviderId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var user = await userManagementService.GetUserByOAuthProviderIdAsync(oAuthProviderId);
|
||||
if (user == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
CreatedTime = user.CreatedTime,
|
||||
LastUpdatedTime = user.LastUpdatedTime,
|
||||
Username = user.Username,
|
||||
Email = user.Email,
|
||||
Disabled = user.Disabled,
|
||||
AvailableInvites = user.AvailableInvites,
|
||||
InviterId = user.InviterId
|
||||
};
|
||||
return dbContext.Users
|
||||
.Where(u => u.OAuthProviderId == oAuthProviderId)
|
||||
.Select(u => new UserDto
|
||||
{
|
||||
Id = u.Id,
|
||||
CreatedTime = u.CreatedTime,
|
||||
LastUpdatedTime = u.LastUpdatedTime,
|
||||
Username = u.Username,
|
||||
Email = u.Email,
|
||||
Disabled = u.Disabled,
|
||||
AvailableInvites = u.AvailableInvites,
|
||||
InviterId = u.InviterId,
|
||||
InvitedUsers = u.InvitedUsers.Select(iu => new InvitedUserDto
|
||||
{
|
||||
Username = iu.Username,
|
||||
Email = iu.Email
|
||||
}).ToList()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FictionArchive.Service.UserService.Models.DTOs;
|
||||
|
||||
public class InvitedUserDto
|
||||
{
|
||||
public required string Username { get; init; }
|
||||
public required string Email { get; init; }
|
||||
}
|
||||
@@ -13,4 +13,5 @@ public class UserDto
|
||||
public bool Disabled { get; init; }
|
||||
public int AvailableInvites { get; init; }
|
||||
public Guid? InviterId { get; init; }
|
||||
public List<InvitedUserDto>? InvitedUsers { get; init; }
|
||||
}
|
||||
|
||||
@@ -15,4 +15,5 @@ public class User : BaseEntity<Guid>
|
||||
// Navigation properties
|
||||
public Guid? InviterId { get; set; }
|
||||
public User? Inviter { get; set; }
|
||||
public ICollection<User> InvitedUsers { get; set; } = new List<User>();
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FictionArchive.Service.UserService.Services.AuthenticationClient.Authentik;
|
||||
|
||||
public class AuthentikAddUserRequest
|
||||
{
|
||||
[JsonPropertyName("username")]
|
||||
[JsonProperty("username")]
|
||||
public required string Username { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
[JsonProperty("name")]
|
||||
public required string DisplayName { get; set; }
|
||||
|
||||
[JsonPropertyName("email")]
|
||||
[JsonProperty("email")]
|
||||
public required string Email { get; set; }
|
||||
|
||||
[JsonPropertyName("is_active")]
|
||||
[JsonProperty("is_active")]
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; } = "external";
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FictionArchive.Service.UserService.Services.AuthenticationClient.Authentik;
|
||||
|
||||
@@ -6,11 +8,16 @@ public class AuthentikClient : IAuthenticationServiceClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<AuthentikClient> _logger;
|
||||
private readonly AuthentikConfiguration _configuration;
|
||||
|
||||
public AuthentikClient(HttpClient httpClient, ILogger<AuthentikClient> logger)
|
||||
public AuthentikClient(
|
||||
HttpClient httpClient,
|
||||
ILogger<AuthentikClient> logger,
|
||||
IOptions<AuthentikConfiguration> configuration)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_configuration = configuration.Value;
|
||||
}
|
||||
|
||||
public async Task<AuthentikUserResponse?> CreateUserAsync(string username, string email, string displayName)
|
||||
@@ -25,7 +32,9 @@ public class AuthentikClient : IAuthenticationServiceClient
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync("/api/v3/core/users/", request);
|
||||
var json = JsonConvert.SerializeObject(request);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
var response = await _httpClient.PostAsync("/api/v3/core/users/", content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -36,7 +45,8 @@ public class AuthentikClient : IAuthenticationServiceClient
|
||||
return null;
|
||||
}
|
||||
|
||||
var userResponse = await response.Content.ReadFromJsonAsync<AuthentikUserResponse>();
|
||||
var responseJson = await response.Content.ReadAsStringAsync();
|
||||
var userResponse = JsonConvert.DeserializeObject<AuthentikUserResponse>(responseJson);
|
||||
_logger.LogInformation("Successfully created user {Username} in Authentik with pk {Pk}",
|
||||
username, userResponse?.Pk);
|
||||
|
||||
@@ -54,7 +64,7 @@ public class AuthentikClient : IAuthenticationServiceClient
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsync(
|
||||
$"/api/v3/core/users/{authentikUserId}/recovery_email/",
|
||||
$"/api/v3/core/users/{authentikUserId}/recovery_email/?email_stage={_configuration.EmailStageId}",
|
||||
null);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
|
||||
@@ -4,4 +4,5 @@ public class AuthentikConfiguration
|
||||
{
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
public string ApiToken { get; set; } = string.Empty;
|
||||
public string EmailStageId { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FictionArchive.Service.UserService.Services.AuthenticationClient.Authentik;
|
||||
|
||||
public class AuthentikUserResponse
|
||||
{
|
||||
[JsonPropertyName("pk")]
|
||||
[JsonProperty("pk")]
|
||||
public int Pk { get; set; }
|
||||
|
||||
[JsonPropertyName("username")]
|
||||
[JsonProperty("username")]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("email")]
|
||||
[JsonProperty("email")]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("is_active")]
|
||||
[JsonProperty("is_active")]
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
[JsonPropertyName("is_superuser")]
|
||||
[JsonProperty("is_superuser")]
|
||||
public bool IsSuperuser { get; set; }
|
||||
|
||||
[JsonPropertyName("uid")]
|
||||
[JsonProperty("uid")]
|
||||
public string Uid { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -118,4 +118,18 @@ public class UserManagementService
|
||||
{
|
||||
return _dbContext.Users.AsQueryable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all users invited by a specific user.
|
||||
/// </summary>
|
||||
/// <param name="inviterId">The ID of the user who sent the invites</param>
|
||||
/// <returns>List of users invited by the specified user</returns>
|
||||
public async Task<List<User>> GetInvitedByUserAsync(Guid inviterId)
|
||||
{
|
||||
return await _dbContext.Users
|
||||
.AsQueryable()
|
||||
.Where(u => u.InviterId == inviterId)
|
||||
.OrderByDescending(u => u.CreatedTime)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,8 @@
|
||||
},
|
||||
"Authentik": {
|
||||
"BaseUrl": "https://auth.orfl.xyz",
|
||||
"ApiToken": "REPLACE_ME"
|
||||
"ApiToken": "REPLACE_ME",
|
||||
"EmailStageId": "10df0c18-8802-4ec7-852e-3cdd355514d3"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"OIDC": {
|
||||
|
||||
Reference in New Issue
Block a user