using System.IdentityModel.Tokens.Jwt; using System.Net.Http.Json; using System.Text.Json; using DysonNetwork.Common.Models; using DysonNetwork.Pass.Data; using DysonNetwork.Pass.Features.Auth.Interfaces; using DysonNetwork.Pass.Features.Auth.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; namespace DysonNetwork.Pass.Features.Auth.Services; public class OidcService : IOidcService { protected readonly IConfiguration _configuration; protected readonly IHttpClientFactory _httpClientFactory; protected readonly PassDatabase _db; protected readonly IAuthenticationService _authService; protected readonly ILogger _logger; public OidcService( IConfiguration configuration, IHttpClientFactory httpClientFactory, PassDatabase db, IAuthenticationService authService, ILogger logger) { _configuration = configuration; _httpClientFactory = httpClientFactory; _db = db; _authService = authService; _logger = logger; } public virtual string GetAuthorizationUrl(string state, string nonce) { throw new NotImplementedException("This method should be implemented by derived classes"); } public virtual async Task ProcessCallbackAsync(OidcCallbackData callbackData) { throw new NotImplementedException("This method should be implemented by derived classes"); } public virtual async Task AuthenticateAsync(string provider, string code, string state) { try { var userInfo = await ProcessCallbackAsync(new OidcCallbackData { Code = code, State = state }); // Find or create user based on the OIDC subject and provider var account = await FindOrCreateUser(userInfo, provider); // Create authentication result return await _authService.AuthenticateWithOidcAsync(provider, code, state); } catch (Exception ex) { _logger.LogError(ex, "Error during OIDC authentication"); return new AuthResult { Success = false, Error = "Authentication failed. Please try again." }; } } public virtual IEnumerable GetSupportedProviders() { var section = _configuration.GetSection("Oidc"); return section.GetChildren().Select(x => x.Key); } protected virtual async Task FindOrCreateUser(OidcUserInfo userInfo, string provider) { // Check if user exists with this provider and subject var user = await _db.Accounts .FirstOrDefaultAsync(u => u.ExternalLogins.Any(ul => ul.Provider == provider && ul.ProviderSubjectId == userInfo.Subject)); if (user != null) return user; // If user doesn't exist, create a new one user = new Account { Id = Guid.NewGuid(), Username = userInfo.PreferredUsername ?? userInfo.Email?.Split('@')[0] ?? Guid.NewGuid().ToString(), Email = userInfo.Email, EmailVerified = userInfo.EmailVerified ?? false, CreatedAt = SystemClock.Instance.GetCurrentInstant() }; // Add external login user.ExternalLogins.Add(new ExternalLogin { Id = Guid.NewGuid(), Provider = provider, ProviderSubjectId = userInfo.Subject, CreatedAt = SystemClock.Instance.GetCurrentInstant() }); await _db.Accounts.AddAsync(user); await _db.SaveChangesAsync(); return user; } protected virtual async Task ValidateIdToken(string token, string issuer, string audience, string signingKey) { var tokenHandler = new JwtSecurityTokenHandler(); var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)); tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = issuer, ValidateAudience = true, ValidAudience = audience, ValidateIssuerSigningKey = true, IssuerSigningKey = key, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }, out var validatedToken); return (JwtSecurityToken)validatedToken; } protected virtual async Task GetFromDiscoveryDocumentAsync(string url) { var client = _httpClientFactory.CreateClient(); var response = await client.GetAsync(url); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize(content); } }