namespace MyOffice.Web.Auth; using Core.Extensions; using Core.Identity; using Data.Models.Users; using Data.Repositories.Users; using Identity.Domain; using Identity.ExternalProviders; using Identity.Repositories; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; using OpenIddict.Server; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; public sealed class ExternalGrantHandler : IOpenIddictServerHandler { private readonly AppUserManager _userManager; private readonly IUserExternalRepository _userExternalRepository; private readonly IEnumerable _externalProviderValidators; private readonly IHttpContextAccessor _httpContextAccessor; public ExternalGrantHandler( AppUserManager userManager, IUserExternalRepository userExternalRepository, IEnumerable externalProviderValidators, IHttpContextAccessor httpContextAccessor ) { _userManager = userManager; _userExternalRepository = userExternalRepository; _externalProviderValidators = externalProviderValidators; _httpContextAccessor = httpContextAccessor; } public async ValueTask HandleAsync(HandleTokenRequestContext context) { if (!string.Equals(context.Request.GrantType, OpenIddictAuthConstants.ExternalGrantType, StringComparison.Ordinal)) return; var provider = context.Request.GetParameter("provider")?.ToString(); if (provider.IsMissing()) { context.Reject(Errors.InvalidRequest, "The provider parameter is required."); return; } var validator = _externalProviderValidators.FirstOrDefault(x => x.Provider.EqualsIgnoreCase(provider)); if (validator is null || !validator.IsConfigured) { context.Reject(Errors.InvalidRequest, $"Provider not supported: {provider}"); return; } var token = context.Request.GetParameter("token")?.ToString(); if (token.IsMissing()) { context.Reject(Errors.InvalidRequest, "The token parameter is required."); return; } var validationResult = await validator.ValidateAsync(token); if (!validationResult.IsSuccessed) { context.Reject(Errors.InvalidGrant, "Token not valid."); return; } if (_httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated == true) { context.Reject(Errors.InvalidGrant, "Authentication failed."); return; } var user = await _userManager.FindByEmailAsync(validationResult.Email!); if (user is null) { if (!validationResult.EmailVerified) { context.Reject(Errors.InvalidGrant, "Email not confirmed."); return; } user = await CreateUserAsync(validationResult.Email!, validationResult.EmailVerified, validationResult.FullName); if (user is null) { context.Reject(Errors.InvalidGrant, "Unable to create user."); return; } } var externalLogin = await _userExternalRepository.GetByUserIdAsync(user.Id, provider) ?? AddExternalLogin(user, validationResult.Email!, validationResult.ExternalId!, provider); context.SignIn(OpenIddictClaimsHelper.CreatePrincipal(user, context.Request.GetScopes())); } private async Task?> CreateUserAsync(string email, bool isEmailConfirmed, string? fullName) { var user = new ApplicationUser { Id = Guid.NewGuid(), UserName = email, Email = email, IsEmailConfirmed = isEmailConfirmed, FullName = fullName }; var password = "Qq1!_" + Guid.NewGuid(); var result = await _userManager.CreateAsync(user, password); return result.Succeeded ? user : null; } private UserExternal AddExternalLogin( ApplicationUser user, string email, string externalId, string provider ) { var existing = _userExternalRepository.GetByUserId(user.Id, provider); if (existing is not null) return existing; var claim = new UserExternal { UserId = user.Id, CreatedOn = DateTime.UtcNow, Provider = provider.ToLower(), ExternalId = externalId, Email = email }; _userExternalRepository.AddUserExternal(claim); return claim; } }