using Fengling.Console.Models.Dtos; using OpenIddict.Abstractions; using System.Security.Cryptography; using System.Text.Json; namespace Fengling.Console.Services; public interface IOAuthClientService { Task<(IEnumerable Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null); Task GetClientAsync(string id); Task CreateClientAsync(CreateClientDto dto); Task GenerateSecretAsync(string id); Task UpdateClientAsync(string id, UpdateClientDto dto); Task DeleteClientAsync(string id); object GetClientOptions(); } public class OAuthClientService : IOAuthClientService { private readonly IOpenIddictApplicationManager _applicationManager; private readonly HttpClient _httpClient; private readonly IConfiguration _configuration; private readonly ILogger _logger; public OAuthClientService( IOpenIddictApplicationManager applicationManager, HttpClient httpClient, IConfiguration configuration, ILogger logger) { _applicationManager = applicationManager; _httpClient = httpClient; _configuration = configuration; _logger = logger; } public async Task<(IEnumerable Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null) { var applications = _applicationManager.ListAsync(); var clientList = new List(); await foreach (var application in applications) { var clientIdValue = await _applicationManager.GetClientIdAsync(application); var displayNameValue = await _applicationManager.GetDisplayNameAsync(application); if (!string.IsNullOrEmpty(displayName) && !displayNameValue.Contains(displayName, StringComparison.OrdinalIgnoreCase)) continue; if (!string.IsNullOrEmpty(clientId) && !clientIdValue.Contains(clientId, StringComparison.OrdinalIgnoreCase)) continue; var clientType = await _applicationManager.GetClientTypeAsync(application); var consentType = await _applicationManager.GetConsentTypeAsync(application); var permissions = await _applicationManager.GetPermissionsAsync(application); var redirectUris = await _applicationManager.GetRedirectUrisAsync(application); var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application); var applicationId = await _applicationManager.GetIdAsync(application); clientList.Add(new OAuthClientDto { Id = applicationId ?? clientIdValue ?? string.Empty, ClientId = clientIdValue ?? string.Empty, DisplayName = displayNameValue ?? string.Empty, RedirectUris = redirectUris.ToArray(), PostLogoutRedirectUris = postLogoutRedirectUris.ToArray(), Scopes = permissions .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope)) .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length)) .ToArray(), GrantTypes = permissions .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType)) .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length)) .ToArray(), ClientType = clientType?.ToString(), ConsentType = consentType?.ToString(), Status = "active" }); } var sortedClients = clientList .OrderByDescending(c => c.ClientId) .Skip((page - 1) * pageSize) .Take(pageSize) .ToList(); return (sortedClients, clientList.Count); } public async Task GetClientAsync(string id) { var application = await _applicationManager.FindByIdAsync(id); if (application == null) { return null; } var clientIdValue = await _applicationManager.GetClientIdAsync(application); var displayNameValue = await _applicationManager.GetDisplayNameAsync(application); var clientType = await _applicationManager.GetClientTypeAsync(application); var consentType = await _applicationManager.GetConsentTypeAsync(application); var permissions = await _applicationManager.GetPermissionsAsync(application); var redirectUris = await _applicationManager.GetRedirectUrisAsync(application); var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application); return new OAuthClientDto { Id = id, ClientId = clientIdValue ?? string.Empty, DisplayName = displayNameValue ?? string.Empty, RedirectUris = redirectUris.ToArray(), PostLogoutRedirectUris = postLogoutRedirectUris.ToArray(), Scopes = permissions .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope)) .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length)) .ToArray(), GrantTypes = permissions .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType)) .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length)) .ToArray(), ClientType = clientType?.ToString(), ConsentType = consentType?.ToString(), Status = "active" }; } public async Task CreateClientAsync(CreateClientDto dto) { var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132"; var token = await GetAuthTokenAsync(); var clientSecret = string.IsNullOrEmpty(dto.ClientSecret) ? GenerateSecureSecret() : dto.ClientSecret; var requestBody = new { dto.ClientId, ClientSecret = clientSecret, dto.DisplayName, dto.RedirectUris, dto.PostLogoutRedirectUris, dto.Scopes, dto.GrantTypes, dto.ClientType, dto.ConsentType, dto.Status, dto.Description }; var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/api/OAuthClients") { Content = new StringContent( System.Text.Json.JsonSerializer.Serialize(requestBody), System.Text.Encoding.UTF8, "application/json") }; request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var result = System.Text.Json.JsonSerializer.Deserialize(content); _logger.LogInformation("Created OAuth client {ClientId}", dto.ClientId); return result!; } public async Task GenerateSecretAsync(string id) { var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132"; var token = await GetAuthTokenAsync(); var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/api/OAuthClients/{id}/generate-secret"); request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var result = System.Text.Json.JsonSerializer.Deserialize(content); _logger.LogInformation("Generated new secret for OAuth client {Id}", id); return result!; } public async Task UpdateClientAsync(string id, UpdateClientDto dto) { var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132"; var token = await GetAuthTokenAsync(); var requestBody = new { dto.DisplayName, dto.RedirectUris, dto.PostLogoutRedirectUris, dto.Scopes, dto.GrantTypes, dto.ClientType, dto.ConsentType, dto.Status, dto.Description }; var request = new HttpRequestMessage(HttpMethod.Put, $"{authServiceUrl}/api/OAuthClients/{id}") { Content = new StringContent( System.Text.Json.JsonSerializer.Serialize(requestBody), System.Text.Encoding.UTF8, "application/json") }; request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); _logger.LogInformation("Updated OAuth client {Id}", id); return new { message = "Client updated successfully" }; } public async Task DeleteClientAsync(string id) { var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132"; var token = await GetAuthTokenAsync(); var request = new HttpRequestMessage(HttpMethod.Delete, $"{authServiceUrl}/api/OAuthClients/{id}"); request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); _logger.LogInformation("Deleted OAuth client {Id}", id); } public object GetClientOptions() { return new { clientTypes = new[] { new { value = "public", label = "Public (SPA, Mobile App)" }, new { value = "confidential", label = "Confidential (Server-side)" } }, consentTypes = new[] { new { value = "implicit", label = "Implicit" }, new { value = "explicit", label = "Explicit" }, new { value = "system", label = "System (Pre-authorized)" } }, grantTypes = new[] { new { value = "authorization_code", label = "Authorization Code" }, new { value = "client_credentials", label = "Client Credentials" }, new { value = "refresh_token", label = "Refresh Token" }, new { value = "password", label = "Resource Owner Password Credentials" } }, scopes = new[] { new { value = "openid", label = "OpenID Connect" }, new { value = "profile", label = "Profile" }, new { value = "email", label = "Email" }, new { value = "api", label = "API Access" }, new { value = "offline_access", label = "Offline Access" } }, statuses = new[] { new { value = "active", label = "Active" }, new { value = "inactive", label = "Inactive" }, new { value = "suspended", label = "Suspended" } } }; } private async Task GetAuthTokenAsync() { var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132"; var clientId = _configuration["AuthService:ClientId"] ?? "console-client"; var clientSecret = _configuration["AuthService:ClientSecret"] ?? "console-secret"; var requestBody = new Dictionary { { "grant_type", "client_credentials" }, { "client_id", clientId }, { "client_secret", clientSecret }, { "scope", "api" } }; var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/connect/token") { Content = new FormUrlEncodedContent(requestBody) }; var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var tokenResponse = System.Text.Json.JsonSerializer.Deserialize>(content); return tokenResponse!["access_token"].GetString() ?? string.Empty; } private static string GenerateSecureSecret(int length = 32) { var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var bytes = RandomNumberGenerator.GetBytes(length); return new string(bytes.Select(b => chars[b % chars.Length]).ToArray()); } }