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 _logger, HttpClient _httpClient, IConfiguration _configuration) : ILogtoAuthService { public async Task 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(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 RetrieveManagementToken(CancellationToken cancellationToken) { var tokenEndpoint = $"{_configuration["Logto:Issuer"]}/token"; var managementApiUrl = _configuration["Logto:ManagementApi"]!; var tokenRequest = new FormUrlEncodedContent(new Dictionary { ["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(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 ); }