116 lines
4.2 KiB
C#

using chat.Domain.Entities;
using chat.Domain.Interfaces;
using chat.Domain.Exceptions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
namespace chat.Infrastructure;
public class LogtoAuthService(
ILogger<LogtoAuthService> _logger,
HttpClient _httpClient,
IConfiguration _configuration) : ILogtoAuthService
{
public async Task<User?> Register(User request, CancellationToken cancellationToken)
{
var token = await RetrieveManagementToken(cancellationToken);
var managementApiUrl = _configuration["Logto:ManagementApi"]!;
var createUserEndpoint = $"{managementApiUrl}/users";
var createUserRequest = new
{
username = request.Username,
primaryEmail = request.Email,
password = request.Password,
name = request.Username
};
var requestMessage = new HttpRequestMessage(HttpMethod.Post, createUserEndpoint)
{
Content = JsonContent.Create(createUserRequest),
Headers =
{
{ "Authorization", $"Bearer {token}" }
}
};
var response = await _httpClient.SendAsync(requestMessage, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync(cancellationToken);
throw new HttpResponseException((int)response.StatusCode, error);
}
var logtoUser = await response.Content.ReadFromJsonAsync<LogtoUserResponse>(cancellationToken);
if (logtoUser == null)
{
throw new InvalidOperationException("Failed to deserialize Logto user response");
}
_logger.LogInformation("User created successfully in Logto. UserId: {UserId}, Username: {Username}",
logtoUser.Id, logtoUser.Username);
return new User
{
Username = logtoUser.Username ?? request.Username,
Email = logtoUser.PrimaryEmail ?? request.Email,
Password = "********"
};
}
private async Task<string> RetrieveManagementToken(CancellationToken cancellationToken)
{
var tokenEndpoint = $"{_configuration["Logto:Issuer"]}/token";
var managementApiUrl = _configuration["Logto:ManagementApi"]!;
var tokenRequest = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = _configuration["Logto:M2M:AppId"]!,
["client_secret"] = _configuration["Logto:M2M:AppSecret"]!,
["resource"] = managementApiUrl,
["scope"] = "all"
});
var response = await _httpClient.PostAsync(tokenEndpoint, tokenRequest, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync(cancellationToken);
throw new HttpResponseException((int)response.StatusCode, $"{error}");
}
var tokenResponse = await response.Content.ReadFromJsonAsync<TokenResponse>(cancellationToken);
if (tokenResponse?.AccessToken == null)
{
throw new InvalidOperationException("M2M token response is invalid");
}
_logger.LogInformation("M2M token obtained successfully. Type: {TokenType}, Expires in: {ExpiresIn}s, scope: {Scope}",
tokenResponse.TokenType,
tokenResponse.ExpiresIn,
tokenResponse.Scope);
return tokenResponse.AccessToken;
}
private record TokenResponse(
[property: JsonPropertyName("access_token")] string AccessToken,
[property: JsonPropertyName("token_type")] string TokenType,
[property: JsonPropertyName("expires_in")] int ExpiresIn,
[property: JsonPropertyName("scope")] string Scope
);
private record LogtoUserResponse(
[property: JsonPropertyName("id")] string Id,
[property: JsonPropertyName("username")] string? Username,
[property: JsonPropertyName("primaryEmail")] string? PrimaryEmail,
[property: JsonPropertyName("name")] string? Name
);
}