:drunk: No idea what did AI did
This commit is contained in:
144
DysonNetwork.Pass/Features/Auth/Services/OidcService.cs
Normal file
144
DysonNetwork.Pass/Features/Auth/Services/OidcService.cs
Normal file
@ -0,0 +1,144 @@
|
||||
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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user