145 lines
4.8 KiB
C#
145 lines
4.8 KiB
C#
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<OidcService> _logger;
|
|
|
|
public OidcService(
|
|
IConfiguration configuration,
|
|
IHttpClientFactory httpClientFactory,
|
|
PassDatabase db,
|
|
IAuthenticationService authService,
|
|
ILogger<OidcService> 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<OidcUserInfo> ProcessCallbackAsync(OidcCallbackData callbackData)
|
|
{
|
|
throw new NotImplementedException("This method should be implemented by derived classes");
|
|
}
|
|
|
|
public virtual async Task<AuthResult> 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<string> GetSupportedProviders()
|
|
{
|
|
var section = _configuration.GetSection("Oidc");
|
|
return section.GetChildren().Select(x => x.Key);
|
|
}
|
|
|
|
protected virtual async Task<Account> 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<JwtSecurityToken> 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<T?> GetFromDiscoveryDocumentAsync<T>(string url)
|
|
{
|
|
var client = _httpClientFactory.CreateClient();
|
|
var response = await client.GetAsync(url);
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
return JsonSerializer.Deserialize<T>(content);
|
|
}
|
|
}
|