From 61c3a27192277f46a8761f0e6d0f990140fafb25 Mon Sep 17 00:00:00 2001 From: Sam <315859133@qq.com> Date: Sat, 7 Feb 2026 17:47:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0OAuth2=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E9=85=8D=E7=BD=AE=E5=92=8C=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加OAuth2认证相关配置文件和服务实现,包括环境变量配置、PKCE流程支持、token管理等功能。主要变更: - 新增OAuth2配置文件 - 实现OAuth2服务层 - 更新请求拦截器支持token自动刷新 - 修改认证API和store以支持OAuth2流程 --- Controllers/OAuthClientsController.cs | 171 ++--------- Models/Dtos/OAuthClientDto.cs | 47 --- Program.cs | 2 +- Repositories/OAuthClientRepository.cs | 107 ------- Services/OAuthClientService.cs | 406 ++++++++++++++------------ 5 files changed, 250 insertions(+), 483 deletions(-) delete mode 100644 Models/Dtos/OAuthClientDto.cs delete mode 100644 Repositories/OAuthClientRepository.cs diff --git a/Controllers/OAuthClientsController.cs b/Controllers/OAuthClientsController.cs index 3aeba55..09a7fc3 100644 --- a/Controllers/OAuthClientsController.cs +++ b/Controllers/OAuthClientsController.cs @@ -1,9 +1,6 @@ -using Fengling.AuthService.Models; -using Fengling.Console.Models.Dtos; using Fengling.Console.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; namespace Fengling.Console.Controllers; @@ -35,26 +32,9 @@ public class OAuthClientsController : ControllerBase { var (items, totalCount) = await _service.GetClientsAsync(page, pageSize, displayName, clientId, status); - var result = items.Select(c => new OAuthClientDto - { - Id = c.Id, - ClientId = c.ClientId, - DisplayName = c.DisplayName, - RedirectUris = c.RedirectUris, - PostLogoutRedirectUris = c.PostLogoutRedirectUris, - Scopes = c.Scopes, - GrantTypes = c.GrantTypes, - ClientType = c.ClientType, - ConsentType = c.ConsentType, - Status = c.Status, - Description = c.Description, - CreatedAt = c.CreatedAt, - UpdatedAt = c.UpdatedAt, - }); - return Ok(new { - items = result, + items, totalCount, page, pageSize @@ -74,7 +54,7 @@ public class OAuthClientsController : ControllerBase } [HttpGet("{id}")] - public async Task> GetClient(long id) + public async Task> GetClient(string id) { try { @@ -84,22 +64,7 @@ public class OAuthClientsController : ControllerBase return NotFound(); } - return Ok(new OAuthClientDto - { - Id = client.Id, - ClientId = client.ClientId, - DisplayName = client.DisplayName, - RedirectUris = client.RedirectUris, - PostLogoutRedirectUris = client.PostLogoutRedirectUris, - Scopes = client.Scopes, - GrantTypes = client.GrantTypes, - ClientType = client.ClientType, - ConsentType = client.ConsentType, - Status = client.Status, - Description = client.Description, - CreatedAt = client.CreatedAt, - UpdatedAt = client.UpdatedAt, - }); + return Ok(client); } catch (Exception ex) { @@ -109,37 +74,12 @@ public class OAuthClientsController : ControllerBase } [HttpPost] - public async Task> CreateClient([FromBody] CreateOAuthClientDto dto) + public async Task> CreateClient([FromBody] CreateClientDto dto) { try { - var client = new OAuthApplication - { - ClientId = dto.ClientId, - ClientSecret = string.IsNullOrEmpty(dto.ClientSecret) ? GenerateSecureSecret() : dto.ClientSecret, - DisplayName = dto.DisplayName, - RedirectUris = dto.RedirectUris ?? Array.Empty(), - PostLogoutRedirectUris = dto.PostLogoutRedirectUris ?? Array.Empty(), - Scopes = dto.Scopes ?? new[] { "openid", "profile", "email", "api" }, - GrantTypes = dto.GrantTypes ?? new[] { "authorization_code", "refresh_token" }, - ClientType = dto.ClientType ?? "confidential", - ConsentType = dto.ConsentType ?? "explicit", - Status = dto.Status ?? "active", - Description = dto.Description, - CreatedAt = DateTime.UtcNow, - }; - - var created = await _service.CreateClientAsync(client); - - return CreatedAtAction(nameof(GetClient), new { id = created.Id }, new OAuthClientDto - { - Id = created.Id, - ClientId = created.ClientId, - ClientSecret = created.ClientSecret, - DisplayName = created.DisplayName, - Status = created.Status, - CreatedAt = created.CreatedAt, - }); + var result = await _service.CreateClientAsync(dto); + return StatusCode(201, result); } catch (InvalidOperationException ex) { @@ -153,17 +93,12 @@ public class OAuthClientsController : ControllerBase } [HttpPost("{id}/generate-secret")] - public async Task GenerateSecret(long id) + public async Task GenerateSecret(string id) { try { - var client = await _service.GenerateSecretAsync(id); - return Ok(new - { - clientId = client.ClientId, - clientSecret = client.ClientSecret, - message = "新密钥已生成,请妥善保管,刷新后将无法再次查看" - }); + var result = await _service.GenerateSecretAsync(id); + return Ok(result); } catch (KeyNotFoundException ex) { @@ -176,74 +111,8 @@ public class OAuthClientsController : ControllerBase } } - [HttpPost("{id}/toggle-status")] - public async Task ToggleStatus(long id) - { - try - { - var client = await _service.ToggleStatusAsync(id); - return Ok(new - { - clientId = client.ClientId, - newStatus = client.Status, - message = $"客户端状态已更改为 {client.Status}" - }); - } - catch (KeyNotFoundException ex) - { - return NotFound(new { message = ex.Message }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error toggling status for client {Id}", id); - return StatusCode(500, new { message = ex.Message }); - } - } - - [HttpPut("{id}")] - public async Task UpdateClient(long id, [FromBody] UpdateOAuthClientDto dto) - { - try - { - var client = await _service.GetClientAsync(id); - if (client == null) - { - return NotFound(); - } - - var updated = new OAuthApplication - { - Id = id, - ClientId = client.ClientId, - ClientSecret = client.ClientSecret, - DisplayName = dto.DisplayName, - RedirectUris = dto.RedirectUris, - PostLogoutRedirectUris = dto.PostLogoutRedirectUris, - Scopes = dto.Scopes, - GrantTypes = dto.GrantTypes, - ClientType = dto.ClientType, - ConsentType = dto.ConsentType, - Status = dto.Status, - Description = dto.Description, - CreatedAt = client.CreatedAt, - }; - - await _service.UpdateClientAsync(id, updated); - return NoContent(); - } - catch (KeyNotFoundException ex) - { - return NotFound(new { message = ex.Message }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error updating client {Id}", id); - return StatusCode(500, new { message = ex.Message }); - } - } - [HttpDelete("{id}")] - public async Task DeleteClient(long id) + public async Task DeleteClient(string id) { try { @@ -261,10 +130,22 @@ public class OAuthClientsController : ControllerBase } } - private static string GenerateSecureSecret(int length = 32) + [HttpPut("{id}")] + public async Task UpdateClient(string id, [FromBody] UpdateClientDto dto) { - var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - var bytes = System.Security.Cryptography.RandomNumberGenerator.GetBytes(length); - return new string(bytes.Select(b => chars[b % chars.Length]).ToArray()); + try + { + await _service.UpdateClientAsync(id, dto); + return NoContent(); + } + catch (KeyNotFoundException ex) + { + return NotFound(new { message = ex.Message }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating client {Id}", id); + return StatusCode(500, new { message = ex.Message }); + } } } diff --git a/Models/Dtos/OAuthClientDto.cs b/Models/Dtos/OAuthClientDto.cs deleted file mode 100644 index d15c60a..0000000 --- a/Models/Dtos/OAuthClientDto.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Fengling.Console.Models.Dtos; - -public record CreateOAuthClientDto -{ - public string ClientId { get; init; } = string.Empty; - public string? ClientSecret { get; init; } - public string DisplayName { get; init; } = string.Empty; - public string[]? RedirectUris { get; init; } - public string[]? PostLogoutRedirectUris { get; init; } - public string[]? Scopes { get; init; } - public string[]? GrantTypes { get; init; } - public string? ClientType { get; init; } - public string? ConsentType { get; init; } - public string? Status { get; init; } - public string? Description { get; init; } -} - -public record UpdateOAuthClientDto -{ - public string? DisplayName { get; init; } - public string[]? RedirectUris { get; init; } - public string[]? PostLogoutRedirectUris { get; init; } - public string[]? Scopes { get; init; } - public string[]? GrantTypes { get; init; } - public string? ClientType { get; init; } - public string? ConsentType { get; init; } - public string? Status { get; init; } - public string? Description { get; init; } -} - -public record OAuthClientDto -{ - public long Id { get; init; } - public string ClientId { get; init; } = string.Empty; - public string? ClientSecret { get; init; } - public string DisplayName { get; init; } = string.Empty; - public string[] RedirectUris { get; init; } = Array.Empty(); - public string[] PostLogoutRedirectUris { get; init; } = Array.Empty(); - public string[] Scopes { get; init; } = Array.Empty(); - public string[] GrantTypes { get; init; } = Array.Empty(); - public string ClientType { get; init; } = "public"; - public string ConsentType { get; init; } = "implicit"; - public string Status { get; init; } = "active"; - public string? Description { get; init; } - public DateTime CreatedAt { get; init; } - public DateTime? UpdatedAt { get; init; } -} diff --git a/Program.cs b/Program.cs index 84c9482..9713dc7 100644 --- a/Program.cs +++ b/Program.cs @@ -25,7 +25,7 @@ builder.Services.AddIdentity() builder.Services.AddHttpContextAccessor(); -builder.Services.AddScoped(); +builder.Services.AddHttpClient(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Repositories/OAuthClientRepository.cs b/Repositories/OAuthClientRepository.cs deleted file mode 100644 index 59ac748..0000000 --- a/Repositories/OAuthClientRepository.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Fengling.AuthService.Models; -using Microsoft.EntityFrameworkCore; - -namespace Fengling.Console.Repositories; - -public interface IOAuthClientRepository -{ - Task GetByIdAsync(long id); - Task GetByClientIdAsync(string clientId); - Task> GetAllAsync(); - Task> GetPagedAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null); - Task CountAsync(string? displayName = null, string? clientId = null, string? status = null); - Task AddAsync(OAuthApplication client); - Task UpdateAsync(OAuthApplication client); - Task DeleteAsync(OAuthApplication client); -} - -public class OAuthClientRepository : IOAuthClientRepository -{ - private readonly Fengling.AuthService.Data.ApplicationDbContext _context; - - public OAuthClientRepository(Fengling.AuthService.Data.ApplicationDbContext context) - { - _context = context; - } - - public async Task GetByIdAsync(long id) - { - return await _context.OAuthApplications.FindAsync(id); - } - - public async Task GetByClientIdAsync(string clientId) - { - return await _context.OAuthApplications.FirstOrDefaultAsync(c => c.ClientId == clientId); - } - - public async Task> GetAllAsync() - { - return await _context.OAuthApplications.ToListAsync(); - } - - public async Task> GetPagedAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null) - { - var query = _context.OAuthApplications.AsQueryable(); - - if (!string.IsNullOrEmpty(displayName)) - { - query = query.Where(c => c.DisplayName.Contains(displayName)); - } - - if (!string.IsNullOrEmpty(clientId)) - { - query = query.Where(c => c.ClientId.Contains(clientId)); - } - - if (!string.IsNullOrEmpty(status)) - { - query = query.Where(c => c.Status == status); - } - - return await query - .OrderByDescending(c => c.CreatedAt) - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToListAsync(); - } - - public async Task CountAsync(string? displayName = null, string? clientId = null, string? status = null) - { - var query = _context.OAuthApplications.AsQueryable(); - - if (!string.IsNullOrEmpty(displayName)) - { - query = query.Where(c => c.DisplayName.Contains(displayName)); - } - - if (!string.IsNullOrEmpty(clientId)) - { - query = query.Where(c => c.ClientId.Contains(clientId)); - } - - if (!string.IsNullOrEmpty(status)) - { - query = query.Where(c => c.Status == status); - } - - return await query.CountAsync(); - } - - public async Task AddAsync(OAuthApplication client) - { - _context.OAuthApplications.Add(client); - await _context.SaveChangesAsync(); - } - - public async Task UpdateAsync(OAuthApplication client) - { - _context.OAuthApplications.Update(client); - await _context.SaveChangesAsync(); - } - - public async Task DeleteAsync(OAuthApplication client) - { - _context.OAuthApplications.Remove(client); - await _context.SaveChangesAsync(); - } -} diff --git a/Services/OAuthClientService.cs b/Services/OAuthClientService.cs index 0e33400..39ed620 100644 --- a/Services/OAuthClientService.cs +++ b/Services/OAuthClientService.cs @@ -1,248 +1,260 @@ -using Fengling.AuthService.Models; -using Fengling.Console.Repositories; using OpenIddict.Abstractions; -using OpenIddict.Server; 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(long id); - Task CreateClientAsync(OAuthApplication client); - Task GenerateSecretAsync(long id); - Task ToggleStatusAsync(long id); - Task UpdateClientAsync(long id, OAuthApplication updatedClient); - Task DeleteClientAsync(long id); + 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 record CreateClientDto +{ + public string ClientId { get; init; } = string.Empty; + public string? ClientSecret { get; init; } + public string DisplayName { get; init; } = string.Empty; + public string[]? RedirectUris { get; init; } + public string[]? PostLogoutRedirectUris { get; init; } + public string[]? Scopes { get; init; } + public string[]? GrantTypes { get; init; } + public string? ClientType { get; init; } + public string? ConsentType { get; init; } + public string? Status { get; init; } + public string? Description { get; init; } +} + +public record UpdateClientDto +{ + public string? DisplayName { get; init; } + public string[]? RedirectUris { get; init; } + public string[]? PostLogoutRedirectUris { get; init; } + public string[]? Scopes { get; init; } + public string[]? GrantTypes { get; init; } + public string? ClientType { get; init; } + public string? ConsentType { get; init; } + public string? Status { get; init; } + public string? Description { get; init; } +} + public class OAuthClientService : IOAuthClientService { - private readonly IOAuthClientRepository _repository; private readonly IOpenIddictApplicationManager _applicationManager; + private readonly HttpClient _httpClient; + private readonly IConfiguration _configuration; private readonly ILogger _logger; public OAuthClientService( - IOAuthClientRepository repository, IOpenIddictApplicationManager applicationManager, + HttpClient httpClient, + IConfiguration configuration, ILogger logger) { - _repository = repository; _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) + public async Task<(IEnumerable Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null) { - var items = await _repository.GetPagedAsync(page, pageSize, displayName, clientId, status); - var totalCount = await _repository.CountAsync(displayName, clientId, status); - return (items, totalCount); + 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); + + clientList.Add(new + { + id = application, + clientId = clientIdValue, + displayName = displayNameValue, + 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)), + grantTypes = permissions + .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType)) + .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length)), + clientType = clientType?.ToString(), + consentType = consentType?.ToString(), + status = "active" + }); + } + + var sortedClients = clientList + .OrderByDescending(c => (c as dynamic).clientId) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToList(); + + return (sortedClients, clientList.Count); } - public async Task GetClientAsync(long id) + public async Task GetClientAsync(string id) { - return await _repository.GetByIdAsync(id); - } - - public async Task CreateClientAsync(OAuthApplication client) - { - var existing = await _repository.GetByClientIdAsync(client.ClientId); - if (existing != null) - { - throw new InvalidOperationException($"Client ID '{client.ClientId}' 已存在"); - } - - var descriptor = new OpenIddictApplicationDescriptor - { - ClientId = client.ClientId, - ClientSecret = client.ClientSecret, - DisplayName = client.DisplayName - }; - - foreach (var uri in client.RedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _))) - { - descriptor.RedirectUris.Add(new Uri(uri)); - } - - foreach (var uri in client.PostLogoutRedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _))) - { - descriptor.PostLogoutRedirectUris.Add(new Uri(uri)); - } - - foreach (var grantType in client.GrantTypes) - { - descriptor.Permissions.Add(grantType); - } - - foreach (var scope in client.Scopes) - { - descriptor.Permissions.Add(scope); - } - - await _applicationManager.CreateAsync(descriptor); - await _repository.AddAsync(client); - - _logger.LogInformation("Created OAuth client {ClientId}", client.ClientId); - - return client; - } - - public async Task GenerateSecretAsync(long id) - { - var client = await _repository.GetByIdAsync(id); - if (client == null) - { - throw new KeyNotFoundException($"Client with ID {id} not found"); - } - - var application = await _applicationManager.FindByClientIdAsync(client.ClientId); + var application = await _applicationManager.FindByIdAsync(id); if (application == null) { - throw new KeyNotFoundException($"OpenIddict application for client {client.ClientId} not found"); + return null; } - var newSecret = GenerateSecureSecret(); + 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); - var descriptor = new OpenIddictApplicationDescriptor + return new { - ClientId = client.ClientId, - ClientSecret = newSecret, - DisplayName = client.DisplayName + id = id, + clientId = clientIdValue, + displayName = displayNameValue, + 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)), + grantTypes = permissions + .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType)) + .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length)), + 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 }; - foreach (var uri in client.RedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _))) + var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/api/OAuthClients") { - descriptor.RedirectUris.Add(new Uri(uri)); - } + 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); - foreach (var uri in client.PostLogoutRedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _))) - { - descriptor.PostLogoutRedirectUris.Add(new Uri(uri)); - } + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); - foreach (var grantType in client.GrantTypes) - { - descriptor.Permissions.Add(grantType); - } + var content = await response.Content.ReadAsStringAsync(); + var result = System.Text.Json.JsonSerializer.Deserialize(content); - foreach (var scope in client.Scopes) - { - descriptor.Permissions.Add(scope); - } + _logger.LogInformation("Created OAuth client {ClientId}", dto.ClientId); - await _applicationManager.UpdateAsync(application, descriptor); - - client.ClientSecret = newSecret; - client.UpdatedAt = DateTime.UtcNow; - await _repository.UpdateAsync(client); - - _logger.LogInformation("Generated new secret for OAuth client {ClientId}", client.ClientId); - - return client; + return result!; } - public async Task ToggleStatusAsync(long id) + public async Task GenerateSecretAsync(string id) { - var client = await _repository.GetByIdAsync(id); - if (client == null) - { - throw new KeyNotFoundException($"Client with ID {id} not found"); - } + var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132"; + var token = await GetAuthTokenAsync(); - var oldStatus = client.Status; - client.Status = client.Status == "active" ? "inactive" : "active"; - client.UpdatedAt = DateTime.UtcNow; - await _repository.UpdateAsync(client); + var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/api/OAuthClients/{id}/generate-secret"); + request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - _logger.LogInformation("Toggled OAuth client {ClientId} status from {OldStatus} to {NewStatus}", client.ClientId, oldStatus, client.Status); + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); - return client; + 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(long id, OAuthApplication updatedClient) + public async Task UpdateClientAsync(string id, UpdateClientDto dto) { - var client = await _repository.GetByIdAsync(id); - if (client == null) - { - throw new KeyNotFoundException($"Client with ID {id} not found"); - } + var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132"; + var token = await GetAuthTokenAsync(); - var application = await _applicationManager.FindByClientIdAsync(client.ClientId); - if (application == null) + var requestBody = new { - throw new KeyNotFoundException($"OpenIddict application for client {client.ClientId} not found"); - } - - var descriptor = new OpenIddictApplicationDescriptor - { - ClientId = client.ClientId, - ClientSecret = client.ClientSecret, - DisplayName = updatedClient.DisplayName ?? client.DisplayName + dto.DisplayName, + dto.RedirectUris, + dto.PostLogoutRedirectUris, + dto.Scopes, + dto.GrantTypes, + dto.ClientType, + dto.ConsentType, + dto.Status, + dto.Description }; - var redirectUris = updatedClient.RedirectUris ?? client.RedirectUris; - foreach (var uri in redirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _))) + var request = new HttpRequestMessage(HttpMethod.Put, $"{authServiceUrl}/api/OAuthClients/{id}") { - descriptor.RedirectUris.Add(new Uri(uri)); - } + 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 postLogoutUris = updatedClient.PostLogoutRedirectUris ?? client.PostLogoutRedirectUris; - foreach (var uri in postLogoutUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _))) - { - descriptor.PostLogoutRedirectUris.Add(new Uri(uri)); - } + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); - var grantTypes = updatedClient.GrantTypes ?? client.GrantTypes; - foreach (var grantType in grantTypes) - { - descriptor.Permissions.Add(grantType); - } + _logger.LogInformation("Updated OAuth client {Id}", id); - var scopes = updatedClient.Scopes ?? client.Scopes; - foreach (var scope in scopes) - { - descriptor.Permissions.Add(scope); - } - - await _applicationManager.UpdateAsync(application, descriptor); - - client.DisplayName = updatedClient.DisplayName ?? client.DisplayName; - client.RedirectUris = redirectUris; - client.PostLogoutRedirectUris = postLogoutUris; - client.Scopes = scopes; - client.GrantTypes = grantTypes; - client.ClientType = updatedClient.ClientType ?? client.ClientType; - client.ConsentType = updatedClient.ConsentType ?? client.ConsentType; - client.Status = updatedClient.Status ?? client.Status; - client.Description = updatedClient.Description ?? client.Description; - client.UpdatedAt = DateTime.UtcNow; - await _repository.UpdateAsync(client); - - _logger.LogInformation("Updated OAuth client {ClientId}", client.ClientId); - - return client; + return new { message = "Client updated successfully" }; } - public async Task DeleteClientAsync(long id) + public async Task DeleteClientAsync(string id) { - var client = await _repository.GetByIdAsync(id); - if (client == null) - { - throw new KeyNotFoundException($"Client with ID {id} not found"); - } + var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132"; + var token = await GetAuthTokenAsync(); - var application = await _applicationManager.FindByClientIdAsync(client.ClientId); - if (application != null) - { - await _applicationManager.DeleteAsync(application); - _logger.LogInformation("Deleted OpenIddict application for client {ClientId}", client.ClientId); - } + var request = new HttpRequestMessage(HttpMethod.Delete, $"{authServiceUrl}/api/OAuthClients/{id}"); + request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - await _repository.DeleteAsync(client); + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); - _logger.LogInformation("Deleted OAuth client {ClientId}", client.ClientId); + _logger.LogInformation("Deleted OAuth client {Id}", id); } public object GetClientOptions() @@ -284,10 +296,38 @@ public class OAuthClientService : IOAuthClientService }; } + 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 = System.Security.Cryptography.RandomNumberGenerator.GetBytes(length); + var bytes = RandomNumberGenerator.GetBytes(length); return new string(bytes.Select(b => chars[b % chars.Length]).ToArray()); } }