116 lines
4.2 KiB
C#
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
|
|
);
|
|
}
|